I2C Sniffer transition parsing problem

Status
Not open for further replies.

paynterf

Well-known member
I have been playing with Kito's I2C sniffer code with a Teensy 3.2 connected to a MPU6050. In order to understand what is going on, I wrote a small test program to hold a VERY simple conversation with the MPU6050 (repeatedly asking it for it's ID code), and then recorded the I2C bus transitions using Kito's program (modified to just write 1024 transition values into an array and then print them all back out again after the array is filled).

Then I wrote an Excel VBA program to parse through all the transition pairs to make sure I understood what the various transition pairs meant (i.e. a 0xC followed by 0x4 ==> START, 0x0 followed by 0x4 ==> 0 or ACK, etc).

In doing this, I discovered some transitions pairs that don't make sense, but must be treated properly (I call them 'NOP' sequences, like the following:

HTML:
0xc		
0x4	RE-START	
0x0		
0x8		
0xc	1	1

The 0xC/0x4 transition is properly parsed as 'RESTART', but the next transition pair (0x0/0x8) isn't a valid transition. However, if the 0x0 is skipped, then the 0x8/0xc pair is a valid '1' code. From the I2C docs at https://i2c.info/i2c-bus-specification I gathered that this may be due to the MPU6050 holding the SCL line low while it does some processing. These 'NOP' sequences only happen after a 7-bit ADDR - R/W - ACK/NAK sequence or a 8-bit DATABYTE - ACK/NAK sequence and I revised my program to handle this case

However, I ran into a case where a 'NOP' sequence occurred in the middle of an 8-bit DATABYTE sequence, which I thought was a real NO-NO. So I'm not sure whether this was just an error in the data collection program (seems unlikely, but...) or something in the I2C specs that I don't fully understand (could easily be the case...) or something else entirely.

So here's the section of the data that's driving me crazy. It starts with a normal RESTART - 7-bit ADDR - R/W - ACK - 8-bit DATABYTE sequence, and then repeats, except the second iteration doesn't complete because of a 'NOP' sequence after the 1st (MSB) bit of the 8-bit DATABYTE section

HTML:
0xc			
0x4	RE-START		
0x0			
0x8			
0xc	1	1	
0x8			
0xc	1	2	
0x0			
0x4	0	3	
0x8			
0xc	1	4	
0x0			
0x4	0	5	
0x0			
0x4	0	6	
0x0			
0x4	0	7	68
0x8			
0xc	READ		
0x0			
0x4	ACK		
0x0			
0x4	0	1	
0x8			
0xc	1	2	
0x8			
0xc	1	3	
0x0			
0x4	0	4	
0x8			
0xc	1	5	
0x0			
0x4	0	6	
0x0			
0x4	0	7	
0x0			
0x4	0	8	68
0x8			
0xc	NAK		
0x8			
0x0			
0x4	0	1	
0xc			
0x4	RE-START		
0x0			
0x8			
0xc	1	1	
0x8			
0xc	1	2	
0x0			
0x4	0	3	
0x8			
0xc	1	4	
0x0			
0x4	0	5	
0x0			
0x4	0	6	
0x0			
0x4	0	7	68
0x0			
0x4	WRITE		
0x0			
0x4	ACK		
0x8			
0x0			
0x4	0	1	
0x0			
0x8			
0xc			
0x8			
0xc			
0x8			
0xc			
0x8			
0x0			
0x4			
0x8			
0xc			
0x8			
0x0			
0x4			
0x0


Zeroing in on the problem section, you see the following:

HTML:
0x4	WRITE	
0x0		
0x4	ACK	
0x8		
0x0		
0x4	0	1
0x0		
0x8		
0xc		
0x8

The WRITE pair and the ACK pair are followed by one 'NOP' transition (expected and parsed correctly), then a 0x0/0x4 pair parsed as a '0' bit. However, the next transition pair is 0x0/0x8, which is an invalid transition pair in the middle of an 8-bit DATABYTE section.

Is this sort of behavior allowed in the I2C specs? Do I have to worry about NOP sequences anywhere in a conversation, even inside ADDR/DATABYTE sequences?

TIA,

Frank
 
Well, I finally figured out how to remove the NOP bytes from the data stream, and as a result my Excel VBA program will correctly parse the entire capture buffer (minus the NOPs, of course). When I parsed a repetitive sequence of 'mpu.testConnection();' calls, I get the following results

Code:
0xc				
0x4	START			
0x8				
0xc	1	1		
0x8				
0xc	1	2		
0x0				
0x4	0	3		
0x8				
0xc	1	4		
0x0				
0x4	0	5		
0x0				
0x4	0	6		
0x0				
0x4	0	7	68	
0x0				
0x4	WRITE			
0x0				
0x4	ACK			
0x0				
0x4	0	1		
0x8				
0xc	1	2		
0x8				
0xc	1	3		
0x8				
0xc	1	4		
0x0				
0x4	0	5		
0x8				
0xc	1	6		
0x0				
0x4	0	7		
0x8				
0xc	1	8	75	
0x0				
0x4	ACK			
0x0				
0x4	0	1		
0xc				
0x4	RE-START			
0x8				
0xc	1	1		
0x8				
0xc	1	2		
0x0				
0x4	0	3		
0x8				
0xc	1	4		
0x0				
0x4	0	5		
0x0				
0x4	0	6		
0x0				
0x4	0	7	68	
0x8				
0xc	READ			
0x0				
0x4	ACK			
0x0				
0x4	0	1		
0x8				
0xc	1	2		
0x8				
0xc	1	3		
0x0				
0x4	0	4		
0x8				
0xc	1	5		
0x0				
0x4	0	6		
0x0				
0x4	0	7		
0x0				
0x4	0	8	68	
0x8				
0xc	NAK			
0x0				
0x4	0	1		
0xc				
0x4	RE-START			
0x8				
0xc	1	1		
0x8				
0xc	1	2		
0x0				
0x4	0	3		
0x8				
0xc	1	4		
0x0				
0x4	0	5		
0x0				
0x4	0	6		
0x0				
0x4	0	7	68	
0x0				
0x4	WRITE			
0x0				
0x4	ACK			
0x0				
0x4	0	1		
0x8				
0xc	1	2		
0x8				
0xc	1	3		
0x8				
0xc	1	4		
0x0				
0x4	0	5		
0x8				
0xc	1	6		
0x0				
0x4	0	7		
0x8				
0xc	1	8	75	
0x0				
0x4	ACK			
0x0				
0x4	0	1		
0xc				
0x4	RE-START			
0x8				
0xc	1	1		
0x8				
0xc	1	2		
0x0				
0x4	0	3		
0x8				
0xc	1	4		
0x0				
0x4	0	5		
0x0				
0x4	0	6		
0x0				
0x4	0	7	68	
0x8				
0xc	READ			
0x0				
0x4	ACK			
0x0				
0x4	0	1		
0x8				
0xc	1	2		
0x8				
0xc	1	3		
0x0				
0x4	0	4		
0x8				
0xc	1	5		
0x0				
0x4	0	6		
0x0				
0x4	0	7		
0x0				
0x4	0	8	68	
0x8				
0xc	NAK			
0x0				
0x4	0	1		
0xc				
0x4	RE-START

which is great, but I didn't expect the odd occurrence of 1 byte of data after each second address/data section, as shown below:

Code:
0x4	0	8	68	
0x8				
0xc	NAK			
0x0				
0x4	0	1		
0xc				
0x4	RE-START

Any idea what this is all about? It doesn't seem to affect anything, but it is a bit disconcerting to start cranking out an 8-bit data byte only to have it terminate after the first bit!

TIA,

Frank
 
It’s been years since I had to dig deep into I2C, and then it was work, so I used a Beagle protocol analyzer - very handy.

But I wonder, have you monitored the same transaction using kito’s original program, to see how it decodes the sequences in question?
 
tele_player,

I was hoping to avoid the cost of a Beagle, as my per-hour labor cost these days is zero $/0 ;-). I have tried to use Kito's original program (and still use his transition capture routine), but his original code has some significant problems that make it difficult to use.

Frank
 
tele_player,

Here's some output from the original un-modified Kito code using the same test program. Judge for yourself:

Code:
Opening port
Port open
-----------------
Legend:
 S=Start
 Sr=Start repeat
 P=stoP
 R=Read
 W=Write
 A=Ack
 N=Nak
-----------------
P
S,P
P
P
P
S,P
P
P
S,P
S,P
P
P
S,P
S,P
S,P
S,P
P
S,P
S,P
S,P
P
S,P
P

As you can see, there's not a whole lot of information there, and it entirely misses all the actual data in the I2C stream.

Frank
 
Oops, forgot that Kito's program reverses the SCL & SDA lines: When the lines are switched to conform to the code, I get

Code:
Opening port
Port open
-----------------
Legend:
 S=Start
 Sr=Start repeat
 P=stoP
 R=Read
 W=Write
 A=Ack
 N=Nak
-----------------
Addr=0x68,W,N,P
S,Addr=0x68,R,N,P
S,Addr=0x68,W,N,P
S,Addr=0x68,R,N,P
S,Addr=0x68,W,N,P
S,Addr=0x68,R,N,P
S,Addr=0x68,W,N,P
S,Addr=0x68,R,N,P
S,Addr=0x68,W,N,P
S,Addr=0x68,R,N,P
S,Addr=0x68,W,N,P

Still not very helpful, as it omits the actual data
 
I've created an Excel VBA program that correctly parses an arbitrary I2C transmission stream. See my Paynter's Palace post here for all the gory details.

As noted in the post, I'm still not sure what is causing the extra '0' to appear in the transmission sequence, but it is clearly a consistent part of the sequence. I have looked around in the protocol documents, but haven't found anything relevant. Anyone else have a clue?

TIA,

Frank
 
That looks very cool Frank. Did I read right that the VB parsing in moving to c++ will be done on the Teensy?

Is there any value in tracking time - and flag where bits don't parse right? Time is driven by the sample rate to the buffer so when all goes right the only skips would be the slave delays and time between transactions. But when things fail it might point to an issue.
 
defragster,

Yes, the plan is to port the VBA code to a Teensy 3.2 I have lying around, (and maybe later to a Teensy 4!).

Yes, I think time-tagging has merit, especially, as you said, when things go wrong. But first I need to convince myself that my sniffer-to-be will actually work over long periods (up to 12 hours as it sometimes takes that long for my MPU6050 setup to fail) so I can use it to figure out who is the culprit in these failures.

In the long run I envision a sniffer with a 'look-back buffer' and some capability to detect I2C bus anomalies. The idea would be that a anomaly detection would freeze the sniffer some short time after detection so that the event that caused the anomaly would be captured in the middle of the look-back buffer. At the moment though, I plan to use the VS2019 serial logging feature as the look-back buffer (AFAIK, the VS2019 serial port log length is limited only by my 1TB SSD).

I plan to post the Teensy code here, in the hopes that the real experts on this forum can help me make it better/faster. My planned application is for a wall-following robot that acquires a single yaw value from the MPU6050 every 200 mSec, so a 'burst capture then analyze/print' method should be OK. However, a real-time 100KHz I2C capture/display system would be pretty cool, too!

Frank
 
Here's the Teensy 3.2 code for parsing a small sample of I2C communications with a MPU6050 IMU. The data used here are the same as the ones used for my previous Excel VBA project, but it turned out that the algorithm was much simpler than I previously thought.

This code uses a fixed array of captured data, but of course that won't work for a real I2C sniffer. My plan is to marry this algorithm to a FIFO implementation (perhaps tonton81's circular buffer library?) so the extracted/decoded sentences can be logged for later analysis in the event of a failure like the intermittent hangups I'm experiencing.

When the code below is run against the sample data (with all debug printouts disabled), the output is:

HTML:
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

For comparison, the output from Jeff Rowberg's I2Cdev code for the same sample set is:
HTML:
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).

Decoding and printing out the results took a little over 1 mSec using a 500Kb serial transfer rate on my XPS15 7590 laptop, so with a sufficiently large FIFO I should be able to keep up with my proposed application which only communicates with the MPU6050 every 200 mSec.

Any and all comments/suggestions are welcome, especially with respect to interfacing this code with tonton81's circular buffer library.




Code:
/*
    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");

}
 
Status
Not open for further replies.
Back
Top