I2C

A lot of breakout boards use Inter-Integrate Circuit (I2C) to communicate with the host microcontroller. This is a nice general purpose communication bus that only requires two pins for the data transmission. When using breakouts that include STEMMA or STEMMA QT connectors, usage is essentially plug-and-play.

However, sometimes things just don't seem to work for some reason. When that happens, the best initial troubleshooting step is to do an I2C scan. This is a simple "Hey! Who's out there?" sort of sanity check.

In this guide, we show how to perform an I2C scan on various platforms and provide some guidance on how to use the results to aid in troubleshooting.

Terminology

Here we define some terms related to I2C.

  • SDA - Serial DAta line. This is the line the 1's and 0's go down to communicate data.
  • SCL - Serial CLock line. This is a square wave clock signal that is used to coordinate when to read the SDA line.
  • VCC - The supply voltage for power, typically 3.3V or 5V.
  • GND - Ground
  • Address - Each I2C device has a specific numerical address.
  • Pull Up Resistors - Resistors required between SCL and VCC as well as between SDA and VCC.

The I2C Bus

Here is a simple diagram showing the general arrangement of an I2C bus with several devices attached.

There are two main items that are important here:

  1. Pull up resistors between SCL/SDA and VCC
  2. Unique addresses for each device

Most I2C issues relate to something wrong with these two factors.

Pull Up Resistors

Only one set of pull up resistors are needed for the entire I2C bus. A stand alone device, like an iPhone, will have taken care of this internally. In that case, the microcontroller and all the I2C devices are self contained within the iPhone itself. But for maker applications, the microcontroller and I2C devices are typically provided as separate items. In that case, where are the pull up resistors? It depends, but in general:

  • Adafruit I2C breakouts include pull up resistors.
  • Mix mode breakouts may or may not include pull up resistors.
  • Raspberry Pi's include pull up resistors on their I2C pins.
Adafruit I2C breakouts include pull up resistors.

It's OK to have more than one pull up resistor. That would happen if you chained more than one Adafruit I2C breakout together, since each includes pull ups. The resistors will act in parallel and thus reduce the overall pull up resistance. But I2C can work with a range of pull up values, so this is generally not an issue.

Addresses

Each device on the I2C bus needs a unique address. How do you determine what the address is for any given breakout? The datasheet is the main source, but it can be a bit buried. Sometimes the address will be mentioned on the product page or in the associated learn guide. Some breakouts will print the address on the PCB itself. The source code for the sensor's library is another option, but again, can be a bit buried. That's why there's this convenient list of nothing but I2C addresses:

But it requires manually updating, so may not be 100% complete.

But also, don't worry - you don't need to know the address to perform an I2C scan. It just helps to know what to expect from the scan results.

Knowing your device I2C address is not required to perform a scan.
There are some devices that have more than one I2C address! Especially address 0x00 (which is special)

Hex Address Nomenclature

By convention, I2C addresses are reported in hexadecimal notation. That is what the leading 0x indicates. We won't discuss hex notation here, just keep in mind that when you see 0x77, it is not the same as 77.

While addresses are rarely referred in decimal, they are sometimes referenced in '8-bit' mode rather than 7-bit mode. In that case, a device with 7-bit address 0x39 would be referred to as 0x39 x 2 = 0x72

OK, let's get into how to perform an I2C address scan. While simple, the exact process is unique depending on what platform you are using. The following sections detail the process for the main scenarios of interest - Arduino, CircuitPython, and Raspberry Pi.

There is an excellent I2C scanner sketch available from the old Arduino Playground here:

Here is a slightly modified version that allows easily specifying an alternate I2C bus. See below for more info. The default Wire should hopefully work for most boards though.

// --------------------------------------
// i2c_scanner
//
// Modified from https://playground.arduino.cc/Main/I2cScanner/
// --------------------------------------

#include <Wire.h>

// Set I2C bus to use: Wire, Wire1, etc.
#define WIRE Wire

void setup() {
  WIRE.begin();

  Serial.begin(9600);
  while (!Serial)
     delay(10);
  Serial.println("\nI2C Scanner");
}


void loop() {
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    WIRE.beginTransmission(address);
    error = WIRE.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4) 
    {
      Serial.print("Unknown error at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);           // wait 5 seconds for next scan
}

Load and run that sketch as you would any other sketch onto your Arduino board.

Specify Alternate I2C Bus

The example sketch above uses the default Wire bus. To run the I2C scan on a different bus, change this line of code:

#define WIRE Wire

to specify the bus to use. For example, to use Wire1, change the line to this:

#define WIRE Wire1

Normal Behavior

If all goes well, you should get a list of addresses for each device found. In the example below, an Adafruit BMP280 breakout is attached to an Arduino UNO.

The BMP280's I2C address of 0x77 shows up as expected.

Missing Device or Pull Ups

If the device is disconnect and/or the pull up resistors are missing, the results will look like this:

Note that the sketch runs just fine, but reports no addresses.

A mis-wired board may hang the sketch after the "Scanning..." text is printed

An I2C scanner example for CircuitPython is provided in the CircuitPython Essentials guide. Here's the code:

"""CircuitPython I2C Device Address Scan"""
# If you run this and it seems to hang, try manually unlocking
# your I2C bus from the REPL with
#  >>> import board
#  >>> board.I2C().unlock()

import time
import board

# To use default I2C bus (most boards)
i2c = board.I2C()

# To create I2C bus on specific pins
# import busio
# i2c = busio.I2C(board.SCL1, board.SDA1)  # QT Py RP2040 STEMMA connector
# i2c = busio.I2C(board.GP1, board.GP0)    # Pi Pico RP2040

while not i2c.try_lock():
    pass

try:
    while True:
        print(
            "I2C addresses found:",
            [hex(device_address) for device_address in i2c.scan()],
        )
        time.sleep(2)

finally:  # unlock the i2c bus when ctrl-c'ing out of the loop
    i2c.unlock()

Use the Welcome to CircuitPython guide to learn how to load and run this on your board.

Specify Alternate I2C Bus

The example sketch above uses the default board.I2C() bus. To run the I2C scan on a different bus the bus will need to be created using busio and the correct pins. There is some commented out code that can be used as a general reference:

# import busio
# i2c = busio.I2C(board.SCL1, board.SDA1)  # QT Py RP2040 STEMMA connector
# i2c = busio.I2C(board.GP1, board.GP0)    # Pi Pico RP2040

Normal Behavior

If all goes well, you should get a list of addresses for each device found. In the example below, an Adafruit BMP280 breakout is attached to a QT Py M0.

The BMP280's I2C address of 0x77 shows up as expected.

Missing Device or Pull Ups

If the device is disconnect and/or the pull up resistors are missing, the results will look like this:

This is a neat feature of CircuitPython. It actually checks for the presence of the pull up resistors and shows an error message if they are not detected.

To do an I2C scan on a Raspberry Pi the i2cdetect command is used. If not already done, be sure to enable I2C on the Raspberry Pi via raspi-config. If the i2cdetect command is not found, install it with:

sudo apt-get install i2c-tools

And then to run a scan, use i2cdetect with the following command line parameters:

i2cdetect -y 1

On modern Raspberry Pi OS releases, you do not need to run the command with sudo. The -y disables interactive mode, so it just goes ahead and scans. The 1 specifies the I2C bus.

Normal Behavior

If all goes well, you should get a list of addresses for each device found. In the example below, an Adafruit BMP280 breakout is attached to a Raspberry Pi 4.

The BMP280's I2C address of 0x77 shows up as expected.

Missing Device or Pull Ups

If the device is disconnect and/or the pull up resistors are missing, the results will look like this:

The scan runs fine, but no addresses are shown. Keep in mind that Raspberry Pi's include pull up resistors on the SCL and SDA pins.

OK, so you've done a scan and gotten some results. How do those help with troubleshooting. Here we discuss various scenarios and what they imply.

Correct Addresses Show Up

In general, you should be all good. There's nothing more to be done with an I2C scan. Go ahead and start trying to use the device, preferably using an example from the devices library.

If you get additional errors when trying to run the library example, post in the Adafruit forums with details.

Incorrect Addresses Show Up

Weird, but can happen in rare cases. Double check the device and make sure it is what you think it is. Some devices can have more than one I2C address, typically settable somehow by the users. Check if the incorrect address is one of these alternate addresses.

If everything seems correct but you are still getting an incorrect address, post in the Adafruit forums with details.

No Addresses Show Up

Try just scanning again. Some devices can be weird and not show up on first scan. If that still does not work, then: Check your wiring and/or your soldering. This accounts for the vast majority of issues.

Wiring

Wiring issues can be incorrect pins or even just bad wires. Double and triple check you are using the correct pins. If you are using a breadboard, try moving to a different location. Breadboards can wear out with time. Also try totally different wires. The pre-crimped prototype wires commonly used can also wear out over time.

Soldering

This is unfortunately a very common issue for beginners. Soldering is not super tough, but does take a little getting used to. There's a good general guide on soldering as well as a guide specific to the commonly used headers pins.

This photo from the first guide is an excellent summary of common soldering issues:

Common

If you've checked all this and are still not getting the expected results, post in the Adafruit forums with details. Photos are very helpful when doing so.

"No pull up found on SDA or SCL"

This error message is unique to CircuitPython. This is most likely also a wiring or soldering issue. See the previous section for some details.

This guide was first published on Sep 16, 2021. It was last updated on Sep 16, 2021.