I2C/SMbus monitor
I've just delved into I2C transactions in recent weeks. I've now started on making a monitor for observing activity. Yet again, I have to give kudos to Chip for an instruction set with conditional execution ... I've built the layer-2 capturing, in assembly, without any need for state tracker changes being decided via extra branching. The only branching, other than a subroutine, is the state changes! In assembly, that's a huge saving on mental resources to prevent bugs, not to mention it producing efficient code execution.
Of course, the speed of the prop2 means assembly wasn't really necessary at all. I'm comfortable with pasm2 so I took it as a challenge to do a high efficiency version. Sysclock can probably be quite low. Below 20 MHz for 100 kHz SCL.
As of yet, there's no Spin/C/Basic coding done and no layer-3 done. I vaguely intend to make it portable equipment.
First draft (entirely untested):
DAT
ORG 0
smb_monitor
fltl mbpin
fltl sclpin
fltl sdapin
hubset ##$4000_0060 | boundedtap ' deglitch "filt0", 8 samples over 12.5% of bit-period
wrpin m_pinmon, sclpin ' Schmitt-triggered and deglitched
wrpin m_pinmon, sdapin
wrpin #1, mbpin ' mailbox repository for byte count (buffer index)
dirh mbpin
wrfast #$100, buffadr ' 16 kByte ring-buffer
testb sclpin, #5 wc
if_nc sets get_sample, #ina ' if port A
if_c sets get_sample, #inb ' if port B
bitl sclpin, #5
bitl sdapin, #5
bus_unsynced
call #get_sample
if_nz jmp #bus_unsynced ' loop while SCL low
getct pa
addct1 pa, ticks50us ' start 50 us timeout
mov oldsam, sample ' latch SDA on SCL rise
unsynced_scl_high
call #get_sample
if_c jct1 #idle_scl_high ' if SDA high and timed-out then synced
testb oldsam, sdapin xorc
if_nc_and_z jmp #unsynced_scl_high ' loop while SCL high and SDA steady
if_nz jmp #bus_unsynced ' ensure SCL high still
idle_scl_high
testb sample, sdapin wc
testbn oldsam, sdapin andc ' if SDA rise
if_c wfword #%01 ' indicates a stop bit
if_c add bytecount, #1
wxpin bytecount, mbpin
.idle_loop
call #get_sample
if_z jmp #.idle_loop ' ignore SDA until SCL falls
if_c jmp #bus_unsynced ' if SDA high then not a start
getct pa
addct2 pa, ticks25ms ' start 25 ms timeout
mov count, #9 ' data bits, including ACK/NAK
mov databyte, #1 ' indicates a start bit
bit_scl_low
jct2 #bus_unsynced ' error on timeout
call #get_sample
if_nz jmp #bit_scl_low ' loop until SCL rises
getct pa
addct1 pa, ticks50us ' start 50 us timeout
rcl databyte, #1 ' msbit first
mov oldsam, sample ' latch SDA on SCL rise
bit_scl_high
jct1 #bus_unsynced ' error on timeout
call #get_sample
testb oldsam, sdapin xorc
if_c_and_z jmp #bus_unsynced ' error if SDA has changed
if_z jmp #bit_scl_high ' loop until SCL falls
djnz count, #bit_scl_low ' loop for next bit
shl databyte, #2 ' reserved for error/stop
wfword databyte ' start + completed byte + ACK
add bytecount, #1
char_scl_low
jct2 #bus_unsynced ' error on timeout
call #get_sample
if_z jmp #char_scl_low ' ignore SDA until SCL rises
getct pa
addct1 pa, ticks50us ' start 50 us timeout
rcl databyte, #1 ' possible next byte
mov oldsam, sample ' latch SDA on SCL rise
char_scl_high
jct1 #bus_unsynced ' error on timeout
call #get_sample
testb oldsam, sdapin xorc
if_nc_and_z jmp #char_scl_high ' loop until SCL falls or SDA changes
if_c jmp #idle_scl_high ' if SDA changed then stop/restart
mov count, #8 ' else next byte has already started
mov databyte, #0
jmp #bit_scl_low
get_sample
mov sample, 0-0 '=== sample pins ===
testb sample, sdapin wc ' check SDA level
_ret_ testb sample, sclpin wz ' check SCL level
bytecount long 0
ticks50us long (_clkfreq + 10000) / 20000
ticks25ms long (_clkfreq + 20) / 40
m_pinmon long P_SYNC_IO | P_SCHMITT_A | P_FILT0_AB | P_HIGH_FLOAT | P_LOW_FLOAT
m_pinsim long P_SYNC_IO | P_SCHMITT_A | P_FILT0_AB | P_LOW_1MA | P_HIGH_1MA
buffadr long 0
sclpin long 0
sdapin long 0
mbpin long 0
sample long 0
oldsam long 0
count long 0
databyte long 0
Comments
I suppose I should ask if anyone has done such a monitor already?
Definitely interested in this Evanh. We're starting to do a whole lot more with i2c
Is it intended to be something along the lines of I2CDriver?
Well, the monitoring part anyway; on second look, it seems that can do some sort of active testing, too.
Either way, yes, tools like that are great to have. 👍
Not a Propeller by the looks. Oh, Forth! That was unexpected. That's one language that bamboozles me.
I don't at all understand how your code works, but here's my take on a i2c monitor.
It's essentially a stripped down version of my P1 slave object, ported to the P2 and with all of the writer pieces removed, converts received bits into an ASCII string.
Thanks Chris. I'll have a read of that.
The monitoring process does not attempt to drive the bus at any stage. It's not really a master nor slave, it passively sits and watches. So the code is less as a result. It should be able to record both sent commands and the responses to those commands. The recording will indicate the various I2C bus states as the recording progresses. Maybe even time-stamped.
Okay, yep, you've got it like that. And the whole test wrapper is nice. Good stuff.
You'll note I've used TESTB, on a "sample" of the pins, rather than TESTP on the live pins. This prevents glitches from creating logic bugs in the state tracking.
There's a few logic differences in the state tracker too. It may not be important. I have yet to do any testing.