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 TIMINGR 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)
  • Sensors with slow ADC conversions (TMP117: 15 ms typical, 50 ms max conversion time)
  • I2C GPIO expanders with slow internal logic (TCA9555: up to 200 µs per register)
  • Battery fuel gauges with complex calculation cycles (BQ27441: 10-30 ms)

The timeout must exceed the slowest slave’s maximum stretch. For a mixed bus with EEPROM (5 ms) + TMP117 (50 ms), configure at least 60 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 cannot generate START/STOP conditions — the bus is electrically dead until the slave releases its output driver or power cycles. This is more severe than clock stretching because SCL toggling cannot recover the bus.

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 (TIMINGR)

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

TIMEOUTA = (max_stretch_ms * 16000) // I2CCLK cycles

For a bus with TMP117 (50 ms max) + EEPROM (5 ms) = 55 ms worst case → 60 ms margin:

TIMEOUTA = 60 * 16000 = 960000 = 0xEA60

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

// STM32H7 I2C1 TIMINGR for 400 kHz
// PRESC=0, SCLL=0x7D (125), SCLH=0x6F (111), SDADEL=0x02, SCLDEL=0x04
I2C1->TIMINGR = (0 << 28) | (0x04 << 20) | (0x02 << 16) | (0x6F << 8) | (0x7D << 0);
// TIMEOUTR: TEXTOEN=1 (bit31), TEXTEN=1 (bit30), TIMEOUTA=0xEA60 (60 ms)
I2C1->TIMEOUTR = (1 << 31) | (1 << 30) | (0xEA60 << 0);
// Enable peripheral
I2C1->CR1 |= I2C_CR1_PE;

Key fields:

  • TEXTOEN=1 (bit 31): Extended clock timeout enable — triggers when SCL low > TIMEOUTA
  • TEXTEN=1 (bit 30): Standard timeout enable — triggers on SCL high timeout (idle bus)
  • TIMEOUTA (bits 11:0): 12-bit timeout value in I2CCLK cycles
  • TIDLE=0: Timeout detected on SCL low (not idle)

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 any ongoing transaction
i2c->CR1 &= ~I2C_CR1_PE;
// 2. Reconfigure SCL/SDA as GPIO open-drain output with pull-up
// Open-drain is critical: allows other masters to pull lines low safely
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);
// 3. Ensure SDA high, then clock 16 pulses on SCL
// 16 pulses guarantees even the slowest slave state machine resets
// (I2C spec: 9 pulses minimum for byte + ACK, 16 adds margin)
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
for (int i = 0; i < 16; i++) {
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);
// Delay calibrated for target bus speed (400 kHz = 2.5 µs period)
// 1 ms delay is conservative but safe for recovery
HAL_Delay(1);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
HAL_Delay(1);
}
// 4. Generate STOP condition: SDA rising edge while SCL high
// This signals all slaves to release bus and return to idle state
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(I2C_GPIO_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
// 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
return (HAL_GPIO_ReadPin(I2C_GPIO_PORT, I2C_SDA_PIN) == GPIO_PIN_RESET) &&
(i2c->SR1 & I2C_SR1_BUSY);
}

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

Step 4: Multi-Master Safe Initialization on Embedded Linux

When Linux i2c-dev and MCU firmware share a bus, enforce single-owner discipline:

// In MCU firmware: acquire bus mutex before ANY I2C transaction
osMutexAcquire(i2c_mutex, osWaitForever);
HAL_I2C_Master_Transmit(&hi2c1, addr, data, len, timeout);
osMutexRelease(i2c_mutex);
// In Linux userspace: use ioctl(I2C_SLAVE) with proper cleanup
int fd = open("/dev/i2c-1", O_RDWR);
ioctl(fd, I2C_SLAVE, addr);
write(fd, data, len);
close(fd); // Always close — kernel releases bus on close

Never leave file descriptors open across crashes — use O_CLOEXEC and signal handlers to close on SIGTERM.

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 (TIMINGR)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 (~1 ms)
Mutex + single-owner (multi-master)Eliminates arbitration racesRequires coordination across OS boundaries

For STM32 + FreeRTOS: combine hardware timeout (fast path) + FreeRTOS timer watchdog (backup) + mutex 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 TIMINGR timeouts for your slowest slave, (2) implement a 9-pulse GPIO recovery sequence triggered by timeout or stuck-SDA detection, (3) enforce single-owner mutex discipline 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/content/ccc/resource/technical/document/reference_manual/group0/c9/a3/76/fa/55/46/45/fa/DM00314099/files/DM00314099.pdf/jcr:content/translations/en.DM00314099.pdf
  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 device holds SDA low indefinitely (clock stretching timeout), when arbitration is lost during multi-master contention, or when a slave fails to release the bus after a transaction due to firmware bugs or power glitches.

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

Perform a bus recovery sequence: toggle SCL 9-16 times while SDA is held high, then issue a START/STOP condition. Many MCUs (including STM32) have hardware support for this via I2C_CR1_STOP or dedicated recovery registers.

Why does clock stretching cause bus hangs on STM32?

STM32 I2C peripherals have a configurable timeout (TIMINGR register). If a slave stretches the clock beyond this timeout, the I2C peripheral locks up. The default timeout is often disabled, causing indefinite hangs when slow slaves stretch the clock.

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

Monitor both SCL and SDA with a logic analyzer. If SCL is low, the master is stretching (master fault). If SDA is low while SCL is high, a slave is holding the bus (slave fault). Add GPIO toggles in ISR entry/exit to trace execution flow.

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

Fixing Sporadic Hard Faults in FreeRTOS Heap Allocation
Fixing Sporadic Hard Faults in FreeRTOS Heap Allocation
September 09, 2026
8 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media