HomeAbout UsContact Us

Fixing I2C Clock Stretching Timeouts on STM32

By Jithin Tom
Published in Embedded Concepts
July 07, 2026
3 min read
Fixing I2C Clock Stretching Timeouts on STM32

Table Of Contents

01
Root Causes of Clock Stretching Timeouts
02
STM32 I2C Timeout Register Configuration
03
Handling Timeout Errors in HAL
04
Bus Recovery Sequence
05
Complete Timeout Handling Wrapper
06
Timeout Configuration Cheat Sheet
07
Debugging Tips
08
Summary
09
References
10
Frequently Asked Questions

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.

Root Causes of Clock Stretching Timeouts

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 v
HAL_TIMEOUT Still busy:
- ADC conversion
- Sensor measurement
- Internal processing

Common causes:

  1. Sensor measurements — Devices like SHT3x temperature/humidity sensors or BMP280 stretch SCL during internal ADC conversion/measurement cycles (if configured in clock-stretching mode).
  2. Slow smart slave firmware — Custom I2C slaves running on microcontrollers (e.g., PIC, AVR, or other STM32s) that handle I2C bytes in software interrupt service routines (ISRs).
  3. Bus capacitance and weak pull-ups — Excessive bus capacitance or high-value pull-up resistors slow down SCL/SDA rise times, which can trigger timeout thresholds on sensitive master peripherals.

STM32 I2C Timeout Register Configuration

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:

FieldBitsDescription
TIMEOUTA11-0Bus timeout A (max SCL low duration when TIDLE=0)
TIDLE12Timeout mode (0: SCL low timeout, 1: Bus idle timeout)
TIMOUTEN15Timeout enable
TIMEOUTB27-16Extended timeout B
TEXTEN31Extended 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 32000000
uint16_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 detection
hi2c1.Instance->TIMEOUTR = (22 << I2C_TIMEOUTR_TIMEOUTA_Pos) | I2C_TIMEOUTR_TIMOUTEN;

Handling Timeout Errors in HAL

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 expired
if ((error & HAL_I2C_ERROR_TIMEOUT) || (status == HAL_TIMEOUT)) {
// Timeout occurred — bus may be stuck with slave holding SCL low
I2C_BusRecovery(&hi2c1);
}

Bus Recovery Sequence

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_7
void I2C_BusRecovery(I2C_HandleTypeDef *hi2c) {
// Save current TIMEOUTR configuration to restore after reset
uint32_t timeoutr_bak = hi2c->Instance->TIMEOUTR;
// Configure SCL and SDA as Open-Drain Output
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);
// CRITICAL: Master must release SDA before clocking SCL
HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
HAL_Delay(1);
// Bit-bang up to 9 clock pulses
for (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 early
if (HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) == GPIO_PIN_SET) {
break;
}
}
// STOP condition: SDA low -> high while SCL is high
HAL_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 configuration
hi2c->Instance->TIMEOUTR = timeoutr_bak;
}

[!IMPORTANT] Some STM32 Reference Manuals suggest using the software reset (SWRST or toggling the PE bit) 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.

Complete Timeout Handling Wrapper

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 set
if (error & HAL_I2C_ERROR_TIMEOUT) {
__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_TIMEOUT);
}
// Attempt bus recovery
I2C_BusRecovery(hi2c);
// Small delay before retry
HAL_Delay(5);
continue;
}
// Other errors (BERR, ARLO, OVR) — attempt recovery once
if (attempt == 0) {
I2C_BusRecovery(hi2c);
HAL_Delay(5);
continue;
}
return I2C_ERR_BUS;
}
return I2C_ERR_RECOVERY_FAILED;
}

Timeout Configuration Cheat Sheet

Slave TypeMax StretchRecommended TIMEOUTA (32 MHz Kernel)
Fast custom slave (quick ISR)5 ms0x4D (77) → ~5.0 ms
Standard sensor (conversion)10 ms0x9B (155) → ~10.0 ms
Slow sensor (measurement)20 ms0x137 (311) → ~20.0 ms
Slow MCU-based slave50 ms0x30C (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.

Debugging Tips

  1. Measure actual stretch time — Use a logic analyzer on SCL. Trigger on SCL falling edge, measure low duration.
  2. Check kernel clockI2C_KERNEL_CLOCK may not equal SYSCLK. On F7, it’s PCLK1. On H7, it’s PER_CK (configured via RCC).
  3. Verify TIMEOUTR value — Read back hi2c->Instance->TIMEOUTR after init to ensure the timeout was properly enabled and configured.
  4. Enable error interrupt — Set the Error Interrupt Enable bit (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

Summary

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:

  1. Measure the actual stretch duration with a logic analyzer
  2. Configure TIMEOUTA in TIMEOUTR to 2–3× the measured maximum
  3. Implement a robust bus recovery sequence (9 clocks + STOP + re-init)
  4. Wrap HAL calls with retry logic that only retries after successful recovery
+---------------------------------------------------------------+
| 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.

References

  1. STMicroelectronics, RM0316 Reference Manual: STM32F303xB/C/D/E, STM32F303x6/8, Section “I2C_TIMEOUTR register”
  2. STMicroelectronics, AN4235 Application Note: I2C timing configuration tool for STM32F3xxxx and STM32F0xxxx, 2018
  3. NXP Semiconductors, UM10204 I2C-bus Specification and User Manual, Rev. 6, 2014 — Section 3.1.9 “Clock Stretching”
  4. STMicroelectronics, AN2824 Application Note: STM32F10xxx I2C optimized examples, 2010
  5. Texas Instruments, SLVA704 Application Report: Understanding the I2C Bus, 2015
  6. Sensirion, SHT3x-DIS Datasheet: Humidity and Temperature Sensor, 2015 — Section 5.3 “Clock Stretching Mode”

Frequently Asked Questions

What causes I2C clock stretching timeouts on STM32?

Clock stretching timeouts occur when a slave device holds SCL low longer than the STM32 I2C peripheral's timeout threshold. Common causes include slow slave processing, heavy interrupt load on the slave, or the slave entering an unexpected wait state during transaction processing.

How does the STM32 I2C timeout mechanism work?

The STM32 I2C peripheral implements a programmable timeout counter (TIMEOUTA/TIMEOUTB in TIMEOUTR register). When SCL is held low by a slave beyond the configured timeout value, the TIMEOUT flag is set and an interrupt can be generated. The timeout value is derived from the I2C kernel clock and the TIMEOUTA field.

Can clock stretching be disabled on STM32 I2C?

Yes, clock stretching can be disabled via the NOSTRETCH bit in the CR1 register (master mode) or via the SBC bit for slave mode. However, disabling clock stretching violates the I2C specification and may cause data corruption with compliant slaves that require stretching. It should only be used with known-compatible devices.

What is the correct way to handle I2C timeout errors in HAL?

Check if the function returned HAL_TIMEOUT (software timeout) or if HAL_I2C_GetError() reports HAL_I2C_ERROR_TIMEOUT (hardware timeout). If either occurs, clear the hardware flag if set, then implement a bus recovery sequence (release SDA, generate up to 9 SCL pulses until SDA is high, then generate a STOP condition) before re-initializing the peripheral and retrying.

How do you calculate the I2C timeout value for a given clock stretch duration?

Timeout (µs) = (TIMEOUTA + 1) × 2048 / I2C_KERNEL_CLOCK(Hz) × 10^6. For a 32 MHz kernel clock with TIMEOUTA=0x17 (23), timeout = 24 × 2048 / 32M × 10^6 = 1.536 ms. Adjust TIMEOUTA to match your slave's maximum stretch time with margin.

Tags

i2cstm32clock-stretchingtimeouthal

Share


Previous Article
Security Best Practices for Embedded Firmware
Jithin Tom

Jithin Tom

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

Related Posts

I2C Protocol Deep Dive for Embedded Systems
I2C Protocol Deep Dive for Embedded Systems
June 15, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media