I2C Protocol in Depth: Addressing, ACK, Pull-ups and Bus Speed
I2C (Inter-Integrated Circuit) connects many chips with just two wires: SDA for data and SCL for clock. Temperature sensors, OLED displays, EEPROMs, RTC modules and IMUs like the MPU-6050 all speak it. Because devices share the bus and are selected by address, you can hang a dozen sensors on two GPIO pins.
This deep dive covers the electrical layer (open-drain lines and pull-ups), the bit-level conditions, a complete register-read transaction, pull-up resistor calculation, and the faults you will actually meet on the bench.
Open-Drain Lines and Pull-up Resistors
No device ever drives SDA or SCL high. Each device can only pull a line low, through an open-drain (or open-collector) transistor. A pull-up resistor to VDD returns the line high when nobody is pulling. This wired-AND arrangement means a device that wants 1 while another wants 0 simply loses; no short circuit occurs. It also enables clock stretching and multi-master arbitration.
| Mode | Max SCL | Max rise time tr | Max bus capacitance |
|---|---|---|---|
| Standard-mode (Sm) | 100 kHz | 1000 ns | 400 pF |
| Fast-mode (Fm) | 400 kHz | 300 ns | 400 pF |
| Fast-mode Plus (Fm+) | 1 MHz | 120 ns | 550 pF |
| High-speed (Hs) | 3.4 MHz | Depends on load | 100 pF to 400 pF |
Figures are from the NXP I2C-bus specification (UM10204). Most hobby sensors support Standard and Fast mode; check the datasheet before going faster.
Start, Stop, Data and ACK
- Data validity: SDA may only change while SCL is low. It is read while SCL is high.
- START (S): SDA falls while SCL is high. This breaks the data rule on purpose, so every device recognises it.
- STOP (P): SDA rises while SCL is high.
- Repeated START (Sr): a START without a STOP before it, used to change direction without releasing the bus.
- ACK: after every 8 bits, the receiver pulls SDA low during a ninth clock. If SDA stays high, that is a NACK.
START ACK STOP
SCL ------+ +-+ +-+ +-+ +-+ +-+ +-+ +-----------
| | | | | | | ... | | | | | | |
+--+ +--+ +--+ + + +--+ +--+ +--+
SDA ---+ <A6 ><A5 ><A4 >...<A0 ><R/W><ACK> +---------
| |
+--- falls while SCL high rises while SCL highAddressing and a Complete Register Read
After START, the master sends a 7-bit address followed by a read/write bit (0 = write, 1 = read). The address byte on the wire is therefore the 7-bit address shifted left by one, plus R/W. Addresses 0x00 to 0x07 and 0x78 to 0x7F are reserved, leaving 112 usable 7-bit addresses.
MPU-6050 with AD0 tied low: 7-bit address 0x68 Write address byte: (0x68 << 1) | 0 = 0xD0 Read address byte: (0x68 << 1) | 1 = 0xD1
Reading a register needs two phases: write the register number, then read the data. Using a repeated start keeps another master from taking the bus between them.
S 0xD0 A 0x75 A Sr 0xD1 A [data] N P | addr+W reg | addr+R byte | stop start WHO_AM_I repeated start master NACKs last byte A = ACK from slave, N = NACK from master Expected data for MPU-6050 WHO_AM_I: 0x68
#include <Wire.h>
const uint8_t MPU_ADDR = 0x68; // AD0 pin low
const uint8_t WHO_AM_I = 0x75;
void setup() {
Serial.begin(115200);
Wire.begin(); // Uno: SDA = A4, SCL = A5
Wire.setClock(400000); // Fast-mode, 400 kHz
Wire.beginTransmission(MPU_ADDR);
Wire.write(WHO_AM_I); // register pointer
uint8_t err = Wire.endTransmission(false); // false = repeated start, no STOP
if (err != 0) {
Serial.print("No ACK, error code ");
Serial.println(err);
return;
}
Wire.requestFrom(MPU_ADDR, (uint8_t)1); // read 1 byte, then STOP
if (Wire.available()) {
uint8_t id = Wire.read();
Serial.print("WHO_AM_I = 0x");
Serial.println(id, HEX); // expect 0x68
}
}
void loop() {
}
Wire.endTransmission() returns 0 on success, 2 when the address was not acknowledged and 3 when a data byte was not acknowledged. Checking it turns a silent failure into a useful message.
Calculating Pull-up Resistor Values
The pull-up must be small enough to charge the bus capacitance within the allowed rise time, but large enough that a device can still pull the line below the low threshold while sinking its rated current.
Rp(min) = (VDD - VOL(max)) / IOL Rp(max) = tr / (0.8473 x Cb) VOL(max) = 0.4 V, IOL = 3 mA (Standard and Fast mode) VDD = 3.3 V: Rp(min) = (3.3 - 0.4) / 0.003 = 967 ohm Bus capacitance Cb = 200 pF: Standard mode: Rp(max) = 1000e-9 / (0.8473 x 200e-12) = 5.9 k Fast mode: Rp(max) = 300e-9 / (0.8473 x 200e-12) = 1.77 k
So at 400 kHz with 200 pF of wiring and inputs, choose something like 1.5 k; at 100 kHz, 4.7 k is safe. Watch out for breakout boards: many already include 4.7 k or 10 k pull-ups, and several boards in parallel can drop the effective value below Rp(min).
Clock Stretching and Arbitration
A slow slave may hold SCL low after a byte to gain time; the master must wait until SCL actually goes high. Some masters, notably older Raspberry Pi I2C hardware, handle clock stretching poorly, so check if a sensor relies on it. When two masters start together, both watch SDA while transmitting; the first one to send a 1 while seeing 0 knows it lost and backs off. The winning message is not corrupted.
Troubleshooting
- Scanner finds nothing: missing pull-ups, SDA and SCL swapped, or no common ground.
- Address differs from tutorial: an address pin (AD0, A0-A2) is strapped differently, or the datasheet lists the 8-bit form (0xD0 instead of 0x68).
- Bus stuck low: a slave was reset mid-byte and is holding SDA. Clock SCL manually up to nine times, then send STOP.
- Works short, fails long: too much capacitance; lower the speed, reduce pull-up value or use a bus buffer.
- 3.3 V sensor on a 5 V Arduino: use a level shifter so pull-ups do not push 5 V into the sensor.
Summary
I2C uses two open-drain lines with pull-ups, START and STOP conditions marked by SDA changing while SCL is high, a 7-bit address plus R/W, and an ACK after every byte. Register reads use write-pointer, repeated start, then read. Size the pull-ups between (VDD - 0.4)/3 mA and tr/(0.8473 Cb), and check every return code. Next, SPI trades extra wires for much higher speed.