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:
| Line | Name | Purpose |
|---|---|---|
| SDA | Serial Data | Carries data in both directions |
| SCL | Serial Clock | Clock 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 --- SHTAll 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.
| Controller | Default SDA | Default SCL | Notes |
|---|---|---|---|
I2C0 (Wire) | GPIO 21 | GPIO 22 | Used 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 17Avoid GPIOs 6--11 (flash), 34--39 (input-only), and check your board's pinout for availability.
🔗Speed Modes
| Mode | Clock Frequency | Typical Use |
|---|---|---|
| Standard mode | 100 kHz | Default, works with all I2C devices |
| Fast mode | 400 kHz | Most modern sensors and displays |
Set the clock speed after Wire.begin():
Wire.setClock(400000); // 400 kHz fast modeThe 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.
| Device | Default Address | Alternate Address | Type |
|---|---|---|---|
| BME280 | 0x76 | 0x77 (SDO HIGH) | Temperature / humidity / pressure |
| SHT31 | 0x44 | 0x45 (ADDR HIGH) | Temperature / humidity |
| BH1750 | 0x23 | 0x5C (ADDR HIGH) | Ambient light |
| SSD1306 OLED | 0x3C | 0x3D (varies) | Display |
| SGP30 | 0x58 | — (fixed) | Air quality (VOC / eCO2) |
| INA219 | 0x40 | 0x41--0x4F (A0/A1) | Current / power monitor |
| VL53L0X | 0x29 | Configurable in software | Time-of-flight distance |
| LCD I2C backpack | 0x27 | 0x3F (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
| Problem | Cause | Fix |
|---|---|---|
| Scanner finds no devices | Missing or wrong wiring | Check SDA/SCL connections, confirm VCC and GND |
| Scanner finds no devices | No pull-up resistors | Use a breakout board with built-in pull-ups, or add 4.7 k$\Omega$ resistors |
| Address conflict | Two devices share the same address | Use 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-transfer | Power-cycle the device, or toggle SCL manually to release the bus |
| Intermittent failures | Wires too long or too many pull-ups | Keep I2C wires under 1 m; remove redundant pull-ups |
| Wrong data | Incorrect register reads | Double-check the datasheet for register addresses and byte order |
🔗Used In
These articles on this site use I2C: