📜 ⬆️ ⬇️

I2C sniffer

Good day! Somehow a problem arose at work - there is a device working on I2C and the protocol of which was necessary to understand. Consequently, we need a sniffer for the I2C interface, which would output everything that comes and goes on I2C, to the UART port and then through the converter to the COM port of the computer.

Start


Only a couple of atmeg8 were lying at hand, and I decided why not to use them. Next, the question arose scheme sniffer.

There were 2 options - to include a sniffer in parallel, or in the open circuit. Obviously, the first option looks much simpler, which in fact turned out to be completely different. But first things first.

In short, about the interface itself. In I2C (TWI at atmelovski) uses two wires - SCL and SDA. The first is responsible for the clocking of the signal, the second for the transfer of information directly. The interface also has START and STOP conditions.
')
So, my first thought was to take a dipstick and, on the one hand, connect it to the foot of an external interrupt on atmega8 with another on the SDA line and catch the leading front, and determine 0 or 1 for the elapsed time. Obviously, it had to work very badly , because the STOP signal was not properly processed.

The second thought was to do the same, but to interrupt the interception on the SCL line, and on the interruption to read the SDA line connected to a regular digital foot. Here everything looked more viable, except for the same STOP condition, but I decided to try to assemble on a breadboard and see what happens.

I apologize in advance if you find obvious mistakes in the code below, since I’m recovering the rejected versions of the code from memory on my knees.

The interrupt handler code looked like this:

ISR(INT0_vect) { cli(); if (bitIsHigh(PINB, 0)) uart_send_char('1'); else uart_send_char('0'); sei(); } 

Zeros and ones flowed into the port, but it immediately became obvious that the data was incorrect - there were far fewer expected ones and they changed when the same request was repeated. In the process of finding out the reasons, it all came down to the fact that the data was lost due to accessing the uart interface, which, by the way, worked at a maximum stable speed of 38 kbps, while the I2C itself worked at 100 kbps. It was not possible to raise the speed of the UART due to the lack of a crystal of the necessary frequency in order to translate uart to an acceptable speed. Therefore, it was necessary to remove the work with uart from the interrupt. Got something like this:

 static uint8_t data = 0; static uint8_t idx = 7; ISR(INT0_vect) { cli(); data |= bitIsHigh(PINB, 0) << (idx--); if (!idx) { uart_send_char(data); data = 0; idx = 7; } sei(); } 

Work has all become more stable, only the data still did not carry any meaning. After several hours of elaboration of the algorithm, the inclusion of STOP processing, etc., it was nevertheless decided to go another way.

On the right track


No matter how hard I tried to implement a sniffer according to a parallel inclusion scheme, nothing came of it. Based on this, only one option remained - it was necessary to turn on the microcontroller into the gap, that is, it should be both a master for the response device and a slave for the original master. It sounds probably confusing, but in reality everything is not so.

Since atmega8 has only one I2C hardware on board, it is obvious that in order to work you need to write software support for the protocol.

The result is the following code:

 ISR(TWI_vect) { cli(); uint8_t status = TWSR; uint8_t b; char s[4]; s[3] = 0; _delay_ms(1); switch (status & I2C_STATUS_MASK) { case I2C_STATUS_SR_RX_ADR_ACK:/* case I2C_STATUS_SR_RX_ADR_NACK:*/ uart_send_str("-AW:"); uart_send_int( TWDR ); i2csoft_start(); i2csoft_open_write(I2C_ADDRESS); break; case I2C_STATUS_SR_RX_DATA_ACK:/* case I2C_STATUS_SR_RX_DATA_NACK:*/ b = TWDR; sprintf(s, " %.2X", b); uart_send_str(s); i2csoft_write_byte(b); break; case I2C_STATUS_SR_RX_STOP_RESTART: uart_send_str("E\n"); _delay_ms(10); do { _delay_us(5); i2csoft_start(); } while (!i2csoft_open_read(I2C_ADDRESS)); break; case I2C_STATUS_BUS_ERROR: uart_send_str("B\n"); break; case TW_ST_SLA_ACK: uart_send_str("-AR:"); uart_send_int( TWDR ); b = i2csoft_read_byte(); sprintf(s, " %.2X", b); uart_send_str(s); TWDR = b; break; case TW_ST_DATA_ACK: b = i2csoft_read_byte(); sprintf(s, " %.2X", b); uart_send_str(s); TWDR = b; break; case TW_ST_DATA_NACK: case TW_ST_LAST_DATA: b = i2csoft_read_byte(false); uart_send_str("L\n"); break; default: uart_send_char('U'); uart_send_int(status); uart_send_char(' '); break; } TWCR |= (1<<TWINT); sei(); } 

The master device is connected to the I2C hardware atmega, the executing device is connected to any 2 digital legs, one of which works in SCL mode, the other - SDA. All that the above code does is receive an I2C interrupt and cause a similar state on the software interface, while the service information is written to the uart to help understand what is happening. Exhibited delays were selected for a specific device and may differ slightly for others. As a result, we get quite a good sniffer.

If anyone is interested, the source can be taken from the github .

Source: https://habr.com/ru/post/357918/


All Articles