/*
Name: Teensy_I2C_Sniffer_V5.ino
Created: 12/31/2019 11:36:00 PM
Author: FRANKNEWXPS15\Frank
This is a port of my Excel VBA code into Arduino/Teensy C++ language.
It parses the exact same data as the Excel program, so it should produce
the exact same I2C sequences.
The idea here is to not only perform the port, but to figure out how to
produce a useful human-readable output format, and to determine how much
time is required to parse the data and output the data to the serial port
The captured data to be parsed is held in the 'simdata' array.
The original VBA code is saved in 'ExcelVBACode.txt'
*/
/* 'Notes:
A typical I2C sentence when communicating with a MPU6050 IMU module goes like:
"I2C(68) wrote 1 byte to 75 - C0 Done."
"I2C(68) wrote 3 bytes to 72 - C0 0C 10 Done."
"I2C(68) read 5 bytes from 6A - C0 0C 10 14 03 Done."
To form a sentence, we need:
Device addr: 68 in the above examples
Read/Write direction
To/From register address: 75, 72 and 6A in the above examples
Data: C0, C0 0C 10, and C0 0C 10 14 03 in the above examples
number of bytes written/read: 1,3 & 5 in the above examples
Each I2C communication proceeds as follows (assuming a START from an IDLE condition):
A START or RESTART condition, denoted by SDA & SCL HIGH, followed by SDA LOW, SCL HIGH
A 7-bit device address, MSB first (0x8/0xC = 1, 0x0/0x4 = 0)
A R/W bit (0x8/0xC = read, 0x0/0x4 = write)
An ACK bit (0x8/0xC = NAK, 0x0/0x4 = ACK)
If the bus direction is WRITE, then
A register address for read/write
zero or more additional data bytes
Else (the bus direction is READ)
One or more additional data bytes
Endif
*/
const int SIM_DATA_ARRAY_SIZE = 928;
byte simdata[SIM_DATA_ARRAY_SIZE] =
{
0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc,
0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0xc,
0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc,
0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0,
0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0xc, 0x4, 0x8,
0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8,
0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4,
0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0,
0x4, 0x8, 0xc, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8,
0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8,
0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc, 0x4,
0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4,
0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0,
0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8,
0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc,
0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc,
0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc,
0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0,
0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0,
0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4,
0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8,
0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4,
0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4,
0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8,
0xc, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0,
0x4, 0x0, 0x4, 0x8, 0xc, 0x8, 0xc, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x0, 0x4, 0x0, 0x4, 0x8, 0xc, 0x0, 0x4, 0xc, 0x4, 0x8, 0xc, 0x8, 0xc, 0x0,
0x4, 0x8, 0xc, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4, 0x0, 0x4,
0x8, 0xc
};
//#define PARSE_LOOP_DEBUG
//#define GET_8BIT_DATABYTE_DEBUG
#pragma region VARIABLES
uint8_t devAddr;
uint8_t regAddr;
//added to handle bus direction changes
enum BUSDIR
{
WRITE,
READ,
UNKNOWN = -1
} RWDir;
BUSDIR BusDir = BUSDIR::UNKNOWN;
int ACKNAKFlag; //can be negative
uint8_t databyte_array[2048]; //holds multiple databytes for later output sentence construction
uint16_t databyte_idx = 0; //index into databyte_array FIFO
uint16_t numbytes = 0; //number of data bytes extracted from data stream
#pragma endregion Variables
void setup()
{
Serial.begin(500000); //trying higher speed with teensy
unsigned long now = millis();
int idx = 0;
while (!Serial && (millis() - now) < 3000)
{
delay(500);
idx++;
}
uint16_t dataidx = 0;
unsigned long startUsec = micros();
while (dataidx < SIM_DATA_ARRAY_SIZE) //outer loop only necessary for simulation runs
{
#ifdef PARSE_LOOP_DEBUG
Serial.printf("looptop: dataidx = %d\n", dataidx);
#endif
//Find a START sequence (0xC followed by 0x4)
while (!IsStart(simdata, dataidx) && dataidx < SIM_DATA_ARRAY_SIZE)
{
dataidx++;
}
#ifdef PARSE_LOOP_DEBUG
Serial.printf("Start sequence found at dataidx = %d\n", dataidx);
#endif
if (dataidx < SIM_DATA_ARRAY_SIZE - 14)//14 entries required for 7-bit address
{
//Get 7-bit device address
devAddr = Get7BitDeviceAddr(simdata, dataidx);
#ifdef PARSE_LOOP_DEBUG
Serial.printf("devAddr = %x, dataidx = %d\n", devAddr, dataidx);
#endif
//get read/write flag 1 = Read, 0 = Write, -1 = error
BusDir = (BUSDIR)GetReadWriteFlag(simdata, dataidx);
#ifdef PARSE_LOOP_DEBUG
Serial.printf("BusDir = %s\n", ((BusDir == BUSDIR::WRITE) ? "WRITE" : "READ"));
#endif
//get ACK/NAK flag
ACKNAKFlag = GetACKNAKFlag(simdata, dataidx);
numbytes = GetDataBytes(simdata, dataidx, databyte_array);
#ifdef PARSE_LOOP_DEBUG
Serial.printf("Got %d bytes from GetDataBytes()\n", numbytes);
#endif
//If the bus direction is WRITE, then extract
// A register address for read / write
// zero or more additional data bytes
if (BusDir == BUSDIR::WRITE)
{
regAddr = databyte_array[0];
#ifdef PARSE_LOOP_DEBUG
Serial.printf("regAddr = %x, dataidx = %d\n", regAddr, dataidx);
#endif
//check for additional data
if (numbytes > 1)
{
#ifdef PARSE_LOOP_DEBUG
Serial.printf("Additional data found!\n");
for (size_t i = 0; i < numbytes; i++)
{
Serial.printf("data[%d] = %x\n", i, databyte_array[i]);
}
#endif
//1st byte is register addr, subsequent bytes are data
OutputFormattedSentence(BusDir, devAddr, regAddr, numbytes, databyte_array, 1 );
}
}
else //all bytes are data
{
#ifdef PARSE_LOOP_DEBUG
Serial.printf("In data block: got %d bytes of data\n",numbytes);
for (size_t i = 0; i < numbytes; i++)
{
Serial.printf("data[%d] = %x\n", i, databyte_array[i]);
}
#endif
OutputFormattedSentence(BusDir, devAddr, regAddr, numbytes, databyte_array, 0);
}
}//if (dataidx < SIM_DATA_ARRAY_SIZE - 14)//14 entries required for 7-bit address
}//while (dataidx < SIM_DATA_ARRAY_SIZE) //outer loop only necessary for simulation runs
Serial.printf("Parsing %d bytes took %lu uSec\n", SIM_DATA_ARRAY_SIZE, micros() - startUsec);
}//Setup()
void loop()
{
}
bool IsStart(byte* data, uint16_t& readidx)
{
bool result = false;
//Serial.printf("IsStart[%d] = %x, IsStart[%d] = %x\n",
// readidx, data[readidx], readidx + 1, data[readidx + 1]);
if (data[readidx] == 0xC && data[readidx + 1] == 0x4)
{
result = true;
readidx += 2; //bump to next byte pair
}
return result;
}
bool IsStop(byte* data, uint16_t& readidx)
{
bool result = false;
//Serial.printf("IsStop[%d] = %x, IsStop[%d] = %x\n",
//readidx, data[readidx], readidx + 1, data[readidx + 1]);
if (data[readidx] == 0x4 && data[readidx + 1] == 0xC)
{
result = true;
readidx += 2; //bump to next byte pair
}
return result;
}
uint8_t Get7BitDeviceAddr(byte* simdata, uint16_t& readidx)
{
//Purpose: Construct a 7-bit address starting from dataidx
//Inputs:
// simdata = pointer to simulated data array
// readidx = starting index of 7-bit address sequence (MSB first)
//Outputs:
// returns the address as an 8-bit value with the MSB = 0, or 0x0 if unsuccessful
// dataidx = pointer to next simdata entry
//Plan:
// Step1: Convert a pair of simdata entries into a 0 or 1
// Step2: Add the appropriate value to an ongoing sum
// Step3: return the total.
//Notes:
// A '0' is coded as a 0x0 followed by a 0x4
// A '1' is coded as a 0x8 followed by a 0xC
uint8_t devAddr = 0x0; //failure return value
//Serial.printf("Get7BitDeviceAddr: readidx = %d\n",readidx);
//devAddr is exactly 7 bits long, so 8 bits with MSB = 0
for (size_t i = 0; i < 7; i++)
{
if (simdata[readidx] == 0x0 && simdata[readidx + 1] == 0x4)
{
readidx += 2; //advance the pointer, but don't add to sum
}
else if (simdata[readidx] == 0x8 && simdata[readidx + 1] == 0xC)
{
//Serial.printf("Get7BitDeviceAddr: '1' found at i = %d, adding %x to devAddr to get %x\n",
// i, 1 << (7 - i), devAddr + (1 << (7-i)));
readidx += 2; //advance the pointer
devAddr += (1 << (7 - i)); //add 2^(7-i) to sum
}
}
devAddr = devAddr >> 1; //divide result by 2 to get 7-bit addr from 8 bits
return devAddr;
}
int Get8BitDataByte(byte* simdata, uint16_t& readidx)
{
//Purpose: Construct a 8-bit data byte starting from dataidx
//Inputs:
// simdata = pointer to simulated data array
// readidx = starting index of 8-bit data byte (MSB first)
//Outputs:
// returns the address as an 8-bit value, or 0x0 if unsuccessful
// dataidx = pointer to next simdata entry
//Plan:
// Step1: Convert a pair of simdata entries into a 0 or 1
// Step2: Add the appropriate value to an ongoing sum
// Step3: return the total.
//Notes:
// A '0' is coded as a 0x0 followed by a 0x4
// A '1' is coded as a 0x8 followed by a 0xC
// 12/29/19 - changed return val to int, so can return -1 when a 'short byte' is detected
int dataval = 0x0; //failure return value
#ifdef GET_8BIT_DATABYTE_DEBUG
Serial.printf("Get8BitDataByte: simdata[%d] = %x, simdata[%d] = %x\n",
readidx, simdata[readidx], readidx + 1, simdata[readidx + 1]);
#endif
//8 bits with MSB = 0
int numbytes = 0;
for (size_t i = 0; i < 8; i++)
{
if (simdata[readidx] == 0x0 && simdata[readidx + 1] == 0x4)
{
readidx += 2; //advance the pointer, but don't add to sum
numbytes++;
}
else if (simdata[readidx] == 0x8 && simdata[readidx + 1] == 0xC)
{
#ifdef GET_8BIT_DATABYTE_DEBUG
Serial.printf("Get8BitDataByte: '1' found at i = %d, adding %x to devAddr to get %x\n",
i, 1 << (7 - i), dataval + (1 << (7 - i)));
#endif
readidx += 2; //advance the pointer
dataval += (1 << (7 - i)); //add 2^(8-i) to sum
numbytes++;
}
}
#ifdef GET_8BIT_DATABYTE_DEBUG
Serial.printf("Get8BitDataByte: numbytes = %d\n", numbytes);
#endif
if (numbytes != 8)
{
dataval = -1; //error return value
}
return dataval;
}
int GetReadWriteFlag(byte* simdata, uint16_t& readidx)
{
//Purpose: decode R/W byte pair
//Inputs:
// simdata = pointer to byte capture array
// readidx = index into simdata to start of R/W byte pair
//Outputs:
// readidx = if successful, points to next byte pair in simdata
// returns 1 for Read (0x8/0xC), 0 for Write (0x0/0x4), -1 for failure
//Notes:
//
//Serial.printf("GetReadWriteFlag: readidx = %d, simdata[readidx] = %x, simdata[readidx+1]= %x\n",
// readidx, simdata[readidx], simdata[readidx + 1]);
int result = 0;
if (simdata[readidx] == 0x8 && simdata[readidx + 1] == 0xC)
{
result = 1; //read detected
readidx += 2; //point to next byte pair
}
else if (simdata[readidx] == 0x0 && simdata[readidx + 1] == 0x4)
{
result = 0; //write detected
readidx += 2; //point to next byte pair
}
else
{
result = -1; //failed to detect read or write
}
return result;
}
int GetACKNAKFlag(byte* simdata, uint16_t& readidx)
{
//Purpose: decode ACK/NAK byte pair
//Inputs:
// simdata = pointer to byte capture array
// readidx = index into simdata to start of ACK/NAK byte pair
//Outputs:
// readidx = if successful, points to next byte pair in simdata
// returns 1 for NAK (0x8/0xC), 0 for ACK (0x0/0x4), -1 for failure
//Notes:
//
//Serial.printf("GetACKNAKFlag: readidx = %d, simdata[readidx] = %x, simdata[readidx+1]= %x\n",
// readidx, simdata[readidx], simdata[readidx + 1]);
int result = 0;
if (simdata[readidx] == 0x8 && simdata[readidx + 1] == 0xC)
{
result = 1; //NAK detected
readidx += 2; //point to next byte pair
}
else if (simdata[readidx] == 0x0 && simdata[readidx + 1] == 0x4)
{
result = 0; //ACK detected
readidx += 2; //point to next byte pair
}
else
{
result = -1; //failed to detect ACK or NAK
}
return result;
}
int GetDataBytes(uint8_t* data, uint16_t& readidx, uint8_t* databytes)
{
//Notes:
// 01/01/2020: removed databyteidx from sig - always starts at zero
uint16_t numbytes = 0;
uint16_t databyte_idx = 0;
bool StartFlag = false;
bool StopFlag = false;
do
{
int dataval = Get8BitDataByte(data, readidx);
//watch out for 'short byte' reads
if (dataval >= 0)
{
uint8_t databyte = (uint8_t)dataval;
databyte_array[databyte_idx] = databyte;
databyte_idx++;
numbytes++;
}
ACKNAKFlag = GetACKNAKFlag(data, readidx);
StartFlag = IsStart(data, readidx);
StopFlag = IsStop(data, readidx);
#ifdef PARSE_LOOP_DEBUG
Serial.printf("IsStart returned %d, IsStop returned %d, dataidx = %d\n",
StartFlag, StopFlag, readidx);
#endif
} while (!StartFlag && !StopFlag && readidx < SIM_DATA_ARRAY_SIZE);
readidx -= 2;//back readidx up so loop top is positioned correctly.
return numbytes;
}
void OutputFormattedSentence(int RW, uint8_t dev, uint8_t reg, uint8_t numbytes, uint8_t* bytearray,uint16_t startidx)
{
Serial.printf("I2C(%x) %s %d bytes %s %x... ",
dev, (RW == 0 ? "writing" : "reading"), numbytes-startidx, (RW == 0 ? "to" : "from"), reg);
for (size_t i = startidx; i < numbytes; i++)
{
Serial.printf("%x ", bytearray[i]);
}
Serial.printf(". Done\n");
}