HomeAbout UsContact Us

Debugging Intermittent I2C Bus Hangs in Embedded Systems

By Jithin Tom
September 15, 2026
4 min read
Debugging Intermittent I2C Bus Hangs in Embedded Systems

Table Of Contents

01
Problem Statement: Silent Bus Hangs with No Error Codes
02
Root Cause Analysis: Three Classes of I2C Bus Hangs
03
Solution: Robust I2C Bus Recovery and Configuration
04
Verification: Provoking and Confirming Fixes
05
Trade-offs and Considerations
06
Summary
07
Related Reading
08
References
09
Frequently Asked Questions

When an I2C bus hangs intermittently, the system appears to freeze — sensor reads return garbage, the MCU stalls in the I2C ISR, and a logic analyzer shows SCL stuck low or SDA held down. Unlike a clean NACK error, a bus hang leaves no error code, only a silent stall that survives software resets. This guide covers diagnosing and fixing the three root causes: slave clock-stretching timeouts, stuck-slave SDA holds, and multi-master arbitration collisions.

+------------------+ +------------------+ +------------------+
| I2C MASTER | | I2C SLAVE | | BUS STATE |
| [STM32/MPU] | <--> | [Sensor/EEPROM] | | SCL / SDA |
| | | | | |
| Sends START | ---> | ACKs | | NORMAL |
| Sends ADDR+R/W | ---> | ACKs | | |
| Sends DATA | ---> | CLOCK STRETCH | | SCL HELD LOW |
| WAITS... | <--- | (too long) | | TIMEOUT! |
| TIMEOUT/RESET | | HOLDS SDA LOW | | SDA HELD LOW |
+------------------+ +------------------+ +------------------+

Problem Statement: Silent Bus Hangs with No Error Codes

Consider an STM32H7-based industrial gateway polling a temperature sensor (TMP117) and an EEPROM (24LC256) on the same I2C bus at 400 kHz. Every few hours, the I2C driver returns HAL_TIMEOUT, the task blocks indefinitely, and a power cycle is the only recovery. The sensor datasheet claims 400 kHz support, and the EEPROM is rated for 1 MHz. Logic analyzer captures show clean transactions 99.9% of the time — then suddenly SCL stays low for 200 ms, the master times out, and the bus never recovers without a hardware reset.

Root Cause Analysis: Three Classes of I2C Bus Hangs

I2C bus hangs fall into three distinct categories, each with different diagnostics and fixes. Understanding which class you’re facing is critical — the recovery strategy differs completely.

1. Slave Clock Stretching Beyond Master Timeout

I2C slaves may stretch SCL low after receiving a byte to buy processing time. The I2C spec allows this indefinitely, but practical masters implement timeouts to prevent indefinite stalls. On STM32, the TIMEOUTR register configures TIMEOUTA (SCL low timeout) and TIMEOUTB (SCL high timeout). If disabled (default reset value), the master waits forever — the most common cause of “random” hangs that only a power cycle resolves.

Common offenders:

  • Slow EEPROMs during internal write cycles (AT24C256: up to 5 ms page write)
  • Humidity/temperature sensors with measurement hold mode (SHT31: up to 15 ms clock stretch during conversion)
  • Battery fuel gauges during data flow control (BQ27441: up to 4 ms in normal mode, ≤ 100 µs in sleep)
  • I2C-connected microcontrollers acting as slaves with variable ISR latency

The timeout must exceed the slowest slave’s maximum stretch. For a mixed bus with EEPROM (5 ms) + SHT31 (15 ms), configure at least 25 ms margin.

2. Slave Holds SDA Low Indefinitely

A slave may hold SDA low after a transaction due to:

  • Firmware bug in slave state machine (missed STOP condition detection)
  • Power glitch causing slave to enter undefined state with output driver enabled
  • Master sends extra clocks after STOP; slave interprets as new transaction start
  • Slave address collision: two devices with same address both drive SDA

When SDA is stuck low, the master’s hardware peripheral cannot generate START or STOP conditions, rendering the bus dead to standard API calls. However, because the slave is usually stuck waiting for clock pulses to finish shifting out a pending data byte or ACK/NACK bit, software can recover the bus by bit-banging clock pulses on SCL until SDA is released.

3. Multi-Master Arbitration Loss Without Recovery

In multi-master systems (e.g., STM32 MCU + Linux kernel i2c-dev both accessing the same physical bus), arbitration loss should cause the loser to back off cleanly. But if the loser’s driver doesn’t properly release the bus or re-initialize its state machine, it may leave SDA/SCL in a stuck state. Common scenarios:

  • Linux i2c-dev userspace holds bus open across ioctl calls; crash leaves bus locked
  • MCU firmware doesn’t implement proper bus-free detection before initiating transactions
  • Both masters attempt START simultaneously; loser’s ISR doesn’t clear BUSY flag

Solution: Robust I2C Bus Recovery and Configuration

Step 1: Calculate and Configure STM32 I2C Timeout (TIMEOUTR)

Always configure TIMEOUTA for SCL low timeout based on your slowest slave’s maximum stretch. The formula for TIMEOUTA at 16 MHz I2CCLK (with TIDLE=0):

TIMEOUTA = (max_stretch_s * I2CCLK / 2048) - 1

For a bus with SHT31 (15 ms max) + EEPROM (5 ms) = 20 ms worst case → 25 ms margin:

TIMEOUTA = (0.025 * 16000000 / 2048) - 1 = 194.3 ≈ 195 = 0x00C3

STM32H7 I2C1 TIMINGR for 400 kHz with 25 ms SCL low timeout:

// STM32H7 I2C1 TIMINGR for 400 kHz (I2CCLK = 16 MHz, Fast Mode)
// PRESC=0, SCLDEL=1 (125 ns setup), SDADEL=0 (0 ns hold), SCLH=7, SCLL=22
I2C1->TIMINGR = (0 << 28) | (0x01 << 20) | (0x00 << 16) | (0x07 << 8) | (0x16 << 0);
// TIMEOUTR: TIMOUTEN=1 (bit15), TIDLE=0 (bit12), TIMEOUTA=0x00C3 (25 ms)
I2C1->TIMEOUTR = (1 << 15) | (0 << 12) | (0x00C3 << 0);
// Enable peripheral
I2C1->CR1 |= I2C_CR1_PE;

Key fields:

  • TIMOUTEN=1 (bit 15): Timeout enable
  • TIDLE=0 (bit 12): Timeout detected on SCL low (not idle)
  • TIMEOUTA (bits 11:0): 12-bit timeout value

Verification: After programming, trigger a 70 ms slave stretch — the TIMEOUT interrupt should fire, ISR executes recovery, and bus returns to functional state within 2 ms.

Step 2: Implement Hardware Bus Recovery Sequence

When timeout fires or stuck SDA detected, execute GPIO-based recovery:

void i2c_bus_recover(I2C_TypeDef *i2c)
{
// 1. Disable I2C peripheral immediately — stops hardware state machine
i2c->CR1 &= ~I2C_CR1_PE;
// 2. Reconfigure SCL/SDA as GPIO open-drain output with pull-up
// Open-drain prevents electrical contention if slave drives SDA low
GPIO_InitTypeDef gpio = {0};
gpio.Mode = GPIO_MODE_OUTPUT_OD;
gpio.Pull = GPIO_PULLUP;
gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
gpio.Pin = I2C_SCL_PIN | I2C_SDA_PIN;
HAL_GPIO_Init(I2C_GPIO_PORT, &gpio);
// Release SDA (set output high in open-drain floats line)
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
// 3. Clock up to 9-16 pulses on SCL until slave releases SDA
// Standard I2C bus clear: 9 clock pulses clock out any stuck data byte + NACK
for (int i = 0; i < 16; i++) {
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);
delay_us(5); // ~100 kHz recovery clock
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
delay_us(5);
// If slave released SDA, break early
if (HAL_GPIO_ReadPin(I2C_GPIO_PORT, I2C_SDA_PIN) == GPIO_PIN_SET) {
break;
}
}
// 4. Generate valid STOP condition: SDA rising edge while SCL is HIGH
// Bring SCL LOW first so we can transition SDA LOW without generating a false START
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);
delay_us(5);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_RESET);
delay_us(5);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
delay_us(5);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
delay_us(5);
// 5. Re-initialize I2C peripheral with full configuration
MX_I2C1_Init();
}

Step 3: Detect Stuck SDA Before Timeout

Poll SDA state in a watchdog task or timer callback:

bool i2c_is_sda_stuck(I2C_TypeDef *i2c)
{
// Read SDA GPIO input state directly when peripheral status indicates busy
// On STM32H7 (I2C v2), the status register is ISR and busy flag is I2C_ISR_BUSY
return (HAL_GPIO_ReadPin(I2C_GPIO_PORT, I2C_SDA_PIN) == GPIO_PIN_RESET) &&
(i2c->ISR & I2C_ISR_BUSY);
}

If SDA stuck low while bus marked busy, trigger recovery immediately — don’t wait for timeout.

Step 4: Multi-Master Safe Operation and Arbitration Handling

In multi-master systems sharing an I2C bus:

  • Heterogeneous SoCs (e.g. STM32MP1 Cortex-A7 Linux + Cortex-M4 MCU): Software mutexes cannot cross processor cores without hardware support. Firmware must acquire a hardware semaphore (HSEM) or communicate via RPMsg before calling HAL_I2C_Master_Transmit().
  • Discrete Multi-Master Wire Contention: If another master pulls SDA low while this MCU transmits a ‘1’, the STM32 hardware detects arbitration loss, automatically sets ARLO in I2C_ISR, releases SCL/SDA, and enters slave/listening mode.
// In MCU firmware: handle arbitration lost (ARLO) flag
if (i2c->ISR & I2C_ISR_ARLO) {
// Clear arbitration loss flag
i2c->ICR = I2C_ICR_ARLOCF;
// Back off with exponential jitter before retrying transfer
osDelay(backoff_ms);
}

On the Linux side, ensure userspace applications close /dev/i2c-X file descriptors with O_CLOEXEC and rely on kernel I2C adapter retries (i2c_adapter.retries) to gracefully handle hardware arbitration loss.

Verification: Provoking and Confirming Fixes

Test 1: Slow Slave Clock Stretch Simulation

// On test slave (Arduino/second MCU): stretch SCL for 30 ms on every read
void requestEvent() {
delay(30); // Exceeds 25 ms timeout
Wire.write(sensor_data, 2);
}

Verify master recovers via timeout ISR and logs recovery event.

Test 2: Stuck SDA Injection

// On test slave: hold SDA low after STOP
void onStop() {
pinMode(SDA_PIN, OUTPUT);
digitalWrite(SDA_PIN, LOW); // Malicious hold
}

Verify watchdog detects stuck SDA within 100 ms and triggers recovery.

Test 3: Multi-Master Stress Test

Run Linux i2cset/i2cget loop concurrently with MCU polling for 24 hours. Verify zero bus hangs and clean arbitration (logic analyzer shows clean START/STOP from both masters).

Trade-offs and Considerations

ApproachProsCons
Hardware timeout (TIMEOUTR)Zero CPU overhead, deterministicRequires precise timing calc per bus speed
Software watchdog + GPIO pollWorks on any MCU, flexible thresholdsConsumes CPU cycles, jitter-sensitive
Bus recovery sequenceRecovers from any stuck stateMomentary bus disruption (~100 µs)
Hardware semaphore / ARLO (multi-master)Eliminates arbitration corruptionRequires coordination or backoff handling

For STM32 + FreeRTOS: combine hardware timeout (fast path) + FreeRTOS timer watchdog (backup) + ARLO retry backoff for multi-master. For bare-metal: hardware timeout + periodic GPIO poll in SysTick.

Summary

Intermittent I2C bus hangs are not random — they are deterministic failures of timeout configuration, slave state management, or multi-master discipline. The fix is threefold: (1) enable and tune TIMEOUTR timeouts for your slowest slave, (2) implement a 9-pulse GPIO recovery sequence triggered by timeout or stuck-SDA detection, (3) handle hardware arbitration loss (ARLO) and cross-core hardware semaphores when Linux and MCU share the bus. With these, the gateway above ran 30 days without a single bus hang.

References

  1. NXP Semiconductors. “I2C-bus Specification and User Manual.” UM10204, Rev. 7, 2021. https://www.nxp.com/docs/en/user-guide/UM10204.pdf
  2. STMicroelectronics. “STM32H7 Series Reference Manual.” RM0433, 2023. https://www.st.com/en/microcontrollers-microprocessors/stm32h7-series/documentation.html
  3. Texas Instruments. “I2C Communication Protocol Overview.” Application Report SLVA704, 2018. https://www.ti.com/lit/an/slva704/slva704.pdf
  4. Linux Kernel Documentation. “I2C Bus Drivers.” https://www.kernel.org/doc/html/latest/i2c/index.html

Frequently Asked Questions

What causes an I2C bus to hang indefinitely?

I2C bus hangs typically occur when a slave holds SDA low mid-byte (e.g. master reset during a read transaction), when a slave stretches SCL low beyond master timeout limits, when multi-master arbitration causes an unhandled arbitration loss (ARLO), or when electrical noise introduces spurious clock edges.

How do you recover an I2C bus without resetting the MCU?

Perform a GPIO bus clear sequence: reconfigure SCL and SDA as GPIO open-drain, toggle SCL 9 to 16 times until the slave releases SDA, then generate a STOP condition before re-enabling the hardware I2C peripheral.

Why does clock stretching cause bus hangs on STM32?

STM32 I2C peripherals feature hardware timeout detection configured via the TIMEOUTR register. If a slave holds SCL low beyond TIMEOUTA and timeout interrupts are unhandled, or if TIMEOUTEN is disabled (the default reset state), the master firmware driver stalls indefinitely waiting for transfer completion.

How can you distinguish between master and slave fault in a bus hang?

Monitor SCL and SDA with a logic analyzer or oscilloscope. If SCL is held low indefinitely, a slave is clock-stretching (or the master is stuck in an ISR holding SCL low). If SDA is held low while SCL is released high, a slave is stuck mid-transaction driving the data line.

Tags

i2cdebuggingbus-hangstm32embedded-linux

Share


Previous Article
Debugging Linux Kernel Oops: Using Serial Console and KGDB
Jithin Tom

Jithin Tom

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

Related Posts

Embedded Linux Kernel Driver Debugging: Common Issues
Embedded Linux Kernel Driver Debugging: Common Issues
October 12, 2026
7 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media