
I2C clock stretching is an optional feature defined in the I2C specification that allows slave devices to hold the SCL line low, forcing the master to wait until the slave is ready to continue. While optional for slave devices to implement, an I2C-compliant master must support SCL clock stretching. On STM32 microcontrollers, the I2C peripheral includes a hardware timeout mechanism to detect when a slave stretches the clock beyond an acceptable duration. When this timeout fires, the HAL returns HAL_ERROR (with a timeout error code) or HAL_TIMEOUT and the transaction fails — often leaving the bus in a stuck state that requires recovery.
This article walks through the root causes of clock stretching timeouts, how to configure the timeout registers correctly, and the proper bus recovery sequence when things go wrong.
Before adjusting timeout registers, understand why the slave is stretching:
+------------------+ SCL Held Low +-------------------+| MASTER | <-------------------- | SLAVE || (STM32 I2C) | | (Sensor/MCU Slave)|+------------------+ +-------------------+| || Timeout fires after TIMEOUTA || (slave still processing) |v vHAL_TIMEOUT Still busy:- ADC conversion- Sensor measurement- Internal processing
Common causes:
The newer STM32 I2C v2 peripheral (found in F0, F3, F7, G0, G4, H7, L0, L4 families) uses the TIMEOUTR register to configure hardware timeout. (Note: Older families like F1 and F4 use I2C v1 and lack this hardware timeout register, requiring software workarounds.)
TIMEOUTR Bit Layout (Reference: RM0433, RM0316, RM0091):+--------+-------+------------+----------+-------+-------+------------+| 31 | 30-28 | 27-16 | 15 | 14-13 | 12 | 11-0 |+--------+-------+------------+----------+-------+-------+------------+| TEXTEN | Res. | TIMEOUTB | TIMOUTEN | Res. | TIDLE | TIMEOUTA |+--------+-------+------------+----------+-------+-------+------------+
Key fields for timeout:
| Field | Bits | Description |
|---|---|---|
TIMEOUTA | 11-0 | Bus timeout A (max SCL low duration when TIDLE=0) |
TIDLE | 12 | Timeout mode (0: SCL low timeout, 1: Bus idle timeout) |
TIMOUTEN | 15 | Timeout enable |
TIMEOUTB | 27-16 | Extended timeout B |
TEXTEN | 31 | Extended clock timeout enable |
Timeout formula (when TIDLE=0):
Timeout (seconds) = (TIMEOUTA + 1) × 2048 / I2C_KERNEL_CLOCK
Example calculation for 1.5 ms timeout with 32 MHz kernel clock:
// Target: 1.5 ms timeout// I2C_KERNEL_CLOCK = 32 MHz (typical for F7/G4)// Timeout = (TIMEOUTA + 1) * 2048 / 32e6 = 0.0015// TIMEOUTA + 1 = 0.0015 * 32e6 / 2048 ≈ 23.4// TIMEOUTA = 22 (0x16)#define I2C_TIMEOUT_MS 1.5f#define I2C_KERNEL_CLK_HZ 32000000uint16_t timeout_a = (uint16_t)((I2C_TIMEOUT_MS / 1000.0f) * I2C_KERNEL_CLK_HZ / 2048.0f) - 1;// timeout_a = 22
Configuration via CMSIS register access:
The standard HAL_I2C driver does not provide a wrapper function to enable the hardware timeout (it is formally part of the HAL_SMBUS driver). For standard I2C, you must configure the TIMEOUTR register directly after HAL_I2C_Init:
// After HAL_I2C_Init(&hi2c1)...// Configure TIMEOUTA to 22 (0x16) for ~1.5ms at 32MHz kernel clock// Set TIMOUTEN=1 to enable the timeout, TIDLE=0 for SCL low detectionhi2c1.Instance->TIMEOUTR = (22 << I2C_TIMEOUTR_TIMEOUTA_Pos) | I2C_TIMEOUTR_TIMOUTEN;
Critical safety note: In the STM32 HAL, a hardware timeout (from TIMEOUTR) typically causes blocking functions to return HAL_ERROR, whereas a software timeout (the timeout_ms parameter) returns HAL_TIMEOUT. You must check both:
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, slave_addr << 1, tx_buf, len, 1000);uint32_t error = HAL_I2C_GetError(&hi2c1);// Check if hardware timeout flag is set OR software timeout expiredif ((error & HAL_I2C_ERROR_TIMEOUT) || (status == HAL_TIMEOUT)) {// Timeout occurred — bus may be stuck with slave holding SCL lowI2C_BusRecovery(&hi2c1);}
When the slave holds SDA/SCL low indefinitely, the master must bit-bang up to 9 clock pulses. Crucially, the master must release SDA before clocking SCL, and abort early if the slave releases SDA.
// Define SCL/SDA Pin and Port assignments for your board#define I2C_SCL_PORT GPIOB#define I2C_SCL_PIN GPIO_PIN_6#define I2C_SDA_PORT GPIOB#define I2C_SDA_PIN GPIO_PIN_7void I2C_BusRecovery(I2C_HandleTypeDef *hi2c) {// Save current TIMEOUTR configuration to restore after resetuint32_t timeoutr_bak = hi2c->Instance->TIMEOUTR;// Configure SCL and SDA as Open-Drain OutputGPIO_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);// CRITICAL: Master must release SDA before clocking SCLHAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET);HAL_Delay(1);// Bit-bang up to 9 clock pulsesfor (int i = 0; i < 9; i++) {HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);HAL_Delay(1); // 1ms delay (slow, but safe for recovery)HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET);HAL_Delay(1);// If slave released SDA, we can stop earlyif (HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) == GPIO_PIN_SET) {break;}}// STOP condition: SDA low -> high while SCL is highHAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);HAL_Delay(1);HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_RESET);HAL_Delay(1);HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET);HAL_Delay(1);HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET);HAL_Delay(1);// Re-init I2C peripheral (DeInit resets peripheral registers)HAL_I2C_DeInit(hi2c);HAL_I2C_Init(hi2c);// Safety Critical: Restore hardware timeout configurationhi2c->Instance->TIMEOUTR = timeoutr_bak;}
[!IMPORTANT] Some STM32 Reference Manuals suggest using the software reset (
SWRSTor toggling thePEbit) to recover the bus. However, if a slave device is holding SDA low (waiting for a clock pulse to complete a transfer), resetting the STM32’s internal peripheral will not release the bus because the physical line is still pulled low by the slave. Manual bit-banging clock pulses is the only reliable recovery method.
typedef enum {I2C_OK = 0,I2C_ERR_TIMEOUT,I2C_ERR_NACK,I2C_ERR_BUS,I2C_ERR_RECOVERY_FAILED} I2C_Result_t;I2C_Result_t I2C_MasterTransmitRobust(I2C_HandleTypeDef *hi2c, uint16_t addr,uint8_t *data, uint16_t len, uint32_t timeout_ms,uint8_t max_retries) {for (uint8_t attempt = 0; attempt <= max_retries; attempt++) {HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(hi2c, addr << 1, data, len, timeout_ms);if (status == HAL_OK) {return I2C_OK;}uint32_t error = HAL_I2C_GetError(hi2c);if (error & HAL_I2C_ERROR_AF) {return I2C_ERR_NACK; // Slave NACK'd — don't retry}if ((error & HAL_I2C_ERROR_TIMEOUT) || (status == HAL_TIMEOUT)) {// Clear hardware timeout flag if setif (error & HAL_I2C_ERROR_TIMEOUT) {__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_TIMEOUT);}// Attempt bus recoveryI2C_BusRecovery(hi2c);// Small delay before retryHAL_Delay(5);continue;}// Other errors (BERR, ARLO, OVR) — attempt recovery onceif (attempt == 0) {I2C_BusRecovery(hi2c);HAL_Delay(5);continue;}return I2C_ERR_BUS;}return I2C_ERR_RECOVERY_FAILED;}
| Slave Type | Max Stretch | Recommended TIMEOUTA (32 MHz Kernel) |
|---|---|---|
| Fast custom slave (quick ISR) | 5 ms | 0x4D (77) → ~5.0 ms |
| Standard sensor (conversion) | 10 ms | 0x9B (155) → ~10.0 ms |
| Slow sensor (measurement) | 20 ms | 0x137 (311) → ~20.0 ms |
| Slow MCU-based slave | 50 ms | 0x30C (780) → ~50.0 ms |
Rule of thumb: Set timeout to 2–3× the slave’s maximum specified stretch time. Too short causes spurious timeouts; too long delays error detection.
I2C_KERNEL_CLOCK may not equal SYSCLK. On F7, it’s PCLK1. On H7, it’s PER_CK (configured via RCC).hi2c->Instance->TIMEOUTR after init to ensure the timeout was properly enabled and configured.ERRIE / I2C_IT_ERRI) in the CR1 register for immediate callback via HAL_I2C_ErrorCallback instead of polling.// Enable I2C Error Interrupts (which includes hardware timeout)__HAL_I2C_ENABLE_IT(&hi2c1, I2C_IT_ERRI);// In stm32g4xx_hal_i2c.c (or your specific I2C v2 family HAL):// The callback is HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)// Check hi2c->ErrorCode for HAL_I2C_ERROR_TIMEOUT
I2C clock stretching timeouts on STM32 are not a bug — they’re a feature protecting your system from hung slaves. The fix is rarely “increase the timeout blindly.” Instead:
TIMEOUTA in TIMEOUTR to 2–3× the measured maximum+---------------------------------------------------------------+| I2C Timeout Handling Checklist |+---------------------------------------------------------------+| [ ] Measured slave max stretch time with logic analyzer || [ ] Calculated TIMEOUTA for 2-3x margin || [ ] Verified TIMEOUTR register after timeout enable || [ ] Implemented 9-clock bus recovery sequence || [ ] Added retry wrapper with max 3 attempts || [ ] Handle NACK separately (don't retry) || [ ] Log timeout events for field diagnostics |+---------------------------------------------------------------+
The timeout mechanism exists because slaves will misbehave in production. Design for it, and your I2C bus will stay reliable even when sensors glitch or smart slaves take longer than expected.
Quick Links
Legal Stuff





