Posted 02 January 2020
On my last post on the I2C subject, I described an Excel VBA program to parse through a 928-byte array containing the captured I2C conversation between an Arduino Mega and a MPU6050 IMU module. The Arduino was running a very simple test program that repeatedly asked the MPU6050 to report the number of bytes available in its FIFO. Then I used Kito’s I2C sniffer code to capture the SDA/SCL transitions, which I then copy/pasted into Excel.
This post describes the next step, which was to port the Excel VBA code into a Teensy sketch using the Arduino version of C++, moving toward the ultimate goal of a Teensy based, fast I2C sniffer that can be used to monitor and log long-term (hours to days) I2C bus conversations to determine what is causing the intermittent hangups I’m seeing with my Arduino Mega/MPU-6050 robot project.
The code port took a while, but mostly because of my own lack of understanding about the details of the I2C protocol and the specifics of the communication between the Arduino test program and my MPU6050 IMU module. After working through these problems, the end result was surprisingly compact – less than 500 lines, including 50 lines of test data, LOTS of comments and debugging printouts, as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
/* 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"); } |
When I ran this program against the captured data, I got the following output:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Opening port Port open I2C(68) reading 1 bytes from 75... 68 . Done I2C(68) reading 1 bytes from 6a... c0 . Done I2C(68) writing 1 bytes to 6a... c4 . Done I2C(68) reading 2 bytes from 72... 0 1c . Done I2C(68) reading 2 bytes from 72... 0 38 . Done I2C(68) reading 2 bytes from 72... 0 54 . Done I2C(68) reading 2 bytes from 72... 0 70 . Done I2C(68) reading 2 bytes from 72... 0 8c . Done I2C(68) reading 2 bytes from 72... 0 c4 . Done I2C(68) reading 2 bytes from 72... 0 e0 . Done Parsing 928 bytes took 1081 uSec |
which, when compared against the debug printout from Jeff Rowberg’s I2CDev program,
1 2 3 4 5 6 7 8 9 10 |
I2C (0x68) reading 1 bytes from 0x75...68. Done (1 read). I2C (0x68) reading 1 bytes from 0x6A...C0. Done (1 read). I2C (0x68) writing 1 bytes to 0x6A...C4. Done. I2C (0x68) reading 2 bytes from 0x72...0 1C. Done (2 read). I2C (0x68) reading 2 bytes from 0x72...0 38. Done (2 read). I2C (0x68) reading 2 bytes from 0x72...0 54. Done (2 read). I2C (0x68) reading 2 bytes from 0x72...0 70. Done (2 read). I2C (0x68) reading 2 bytes from 0x72...0 8C. Done (2 read). I2C (0x68) reading 2 bytes from 0x72...0 C4. Done (2 read). I2C (0x68) reading 2 bytes from 0x72...0 E0. Done (2 read). |
shows that my Teensy program correctly decoded the entire test dataset.
The next step in the process is to modify the above program to allow long-term real-time monitoring and logging of a live I2C bus. By ‘long-term’, I mean hours if not days, as the object of the exercise is to figure out why the I2C bus connection to the MPU6050 on my two-motor robot intermittently fails when the motors are running. A failure can occur within minutes, or only after several hours, and there doesn’t seem to be any rhyme nor reason, except that the motors have to be running.
In normal operation, my two-motor robot obtains a heading value from the MPU6050 once every 200 mSec or so. This I2C bus activity might comprise only 100 SCL/SDA transitions or so, and no other I2C bus activity takes place in the times between heading value requests. So, there will be few mSec of burst activity, followed by a 150-190 mSec idle period. To monitor and log in real time, I need some sort of FIFO arrangement, where the I2C transition data can be saved into the FIFO during the burst, and then processed and saved into a log file during the idle period.
While I was searching the web for I2C sniffer code, I also ran across this thread by tonton81 describing his template based circular buffer library for the Teensy line. The thread started a little over two years ago, but has been quite active during that period, and tonton81 has made several bugfixes, updates, and enhancements to his code. This might be just the thing for my project.
06 January 2020 Update:
After integrating tonton81’s circular buffer library into the project (thanks tonton81 for being so responsive with bugfixes!), I was able to demonstrate that the circular buffer version, when run against the same 928-byte simulated dataset, produced the same output as before, as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Opening port Port open Port open Serial available after 2500 mSec 3001: cb_trans contains = 928 elements I2C(68) reading 1 bytes from 75... 68 . Done I2C(68) reading 1 bytes from 6a... c0 . Done I2C(68) writing 1 bytes to 6a... c4 . Done I2C(68) reading 2 bytes from 72... 0 1c . Done I2C(68) reading 2 bytes from 72... 0 38 . Done I2C(68) reading 2 bytes from 72... 0 54 . Done I2C(68) reading 2 bytes from 72... 0 70 . Done I2C(68) reading 2 bytes from 72... 0 8c . Done I2C(68) reading 2 bytes from 72... 0 c4 . Done I2C(68) reading 2 bytes from 72... 0 e0 . Done 3003: cb_trans contains = 0 elements |
From the output above, it is clear that the Teensy can parse and print a typical 1000-byte burst in just a few mSec (3 in the above run), so it should have no problem keeping up with a 200 mSec data burst interval, and should be able to keep up with burst intervals down to around 10 mSec (100 bursts/sec!) I suspect that the Teensy’s major problem will be not dying of boredom waiting for the next burst to process!
Here’s the full code (not including circular_buffer.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
/* Name: Teensy_I2C_Sniffer_V6.ino Created: 1/4/2020 8:40:20 AM Author: FRANKNEWXPS15\Frank This is a port of my Excel VBA code into Arduino/Teensy C++ language. It parses the exact same sample data as the Excel program, so it should produce the exact same I2C sequences. The captured sample 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 */ /* This version adds tonton81's circular buffer class (https://github.com/tonton81/Circular_Buffer) to simulate real-time processing and display of I2C bus activity. The sample data is loaded into a FIFO by an ISR triggered every microsecond by a timer, and then the processing code pulls it out and parses it as fast as possible. If this works, then the next version will eliminate the sample data and connect to a live I2C bus. */ #include <TimerOne.h> //needed for ISR #include "circular_buffer.h" Circular_Buffer<uint8_t, 2048> cb_trans; //holds transition values from ISR #define MONITOR_OUT1 2 //so can monitor ISR activity with O'scope #define MONITOR_OUT2 3 //so can monitor ISR activity with O'scope 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 const int BUFFER_CHECK_INTERVAL_MSEC = 200; //const int BUFFER_CHECK_INTERVAL_MSEC = 5; #pragma region PROCESSING_VARIABLES uint8_t devAddr; uint8_t regAddr; //added for bus direction labels 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 uint16_t numbytes = 0; //number of data bytes extracted from data stream uint8_t killbuff[2]; //used to consume start/stop bytes #pragma endregion ProcVars #pragma region ISR_VARS uint16_t write_index = 0; uint16_t read_index = 0; uint8_t current_portb = 0xFF; uint8_t last_portb = 0xFF; bool bBufferFull = false; elapsedMillis mSecSinceLastBufferCheck; #pragma endregion ISRVars int DecodeAndPrintFIFOContents(Circular_Buffer<uint8_t, 2048>& cb); //need forward reference here //------------------------------------------------------------------------------- //-------------------------------- ISR ------------------------------------ //------------------------------------------------------------------------------- //01/04/2020 modified to 'capture' data from simdata[] void capture_data(void) { if (!bBufferFull) //for debug - writes only one set of data to FIFO { last_portb = current_portb; current_portb = simdata[read_index]; //pull one transition value from simdata read_index++; if (last_portb != current_portb && !bBufferFull) { cb_trans.write(current_portb); //add current transition value to FIFO digitalWriteFast(MONITOR_OUT1, !digitalReadFast(MONITOR_OUT1)); } //pause writing to cb_trans circular buffer if (read_index >= SIM_DATA_ARRAY_SIZE) { bBufferFull = true; read_index = 0; digitalWriteFast(MONITOR_OUT1, LOW); } } } void setup() { Serial.begin(1); //rate value ignored unsigned long now = millis(); int idx = 0; while (!Serial && (millis() - now) < 3000) { delay(500); idx++; } Serial.printf("Serial available after %lu mSec\n", millis() - now); pinMode(MONITOR_OUT1, OUTPUT); pinMode(MONITOR_OUT2, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); Timer1.initialize(1); // run every mico second Timer1.attachInterrupt(capture_data); mSecSinceLastBufferCheck = 0; } void loop() { if (mSecSinceLastBufferCheck > BUFFER_CHECK_INTERVAL_MSEC) { Serial.printf("%lu: cb_trans contains = %d elements\n", millis(), cb_trans.size()); mSecSinceLastBufferCheck -= BUFFER_CHECK_INTERVAL_MSEC; DecodeAndPrintFIFOContents(cb_trans); //decode and print everything captured so far Serial.printf("%lu: cb_trans contains = %d elements\n", millis(), cb_trans.size()); //cb_trans.clear(); read_index = 0; bBufferFull = false; digitalWriteFast(MONITOR_OUT2, !digitalReadFast(MONITOR_OUT2)); while (true) { } } } void PrintNextFIFOBytes(Circular_Buffer<uint8_t, 2048>& cb, uint8_t numbytes) { Serial.printf("Next %d FIFO Bytes: ", numbytes); for (uint8_t i = 0; i < numbytes-1; i++) { Serial.printf("%x,", cb.peek(i)); } Serial.printf("%x\n", cb.peek(numbytes-1)); } bool IsStart(Circular_Buffer<uint8_t, 2048>& cb) { bool result = false; uint8_t data[2]; data[0] = data[1] = 0; data[0] = cb.peek(0); data[1] = cb.peek(1); if (data[0] == 0xC && data[1] == 0x4) { //Serial.printf("Start detected at size = %d, data idx = %d\n", cb.size(), SIM_DATA_ARRAY_SIZE - cb.size()); result = true; } return result; } bool IsStop(Circular_Buffer<uint8_t, 2048>& cb) { bool result = false; uint8_t data[2]; cb.peekBytes(data, 2); if (data[0] == 0x4 && data[1] == 0xC) { //cb.readBytes(data, 2); //consume these bytes result = true; } return result; } uint8_t Get7BitDeviceAddr(Circular_Buffer<uint8_t, 2048>& cb) { //Purpose: Construct a 7-bit address starting from next FIFO element //Inputs: // cb = reference to cb_trans FIFO //Outputs: // returns the address as an 8-bit value with the MSB = 0, or 0x0 if unsuccessful // the 14 transitions associated with the 7-bit address removed from FIFO //Plan: // Step1: Convert a pair of FIFO elements 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 //devAddr is exactly 7 bits long, so 8 bits with MSB = 0 uint8_t data[2]; //holds one transition pair for (size_t i = 0; i < 7; i++) { cb.readBytes(data, 2); //data[0] = cb.read(); //data[1] = cb.read(); //Serial.printf("data[0] = %x, data[1] = %x\n", data[0], data[1]); if (data[0] == 0x8 && data[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))); 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 GetReadWriteFlag(Circular_Buffer<uint8_t, 2048>& cb) { //Purpose: decode R/W byte pair //Inputs: // cb = reference to cb_trans FIFO //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: // int result = 0; uint8_t data[2]; data[0] = cb.read(); data[1] = cb.read(); //Serial.printf("data[0] = %x, data[1] = %x\n", data[0], data[1]); if (data[0] == 0x8 && data[1] == 0xC) { result = 1; //read detected } //else if (simdata[readidx] == 0x0 && simdata[readidx + 1] == 0x4) else if (data[0] == 0x0 && data[1] == 0x4) { result = 0; //write detected } else { result = -1; //failed to detect read or write } return result; } int GetACKNAKFlag(Circular_Buffer<uint8_t, 2048>& cb) { //Purpose: decode ACK/NAK byte pair //Inputs: // cb = reference to cb_trans circular buffer holding transition values //Outputs: // returns 1 for NAK (0x8/0xC), 0 for ACK (0x0/0x4), -1 for failure //Notes: // this code is identical to the code for GetReadWriteFlag, so use it instead return GetReadWriteFlag(cb_trans); } int Get8BitDataByte(Circular_Buffer<uint8_t, 2048>& cb) { //Purpose: Construct a 8-bit data byte starting from dataidx //Inputs: // cb = reference to cb_trans circular buffer holding transition values //Outputs: // returns the address as an 8-bit value, or -1 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; int numbits = 0; uint8_t data[2]{ 0x0,0x0 }; //holds one transition pair ////DEBUG!! // Serial.printf("Get8BitDataByte top: data[0] = %x,data[1] = %x, size = %d, idx = %d\n", // data[0], data[1], cb.size(), SIM_DATA_ARRAY_SIZE - cb.size()); // PrintNextFIFOBytes(cb, 20); ////DEBUG!! //8 bits with MSB = 0 for (size_t i = 0; i < 8; i++) { data[0] = cb.peek(0); data[1] = cb.peek(1); //Serial.printf("data[0] = %x, data[1] = %x\n", data[0], data[1]); if (data[0] == 0x0 && data[1] == 0x4) { cb.readBytes(data, 2);// now consume the bytes numbits++; } else if (data[0] == 0x8 && data[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 cb.readBytes(data, 2);// now consume the bytes dataval += (1 << (7 - i)); //add 2^(8-i) to sum numbits++; } else //not a 1 or 0 - get me outa here! { break; } } ////DEBUG!! // Serial.printf("Get8BitDataByte bottom: numbits = %d, data[0] = %x,data[1] = %x, size = %d, idx = %d\n", // numbits, data[0], data[1], cb.size(), SIM_DATA_ARRAY_SIZE - cb.size()); // PrintNextFIFOBytes(cb, 20); ////DEBUG!! if (numbits != 8) { dataval = -1; //error return value } return dataval; } int GetDataBytes(Circular_Buffer<uint8_t, 2048>& cb, 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; int dataval; do { dataval = Get8BitDataByte(cb); ////DEBUG!! // Serial.printf("GetDataBytes() just after Get8BitDataByte(): datval = %x, size = %d, idx = %d\n", // dataval, cb.size(), SIM_DATA_ARRAY_SIZE-cb.size()); // PrintNextFIFOBytes(cb, 10); ////DEBUG!! //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(cb_trans); //01/05/20 moved inside 'good byte' block } //ACKNAKFlag = GetACKNAKFlag(cb_trans); ////DEBUG!! // Serial.printf("GetDataBytes() just after GetACKNAKFlag(): ACKNAK = %d, size = %d, idx = %d\n", // ACKNAKFlag, cb.size(), SIM_DATA_ARRAY_SIZE-cb.size()); // PrintNextFIFOBytes(cb, 10); ////DEBUG!! StartFlag = IsStart(cb_trans); ////DEBUG!! // Serial.printf("GetDataBytes() just after IsStart(): StartFlag = %d, size = %d, idx = %d\n", // StartFlag, cb.size(), SIM_DATA_ARRAY_SIZE-cb.size()); // PrintNextFIFOBytes(cb, 10); ////DEBUG!! StopFlag = IsStop(cb_trans); #ifdef PARSE_LOOP_DEBUG Serial.printf("IsStart %d, IsStop %d, next two bytes are %x, %x, data idx = %d\n", StartFlag, StopFlag, cb.peek(0), cb.peek(1), SIM_DATA_ARRAY_SIZE-cb.size()); #endif } while (!StartFlag && !StopFlag && cb_trans.size() > 0); 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"); } int DecodeAndPrintFIFOContents(Circular_Buffer<uint8_t, 2048>& cb) { //Purpose: decode and print I2C conversation held in cb_trans FIFO //Inputs: // cb = 2048 element FIFO //Outputs: // returns number of bytes processed, or -1 for failure // outputs structured I2C sentence to serial monitor //Plan: // Step1: Determine if there is anything to do (have to have more than one transition in FIFO) // Step2: Parse transitions into I2C sentence structure // Step3: Output sentence to serial monitor if (cb.size() < 2) { return 0; } while (cb.size() > 0) { #ifdef PARSE_LOOP_DEBUG Serial.printf("At top of while (cb.size() > 0): size = %d\n", cb.size()); Serial.printf("Next two bytes in FIFO are %x, %x\n", cb.peek(0), cb.peek(1)); #endif //Find a START sequence (0xC followed by 0x4) while (!IsStart(cb)) { Serial.printf("looking for start...\n"); } cb.readBytes(killbuff,2); //01/05/20 moved START byte consume out of IsStart() #ifdef PARSE_LOOP_DEBUG Serial.printf("Start sequence found, FIFO size = %d, idx = %d\n", cb.size(), SIM_DATA_ARRAY_SIZE-cb.size()); //PrintNextFIFOBytes(cb_trans, 20); #endif if (cb.size() > 14)//14 entries required for 7-bit address { //Get 7-bit device address devAddr = Get7BitDeviceAddr(cb); //Serial.printf("devAddr = %x, FIFO size = %d, idx = %d\n", devAddr, cb.size(), SIM_DATA_ARRAY_SIZE - cb.size()); } //get read/write flag 1 = Read, 0 = Write, -1 = error BusDir = (BUSDIR)GetReadWriteFlag(cb); #ifdef PARSE_LOOP_DEBUG Serial.printf("BusDir = %s\n", ((BusDir == BUSDIR::WRITE) ? "WRITE" : "READ")); //PrintNextFIFOBytes(cb_trans, 20); #endif //get ACK/NAK flag ACKNAKFlag = GetACKNAKFlag(cb); numbytes = GetDataBytes(cb, databyte_array); //terminates on a START, but the start bytes are not consumed #ifdef PARSE_LOOP_DEBUG Serial.printf("Got %d bytes from GetDataBytes() --> ", numbytes); for (size_t i = 0; i < numbytes; i++) { Serial.printf(" %x ", databyte_array[i]); } Serial.printf("\n"); //PrintNextFIFOBytes(cb_trans, 20); #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, cb size = %d\n", regAddr, cb.size()); #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); } #ifdef PARSE_LOOP_DEBUG Serial.printf("At end of while (cb.size() > 0): size = %d\n", cb.size()); Serial.printf("Next two bytes in FIFO are %x, %x\n", cb.peek(0), cb.peek(1)); #endif }//while (cb.size() > 0) return 1; } |
The next step in the project will be to modify the code (hopefully for the last time) to capture and process live I2C traffic bursts in real time.
I modified the interval processing code in loop() to reset the stop/restart Timer1 & clear FIFO each time the interval block is executed, and then reduced the processing interval from 200 to 50 mSec, to produce the following output:
The modified code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void loop() { if (mSecSinceLastBufferCheck > BUFFER_CHECK_INTERVAL_MSEC) { Timer1.stop(); Serial.printf("%lu: cb_trans contains = %d elements\n\n", millis(), cb_trans.size()); PrintNextFIFOBytes(cb_trans, 20); mSecSinceLastBufferCheck -= BUFFER_CHECK_INTERVAL_MSEC; DecodeAndPrintFIFOContents(cb_trans); //decode and print everything captured so far Serial.printf("%lu: cb_trans contains = %d elements\n\n", millis(), cb_trans.size()); cb_trans.clear(); //required to reset pointers { head = tail = _available = 0; } last_portb = current_portb = 0; read_index = 0; bBufferFull = false; digitalWriteFast(MONITOR_OUT2, !digitalReadFast(MONITOR_OUT2)); Timer1.start(); } } |
and a short segment of the output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
Serial available after 500 mSec 851: cb_trans contains = 928 elements Next 20 FIFO Bytes: c,4,8,c,8,c,0,4,8,c,0,4,0,4,0,4,0,4,0,4 I2C(68) reading 1 bytes from 75... 68 . Done I2C(68) reading 1 bytes from 6a... c0 . Done I2C(68) writing 1 bytes to 6a... c4 . Done I2C(68) reading 2 bytes from 72... 0 1c . Done I2C(68) reading 2 bytes from 72... 0 38 . Done I2C(68) reading 2 bytes from 72... 0 54 . Done I2C(68) reading 2 bytes from 72... 0 70 . Done I2C(68) reading 2 bytes from 72... 0 8c . Done I2C(68) reading 2 bytes from 72... 0 c4 . Done I2C(68) reading 2 bytes from 72... 0 e0 . Done 852: cb_trans contains = 0 elements 901: cb_trans contains = 928 elements Next 20 FIFO Bytes: c,4,8,c,8,c,0,4,8,c,0,4,0,4,0,4,0,4,0,4 I2C(68) reading 1 bytes from 75... 68 . Done I2C(68) reading 1 bytes from 6a... c0 . Done I2C(68) writing 1 bytes to 6a... c4 . Done I2C(68) reading 2 bytes from 72... 0 1c . Done I2C(68) reading 2 bytes from 72... 0 38 . Done I2C(68) reading 2 bytes from 72... 0 54 . Done I2C(68) reading 2 bytes from 72... 0 70 . Done I2C(68) reading 2 bytes from 72... 0 8c . Done I2C(68) reading 2 bytes from 72... 0 c4 . Done I2C(68) reading 2 bytes from 72... 0 e0 . Done 902: cb_trans contains = 0 elements 951: cb_trans contains = 928 elements Next 20 FIFO Bytes: c,4,8,c,8,c,0,4,8,c,0,4,0,4,0,4,0,4,0,4 I2C(68) reading 1 bytes from 75... 68 . Done I2C(68) reading 1 bytes from 6a... c0 . Done I2C(68) writing 1 bytes to 6a... c4 . Done I2C(68) reading 2 bytes from 72... 0 1c . Done I2C(68) reading 2 bytes from 72... 0 38 . Done I2C(68) reading 2 bytes from 72... 0 54 . Done I2C(68) reading 2 bytes from 72... 0 70 . Done I2C(68) reading 2 bytes from 72... 0 8c . Done I2C(68) reading 2 bytes from 72... 0 c4 . Done I2C(68) reading 2 bytes from 72... 0 e0 . Done 952: cb_trans contains = 0 elements |
Note that the processing block is indeed called every 50 mSec, and takes only a few mSec to complete. The following is an O’scope image showing multiple 50 mSec periods. As can be seen on the image, there is still a LOT of dead time between 928-byte bursts.
Stay tuned!
Frank