
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.
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.
| Failure Mode | Root Cause | Observable Symptom |
|---|---|---|
| Late arbitration loss detection | Master I2C peripheral does not monitor SDA during active write phase | Winning master data corrupted; slave receives corrupt address or payload |
| Clock stretching conflict | Slave or secondary master holds SCL LOW past peripheral timeout | Master times out waiting for SCL rise; triggers unhandled bus fault |
| Repeated START collision | Two masters issue repeated START (Sr) within identical t_BUF window | Arbitration restarts mid-frame; address phase corrupted |
| Stuck slave holding SDA low | Slave reset or interrupted during multi-byte READ sequence | SDA held LOW indefinitely; bus appears permanently busy |
| Insufficient bus free time | Firmware violates t_BUF (bus free time) specification between STOP and START | Back-to-back packets collide; arbitration starts mid-byte |
Not all microcontroller I2C peripherals handle multi-master arbitration autonomously. Consult the vendor reference manual:
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).ARLO in ISR (bit 9). Software clears the condition by setting ARLOCF in the ICR (Interrupt Clear Register).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).AL (Arbitration Lost) flag in the I2CSTR status register (bit 11), generating an AL interrupt and releasing the bus lines.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.
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 5Ustatic 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:
ARLO alongside SB, ADDR, and TXE to prevent deadlocks).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:
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 nsMaximum 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 pFR_p(max) = 300 ns / (0.8473 * 90 pF)= 300e-9 / (0.8473 * 90e-12)= 300e-9 / 7.6257e-11~= 3.93 kOhmMinimum 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 OhmDesign 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.
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);}}
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.
Quick Links
Legal Stuff





