
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 |+------------------+ +------------------+ +------------------+
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.
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.
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:
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.
A slave may hold SDA low after a transaction due to:
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.
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:
i2c-dev userspace holds bus open across ioctl calls; crash leaves bus lockedAlways 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=0x04I2C1->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 peripheralI2C1->CR1 |= I2C_CR1_PE;
Key fields:
TEXTOEN=1 (bit 31): Extended clock timeout enable — triggers when SCL low > TIMEOUTATEXTEN=1 (bit 30): Standard timeout enable — triggers on SCL high timeout (idle bus)TIMEOUTA (bits 11:0): 12-bit timeout value in I2CCLK cyclesTIDLE=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.
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 transactioni2c->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 safelyGPIO_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 recoveryHAL_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 stateHAL_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 configurationMX_I2C1_Init();}
Poll SDA state in a watchdog task or timer callback:
bool i2c_is_sda_stuck(I2C_TypeDef *i2c){// Read SDA GPIO input state directlyreturn (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.
When Linux i2c-dev and MCU firmware share a bus, enforce single-owner discipline:
// In MCU firmware: acquire bus mutex before ANY I2C transactionosMutexAcquire(i2c_mutex, osWaitForever);HAL_I2C_Master_Transmit(&hi2c1, addr, data, len, timeout);osMutexRelease(i2c_mutex);// In Linux userspace: use ioctl(I2C_SLAVE) with proper cleanupint 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.
// On test slave (Arduino/second MCU): stretch SCL for 30 ms on every readvoid requestEvent() {delay(30); // Exceeds 25 ms timeoutWire.write(sensor_data, 2);}
Verify master recovers via timeout ISR and logs recovery event.
// On test slave: hold SDA low after STOPvoid onStop() {pinMode(SDA_PIN, OUTPUT);digitalWrite(SDA_PIN, LOW); // Malicious hold}
Verify watchdog detects stuck SDA within 100 ms and triggers recovery.
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).
| Approach | Pros | Cons |
|---|---|---|
| Hardware timeout (TIMINGR) | Zero CPU overhead, deterministic | Requires precise timing calc per bus speed |
| Software watchdog + GPIO poll | Works on any MCU, flexible thresholds | Consumes CPU cycles, jitter-sensitive |
| Bus recovery sequence | Recovers from any stuck state | Momentary bus disruption (~1 ms) |
| Mutex + single-owner (multi-master) | Eliminates arbitration races | Requires 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.
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.
Quick Links
Legal Stuff





