I²C or SMBus sniffer using Arduino

I had a need to continuously monitor a I²C bus, so I looked around for an Arduino solution, but didn’t find anything that suited my needs, so I wrote one myself.  None of the I²C-sniffers I was able to find could monitor continously, if you only need a sample-size that will fit in the 2kbyte Arduino RAM, there are many options:

In Testato overview of I²C-sniffers, he states A Arduino board or a ATmega or ATtiny as a logic analyzer is not a serious tool. It is a gimmick. Now you have been warned, we are moving into the land of the impossible.  To be fair I am not attempting to build a logic analyzer I am building an continues I²C transfer logger, which could be useful in figuring out potential i2c-clashes when using unsupported graphics cards in an older iMac.   This blog-post only deals with the low-level I²C-sampling. the end product  here will be something like S 4e+ 48+ P being displayed in a terminal-window.

To control an I²C device from Linux I use an USBASP with Till Harbaum’s I²C-Tiny-USB firmware, checkout my blog-post from 2015 i2c og One-Wire it is in Danish, for English or other languages, there is a Translate button on the top right hand side on the WEB-page.

Basic information abut I²C can be found at I²C wikipedia, while timing and the full standard  is at NXP I²C-bus specification

  • in I²C standard mode (100 kHz)  the hold time is generally 4 or 4.7 usec (NXP table 11)
    in 4 usec an atmega*8 can execute 64 instructions.
  • in I²C fast mode (400 kHz) the SCL high time can be as low as 0.6 usec, so this gimmick will not be able to reliably detect that.

It is worth noting that the SMBus, and many common I²C devices like PCF8574 operate at max 100kHz.

This sniffer is divided into two parts, a small sampler running on the Arduino, sending serial data to a Linux/MacOs host where a another program presents the data to the user. You could of course tailor this to your specific needs to obtain the information you are hunting.

 The key to this implementation is running the serial port at 2 Mbit/sec avoiding a lot of handshaking on the Arduino part, just write the data to UartDataRegister and you are done, as long as you are doing this more than 8 assembler instructions apart, everything should be fine.

I have good experience using 5.3 Mbit/s on serial ports while debugging atmega/stm8s, this moves the buffering complexity out of the micro-controller and lets the USB serial-adapter handle it,  although I must say that I prefer PL2303 over the  CH340 which is standard in Chinese Arduino-clones. I have written about high speed debugging in:

    ___    _   _   _   _   _   _   _   _   _     _   _   _   _   _   _   _   _   _   _____
SCL    \__/1\_/2\_/3\_/4\_/5\_/6\_/7\_/8\_/9\___/1\_/2\_/3\_/4\_/5\_/6\_/7\_/8\_/9\_/
    _     ___ ___ ___ ___ ___ ___ ___ ___       ___ ___ ___ ___ ___ ___ ___ ___ ___     __
SDA  \___X___X___X___X___X___X___X___X___X_____X___X___X___X___X___X___X___X___X   \___/
    Start <----- 7 bit addres ------>  rw  ack  <-------- 8 bit Data ---------> Nack  Stop

state          SCL   SDA    comment
(S) Start       1     \_    controller pull SDA low
(0) bit=0       _/    0     SDA unchanged while SCL high
(0) bit=0       \_    0     controller pull SCL low SCL
(1) bit=1       _/    1     SDA unchanged while SCL high
(1) bit=1       \_    1     controller pull SCL low
(P) Stop        1     _/    controller releases SDA                                                             

The I²C protocol consists of:

  • Start condition
  • a number of 9 bit transfers
  • Stop condition.

A 9 bit bursts can further be decoded as:

  • first one is: 7 bit address. 1 bit  r/w, 1 bit Ack/Nack
  • the following are: 8 bit data, 1 bit Ack/Nack

the target supplies Ack, or nothing which (due to the pull-up) will be seen as a Nack

The protocol I designed for the uplink, sends 2 bytes on rs232 for every 9 bit burst seen on I²C, so for a 100 kHz  I²C bus this would amount to max 200 kbit/sec so the UART connection should never be a bottle-neck here. 2 Mbit/sec is max baudrate for the atmega UART, so thats what I am using here.

I have included code to check the sampling rate, which was very help full when developing/tuning the program, Timer-0 running @ 16 MHz is used to measure instructions per loop, which maxes out at 25, of which 5 can be attributed to the code measuring it.

The sampling rate is better than 20/16Mhz -> 1.25 usec -> 800 kHz  (25/16 ->640kHz with sample rate report enabled),  in standard  I²C bus speed @ 100kbit/sec. no state is faster than 4 usec (125kHz).

Timer-1 running @ 62.5 kHz, is used to report the I²C-speed and idle time between transaction. The time is coded logarithmic in 16 steps from 16 usec to 1 second, before being send to Linux/MacOS host.

Below is the output by i2c_snffer, sniffing on a LCD controlled via pcf8574 from another Arduino. The traffic is the result of sending one character to the LCD every 10 seconds.  Exercise: what are the two characters beeing send? the answer can be found at the end of this post

$ i2c_sniffer -?
Usage: i2c_sniff <options>
-l /dev/ttyUSB0
-v show speed. idle time
-vv also show sample, 16cycles = 1usec
-vvv also show raw input

$ i2c_sniffer -vv -l /dev/ttyUSB1
i2c_sniffer on /dev/ttyUSB1 @ 2000000
tty_open /dev/ttyUSB1 baud=2000000 fd=3
dtr/rts reset
i2c_sniff
idle 8 seconds idle 32 msec sample=25 cycles
S 4e+ 48+ P 10 kHz idle 16 usec
S 4e+ 4c+ P 20 kHz idle 16 usec
S 4e+ 48+ P 20 kHz idle 32 usec
S 4e+ 99+ P 20 kHz idle 32 usec
S 4e+ 9d+ P 20 kHz idle 32 usec
S 4e+ 98+ P 20 kHz idle 10 seconds
S 4e+ 48+ P 10 kHz idle 16 usec
S 4e+ 4d+ P 20 kHz idle 16 usec
S 4e+ 49+ P 20 kHz idle 16 usec
S 4e+ a9+ P 20 kHz idle 16 usec
S 4e+ ad+ P 20 kHz idle 32 usec
S 4e+ a8+ P 10 kHz idle 10 seconds
...
  • S Start condition
  • + Ack
  • – Nack
  • P Stop condition and the frequency seen for this transaction
  • idle time between transactions

The source code consist of 3 files

  • i2_sniff.h  96 lines also describes the i2c and the uplink protocol
  • i2_sniff.ino 190 lines Arduino code
  • i2c_sniffer.c 305 lines, on Linux/Macos to compile execute make i2c_sniffer

Thats it, hope it might help some-one, as always the code is written by me and the license is (c) Beerware

i2c_sniff.h

#ifndef i2c_sniff_h
/*
 * i2c sniffer by storepeter (C) 2022 Beerware
 * sends sampled data on rs232 @ 2Mbit for further analysis on linux
 *
 * https://en.wikipedia.org/wiki/I%C2%B2C
 * for timing i2c timing see https://rheingoldheavy.com/i2c-signals/
 *     ___    _   _   _   _   _   _   _   _   _     _   _   _   _   _   _   _   _   _   _____
 * SCL    \__/1\_/2\_/3\_/4\_/5\_/6\_/7\_/8\_/9\___/1\_/2\_/3\_/4\_/5\_/6\_/7\_/8\_/9\_/
 *     _     ___ ___ ___ ___ ___ ___ ___ ___       ___ ___ ___ ___ ___ ___ ___ ___ ___     __
 * SDA  \___X___X___X___X___X___X___X___X___X_____X___X___X___X___X___X___X___X___X   \___/
 *     Start <----- 7 bit addres ------>  rw  ack  <-------- 8 bit Data ---------> Nack  Stop
 * 
 * state		SCL	SDA	comment
 * (N) inactive		1	1	pulled up, free to claim arbitration
 * (S) Start		1	\_	controller pull SDA low (falling edge)
 * (i) idle		1	0	held low by controller
 * (0) bit=0		_/	0	SDA unchanged while SCL high (rising edge)
 * (0) bit=0		\_	0	controller pull SCL low (falling edge)
 * (1) bit=1		_/	1	SDA unchanged while SCL high (rising edge)
 * (1) bit=1		\_	1	controller pull SCL low (falling edge)
 * (P) Stop		1	_/	controller releases SDA  (rising edge)
 * (CS) clock stretch	0	X	target pulls SCL low until SDA set (if rd) (while it was low)
 * (Sr) Repeated Start	1	\_	Same as start but not stop condition since last start
 *
 *  7bit address: S adr7 R/W Ack data8 Ack data8 Nack Stop
 * S xxxxxxx r/w ack
 * 10bit address: S 11110 adr2 R/W Ack adr8 data8 Ack data8 Nack Stop
 * S 11110xx r/w ack xxxxxxxx ack
 *
 * SMBus is i2c @ 100kbit
 *
 * The Protocol send to linux is simple
 * State 2 bit + 4 bit data,
 *
 * START + 4 msb
 * DATA  + 4 msb
 * Ack   + 4 lsb
 * Nack  + 4 lsb
 * STOP  + 4bit time since last START, logarithmic
 * IDLE  + 4bit time since last STOP/IDLE, logarithmic, idle -> 2 Hz heartbeat
 */
#define START	0x20
#define DATA	0x30
#define ACK	0x40
#define NACK	0x50
#define STOP	0x60
#define IDLE	0x70
#ifdef RECORD_SAMPLING_TIME
#define SAMPLE_CHK	0x90
#endif
/* timing 400kHz, minimum xfer would be 20 bit i.e. 20kHz = 50 usec. is enough resolution
 * Timer prescaler options
 * 16 MHz  8_prescaler -> 500 nsec
 * 16 MHz  64_prescaler -> 4 usec
 * 16 MHz  256_prescaler -> 16 usec	This i what we will use
 * 16 MHz  1024_prescaler -> 64 usec
 * 0x0001 0x01  16 usec
 * 0x0002 0x02  32 usec
 * 0x0004 0x03  64 usec
 * 0x0008 0x04 128 usec
 * 0x0010 0x05 256 usec
 * 0x0020 0x06 512 usec
 * 0x0040 0x07   1 msec
 * 0x0080 0x08   2 msec
 * 0x0100 0x09   4 msec
 * 0x0200 0x0a   8 msec
 * 0x0400 0x0b  16 msec
 * 0x0800 0x0c  32 msec
 * 0x1000 0x0d  64 msec
 * 0x2000 0x0e 128 msec
 * 0x4000 0x0f 256 msec
 * 0x8000 0x10 512 msec
 *
 * 0xFFF8 0x80  1 sec	i.e 1Hz heartbeat when no trafic
 */
/* S adr7 R/W Ack  - data8 Ack - data8 Nack - Stop
 * 0x2? 0x4?       - 0x3? 0x4? - 0x3? 0x5?  - 0x6? 0x80 0x80..
 * logarithmic idle timer:
 * STOP=0x63 ->  32 usec -> 400kBit 20bit  50usec
 * STOP-0x65 -> 128 usec -> 100kBit 20bit 200usec
 */
//    prev    currrent
//    scl sda scl sda
// 	0 0	0 0	-
// 	0 0	0 1	normal setting of SDA
// 	0 0	1 0	SDA bit=0  stable until scl_pulldown
// 	0 0	1 1	UNEXPECTED SDA should not change when SCL changes
// 	0 1	0 0	normal clearing of SDA
// 	0 1	0 1	-
// 	0 1	1 0	UNEXPECTED SDA should not change when SCL changes
// 	0 1	1 1	SDA bit=1 stable until scl_pulldown
// 	1 0	0 0	bit=0	scl_pulldown
// 	1 0	0 1	bit=1	scl_pulldown
// 	1 0	1 0	-
// 	1 0	1 1	STOP condition
// 	1 1	0 0	bit=0	scl_pulldown
// 	1 1	0 1	bit=1	scl_pulldown
// 	1 1	1 0	STAR conditionT
// 	1 1	1 1	-

#endif	// i2c_sniff_h

i2c_sniff.ino – Arduino IDE compiles and loads this onto HW

#ifdef NQ_HEADER_BEGIN_do_not_delete
#endif	// NQ_HEADER_END_do_not_delete
/*
 * i2c sniffer by storepeter (C) 2022 Beerware
 * sends sampled data on rs232 @ 2Mbit for further analysis on linux
 *
 * SCL PB1 - pin 9 on Arduino	to A5 SCl on arduino being monitored
 * SDA PB0 - pin 8 on Arduino	to A4 SDA on arduino being monitored
 */
#define  RECORD_SAMPLING_TIME 1
#ifdef ARDUINO
// I am using my own build system,
// here is what is needed to make this compile from the Arduino IDE
# define NQ_PUBLIC
# if   defined(__AVR_ATmega8__)
#  define _BV_URSEL _BV(URSEL)	// atmega8 need to set URSEL to modify UCSRC
# else				// Newer atmega168/atmega328p
#  define _BV_URSEL 0		// atmega8 need to set URSEL to modify UCSRC
#  define TXEN   TXEN0
#  define U2X    U2X0
#  define UBRRL  UBRR0L
#  define UCSRA  UCSR0A
#  define UCSRB  UCSR0B
#  define UCSRC  UCSR0C
#  define UCSZ0  UCSZ00
#  define UCSZ1  UCSZ01
#  define UDRE  UDRE0
#  define UDR    UDR0
# endif
#else
# include "nq.h"
#endif

#include "i2c_sniff.h"

#define SCL _BV(1)
#define SDA _BV(0)

const uint8_t my_log[256]
    __attribute__((aligned(256)))	// aligned to 256 so r2,r3=my_log -> r3=index
    __attribute__((section(".fini9"))) = {
	0 + STOP, 1 + STOP, 2 + STOP, 2 + STOP, 3 + STOP, 3 + STOP, 3 + STOP, 3 + STOP, 4 + STOP, 4 + STOP, 4 + STOP, 4 + STOP, 4 + STOP, 4 + STOP, 4 + STOP, 4 + STOP,	// 0x00
	5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP, 5 + STOP,	// 0x10
	6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP,	// 0x20
	6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP, 6 + STOP,	// 0x30
	7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP,	// 0x40
	7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP,	// 0x50
	7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP,	// 0x60
	7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP, 7 + STOP,	// 0x70
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0x80
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0x90
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0xa0
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0xb0
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0xc0
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0xd0
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP,	// 0xe0
	8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP, 8 + STOP	// 0xf0
};

static inline uint8_t do_my_log(int8_t offset)
{
	uint8_t value;
	asm volatile ("mov r31,r3\n\t" "mov r30,%1\n\t" "lpm %0,z\n\t":"=r" (value)
		      :"r"(offset)
	    );
	return value;
}

NQ_PUBLIC void _nqprintc(char ch)
{
	while (!(UCSRA & _BV(UDRE))) ;
	UDR = ch;
}

#ifdef SAMPLE_CHK
#define timer0_reset()	do { TCNT0 = SAMPLE_CHK; } while (0)
#else
#define timer0_reset()	while (0)
#endif
int main()
{
	register uint8_t i2c asm("r16");
	register uint8_t prev asm("r17");
	uint8_t bit;
	uint8_t data;
	uint8_t c_time;
	uint8_t c_state;
	register const uint8_t *my_log_pt asm("r2");	// r2=lsb r3=msb
	my_log_pt = &(my_log[0]);
#ifdef SAMPLE_CHK
	register uint8_t max;
	max = 0;
	TCCR0B = _BV(CS00);	// Timer0 8bit  prescaler=18  16 Mhz clk
#endif
	TCCR1B = _BV(CS12);	// Timer1 16bit prescaler=256 16 usec clk
	UCSRA |= _BV(U2X);	// double speed
	UCSRB |= _BV(TXEN);
	UCSRC = _BV_URSEL | _BV(UCSZ1) | _BV(UCSZ0);	// config USART; 8N1 (BV_URSEL for atmega8
	UBRRL = 0;		// 2 Mbit/sec

	const char *cpt = "i2c_sniff\n";
	while (*cpt)
		_nqprintc(*cpt++);

	bit = 0;
	data = 0;
	c_time = 0;
	c_state = 0;
	i2c = PINB & 0x03;	// SCL=PB0, SDA=PB1
	prev = i2c;		// SCL=PB1, SDA=PB0
	timer0_reset();
	while (1) {
#ifdef SAMPLE_CHK
		if (TCNT0 > max) {	// will settle after a few cycles
			max = TCNT0;
			UDR = max;	// get it out there hints where it was created too
		}
		timer0_reset();
#endif
		prev = i2c;
		i2c = PINB;	// SCL=PB1, SDA=PB0
		if (prev & SCL) {	// SCL = """\__
			if ((i2c & SCL) == 0) {	// SCL pulled down
				if (bit == 9) {
					if (prev & SDA) {	// ACK
						data |= NACK;
					} else {
						data |= ACK;
					}
					UDR = data;
					bit = 1;
					continue;
				}
				data <<= 1;
				data &= 0x0f;	// 4 lsb contains half byte
				data |= i2c & SDA;
				if (bit == 4) {
					data |= c_state;	// first START then DATA
					UDR = data;
					c_state = DATA;
				}
				bit++;
				continue;
			}	// SCL unchanged high, SDA must have changed
			if (prev & SDA) {	// SDA was 0
				if ((i2c & SDA) == 0) {	// SDA is now 1
					// SDA was pulled low START condition
					if (c_time) {	// send idle time if there is one
						UDR = c_time;
					}
					c_time = 0;	// clear timer1 (16 bit)
					c_state = START;
					bit = 0;	// falling scl advances to 1,while SDA=0
					continue;;
				}
			} else {	// SDA was 0
				if (i2c & SDA) {	// SDA is now 1
					// SDA went high, STOP condition
					UDR = do_my_log(TCNT1L);
					c_time = 0;	// clear timer1 (16 bit)
					timer0_reset();	// sample time only recorded when active
					continue;
				}
			}
			// SDA unchanged
		}
		if (c_time == 0) {
			TCNT1 = 0;	// clear timer1 (16 bit)
		}
		// we are not busy so just figure out what c_time is now
		union {
			uint8_t u8[2];
			uint16_t u16;
		} count;
		count.u16 = TCNT1;
		if (count.u8[1] > 0xf8) {
			UDR = IDLE + 8 + 8;
			c_time = 0;	// clear timer1 (16 bit)
			timer0_reset();	// sample time only recorded when active
			continue;
		}
		if (count.u8[1]) {
			c_time = IDLE - STOP + 8 + do_my_log(count.u8[1]);
		} else {
			c_time = IDLE - STOP + do_my_log(count.u8[0]);
		}
		timer0_reset();	// sample time only recorded when active
	}
}

i2c_sniffer.c  – on linux/MacOS  compile with make i2c_sniffer

#ifndef ARDUINO
/*
 * i2c sniffer by storepeter (C) 2022 Beerware
 * decode the i2c sniff stream @ 2Mbit from arduino
 */
#define MAIN
#define RECORD_SAMPLING_TIME 1	//no harm in havin decoding present allways
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <strings.h>
#ifdef __linux__
# include <asm/termios.h>
# include <asm/termbits.h>
int tcdrain (int __fd);
int ioctl(int fd, unsigned long request, ...);
#else
# include <sys/ioctl.h>
# include <termios.h>
#endif
//#include "nq.h"
uint8_t dprint_level=0;
#define   do_nothing  while (0)
#define DO(something) do {something;} while (0)
#define     DTRACE(args...) DO(if (dprint_level>0) args)
#define    DDTRACE(args...) DO(if (dprint_level>1) args)
#define   DDDTRACE(args...) DO(if (dprint_level>2) args)
#define  DDDDTRACE(args...) do_nothing
#define     dprint(fmt...)    DTRACE( printf(fmt))
#define    ddprint(fmt...)   DDTRACE( printf(fmt))
#define   dddprint(fmt...)  DDDTRACE( printf(fmt))
#define  ddddprint(fmt...) DDDDTRACE( printf(fmt))
#define   dhexdump(pt,l)      DTRACE( hexdump(pt,l))
#define  ddhexdump(pt,l)     DDTRACE( hexdump(pt,l))
#define dddhexdump(pt,l)    DDDTRACE( hexdump(pt,l))
#define     dwheel()          DTRACE( wheel())

#include "i2c_sniff.h"

void usage(char *fmt, ...)
{
	va_list va;
	va_start(va, fmt);

	printf("Usage: i2c_sniff <options>\n");
	printf(" -l  /dev/ttyUSB0\n");
	printf(" -v   show speed. idle time\n");
	printf(" -vv  also show sample cycles 16=1usec\n");
	printf(" -vvv also show raw inout\n");
	if (fmt) {
		printf("ERROR: ");
		vprintf(fmt, va);
		printf("\n");
	}
	va_end(va);
	exit(1);
}

#define return_onfail(function) do { if ((function) < 0) { perror(__func__); return -1; } } while (0)
int tty_open(const char *devname, int baudrate)
{
	int fd;
#ifdef __linux__
        struct termios2 tio;
#else
        struct termios tio;
#endif

	dprint("tty_open %s baud=%d", devname, baudrate);

	return_onfail( fd = open(devname, O_RDWR | O_NOCTTY | O_NONBLOCK) );
#ifdef __linux__
	return_onfail( ioctl(fd, TCGETS2, &tio) );
#else
	return_onfail( tcgetattr(fd, &tio) );
#endif

	tio.c_cflag = CS8 | CLOCAL | CREAD;
	tio.c_iflag = IGNBRK;
	tio.c_lflag = tio.c_oflag = 0;
#ifdef __linux__
	tio.c_cflag &= ~CBAUD;
	tio.c_cflag |= CBAUDEX;
	tio.c_ispeed = baudrate;
	tio.c_ospeed = baudrate;
#else
	cfsetospeed(&tio, baudrate);
	cfsetispeed(&tio, baudrate);
#endif

	tio.c_cc[VMIN]  = 1;
	tio.c_cc[VTIME] = 0;

#ifdef __linux__
	return_onfail( ioctl(fd, TCSETS2, &tio) );
#else
	return_onfail( tcsetattr(fd, TCSANOW, &tio) );
#endif

	int r = fcntl(fd, F_GETFL, 0);
  	if (r != -1) {
    		fcntl(fd, F_SETFL, r & ~O_NONBLOCK);
	}

	dprint(" fd=%d\n", fd);
	return fd;
}
int set_dtr_rts(int fd, int value)
{
	unsigned int  ctl;
	return_onfail( ioctl(fd, TIOCMGET, &ctl) );
	if (value) { // Set DTR and RTS
		ctl |= (TIOCM_DTR | TIOCM_RTS);
	} else { // Clear DTR and RTS
		ctl &= ~(TIOCM_DTR | TIOCM_RTS);
	}
	return_onfail(ioctl(fd, TIOCMSET, &ctl) );
	return 0;
}

void arduino_reset(int fd)	// arduino_reset ( -- )
{
	ddprint("dtr/rts reset\n");
	set_dtr_rts(fd, 0);	// Clear DTR and RTS to unload the RESET capacitor 
	usleep(500*1000);
	tcdrain(fd);		// get rid of crap in inbuf
	set_dtr_rts(fd, 1);	// Set DTR and RTS back to high
}

int input_available(int fd)
{
	struct timeval tv;
	int r;
	int len = 0;
	fd_set fds;

	tv.tv_sec = 0;
	tv.tv_usec = 1000;	// 1 msec, 100000 baud, 10 kbyte, 1msec = 10 char

	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	r = select(fd + 1, &fds, NULL, NULL, &tv);
	if (r < 0) {
		usage( "select failed%s\n", strerror(errno));
	}
	if ((FD_ISSET(fd, &fds))) {
		if (ioctl(fd, FIONREAD, &len) < 0) {
			usage("Failed to get byte count on serial.\n");
		}
	}
	return len;
}
void wheel()
{
        static uint8_t i;
        i++;
        i &= 0x3;
        printf(" %c\b\b", "-/|\\"[i]);
}
void hexdump(const void *cv, int len)
{
        const uint8_t *s = (const uint8_t *) cv;
	while ( len) {
        	char c;
        	int i;
		int l = (len>16) ? 16 : len;
        	for (i=0; i< l; i++) {
                	printf("%s %02x", (i==8) ? " " : "",s[i]);
        	}
        	if (i<=8) printf(" ");
        	for (i=0; i<(3*(16-l)); i++) {
        		printf(" ");
        	}
        	printf(" [");
        	for (i=0; i< l; i++) {
			c = s[i];
                	if ((c < ' ') || (c > '~'))  c='.';
                	printf("%s%c", (i==8) ? " " : "", c);
        	}
        	printf("]\n");
		len -= l;
	}
}
char *duration(int l)
{
	static char buf[20];
	ddddprint(" l=%d ", l);
	if (l < 7) {
		sprintf( buf, "%d usec", 16<<l);
	} else {
		sprintf( buf, "%d msec", 1<<(l-7));
	}
	return buf;
}
char *speed(int bits, int l)
{
	static char buf[30];
	int t;
	if (l < 7) {
		t = 16<<l;
	} else {
		t = 1000<<(l-7);
	}
	int bps = bits * 1000000 / t;
	ddddprint(" %d bits/%d usec =  bps=%d ", bits, t, bps);
	if (bps > 1000) {
		sprintf(buf, "%d kHz", bps/1000);
	} else {
		sprintf(buf, "%d Hz", bps);
	}
	return buf;
}
int main(int argc, char *argv[])
{
	int opt;
	int fd;
	int len;
	char *devname = "/dev/ttyUSB0";
	uint8_t buf[256];
	dprint_level=0;
	while ((opt = getopt(argc, argv, "l:v")) != -1) {
		switch (opt) {
		case 'l':
			devname = optarg;
			break;
		case 'v':
			dprint_level++;
			break;
		default:
			usage("unknown option");
		}
	}
	dprint("i2c_sniffer on %s @ %d\n", devname, 2000000);

	if ((fd = tty_open(devname, 2000000)) <= 0) {		// do not have an NQ_INIT
		usage("cannot open %s", devname);
	}
	arduino_reset(fd);
	do {
		len = input_available(fd);
		if (1 != read(fd, buf, 1)) {
			usage("read failed");
		}
		printf("%c", buf[0]);
	} while (buf[0] != '\n');
	int idle = 0;
	while (1) {
		int n = 0;
		if ((len = input_available(fd)) > 0) {
			if (len > 256) {
				len = 256;
			}
	       		if (len != read(fd, buf, len)) {
				usage("read failed");
			};
			for (int i=0; i<len; i+=16) {
				dddhexdump( buf + i, len>16 ? 16 : len);
			}
			for (int i=0; i<len; i++) {
				uint8_t c = buf[i];
				uint8_t state = c & 0xf0;
				uint8_t lsb = c & 0x0f;
				if (idle && (c != (IDLE+16)) ) {
					dprint(" idle %d seconds", idle);
					idle = 0;
				}
				if ( state == START) {
					printf("\nS %x", lsb);
					n = 1;
				} else if ( state == DATA) {
					printf(" %x", lsb);
					n++;
				} else if ( state == ACK) {
					printf("%x+", lsb);
				} else if ( state == NACK) {
					printf("%x-", lsb);
				} else if ( state == STOP) {
					printf(" P");
					dprint(" %s", speed( n * 10, lsb));
				} else if ( c >= SAMPLE_CHK) {
					ddprint(" sample=%d cycles", c - SAMPLE_CHK);
				} else if ( c == (IDLE+16)) {
					idle++;
					dwheel();
				} else if ( c >= IDLE) {
					dprint(" idle %s", duration(c - IDLE));
				} else {
					printf(" ?=%02x ",c & 0xff);
				}
				fflush(stdout);
			}
		}
	}
}
#endif

Amd the solution to the exercise is 0x49 0x4a ‘I’ and ‘J’, The LCD is connected in driven in 4 bit mode, with 4-msb connected tot the datalines,

This entry was posted in Arduino, Embedded, Linux, Mac. Bookmark the permalink.