Extracting data from I2C bus (I2C Sniffer)

Hello everyone

Let me start off by explaining what exactly my project is about. I have a radio that I'm trying to tap into the volume data being sent from the radios main processor to an audio control chip that handles the volume.

The reason why I want to get this data out, is because I want to convert the radio to only output digital audio via optical (This part is already done and working), but the digital signal is unregulated and I would need to handle volume control at the receiving end of the signal.

The data being sent between the processor and audio control chip seems to be a burst of 22 bytes:

I've also measured the speed of the data plus the minimum time between each burst of data:

  • SCL speed: Around 300KHz
  • Minimum time between bursts: 40 ms

And I have mapped out that one of the bytes is the volume byte. So far so good.

Now to the actual question - Is it possible to listen to this data, extract it and send it via serial as hex data? and if so, any pointers to how?

In my research I have found mixed results about whether or not it is possible to do on an Arduino at the speed of this bus. The only one I seemed to have found was this:
https://www.avrfreaks.net/projects/i2c-twi-sniffer

Any input would be greatly appreciated
/Kenneth

From AVRfreak

...no ATmega can be used because they can not listen only...

Looks like it was a tiny85 they were using. Maybe the tiny USI two wire mode can SPY on I2C. I had no luck with it as a slave, the mega TWI hardware worked fine as a slave. There is no Makefile so my investigating stopped.

Can't you use a scope with builtin i2c decoding feature?

I have designed this in the past to sniff a i2c display of a DAB radio. The trick is to 'sit' on the same address to catch commands and print it to the serial monitor. It creates macros with hex values of each command that you can replay with displayrawwrite.ino below first sketch. You can copy the macros in the serial monitor into this sketch.

displaysniffer.ino:

#include "Wire.h"

#define IPMB_I2C_ADDRESS 0x3c // The I2C address of this device
#define IPMB_BHS_SERIAL_BAUD_SPEED 115200UL
#define IPMB_BHS_I2C_CLOCK_SPEED 200000UL // 200Mhz is max, higher don't work

static const char szNibbleToHex[16] = { "0123456789ABCDEF" };

char* getHexStr( byte n )
{
static char sHex[5] = {'0', 'x', '?', ' ', '\0' };
sHex[2] = szNibbleToHex[(byte)(n >> 4)];
sHex[3] = szNibbleToHex[(byte)(n & 0x0F) ];
return (char *)&sHex[0];
}

bool getDataAvailable()
{ return ( Wire.available() > 0 ); }

uint8_t getData()
{ return Wire.read(); }

/*
The event handlers, how it works:

When a master (or an other slave) do a request with Wire.beginTransmission() to this
device and writes some byte data to it with the Wire.write() method and ends the
transmission with Wire.endTransmission(), the eventCollectRequestDataHandler() method
is called. The eventCollectRequestDataHandler() 'eats', collects all of the data on the
bus (buffer) that has been send. It is important to consume all data to avoid data will
messed up when there is a next I2C request. The data represent a call for information,
data or instruction and is based upon the 'ipmbDataStruc' record format.

To finalize a request, to perform a result and release/restart the bus, the master (or an
other slave) must call the Wire.requestFrom() method to trigger the
eventHandleRequestReplyHandler() method. This method verifies the data previously
collected by the eventCollectRequestDataHandler() method and finally performs an action
and report the result to the master (or an other slave).
*/

void eventCollectRequestDataHandler(int iAvailBytes ) // #1 Get bytes to progress
{
Serial.print( "Wri(" );
//while ( getDataAvailable() )
if( iAvailBytes > 0 )
while( iAvailBytes-- )
{
//Serial.print( "0x" );
Serial.print( " "+String(getHexStr(getData()))+(iAvailBytes?",":"") );
}
Serial.println( " );" );

// Flush/eat/read unhandled bytes
//while( getDataAvailable() )
// { getData(); }
}

void eventHandleRequestReplyHandler() // #2 Finish request, implement received data
{
Serial.println("");
}

void setup()
{
// Start serial
Serial.begin( IPMB_BHS_SERIAL_BAUD_SPEED );

// Start I2C
Wire.begin( IPMB_I2C_ADDRESS );
//Wire.setClock( IPMB_BHS_I2C_CLOCK_SPEED );
Wire.onReceive( eventCollectRequestDataHandler );
Wire.onRequest( eventHandleRequestReplyHandler );
}

void loop()
{
}


displayrawwrite.ino (just an example how to replay):

#include <Wire.h>

void setup() {
// put your setup code here, to run once:

Wire.begin();

}

void loop() {
// put your main code here, to run repeatedly:
test();
delay(2000);

}

void Start()
{
delay(100);
Wire.beginTransmission(0x3C);
}

void Stop()
{
Wire.endTransmission();
}

#define _wri(n) if(n!=0xFF) {Wire.write(n); delay(1);}

void Wri( uint8_t d01 = -1,
uint8_t d02 = 0xFF,
uint8_t d03 = 0xFF,
uint8_t d04 = 0xFF,
uint8_t d05 = 0xFF,
uint8_t d06 = 0xFF,
uint8_t d07 = 0xFF,
uint8_t d08 = 0xFF,
uint8_t d09 = 0xFF,
uint8_t d10 = 0xFF,
uint8_t d11 = 0xFF,
uint8_t d12 = 0xFF,
uint8_t d13 = 0xFF,
uint8_t d14 = 0xFF,
uint8_t d15 = 0xFF,
uint8_t d16 = 0xFF,
uint8_t d17 = 0xFF,
uint8_t d18 = 0xFF,
uint8_t d19 = 0xFF,
uint8_t d20 = 0xFF )
{
delay(50);
Wire.beginTransmission(0x3C);
_wri(d01); _wri(d02); _wri(d03); _wri(d04); _wri(d05);
_wri(d06); _wri(d07); _wri(d08); _wri(d09); _wri(d10);
_wri(d11); _wri(d12); _wri(d13); _wri(d14); _wri(d15);
_wri(d16); _wri(d17); _wri(d18); _wri(d19); _wri(d20);
Wire.endTransmission();
}

void test()
{
Wri( 0x00, 0x01 );
Wri( 0x00, 0x0C, 0x06 );

// Welcome to digital radio:
Wri( 0x80, 0x80, 0x40, 0x20, 0x20, 0x20, 0x57, 0x65, 0x6C, 0x63, 0x6F, 0x6D, 0x65, 0x20, 0x21, 0x20, 0x20, 0x20, 0x20 );
Wri( 0x80, 0xC0, 0x40, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, 0x52, 0x61, 0x64, 0x69, 0x6F, 0x20, 0x20 );

// Scanning:
Wri( 0x80, 0x80, 0x40, 0x53, 0x63, 0x61, 0x6E, 0x6E, 0x69, 0x6E, 0x67, 0x2E, 0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x30 );

// etc
}

Have fun!