Protocols

I2C — Inter-Integrated Circuit

A two-wire, multi-drop, addressed bus. Where SPI trades pins for speed, I2C trades speed for pin count — many devices can share the same two wires because addressing happens in-band.

Quick reference
What it is
A two-wire, multi-drop bus where every device shares SDA/SCL and is picked out by an in-band address, not a dedicated pin.
Mental model
A party line: everyone hears everything, but only the device whose address was called speaks up.
Key numbers
2 wires (SDA, SCL), 100 kHz–3.4 MHz depending on mode, 7-bit addresses (0x08–0x77 typical).
Reach for it when
You're wiring up several low-speed sensors and pin count matters more than raw throughput.

Wires and pull-ups

SDA
Serial data — bidirectional, shared by every device on the bus.
SCL
Serial clock — driven by the current bus master; can also be held low by a slave (clock stretching).

Both lines are open-drain: every device can only pull them low, never drive them high. That's deliberate, not a limitation — if devices could actively drive the line both ways, two devices disagreeing at the same instant would short their outputs together. Open-drain makes the worst case harmless: multiple devices pulling low at once is still just "low." Since nothing on the bus can drive the line high, something else has to — that's the external pull-up resistor (typically 2.2 kΩ–10 kΩ, board-dependent) tying each line back to the logic-high rail. A device idles by simply releasing the line and letting the resistor win.

A pull-up resistor ties SDA to VDD; every device can only pull the line toward ground, so the line reads high only when every device has released it VDD Rp SDA Device A open-drain output Device B open-drain output Every device can only pull SDA toward 0 V — Rp is what actually returns it to logic 1 once everyone releases it.
This is the same wired-AND idea as CAN's dominant/recessive bus: the line reads high only if every device agrees to let it, and low the instant any device pulls it down.

Missing or too-weak pull-ups is the most common reason a "correctly wired" I2C bus doesn't work at all — with nothing to source current onto the line, SDA/SCL just sit near 0 V and no device ever sees a valid logic 1.

Anatomy of a transaction

  1. START — master pulls SDA low while SCL is high.
  2. Address byte — 7 (or 10) address bits + 1 read/write bit, MSB first.
  3. ACK/NACK — receiver pulls SDA low for one clock to acknowledge each byte.
  4. Data byte(s) — 8 bits each, ACKed individually.
  5. STOP — master releases SDA while SCL is high.
I2C transaction fields from START through STOP: address, read/write bit, ACK, data byte, ACK START Address R/W ACK Data ACK STOP 1b 7 (or 10) b 1b 1b 8b 1b 1b
Data + ACK repeats for each additional byte — a repeated START (a new START without an intervening STOP) restarts this sequence mid-transaction instead of releasing the bus.

A repeated START is how a master switches from writing a register address to reading its value without releasing the bus — critical for the common "write register pointer, then read" pattern used by most I2C sensors.

Addressing

Most parts use 7-bit addresses (0x08–0x77 usable; some ranges reserved). Datasheets sometimes list an 8-bit "write address" that already includes the R/W bit shifted in — a frequent source of off-by-one-bit confusion when porting code between platforms. A less common 10-bit addressing mode exists for when 7 bits' worth of address space genuinely isn't enough — it spends part of the first address byte as an escape sequence (1111 0XX) signaling "the real address continues in the next byte," which is why a 10-bit device is invisible to a controller that only knows how to send 7-bit addresses.

Speed grades

ModeMax clock
Standard100 kHz
Fast400 kHz
Fast Plus1 MHz
High Speed3.4 MHz

Clock stretching and arbitration

Because SCL is open-drain, a slow slave can hold it low to pause the master mid-byte (clock stretching) — useful for peripherals that need time to prepare data, but it means SCL should always be read back, not assumed. In multi-master systems, arbitration works because a master monitors SDA while transmitting and backs off if it sees a bit it didn't drive — no separate bus-grant mechanism is needed.

Beyond 127 addresses: muxes and expanders

A 7-bit address space, combined with the reality that many sensor families ship with only one or two selectable addresses (often just a single address pin, giving two choices), runs out fast on a board with several identical sensors. Two standard ways around it:

SMBus: I2C's stricter cousin

SMBus (System Management Bus) is a stricter subset/superset of I2C — electrically and at the protocol level close enough that most SMBus devices work on a general I2C bus and vice versa, but SMBus adds things I2C leaves optional: a fixed timeout that forces a stuck bus to reset itself, tighter voltage-level and timing specs, and standardized higher-level commands (like the "read word" / "write word" transaction formats Linux's i2c-dev exposes as i2c_smbus_read_word_data() and friends). In practice, most of what looks like "the I2C API" in Linux userspace tooling is actually the SMBus subset, because it's specific enough to have well-defined helper functions where raw I2C intentionally isn't.

Why I2C, and when
Pros
Only two wires no matter how many devices are on the bus — adding a tenth sensor costs nothing extra in pins, unlike SPI's one-CS-per-device. Addressing is in-band, so the wiring itself never has to change as devices are added.
Cons
Half-duplex and slower than SPI at any given clock rate, a 7-bit address space that runs out fast with several identical sensors (see muxes/expanders above), and a stuck slave can hang the whole bus in a way that needs manual recovery — none of which SPI's point-to-point wiring is exposed to.
Reach for I2C when
You're wiring up several low-speed sensors/peripherals and pin count matters more than throughput — the common case on a densely packed board.
Reach for something else when
You need real throughput (a display, flash memory, an ADC streaming samples) — SPI's extra pins buy meaningfully higher speed and simpler, more robust framing. Need multiple independent controllers sharing a bus over real distance with guaranteed priority instead of just several peripherals near one controller? That's CAN's job, not I2C's.

Real-world applications

Gotchas

Field notes

→ Code examples for I2C