I2C has been my default choice for connecting low to medium-speed sensors for over a decade, and for good reason. With just two wires, you can hang a dozen devices off the same bus, hot-swap addresses, and still keep pin count low on cost-sensitive microcontrollers. But that simplicity hides a few subtleties. I’ve seen perfectly good prototypes fail in the field because a sensor stretched the clock a few microseconds too long, or because two masters stepped on each other and no one handled arbitration correctly. In this tutorial, I’ll walk through the parts of this embedded protocol that trip up beginners the most — addressing, clock stretching, and multi-master operation — and show you how to implement reliable sensor communication using i2c without chasing phantom bus hangs at 2 AM.
The Two-Wire Foundation: How SDA and SCL Create a Shared Bus
Every I2C bus consists of two lines: SDA (Serial Data) and SCL (Serial Clock). Both are open-drain, which means no device ever actively drives the line high. Instead, devices can only pull the line low to ground, and external pull-up resistors bring the line back high when no one is pulling. This wired-AND arrangement is what makes multi-device and multi-master operation possible in the first place.
In my experience, the most common beginner mistake is treating SDA and SCL like push-pull UART lines. If you configure your microcontroller pins as push-pull outputs, you will create a short when one device pulls low and another tries to drive high. Always configure I2C pins as open-drain or open-collector with the alternate function enabled. On STM32, nRF52, and most ESP32 variants, the hardware peripheral handles this, but if you ever bit-bang I2C, you must switch the pin direction between input (released/high) and output-low.
Start, Stop, and Repeated Start Conditions
The bus is considered idle when both lines are high. A master initiates communication with a START condition: SDA transitions from high to low while SCL is high. A STOP condition is the opposite — SDA goes from low to high while SCL is high. Data is only valid when SCL is high, and SDA must be stable while SCL is high. The only exceptions are those START and STOP transitions.
A Repeated START (Sr) is simply a START without a preceding STOP. I rely on it constantly for sensor reads. For example, to read from most temperature or IMU sensors, you write the register address and then immediately re-start to read back data, all without releasing the bus. If you issue a STOP between the two, another master could seize the bus and you’ll lose atomicity.
ACK and NACK: The One-Bit Handshake
After every 8-bit byte, the receiver must acknowledge. The transmitter releases SDA during the 9th clock pulse, and the receiver pulls SDA low to signal ACK. If SDA stays high, that’s a NACK. In my debugging work, a NACK after the address byte almost always means one of three things: wrong address, sensor not powered or not ready, or pull-ups are too weak and the rise time is violating timing.
Untangling I2C Addressing: From 7-Bit Basics to 10-Bit Expansion
The first byte after a START is always the address byte. In 7-bit addressing mode, which covers 99% of sensors you’ll encounter, that byte consists of 7 address bits plus a single R/W bit. The 8th bit (LSB) is 0 for write, 1 for read. This is why you’ll often see the same sensor listed as 0x68 and 0x69 in different datasheets — some vendors quote the 7-bit address (0x68), others quote the 8-bit write address (0xD0).
I’ve found that this confusion causes more integration delays than any electrical issue. When porting code between HALs, always check whether the API expects a 7-bit address or an 8-bit address. STM32 HAL expects the 7-bit address left-shifted by one (so 0x68 becomes 0xD0 for write), while the Zephyr I2C API expects the pure 7-bit address.
// STM32 HAL: Note the address is left-shifted by 1
// Sensor 7-bit address is 0x68 (MPU6050)
#define MPU6050_ADDR (0x68 << 1)
HAL_StatusTypeDef status = HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR,
0x3B, I2C_MEMADD_SIZE_8BIT,
buffer, 6, 100);
// Zephyr: address is passed as 7-bit, no shift
// See Zephyr Project Documentation (https://docs.zephyrproject.org/) for i2c_transfer API
i2c_write_read_dt(&mpu6050_dev, ®_addr, 1, buffer, 6);
Reserved Addresses and Address Conflicts
Not all 128 possible 7-bit addresses are available. The I2C specification reserves several: 0x00 is the General Call, 0x03 to 0x07 are reserved for other bus formats, and 0x78-0x7F are reserved for 10-bit addressing prefixes. That leaves you with 112 usable addresses from 0x08 to 0x77. In practice, sensor vendors cluster around 0x18-0x5F.
Address conflicts happen often when you use two identical sensors. In my last environmental logger, I needed two SHT31 humidity sensors on the same bus. Both default to 0x44. The fix was using the ADDR pin: pulling it high moved the second sensor to 0x45. When that option isn’t available, I use an I2C multiplexer like the TCA9548A, which gives you eight isolated buses. It’s cheaper than spinning a new PCB to fix a collision.
When You Need 10-Bit Addressing
10-bit addressing expands the space to 1024 addresses using two bytes. The first byte starts with 11110xx, where xx are the two MSBs of the 10-bit address, followed by the R/W bit. The second byte carries the remaining 8 bits. I’ve only needed this once, on a large factory test jig with over 200 identical nodes, and most microcontroller peripherals and sensor libraries don’t support it cleanly. If you are just starting out, stick to 7-bit devices and use a mux if you run out of addresses.
When the Slave Pauses Time: Understanding Clock Stretching in Practice
Clock stretching is the mechanism that lets a slave hold SCL low to pause the transaction. Since the master generates the clock, a slow slave would otherwise lose data. By holding SCL low, the slave tells the master: “I’m not ready, wait.” The master must wait, with SCL low, until the slave releases the line before it can generate the next clock pulse.
This is entirely legal per the spec, but it breaks many naive implementations. I learned this the hard way with a BME280 pressure sensor that stretches the clock for up to 2ms during its forced-mode conversion. My original bit-banged driver had a tight 500µs timeout and flagged every conversion as a bus error.
Which Devices Stretch and Why
Microcontroller-based slaves (like an ATtiny acting as an I2C peripheral) stretch frequently while servicing interrupts. Memory devices like EEPROMs stretch during write cycles. MEMS sensors often stretch when the internal ADC or compensation engine is busy. If you are working on MEMS Sensor Calibration: Offset, Scale Factor and Cross-Axis Compensation, you will notice that calibration reads often take longer and trigger stretching — that’s the sensor fetching factory trim coefficients from NVM.
Unlike SPI, where the slave has a dedicated MISO line and can delay at will, I2C clock stretching directly impacts bus timing for everyone. This is one of the key trade-offs when choosing between protocols. For a deeper look at that decision, compare with SPI Communication: Full-Duplex Data Transfer for High-Speed Sensors, where the separate clock and data lines eliminate this issue entirely but cost you two extra pins per device.
Implementing Stretch-Aware Masters
Hardware I2C peripherals handle stretching automatically — they simply monitor SCL and don’t generate the next pulse until SCL goes high. Software/bit-banged masters must implement this explicitly. Here is a pattern I use for robust stretching support with a timeout to avoid bus lockup:
// Bit-banged clock stretching with timeout
// Returns 0 on success, -1 on slave timeout (bus recovery needed)
int i2c_wait_for_scl_high(uint32_t timeout_us) {
uint32_t start = micros();
while (read_scl() == 0) {
if ((micros() - start) > timeout_us) {
return -1; // Slave held SCL too long
}
// Optional: feed watchdog if timeout is > 10ms
}
return 0;
}
void i2c_generate_clock_pulse(void) {
set_scl_low();
delay_half_period();
release_scl(); // Let pull-up bring SCL high
// CRITICAL: Wait for slave to release SCL
if (i2c_wait_for_scl_high(5000) != 0) {
// Attempt bus recovery: 9 clocks + STOP
i2c_bus_recovery();
}
delay_half_period();
}
In my experience, a timeout between 5ms and 25ms works for most sensors. Anything shorter and you’ll get false errors with slower devices; anything longer and a genuinely stuck slave will block your entire system. If you use an RTOS, never use a busy-wait loop that blocks the scheduler for 25ms. Instead, use a timer or task delay and let other tasks run. The FreeRTOS Documentation shows patterns for non-blocking I2C drivers using semaphores and task notifications that handle stretching without stalling the system.
Who Talks First? Multi-Master Arbitration and Bus Contention Logic
Single-master is the norm — one microcontroller polling several sensors. Multi-master is where two or more controllers share the same bus and can each initiate transactions. Think of a system with an application MCU and a separate communications MCU that both need to talk to a shared FRAM or power monitor, or a modular system where hot-swappable boards each have a master.
I2C arbitration is elegant because it’s non-destructive and requires no extra wires. It relies entirely on the open-drain nature and the rule that a master must read back SDA while it transmits.
How Arbitration Works Bit by Bit
When two masters start at nearly the same time, they both generate a START and begin clocking out their address. Each master monitors SDA. If a master transmits a 1 (releases SDA) but reads back a 0, it knows another master is pulling SDA low and it has lost arbitration. The loser immediately stops driving SCL and SDA and switches to slave mode, waiting for a STOP. The winner continues uninterrupted, never knowing contention happened.
Arbitration is prioritized by address: lower addresses win because they contain more leading zeros, and 0 is the dominant state. A master trying to write to 0x10 will win over one trying to write to 0x50. If two masters send the same address, arbitration continues into the data bytes. This is deterministic but can starve higher-address masters if the bus is saturated.
Design Rules I Follow for Multi-Master Systems
First, always enable multi-master support in your peripheral. On many MCUs, this is a separate mode bit. If you leave the peripheral in single-master mode, it won’t check for bus busy or handle lost arbitration flags — it will just corrupt the bus.
Second, implement proper bus-busy detection. A master must not generate a START if it sees SDA or SCL low, indicating another transaction. Check the bus busy flag before every transaction.
Third, expect and handle the arbitration-lost interrupt. In my firmware, an arbitration loss is not an error — it’s an expected event. The correct response is to back off for a random delay (1-5ms) and retry. If you treat it as a hard fault and reset the peripheral, you’ll create an endless retry storm.
// Zephyr-style multi-master I2C write with arbitration handling
int i2c_write_with_retry(const struct device *dev, uint8_t addr,
uint8_t *data, size_t len) {
int ret;
int retries = 3;
while (retries--) {
ret = i2c_write(dev, data, len, addr);
if (ret == 0) return 0; // Success
if (ret == -EAGAIN) {
// Arbitration lost - common in multi-master
// Exponential backoff: 2ms, 4ms, 8ms
k_msleep(2 << (3 - retries));
continue;
}
// Other errors (NACK, bus fault) - don't retry immediately
break;
}
return ret;
}
For logging and debugging multi-master issues, I often add a dedicated UART Serial Communication: Baud Rate, Flow Control and Debugging port just to print arbitration events and bus recovery attempts. UART is ideal here because it operates independently of the I2C bus, so you can see what happened even when I2C is locked.
Sizing Pull-Ups and Taming Capacitance: Hardware Lessons From the Bench
Theory is clean; hardware is messy. The pull-up resistor value and total bus capacitance determine rise time, and rise time determines whether your bus runs reliably at 400kHz or fails intermittently at temperature extremes. I’ve debugged too many boards where the software was perfect but the pull-ups were 10kΩ on a 30cm bus — fine at 100kHz, failures at 400kHz.
The I2C spec defines maximum rise time (Tr) for each mode. For Fast Mode (400kHz), rise time on SDA/SCL must be under 300ns. The rise time is roughly 0.847 × Rp × Cb, where Rp is pull-up resistance and Cb is total bus capacitance.
Total bus capacitance includes PCB trace capacitance (~1-2pF/cm), pin capacitance (5-10pF per device), and cable capacitance if you go off-board (40-100pF/meter for ribbon cable). A bus with four sensors on a 10cm × 10cm board can easily hit 80-120pF.
Use this formula to estimate Rp:
Rp(min) = (Vcc - Vol) / Iol — ensures you don’t exceed the driver’s sink current (typically 3mA). For 3.3V and Vol=0.4V, Rp(min) ≈ 967Ω.
Rp(max) = Tr / (0.847 × Cb) — ensures rise time is fast enough. For Tr=300ns and Cb=150pF, Rp(max) ≈ 2.36kΩ.
In practice, I start with 4.7kΩ for 100kHz with low capacitance, 2.2kΩ for 400kHz, and 1.5kΩ to 1kΩ for Fast Mode Plus (1MHz) or long buses. Always measure the actual rise time with a scope. If the edge looks like an RC curve and not a sharp edge, your pull-ups are too weak or capacitance too high.
| I2C Mode | Max Clock | Max Rise Time (Tr) | Typical Pull-Up (3.3V, 100pF Bus) | Use Case |
|---|---|---|---|---|
| Standard Mode | 100 kHz | 1000 ns | 4.7 kΩ – 10 kΩ | Low power, long buses, EEPROMs |
| Fast Mode | 400 kHz | 300 ns | 2.2 kΩ – 4.7 kΩ | Most sensors (IMU, temp, pressure) |
| Fast Mode Plus | 1 MHz | 120 ns | 1.0 kΩ – 2.2 kΩ | High-speed displays, FRAM, buses >200pF |
| High Speed Mode | 3.4 MHz | 40 ns | ~680 Ω (active pull-up) | Specialized MCUs, not sensor-typical |
One more hardware tip: if you run I2C off-board to a sensor module, keep the ground return tight and consider an I2C buffer like the P82B715 or LTC4301. I once chased intermittent NACKs for a week that turned out to be ground bounce on a 20cm jumper wire. The buffer fixed it instantly.
From Transaction to Data: A Complete Sensor Read Without Bus Lockup
Let’s tie it together with a real read sequence — reading 6 bytes from a typical accelerometer. The transaction is: START, address+W, register address, Repeated START, address+R, read 6 bytes with ACK/NACK handling, STOP. Every part must be robust.
The most important reliability feature is bus recovery. If a slave holds SDA low mid-transaction due to a power glitch or firmware crash, the bus will appear busy forever. The I2C spec recovery method is to clock SCL nine times while monitoring SDA; if the slave was stuck mid-byte, it will release SDA after the 9th clock, and you can then issue a STOP to reset the bus.
In my production code, I implement three layers of protection: 1) Clock stretching with timeout as shown earlier, 2) NACK detection with 2-3 retries, and 3) Full bus recovery if SDA stays low for more than 10ms after a transaction should have ended. I also run all I2C access through a mutex if using an RTOS, so a high-priority task reading a sensor doesn’t interleave with a low-priority task mid-transaction. The Zephyr I2C driver and FreeRTOS I2C wrappers both support this, and the pattern is documented in the Zephyr Project Documentation.
Checking Signal Integrity First
Before writing any code, I probe SCL and SDA with a logic analyzer. Cheap Saleae clones are sufficient for 400kHz. I look for four things: correct START/STOP/Repeated START shapes, ACK on address and data, rise time under the spec limit, and no unexpected clock stretching beyond the timeout. If any of those fail, no amount of software retries will make the system stable.
Once the bus is electrically clean, the protocol becomes straightforward. Keep transactions short — read only the registers you need. Batch reads where possible. And document the 7-bit address next to every sensor on your schematic. The hour you spend labeling will save days of debugging later.
Frequently Asked Questions
What is the difference between 7-bit and 10-bit I2C addressing, and when should I use 10-bit?
7-bit addressing provides 112 usable addresses (0x08 to 0x77) and is used by virtually all sensors and peripherals. 10-bit addressing provides 1024 addresses and uses two bytes: a header starting with 11110 plus two address bits, followed by 8 more bits. Use 10-bit only when you must exceed 112 devices on one bus, which is rare. Most HALs, sensor libraries, and scanning tools default to 7-bit, so mixing 10-bit devices adds software complexity. For duplicates, use the sensor’s ADDR pin or an I2C mux instead.
How do I know if my sensor is clock stretching and how should I handle it?
You will see SCL held low by the slave after you release it, visible on a scope or logic analyzer as an extended low period between clock pulses. Many datasheets list maximum stretching time, often 0.5ms to 5ms. Your master must wait until SCL goes high again before continuing. Hardware peripherals do this automatically. For bit-banged masters, implement a stretch wait with a 5ms to 25ms timeout. If the timeout expires, perform a bus recovery sequence (9 SCL pulses + STOP) and retry the transaction.
Can I use I2C at 400kHz with 10kΩ pull-ups?
Usually no. With 10kΩ pull-ups and 100-150pF bus capacitance, rise time will exceed the 300ns limit for Fast Mode, causing timing violations and intermittent NACKs. At 100kHz, 10kΩ may work on a short, low-capacitance bus. For reliable 400kHz operation, use 2.2kΩ to 4.7kΩ at 3.3V, calculate from Rp(max) = Tr / (0.847 × Cb), and verify rise time with an oscilloscope.
What causes I2C bus lockup and how do I recover without power cycling?
Lockup typically happens when a slave holds SDA low — often because it missed a clock, lost power mid-byte, or was reset during a transaction. The master sees the bus as busy and cannot generate a STOP. To recover without power cycling, implement a bus recovery routine: configure SCL as GPIO, generate 9 clock pulses while monitoring SDA, then issue a STOP condition (SDA low-to-high while SCL is high). After recovery, re-initialize the I2C peripheral. Always add this routine behind a timeout so your firmware self-heals in the field.