HomeAbout UsContact Us

Debugging I2C Bus Contention in Multi-Master Systems

By Jithin Tom
Published in Embedded Concepts
October 21, 2026
4 min read
Debugging I2C Bus Contention in Multi-Master Systems

Table Of Contents

01
Problem Statement
02
Root Cause Analysis
03
Solution Approaches
04
Verification Steps
05
Summary
06
Related Reading
07
References
08
Frequently Asked Questions

Problem Statement

I2C multi-master systems are common in embedded designs where multiple MCUs or SoCs share a single sensor bus — for example, a main application processor and a BMC (Baseboard Management Controller) both reading temperature sensors, or redundant flight computers accessing the same IMU. While the I2C specification defines hardware arbitration via wired-AND logic on the SDA line, real-world multi-master deployments frequently suffer from bus contention that manifests as intermittent communication failures, corrupted data, or complete bus lockup.

Engineers searching for “I2C multi-master arbitration not working”, “two masters on same I2C bus corrupting data”, or “I2C bus contention debugging” encounter a gap: the spec describes arbitration cleanly, but practical debugging requires understanding timing windows, firmware retry logic, and logic analyzer capture techniques that aren’t in the datasheet.

This article provides a systematic approach to debugging I2C bus contention in multi-master systems, covering arbitration mechanics, failure symptoms, capture strategies, and firmware mitigations.

Root Cause Analysis

I2C Wired-AND Arbitration Mechanics

I2C arbitration relies on the open-drain nature of the SDA and SCL lines with pull-up resistors. When multiple masters drive the bus simultaneously:

+-------------------------------------------------------------------------+
| I2C MULTI-MASTER ARBITRATION |
+-------------------------------------------------------------------------+
| |
| Bit Index: Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 ... Bit 0|
| |
| MASTER A: 1 1 1 0 1 0 ... 0 |
| (Target: 0xEA) |
| |
| MASTER B: 1 1 1 1 1 0 ... 0 |
| (Target: 0xFA) |
| | | | | |
| v v v v |
| BUS (SDA): 1 1 1 0 <-- Wired-AND (LOW wins) |
| ^ ^ ^ ^ |
| MASTER A SENSES: 1 1 1 0 <-- Match! Master A wins |
| MASTER B SENSES: 1 1 1 0 <-- MISMATCH! B loses |
| |
| Event Sequence at Bit 4: |
| 1. Master A pulls SDA LOW ('0'); Master B releases SDA HIGH ('1'). |
| 2. Bus voltage remains LOW due to open-drain wired-AND pull-down. |
| 3. While SCL is HIGH, Master B samples SDA and detects '0' != '1'. |
| 4. Master B detects arbitration loss, shuts off SDA driver immediately.|
| 5. Master A continues transmission without interruption or corruption. |
| |
| Key Property: Arbitration is completely non-destructive to Master A. |
+-------------------------------------------------------------------------+

Critical timing constraint: Arbitration occurs bit-by-bit during the address and data acknowledge phases. A master can only lose arbitration when it transmits a logic 1 (releases SDA to float high) while another device pulls SDA to logic 0 (asserts LOW). The losing master samples SDA while SCL is HIGH, detects the mismatch, and must shut off its SDA output driver before the falling edge of SCL. If the losing master fails to detect the loss before the next clock cycle, it may assert 0 on a subsequent bit when the winning master attempts to float 1, causing corrupt data frames and bus lockups.

Common Failure Modes

Failure ModeRoot CauseObservable Symptom
Late arbitration loss detectionMaster I2C peripheral does not monitor SDA during active write phaseWinning master data corrupted; slave receives corrupt address or payload
Clock stretching conflictSlave or secondary master holds SCL LOW past peripheral timeoutMaster times out waiting for SCL rise; triggers unhandled bus fault
Repeated START collisionTwo masters issue repeated START (Sr) within identical t_BUF windowArbitration restarts mid-frame; address phase corrupted
Stuck slave holding SDA lowSlave reset or interrupted during multi-byte READ sequenceSDA held LOW indefinitely; bus appears permanently busy
Insufficient bus free timeFirmware violates t_BUF (bus free time) specification between STOP and STARTBack-to-back packets collide; arbitration starts mid-byte

Solution Approaches

1. Verify Hardware Arbitration Support

Not all microcontroller I2C peripherals handle multi-master arbitration autonomously. Consult the vendor reference manual:

  • STM32 (F1/F2/F4 - RM0090): The classic I2C peripheral asserts the ARLO (Arbitration Lost) bit in SR1 (bit 9). On loss, hardware automatically clears MSL (switching to slave mode) and releases SDA/SCL. Software clears ARLO by writing 0 to the bit (i2c->SR1 = (uint16_t)~I2C_SR1_ARLO).
  • STM32 (F7/H7/G4/L4/U5 - RM0433/RM0399): The modern I2C peripheral sets ARLO in ISR (bit 9). Software clears the condition by setting ARLOCF in the ICR (Interrupt Clear Register).
  • NXP LPC / i.MX (Classic I2C vs LPI2C): Classic Freescale/NXP I2C controllers set the ARBL flag in I2Cx_S (bit 4, cleared by writing 1). Newer Low-Power I2C (LPI2C) modules set the ALF (Arbitration Lost Flag) in the Master Status Register MSR (bit 10, cleared by writing 1).
  • TI TMS570 / AM65x / Sitara: The I2C peripheral asserts the AL (Arbitration Lost) flag in the I2CSTR status register (bit 11), generating an AL interrupt and releasing the bus lines.
  • Renesas RA (RA4/RA6): The IIC module asserts the AL flag in ICSR2 (bit 2) and drops MST back to slave reception mode.

Verification test: Connect two development boards running independent masters to the shared I2C bus. Direct both to transmit simultaneously to distinct target addresses (e.g., 0x50 and 0x68). Capture SCL and SDA on a logic analyzer to verify that the losing master trips its arbitration-lost interrupt/status flag and releases SDA within the single colliding bit period.

2. Implement Robust Firmware Retry Logic

Every multi-master firmware implementation must handle arbitration loss gracefully without hanging in polling loops:

/*
* Robust Multi-Master I2C Write with Arbitration Handling (STM32 RM0090 style)
* Handles ARLO (Arbitration Lost) non-blockingly during START, ADDR, and DATA phases.
*/
#define I2C_BUS_TIMEOUT_US 25000U /* 25 ms maximum transaction timeout */
#define I2C_MAX_RETRIES 5U
static inline void i2c_random_backoff(uint32_t retry_count)
{
/* Truncated exponential backoff with pseudo-random jitter (1 to 2^retry * 250 us) */
uint32_t max_delay_us = (1U << (retry_count + 1U)) * 250U;
uint32_t delay_us = 250U + ((uint32_t)rand() % (max_delay_us ? max_delay_us : 250U));
delay_microsecond(delay_us);
}
int i2c_master_write_multi(I2C_TypeDef *i2c, uint8_t addr_7b, const uint8_t *data, uint16_t len)
{
uint32_t retries = 0;
while (retries < I2C_MAX_RETRIES) {
/* 1. Wait for bus free (SDA and SCL HIGH, BUSY cleared) before START */
uint32_t t_start = get_microseconds();
while (i2c->SR2 & I2C_SR2_BUSY) {
if ((get_microseconds() - t_start) > I2C_BUS_TIMEOUT_US) {
/* Bus stuck low by misbehaving slave or crashed master: run recovery */
i2c_bus_recovery(i2c);
break;
}
}
/* 2. Generate START condition */
i2c->CR1 |= I2C_CR1_START;
/* Wait for SB (Start Bit generated) or ARLO (Arbitration lost during START) */
t_start = get_microseconds();
while (!(i2c->SR1 & (I2C_SR1_SB | I2C_SR1_ARLO))) {
if ((get_microseconds() - t_start) > I2C_BUS_TIMEOUT_US) {
i2c->CR1 |= I2C_CR1_STOP;
return -ETIMEDOUT;
}
}
/* Check if arbitration was lost immediately at START condition */
if (i2c->SR1 & I2C_SR1_ARLO) {
i2c->SR1 = (uint16_t)~I2C_SR1_ARLO; /* Clear ARLO flag (rc_w0) */
retries++;
i2c_random_backoff(retries);
continue;
}
/* 3. Send 7-bit Slave Address + WRITE bit (0) */
i2c->DR = (uint8_t)(addr_7b << 1);
/* Wait for ADDR (Address Sent), ARLO (Arbitration Lost), or AF (NACK) */
t_start = get_microseconds();
while (!(i2c->SR1 & (I2C_SR1_ADDR | I2C_SR1_ARLO | I2C_SR1_AF))) {
if ((get_microseconds() - t_start) > I2C_BUS_TIMEOUT_US) {
i2c->CR1 |= I2C_CR1_STOP;
return -ETIMEDOUT;
}
}
/* Handle arbitration loss during address phase */
if (i2c->SR1 & I2C_SR1_ARLO) {
i2c->SR1 = (uint16_t)~I2C_SR1_ARLO;
retries++;
i2c_random_backoff(retries);
continue;
}
/* Handle slave NACK */
if (i2c->SR1 & I2C_SR1_AF) {
i2c->SR1 = (uint16_t)~I2C_SR1_AF;
i2c->CR1 |= I2C_CR1_STOP;
return -ENXIO; /* Target device did not acknowledge address */
}
/* Clear ADDR flag by sequential read of SR1 followed by SR2 */
(void)i2c->SR1;
(void)i2c->SR2;
/* 4. Stream Data Bytes with per-byte ARLO checking */
bool arlo_detected = false;
for (uint16_t i = 0; i < len; i++) {
i2c->DR = data[i];
t_start = get_microseconds();
while (!(i2c->SR1 & (I2C_SR1_TXE | I2C_SR1_ARLO | I2C_SR1_AF))) {
if ((get_microseconds() - t_start) > I2C_BUS_TIMEOUT_US) {
i2c->CR1 |= I2C_CR1_STOP;
return -ETIMEDOUT;
}
}
if (i2c->SR1 & I2C_SR1_ARLO) {
i2c->SR1 = (uint16_t)~I2C_SR1_ARLO;
arlo_detected = true;
break;
}
if (i2c->SR1 & I2C_SR1_AF) {
i2c->SR1 = (uint16_t)~I2C_SR1_AF;
i2c->CR1 |= I2C_CR1_STOP;
return -EIO;
}
}
if (arlo_detected) {
retries++;
i2c_random_backoff(retries);
continue;
}
/* Wait until Byte Transfer Finished (BTF) before issuing STOP */
t_start = get_microseconds();
while (!(i2c->SR1 & I2C_SR1_BTF)) {
if ((get_microseconds() - t_start) > I2C_BUS_TIMEOUT_US) {
break;
}
}
/* 5. Issue STOP condition */
i2c->CR1 |= I2C_CR1_STOP;
return 0; /* Transfer completed successfully */
}
return -EBUSY; /* Exceeded maximum retry attempts */
}
/*
* Hardware Bus Recovery Routine (NXP UM10204 Section 3.1.16 compliant)
* Resets stuck slaves holding SDA LOW by pulsing SCL up to 9 times,
* followed by a formal STOP condition.
*/
int i2c_bus_recovery(I2C_TypeDef *i2c)
{
/* 1. Disable the hardware I2C peripheral to relinquish internal pin control */
i2c->CR1 &= ~I2C_CR1_PE;
/* 2. Configure SCL and SDA as GPIO open-drain with pull-ups */
GPIO_InitTypeDef gpio = {0};
gpio.Mode = GPIO_MODE_OUTPUT_OD;
gpio.Pull = GPIO_PULLUP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
gpio.Pin = I2C_SCL_PIN;
HAL_GPIO_Init(I2C_SCL_PORT, &gpio);
gpio.Pin = I2C_SDA_PIN;
HAL_GPIO_Init(I2C_SDA_PORT, &gpio);
/* Float both lines HIGH initially */
HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
delay_microsecond(10);
/* 3. If SDA is held LOW by a stuck slave, issue up to 9 clock pulses on SCL */
for (int i = 0; i < 9; i++) {
if (HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) == GPIO_PIN_SET) {
break; /* Slave released SDA */
}
HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);
delay_microsecond(5);
HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
delay_microsecond(5);
}
/* 4. Generate STOP condition per I2C spec: transition SDA LOW to HIGH while SCL is HIGH */
HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);
delay_microsecond(5);
HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_RESET); /* SDA LOW */
delay_microsecond(5);
HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); /* SCL HIGH */
delay_microsecond(5);
HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET); /* SDA rises HIGH -> STOP */
delay_microsecond(10);
/* 5. Verify that SDA is now released HIGH */
bool recovered = (HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) == GPIO_PIN_SET);
/* 6. Re-enable I2C peripheral and restore hardware control */
i2c->CR1 |= I2C_CR1_PE;
return recovered ? 0 : -EIO;
}

Key firmware requirements:

  • Random exponential backoff after arbitration loss (prevents synchronized retry stampedes).
  • Non-blocking condition polling (evaluates ARLO alongside SB, ADDR, and TXE to prevent deadlocks).
  • Bus free timeout with formal bus recovery (handles stuck slaves without clobbering active master traffic).
  • Per-byte arbitration check (detects multi-master data collisions across entire packet length).
  • Deterministic retry limit (prevents infinite CPU starvation on hard hardware faults).

3. Logic Analyzer Capture Strategy

Set up the analyzer to catch arbitration events in real-time:

+-------------------------------------------------------------------------+
| LOGIC ANALYZER TRIGGER SETUP |
+-------------------------------------------------------------------------+
| |
| CHANNELS: |
| CH0: SCL (common clock line) |
| CH1: SDA (common data line) |
| CH2: MASTER_A_IRQ / DEBUG_GPIO (asserted on ARLO interrupt) |
| CH3: MASTER_B_IRQ / DEBUG_GPIO (asserted on ARLO interrupt) |
| |
| TRIGGER 1: Start / Repeated Start Contention |
| --------------------------------------------------------------------- |
| Condition: SDA edge FALLING while SCL is HIGH |
| Qualify with debug GPIO pulse from Master ISR on ARLO event. |
| |
| TRIGGER 2: Dual-Probe SDA Mismatch |
| --------------------------------------------------------------------- |
| Tap Master A internal TX pin vs common bus SDA: |
| - Master A output stage: attempting to float HIGH ('1') |
| - Common bus SDA: pulled LOW ('0') by Master B |
| - Trigger condition: (Master_A_TX == 1) AND (Bus_SDA == 0) while SCL=1 |
| |
| CAPTURE SETTINGS: |
| - Sample rate: >= 10 MHz (>= 25x oversampling for 400 kHz Fast-Mode) |
| - Capture depth: 50 ms to 100 ms (captures collision + backoff retry) |
| - Pre-trigger: 20% to 30% (captures bus state prior to collision) |
| |
| DECODER: Enable I2C protocol analyzer with "Show Bit Details / NACK" |
| |
+-------------------------------------------------------------------------+

What to look for in captures:

  1. Clean arbitration: Losing master releases SDA within 1 bit period; winning master continues uninterrupted.
  2. Glitched arbitration: SDA displays runt pulses or delayed rise time at the collision point -> check pull-up resistor sizing and bus capacitance.
  3. Clock stretching collision: SCL held low by one master or slave while another initiates a START -> both stall.
  4. Retry stampede: Multiple arbitration losses in rapid succession -> random backoff algorithm missing or insufficiently dispersed.

4. Pull-Up Resistor Sizing for Multi-Master

Multi-master buses inherently present higher capacitive loading due to multiple master input/output pin stages and extended PCB trace routing. Standard 4.7 kOhm pull-ups frequently violate Fast Mode rise-time specifications:

I2C Fast Mode (400 kHz) Timing Limits (NXP UM10204):
- Maximum bus capacitance (C_b): 400 pF
- Maximum rise time (t_r, 30% to 70%): <= 300 ns
- Maximum fall time (t_f): <= 300 ns
Maximum Pull-up Resistor Calculation:
R_p(max) = t_r / (0.8473 * C_b)
Example Multi-Master Bus Load:
- Master A pin capacitance: 10 pF
- Master B pin capacitance: 10 pF
- 4 Slaves (10 pF each): 40 pF
- PCB traces (15 cm * 2 pF/cm): 30 pF
--------------------------------------
Total Bus Capacitance (C_b) = 90 pF
R_p(max) = 300 ns / (0.8473 * 90 pF)
= 300e-9 / (0.8473 * 90e-12)
= 300e-9 / 7.6257e-11
~= 3.93 kOhm
Minimum Pull-up Resistor Calculation (V_OL <= 0.4V at I_OL = 3 mA):
R_p(min) = (V_DD - V_OL) / I_OL
= (3.3 V - 0.4 V) / 3 mA
= 2.9 V / 0.003 A
~= 967 Ohm
Design Recommendations:
- For 400 kHz Fast Mode with C_b = 90 pF: Select 2.2 kOhm or 3.3 kOhm.
- For 1 MHz Fast Mode Plus (Fm+): R_p typically 1.0 kOhm to 1.5 kOhm
(requires I_OL = 20 mA driver capability).

Warning: Pull-ups sized too small (R_p < R_p(min)) cause V_OL to rise above 0.4 V, preventing devices from recognizing logic 0 and potentially violating the maximum output sink current (I_OL) of transceiver pins.

5. Bus Recovery and Stuck Slave Handling

A single malfunctioning slave holding SDA low can deadlock the entire multi-master bus. Implement periodic bus health monitoring:

/* Periodic bus health check - invoke from a low-priority supervisor task */
void i2c_bus_health_check(I2C_TypeDef *i2c)
{
static uint32_t last_check = 0;
uint32_t now = millis();
if (now - last_check < 1000) {
return; /* Execute check every 1000 ms */
}
last_check = now;
/* Verify if bus indicates busy state */
if (!(i2c->SR2 & I2C_SR2_BUSY)) {
return; /* Bus is idle and healthy */
}
/* Bus remains busy continuously for > 1 second - likely stuck slave */
if (HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) == GPIO_PIN_RESET) {
LOG_WRN("I2C bus lockup detected: SDA held LOW > 1s, initiating recovery");
i2c_bus_recovery(i2c);
}
}

Verification Steps

  1. Two-Master Stress Test: Run both masters continuously writing/reading for 24 hours. Zero unhandled transaction failures, zero data corruption, and zero bus lockups expected (arbitration losses will occur normally and must be cleanly recovered via backoff).
  2. Clock Stretching Injection: Program one slave to stretch clock for 10 ms. Verify other master handles it without timeout.
  3. Power Glitch Test: Power-cycle one master during active transaction. Verify bus recovers within 100 ms.
  4. Capacitance Margin: Add 100 pF capacitor to bus. Verify arbitration still works at 400 kHz.
  5. Logic Analyzer Validation: Capture 100+ arbitration events. Verify all show clean loss detection within 1 bit period.

Summary

I2C multi-master contention is solvable with three pillars: hardware arbitration verification (confirm your MCU’s peripheral implements ARLO/AL correctly), firmware resilience (random backoff, bus recovery, per-byte arbitration checks), and physical layer margins (adequate pull-ups for total bus capacitance). The logic analyzer is your primary diagnostic tool — trigger on SDA mismatch during SCL high to catch arbitration events in real-time. Most “mysterious” multi-master failures trace to insufficient bus free time between transactions or missing random backoff causing synchronized retry storms.

References

  1. NXP Semiconductors. I2C-bus specification and user manual, User Manual UM10204, Rev 7.0.
  2. STMicroelectronics. STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 Advanced Arm-based 32-bit MCUs Reference Manual, RM0090, Rev 19. https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series/documentation.html
  3. STMicroelectronics. Using the hardware I2C peripheral on STM32 microcontrollers, Application Note AN4759, Rev 3.
  4. Texas Instruments. I2C Bus Pull-Up Resistor Calculation, Application Report SLVA689. https://www.ti.com/lit/an/slva689a/slva689a.pdf
  5. Philips Semiconductors. The I2C-bus specification version 2.1, January 2000.
  6. Zephyr Project. I2C API Reference, Release 3.7.0. https://docs.zephyrproject.org/latest/hardware/peripherals/i2c.html

Frequently Asked Questions

How does I2C multi-master arbitration work?

I2C uses wired-AND arbitration on the SDA line. When two masters drive SDA simultaneously, the one driving LOW wins because the bus is open-drain with pull-ups. The losing master detects the mismatch between what it drove and what it reads on SDA, then backs off and retries after the bus is free.

What are the symptoms of I2C bus contention in a multi-master system?

Symptoms include: intermittent NACK responses, corrupted data bytes, slaves not responding to correct addresses, masters timing out waiting for bus free, and logic analyzer captures showing SDA glitches or unexpected transitions during address/data phases.

How can you prevent a master from hogging the I2C bus?

Implement a bus occupancy timeout in each master's firmware — if the bus isn't free within a configured window (e.g., 10 ms), the master should back off and retry later. Also use the I2C bus recovery sequence (9 clock pulses with SDA high) to clear stuck slaves before retrying.

What logic analyzer trigger setup catches multi-master arbitration events?

Trigger on SDA falling edge while SCL is HIGH (repeated START condition) combined with a second trigger on SDA mismatch: configure the analyzer to capture when Master A drives SDA high but reads low, indicating another master is holding the bus low. Set capture depth to 10-50 ms to see the full arbitration + retry sequence.

Can you use I2C multi-master without hardware arbitration support?

No — multi-master requires hardware-level wired-AND arbitration on the SDA line. Software-only approaches (polling a GPIO for bus busy) cannot guarantee atomic arbitration because the check-then-act sequence is not atomic. All I2C controllers claiming multi-master support implement arbitration in hardware.

Tags

i2cmulti-masterarbitrationcontentiondebuggingembedded

Share


Previous Article
Analyzing RTOS Task Deadline Misses: Causes and Solutions
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Fixing SPI Clock Phase Polarity Mismatch in Multi-Slave
Fixing SPI Clock Phase Polarity Mismatch in Multi-Slave
October 11, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media