Embed With Elliot: I2C Bus Scanning

A lot of great ICs use I2C to communicate, but debugging a non-working I2C setup can be opaque, especially if you’re just getting started with the protocol/bus. An I2C bus scanner can be a helpful first step in debugging an I2C system. Are all the devices that I think should be present actually there and responding? Do they all work at the bus speed that I’m trying to run? If you’ve got an Arduino or Bus Pirate sitting around, you’re only seconds away from scanning your I2C bus, and answering these questions.

The Lowdown: I2C in Brief

I2C is a two-wire (plus ground) communications bus and protocol. The physical layer is just two signal wires: a clock line (SCL) that’s controlled by the master, and a data line (SDA) that can be controlled by either the master or the addressed slave unit. Data is always read on the SDA line when the clock is high, and a new value is established while the clock is low. The two exceptions to this rule are the stop and start signals, where the master is allowed to raise and drop the data line while the clock is still high. Because this change shouldn’t ever happen during data transfer, the stop and start signals are easy to detect.

i2c_timing

All data is sent in eight-bit packets and each packet is acknowledged by the recipient, whether master or slave. To start up a conversation, the master sends the start signal and then the seven-bit address of the slave device that it wants to speak to. The eighth bit in the master’s first packet tells the slave whether the master is going to transmit more data (a “write” command, a zero) or whether the master is requesting data back from the slave (a “read” value, a one).

After the eight bits are sent, the slave is required to acknowledge receipt by pulling the data line low.  This acknowledge signal is exactly what the I2C bus scanning software will need to look for in order to detect a chip with the given address on the bus.

There are, of course, a lot more complicating details to I2C. For instance, there are a whole range of permissible clock speeds at which the transmissions can take place: ranging from the default 100kHz data rate, through 400kHz “fast mode”, 1MHz “fast mode plus”, and up as far as 5MHz “ultra-fast mode”. (We await the 10MHz “super-duper, really-really-fast mode” with baited breath.) And since the bus is clocked by the SCL line, almost any slower data rate up to the maximum allowed will work just fine.

i2c_master_slave_circuitThe physical lines are pulled up to a logic-high voltage level by pullup resistors, and the devices signal low by pulling the line down. This means that the voltage transitions can be a little blurry, especially on long runs or other situations where the line itself capacitively couples to the circuit. These physical factors will play a role in determining how fast you can send signals on the I2C bus, and you may need to fine-tune the pullup resistors in your particular system.

There are a surprisingly large number of other ways that things can go wrong on an I2C bus, so it’s great to be able to start debugging at the very beginning — is the slave even getting my first (address) packet at the speed I’m sending? Hence the utility of an I2C scanner.

A Scanner

A first cut at an I2C bus scanner, then, can be made by just cycling through all 127 possible slave device addresses, and checking whether or not they acknowledge. Next, you might want to re-run the same test at a bunch of different bus speeds, if you thought that you might be having troubles with signal rise- and fall-times. Finally, and we’ve never seen this implemented, it might be cool to have a database of common I2C slave device addresses so that the scanner itself can report back which particular chips it’s found.

For ti2c_scanhe Arduino, the most featureful scanner we’ve seen is posted on the Arduino forums, with the code hosted on Github, in the “sketches/MultiSpeedI2CScanner” folder.  It actually does everything that we’d want in a simple scanner: scans the entire bus at different speeds and plots the results out nicely over the serial port for perusal on your computer. It’s configured to do a full scan on reboot. Type “ps” to print only the found devices and start a scan. Bam!

i2c_arduino_image_3The one caveat with the Arduino scanner is that if you’ve neglected to connect pullup resistors on the SDA and SCL lines (we would never!) the scanner seems to hang somewhere when running at 800kHz. We suspect it’s waiting to become bus master and just gets stuck; we wonder why there’s not a timeout in the twi_writeTo() function in the Arduino “twi.c” library. (Anyone have a good guess?) Other speed modes worked just fine, and everything was peachy again after adding a 10k pullup resistor to SDA.

Naturally, the Bus Pirate (the swiss-army knife of serial communications) will do an I2C scan. It only runs one frequency at a time, but it’s quick enough that you can step through them all in short order. It’s got a quirk, or maybe a feature; it treats the read/write bit as part of the address, so it will test each chip in both directions. Enter the I2C mode, set the desired speed and pullup/power options, and finally type “(1)” for option 1: 7-bit address search. You should see all the devices that responded on the bus listed out for you.

Writing your own code to do a scan is surprisingly simple as well, if you know the chips you’re working with. Most microcontrollers’ dedicated hardware I2C interfaces will report error codes in a specific register. If you can figure out how to test for the “didn’t acknowledge after sending the address and data-direction packet” error, the code pretty much writes itself.

Going Further

Once you’ve got the basics verified — the slave responds when addressed at the desired speed — and your I2C setup still isn’t working, you’re on to debugging the harder problems. There are other tests you might like to do, but unfortunately they all run quickly into the slave-device specific command sets. For instance, many devices will receive a command to reply with a known device ID, or the contents of a default register, or similar. These are useful to make certain that you’ve got multi-byte commands working as expected.

If you suspect that you’re having problems with the signals not rising or falling fast enough, perhaps because you’ve seen chips respond at low speeds but not at higher ones during the scan above, you’re going to need an oscilloscope to actually probe out the analog voltages on the lines. Or try lower-value pullup resistors to speed up the rising edges and test again.

Harder to catch or infrequently occurring glitches on a multi-master I2C bus get really hard to track down really fast. But getting the simple stuff verified working first — all parts are on the addresses that you think they are — can get you set on the right path.

Good luck with your I2C projects! And if you’ve got any other useful I2C debugging tools or strategies up your sleeves, feel free to discuss in the comments.

15 thoughts on “Embed With Elliot: I2C Bus Scanning

    1. Ah, that was a fine example of idiot companies using security by obscurity to fund countless assholes who made quite a lot of money from “helping” victims of the bug.

      Funnily another bricking bug hit thinkpads with some EFI firmwares a decade later :)

    1. Thanks for the hint, looks interesting. As I’ve got multiple supported devices I’ll give it a try.

      One thing that I want to do for a long time it to play with the USB protocol, but without modifying the host (e.g. on embedded devices such as routers / game consoles). There is support for Rigol DS1000s scopes and usb protocol, as I can tell from the wiki. Is it (out of the box) possible to hook up a DS1052E (not D) to the USB data lines and get an usb packet dump from sigrok, or do I need to create an “comparator with hysteresis protocol” ?

  1. My strategy for reverse engineering/debugging i2c eeprom layouts: a python class that acts like an eeprom and keeps track of the changes made and displaying visual representation of the internal state via pygame. Linked with a buspirate to the real i2c bus, it is possible to watch the changes made to the eeprom near realtime. Worked great for a central heater control with weekly time schedule which stored settings in an eeprom (i2c exposed at the expansion port for external temperatuer probes).

    1. For years I’ve used Python to model/simulate/emulate I2C devices that either weren’t yet released or were so out of stock that not even samples were available. Once the hardware does arrive, the simulator is a huge help validating the hardware and my driver for it, since the simulator is based on the datasheet (+ errata). Hadn’t thought to connect it to pygame!

    2. When I have a say in the matter, I use standard Linux I2C chip drivers, specifically the eeprom module. If you have this luxury, then mocking a 24C02 is a matter of creating a 256 byte file of 0xFFs and pointing your code at that instead of the eeprom file in sysfs. Same thing can be done for mtdchar devices like SPI flash.

  2. Nice article! I think you can enhance it by addressing (sorry for the pun) ¨clock stretching” feature from I2C that is often neglected by the built-in I2C libraries from some compilers and can be tricky to debug if you are unaware of its existence.

  3. Scanning all 127 addresses works fine, EXCEPT

    for addresses that start with 11110xx – this is a signal that a 10-bit address is being sent. The ‘xx’ is replaced by the 2 msb of the address, and the R/w bit. A second byte contains the remaining 8 bits (the R/w having already been sent).

    for addresses that start with 11111xx – these are reserved for future use.

    for addresses in the range of 0x00-0x07 – these are are special function “addresses”.

    Except for these addresses, scanning in read mode is likely not to cause too many problems as long as you execute a STOP after each address.

    Go see the “I2C-bus Specification and User Manual” hosted by NXP

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.