I2C Protocol Reference

ESP32 I2C reference — addressing, pin assignments, pull-ups, speed modes, common device addresses, and troubleshooting

I2C (Inter-Integrated Circuit, pronounced "eye-squared-see") is a two-wire serial protocol for connecting sensors, displays, and other low-speed peripherals to a microcontroller. It is the most common protocol you will encounter in ESP32 projects because it keeps wiring simple — many devices share just two lines.

🔗How I2C Works

I2C is a master-slave protocol. The ESP32 acts as the master: it generates the clock signal and initiates every transaction. Slave devices (sensors, displays, etc.) only respond when spoken to.

Communication happens over two open-drain lines:

LineNamePurpose
SDASerial DataCarries data in both directions
SCLSerial ClockClock signal generated by the master

Both lines need pull-up resistors to the supply voltage. Most breakout boards already include them — more on this below.

🔗Addressing

Every device on the bus has a unique 7-bit address (values 0x00--0x7F), giving a theoretical maximum of 128 devices. In practice, some addresses are reserved, so you can have up to 112 devices on a single bus.

When the master wants to communicate, it sends a start condition followed by the 7-bit address and a read/write bit. Only the device with the matching address responds.

The full byte sent on the wire is:

$$\text{Address byte} = (\text{7-bit address} \ll 1) ;|; \text{R/W bit}$$

You usually do not need to think about this — the Wire library handles it for you. You just pass the 7-bit address (e.g., 0x76).

🔗Clock Stretching

Some slower devices hold the SCL line LOW to pause the master while they prepare data. This is called clock stretching. The ESP32 supports it, but poorly-designed devices can occasionally cause bus lockups this way.

graph TB
    ESP32["ESP32<br/>(Master)"]

    subgraph I2C Bus
        direction LR
        SDA["SDA line"]
        SCL["SCL line"]
    end

    BME["BME280<br/>0x76"]
    OLED["SSD1306 OLED<br/>0x3C"]
    BH["BH1750<br/>0x23"]
    SHT["SHT31<br/>0x44"]

    ESP32 --- SDA
    ESP32 --- SCL

    SDA --- BME
    SDA --- OLED
    SDA --- BH
    SDA --- SHT

    SCL --- BME
    SCL --- OLED
    SCL --- BH
    SCL --- SHT

All devices share the same two wires. The master selects which device to talk to by sending its address.

🔗ESP32 I2C Pins and Controllers

The ESP32 has two independent I2C controllers: I2C0 and I2C1. Each can be assigned to almost any GPIO pin through the ESP32's GPIO matrix.

ControllerDefault SDADefault SCLNotes
I2C0 (Wire)GPIO 21GPIO 22Used by default when you call Wire.begin()
I2C1 (Wire1)No defaults — you must specify pins

Tip: If you have two devices with the same I2C address, put them on separate controllers instead of trying to use them on the same bus.

🔗Remapping Pins

Wire.begin(16, 17);  // SDA on GPIO 16, SCL on GPIO 17

Avoid GPIOs 6--11 (flash), 34--39 (input-only), and check your board's pinout for availability.

🔗Speed Modes

ModeClock FrequencyTypical Use
Standard mode100 kHzDefault, works with all I2C devices
Fast mode400 kHzMost modern sensors and displays

Set the clock speed after Wire.begin():

Wire.setClock(400000);  // 400 kHz fast mode

The effective data rate is lower than the clock frequency due to protocol overhead. At 400 kHz, expect roughly $50\,\text{KB/s}$ of actual throughput.

🔗Pull-Up Resistors

I2C lines are open-drain, meaning devices can only pull them LOW. Pull-up resistors are required to bring the lines back HIGH. Typical values are 4.7 k$\Omega$ for 3.3 V systems.

Do you need to add them? Usually not. Most breakout boards (Adafruit, SparkFun, generic purple boards) include pull-ups on the module. If you connect multiple boards that each have pull-ups, the effective resistance drops — this is fine for two or three boards but can cause signal problems with many devices. In that case, remove the extra pull-ups or use a single pair of external resistors.

🔗I2C Address Scanner

When a device does not respond, the first debugging step is to scan the bus. This sketch checks every address and prints the ones that acknowledge:

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  delay(1000);
  Wire.begin();  // Default: SDA=21, SCL=22

  Serial.println("Scanning I2C bus...\n");

  int found = 0;
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.printf("  Device at 0x%02X\n", addr);
      found++;
    }
  }
  Serial.printf("\n%d device(s) found.\n", found);
}

void loop() {}

A longer version with device identification is in the Communication Protocols overview.

🔗Common I2C Addresses

Many devices have configurable addresses (usually via an ADDR pin or solder bridge) so you can put two of the same sensor on one bus.

DeviceDefault AddressAlternate AddressType
BME2800x760x77 (SDO HIGH)Temperature / humidity / pressure
SHT310x440x45 (ADDR HIGH)Temperature / humidity
BH17500x230x5C (ADDR HIGH)Ambient light
SSD1306 OLED0x3C0x3D (varies)Display
SGP300x58— (fixed)Air quality (VOC / eCO2)
INA2190x400x41--0x4F (A0/A1)Current / power monitor
VL53L0X0x29Configurable in softwareTime-of-flight distance
LCD I2C backpack0x270x3F (varies by chip)Character LCD

If a scanner finds an address you do not recognize, check datasheets or look it up on i2cdevices.org.

🔗Common Issues

ProblemCauseFix
Scanner finds no devicesMissing or wrong wiringCheck SDA/SCL connections, confirm VCC and GND
Scanner finds no devicesNo pull-up resistorsUse a breakout board with built-in pull-ups, or add 4.7 k$\Omega$ resistors
Address conflictTwo devices share the same addressUse the alternate address (ADDR pin), or put one device on I2C1
Bus lockup (SDA stuck LOW)A device is clock-stretching indefinitely or crashed mid-transferPower-cycle the device, or toggle SCL manually to release the bus
Intermittent failuresWires too long or too many pull-upsKeep I2C wires under 1 m; remove redundant pull-ups
Wrong dataIncorrect register readsDouble-check the datasheet for register addresses and byte order

🔗Used In

These articles on this site use I2C: