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.

ModeMax SCLMax rise time trMax bus capacitance
Standard-mode (Sm)100 kHz1000 ns400 pF
Fast-mode (Fm)400 kHz300 ns400 pF
Fast-mode Plus (Fm+)1 MHz120 ns550 pF
High-speed (Hs)3.4 MHzDepends on load100 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 high

Addressing 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
Arduino sketch reading the MPU-6050 WHO_AM_I register using a repeated start
#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.