I²C Communication
Addressing, ACK/NAK, Clock Stretching
Why it matters
I²C lets you connect many peripherals (IMUs, OLEDs, environmental sensors) using only two wires.
The idea
The core idea
I²C (Inter-Integrated Circuit) is a two-wire bus protocol that lets you connect many devices to a microcontroller:
- SDA (Serial Data): Carries the actual data bits
- SCL (Serial Clock): Synchronizes data transfer
Both lines are open-drain and require pull-up resistors (typically 2.2kΩ–10kΩ) to 3.3V.
Why Open-Drain?
Open-drain means devices can only pull the line LOW, never HIGH. The pull-up resistor pulls it HIGH when no device is pulling LOW. This allows multiple devices to share the same bus safely:
- Any device can pull the line LOW (wired-AND logic)
- If one device pulls LOW, the whole bus goes LOW
- All devices must release for the bus to go HIGH
- No conflicts — devices can’t fight over the bus
Addressing: How Devices Are Selected
Each I²C device has a unique 7-bit address (0x08 to 0x77). The master sends the address first:
- 7 address bits identify the device
- 1 R/W bit: 0 = write (master→slave), 1 = read (slave→master)
- Combined into one 8-bit byte on the wire
Example: Address 0x3C (0b0111100) + Write (0) = byte 0x78 (0b01111000)
Protocol Flow
sequenceDiagram
participant Master
participant Slave
Master->>Slave: START (SDA↓ while SCL high)
Master->>Slave: Address + R/W (8 bits, MSB first)
Slave-->>Master: ACK (SDA LOW on 9th clock)
Master->>Slave: Data Byte 1 (8 bits)
Slave-->>Master: ACK
Master->>Slave: Data Byte 2 (8 bits)
Slave-->>Master: ACK
Master->>Slave: STOP (SDA↑ while SCL high)
ACK vs NACK
After every 8 data bits, there’s a 9th clock cycle for acknowledgment:
- ACK (Acknowledge): Slave pulls SDA LOW → “I received the byte”
- NACK (Not Acknowledge): Slave leaves SDA HIGH → “Error” or “Stop sending”
NACK can mean:
- No device at that address (device disconnected)
- Device busy (can’t accept more data)
- Read complete (master sends NACK to signal “last byte”)
Clock Stretching
Sometimes a slave needs more time to process data. It can hold SCL LOW (clock stretching):
- Slave pulls SCL LOW after receiving a byte
- Master waits (SCL is shared, so master sees it LOW)
- Slave releases SCL when ready
- Master continues the transaction
This allows slow devices (like EEPROMs) to work on fast buses.
Common I²C Devices
Many sensors and displays use I²C:
- Sensors: SHT31 (temp/humidity), BMP280 (pressure), MPU6050 (IMU), TCS34725 (color)
- Displays: SSD1306 OLED (0x3C), LCD backpacks
- Storage: EEPROMs (AT24C32, etc.)
- Real-time clocks: DS1307, DS3231
ESP‑WROOM‑32 Notes
- Default pins: GPIO21 (SDA) and GPIO22 (SCL)
- ESP32 can route I²C to other pins (software I²C)
- Hardware I²C is faster and more reliable than software bit-banging
- Keep everything at 3.3V logic unless you add proper level shifting
- Internal pull-ups exist but are weak (~45kΩ); external 2.2kΩ–10kΩ recommended
Speed Limits
I²C has several speed modes:
- Standard mode: 100 kHz (most common)
- Fast mode: 400 kHz
- Fast mode plus: 1 MHz
Bus capacitance limits speed — longer wires = slower max speed. Keep wires short and use proper pull-ups.
Multi-Master
I²C supports multiple masters on the same bus:
- Masters use arbitration: if two masters start simultaneously, the one sending LOW wins
- The losing master backs off and retries later
- Rare in embedded systems (usually one master, many slaves)
Mini-lab
Increase NAK chance and see how a transaction aborts. Add clock stretching and watch SCL LOW extend. Try different addresses and observe how only the addressed device responds.
Demo
Top line is SCL, bottom is SDA. Watch for:
- START: SDA falls while SCL is high
- STOP: SDA rises while SCL is high
- ACK/NACK: the 9th clock after each byte
Key takeaways
- I²C uses two wires (SDA, SCL) with open-drain outputs requiring pull-up resistors
- 7-bit addresses (0x08–0x77) + 1 R/W bit = 8 bits on wire
- START: SDA falls while SCL high; STOP: SDA rises while SCL high
- ACK (SDA LOW) = success; NACK (SDA HIGH) = error or end of read
- Clock stretching lets slow slaves pause the master by holding SCL LOW
- Multiple devices share the bus; addressing selects which responds
- Standard speed: 100 kHz; Fast: 400 kHz; bus capacitance limits max speed
Going deeper
Troubleshooting I²C Issues
- No response (NACK): Check address, power, wiring, pull-ups
- Garbled data: Reduce bus speed, check for loose connections
- Bus stuck LOW: A device may be holding SDA/SCL LOW; power cycle
- Address conflicts: Some devices have address-select pins (A0, A1)
Address Scanning
Scan addresses 0x08–0x77: send START + address + R/W, check for ACK. This identifies all devices on the bus — essential for debugging.
Pull-up Resistor Selection
- Too weak (high resistance): Bus rises slowly, limits speed
- Too strong (low resistance): High current when pulled LOW, wastes power
- Sweet spot: 2.2kΩ–10kΩ for 3.3V (depends on bus capacitance)
- Long wires need stronger pull-ups (lower resistance)
Reading vs Writing
- Write: Master sends data bytes, slave ACKs each
- Read: Master sends address+R, slave sends data, master ACKs (or NACKs last byte)
- Some devices need register writes before reads (e.g., “read temperature register”)
Math details
I²C frame (write, 7-bit address):
START
[A6 A5 A4 A3 A2 A1 A0 W]
ACK
[D7 D6 D5 D4 D3 D2 D1 D0]
ACK
STOP
Implementation
LLM Prompt: I²C Sensor Reading
Write Rust code for ESP32 I²C communication using esp-hal.
Connect to SHT31 sensor (address 0x44). Implement: write register,
read register, read temperature/humidity. Include error handling
for NACK and timeout. Use GPIO21 (SDA) and GPIO22 (SCL).
Lab Exercise
- Connect SHT31: VCC→3.3V, GND→GND, SDA→GPIO21, SCL→GPIO22
- Add 10kΩ pull-up resistors on SDA and SCL to 3.3V
- Implement I²C address scan — verify sensor at 0x44
- Read temperature register — verify reasonable values
- Add error handling for disconnected sensor (NACK)