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:
- Gthub Testato I2C-Sniffers Overview
- Bill Grundmann’s i2c sniffer from 2009
- Github teknoid/i2c-sniffer from 2919
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:
- Debugging @ 5.3Mbit/sec (5333333 Baud) on Arduino and other Embedded systems
- Debugging @ 5.3Mbit/sec (5333333 Baud) on stm8s microprocessors
___ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _____
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,
You must be logged in to post a comment.