The Symptom

You are reading from an environmental sensor — SHT31, BME280, or similar — over I²C on STM32F4 or STM32H7. Everything works fine until the board experiences a power glitch, a noisy environment event, or the sensor is unexpectedly reset mid-transaction. After that, every call to HAL_I2C_Master_Transmit() or HAL_I2C_Master_Receive() returns HAL_BUSY, and nothing you do through the HAL API clears it.

Calling HAL_I2C_DeInit() followed by HAL_I2C_Init() does not help. Neither does toggling the peripheral clock reset. The BUSY flag in the I2C_SR2 register (STM32F4) or ISR register (STM32H7) remains set.

Root Cause

ST's errata document for the STM32F4 (section 2.14.4, "Spurious Bus Error detection in Master mode") describes this scenario precisely. When the I²C bus encounters an unexpected START or STOP condition — or when SDA is held low by a slave that was interrupted mid-byte — the peripheral's internal state machine reaches a state it cannot leave through a software reset alone.

The BUSY flag is driven partly by the hardware detection of bus activity at the GPIO level, not just internal register state. A peripheral reset clears the internal state, but if SDA or SCL is being held by the slave device, the hardware sees the bus as still active and immediately re-asserts BUSY after the reset completes.

Why nine clock pulses? A single I²C byte transfer is eight data bits plus one ACK bit. If the slave was interrupted partway through a byte and is holding SDA low waiting to clock out the remaining bits, nine SCL pulses are guaranteed to complete any in-progress transfer and release the bus, regardless of where in the byte the slave was.

The Recovery Sequence

The correct approach is to temporarily reconfigure the SCL and SDA pins as GPIO outputs, manually clock the bus, generate a STOP condition, and then re-initialise the I²C peripheral. Here is the complete implementation for STM32F4 (I²C1 on PB6/PB7). Adapt pin and port references to match your hardware.

C — i2c_recovery.c
#include "stm32f4xx_hal.h"

/**
 * I2C_RecoverBus — unstick a slave holding SDA low.
 *
 * Call this when HAL_I2C_Master_Transmit / Receive returns HAL_BUSY
 * and a DeInit+Init cycle has not helped.
 *
 * @param hi2c      Pointer to I2C handle
 * @param scl_port  GPIO port for SCL (e.g. GPIOB)
 * @param scl_pin   GPIO pin for SCL  (e.g. GPIO_PIN_6)
 * @param sda_port  GPIO port for SDA (e.g. GPIOB)
 * @param sda_pin   GPIO pin for SDA  (e.g. GPIO_PIN_7)
 */
HAL_StatusTypeDef I2C_RecoverBus(I2C_HandleTypeDef *hi2c,
                                  GPIO_TypeDef *scl_port, uint16_t scl_pin,
                                  GPIO_TypeDef *sda_port, uint16_t sda_pin)
{
    GPIO_InitTypeDef gpio = {0};

    /* Step 1 — de-init the I2C peripheral */
    HAL_I2C_DeInit(hi2c);

    /* Step 2 — reconfigure SCL and SDA as open-drain GPIO outputs */
    gpio.Mode  = GPIO_MODE_OUTPUT_OD;
    gpio.Pull  = GPIO_NOPULL;
    gpio.Speed = GPIO_SPEED_FREQ_LOW;

    gpio.Pin = scl_pin;
    HAL_GPIO_Init(scl_port, &gpio);

    gpio.Pin = sda_pin;
    HAL_GPIO_Init(sda_port, &gpio);

    /* Both lines idle high */
    HAL_GPIO_WritePin(scl_port, scl_pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(sda_port, sda_pin, GPIO_PIN_SET);
    HAL_Delay(1);

    /* Step 3 — clock SCL nine times to complete any in-progress byte */
    for (int i = 0; i < 9; i++) {
        HAL_GPIO_WritePin(scl_port, scl_pin, GPIO_PIN_RESET);
        HAL_Delay(1);
        HAL_GPIO_WritePin(scl_port, scl_pin, GPIO_PIN_SET);
        HAL_Delay(1);

        /*
         * If SDA is released (high) the slave has finished its byte.
         * We still complete all 9 pulses to be safe.
         */
        if (HAL_GPIO_ReadPin(sda_port, sda_pin) == GPIO_PIN_SET) {
            /* Slave released SDA — we can stop early after this pulse */
        }
    }

    /* Step 4 — generate a STOP condition: SDA low → high while SCL high */
    HAL_GPIO_WritePin(sda_port, sda_pin, GPIO_PIN_RESET);
    HAL_Delay(1);
    HAL_GPIO_WritePin(scl_port, scl_pin, GPIO_PIN_SET);
    HAL_Delay(1);
    HAL_GPIO_WritePin(sda_port, sda_pin, GPIO_PIN_SET);
    HAL_Delay(1);

    /* Step 5 — re-initialise the I2C peripheral */
    return HAL_I2C_Init(hi2c);
}

Wiring It Into Your Application

The recovery function is most useful wrapped in a retry helper so the application code stays clean. The pattern below attempts a transmission, and if it gets HAL_BUSY or HAL_TIMEOUT, runs bus recovery and retries once.

C — sensor_read.c
/* I2C1 on PB6 (SCL) / PB7 (SDA) for STM32F4 */
#define I2C_SCL_PORT  GPIOB
#define I2C_SCL_PIN   GPIO_PIN_6
#define I2C_SDA_PORT  GPIOB
#define I2C_SDA_PIN   GPIO_PIN_7

HAL_StatusTypeDef I2C_TransmitWithRecovery(I2C_HandleTypeDef *hi2c,
                                            uint16_t dev_addr,
                                            uint8_t *data, uint16_t len)
{
    HAL_StatusTypeDef status;

    status = HAL_I2C_Master_Transmit(hi2c, dev_addr, data, len, 25);

    if (status == HAL_BUSY || status == HAL_TIMEOUT) {
        /* Bus is stuck — run the nine-clock recovery */
        I2C_RecoverBus(hi2c,
                       I2C_SCL_PORT, I2C_SCL_PIN,
                       I2C_SDA_PORT, I2C_SDA_PIN);

        /* Single retry with a more generous timeout */
        status = HAL_I2C_Master_Transmit(hi2c, dev_addr, data, len, 100);
    }

    return status;
}

Clock Stretching Timeouts Are a Separate Issue

If you are using sensors that perform long measurements before responding — MLX90614 (thermopile IR), SHT31 in high-repeatability mode, or any sensor with on-board processing — clock stretching timeout is a distinct problem from the stuck-BUSY errata.

The slave holds SCL low legitimately while it completes a measurement. The HAL default timeout of 25 ms can expire before the measurement is done. The sensor then returns HAL_TIMEOUT, not HAL_BUSY. The fix here is a larger timeout value — consult the sensor datasheet for maximum measurement time and add 20% margin.

C — timeout example
/*
 * SHT31 high-repeatability measurement takes up to 15 ms.
 * SHT31 datasheet table 4 — use 20 ms minimum, 30 ms with margin.
 */
#define SHT31_MEAS_TIMEOUT_MS  30

HAL_I2C_Master_Receive(&hi2c1,
                        SHT31_ADDR << 1,
                        rx_buf, sizeof(rx_buf),
                        SHT31_MEAS_TIMEOUT_MS);

Prevention: A Supervisory Task

In production systems running FreeRTOS, a lightweight supervisor task that periodically checks the I2C state and runs recovery if needed is more robust than relying on inline retry logic. This is especially important in systems where the I2C bus is shared between multiple peripherals.

C — FreeRTOS supervisor
void I2C_SupervisorTask(void *arg)
{
    I2C_HandleTypeDef *hi2c = (I2C_HandleTypeDef *)arg;

    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(5000)); /* check every 5 seconds */

        if (hi2c->State == HAL_I2C_STATE_BUSY ||
            hi2c->State == HAL_I2C_STATE_BUSY_TX ||
            hi2c->State == HAL_I2C_STATE_BUSY_RX)
        {
            /* I2C has been stuck for at least 5 seconds — recover */
            I2C_RecoverBus(hi2c,
                           I2C_SCL_PORT, I2C_SCL_PIN,
                           I2C_SDA_PORT, I2C_SDA_PIN);

            /* Log the event for diagnostics */
            Error_Log("I2C bus recovered by supervisor");
        }
    }
}
STM32H7 note: The register layout differs from STM32F4 — BUSY is in I2C_ISR rather than I2C_SR2, and the recovery sequence is the same but the peripheral re-init calls the H7 HAL. The nine-clock GPIO approach is silicon-agnostic and works across all STM32 families.

Summary