EmbeddedEra EmbeddedEra

← Notes from the bench.

I2C bus stuck low after a reset

Oct 19, 2026firmwaredesign-for-testpcb-designDrafted with AI assistance, reviewed and edited by our engineer.

The board works for hours. Then the microcontroller resets, maybe from a watchdog, a brownout or a firmware update, and after the reboot every I2C read fails. The bus reads low. Power-cycling the whole board fixes it, so the bug is hard to reproduce and easy to ship.

This is one of the most common intermittent faults on boards with I2C sensors, EEPROMs, PMICs and fuel gauges. It is also fixable. Here is what happens, how to prove it, and what to build in.

What actually happens

I2C is a controller-driven protocol. A target (the sensor, EEPROM or whatever) only changes the data line while the controller is clocking it. During a read, the target drives each data bit onto SDA while SCL is low and the controller releases SDA to let the target talk.

Now reset the microcontroller halfway through a byte. The controller stops clocking. The target is still in the middle of the transaction. If the bit it was about to send is a zero, it holds SDA low and waits for the next clock edge. The controller comes back up, sees SDA low, and cannot generate a START condition because START needs SDA to fall while SCL is high. Most I2C peripherals report a busy bus or an arbitration loss and give up.

The target is not broken. It is waiting for clocks that never came. Nothing in the microcontroller’s reset knows the target exists, and the target’s own power never dropped.

A second, rarer case is SCL held low. That usually means a target with a hung internal state machine, or a controller that was reset while driving the line. Clock pulses cannot fix that one, because you cannot clock a line that someone else is holding low.

The standard recovery: bus clear

The I2C specification (NXP’s UM10204) describes a bus clear procedure. Its guidance, in plain terms:

  • If SDA is stuck low, the controller sends nine clock pulses on SCL. The target that was holding the line should release it within those nine clocks.
  • If that does not work, use a hardware reset of the targets if the design has one, or cycle their power.
  • If SCL is stuck low, the preferred route is a hardware reset of the bus, if the design provides a reset signal.

Nine is not arbitrary. A byte is eight data bits plus an acknowledge bit. Nine clocks walk any target out of whatever byte it was in the middle of. After the bus is released, the controller sends a STOP condition so every target returns to idle.

The Linux kernel implements this in its I2C core and is a good reference to read. Its generic recovery routine toggles SCL for nine falling and rising edges, checks that SCL really is high when released (returning a busy error if something else is holding it low), and lets SDA follow so a proper STOP is produced. You do not need Linux to borrow the idea. The logic fits in a short function on any microcontroller.

How to do it on a microcontroller

The procedure on an STM32, nRF52, SAM, ESP32 or similar part is the same, whatever the vendor’s HAL calls things.

  1. Disable the I2C peripheral. Leave it off until recovery finishes.
  2. Reconfigure SCL and SDA as GPIO. SCL as open-drain output, SDA as input with the pull-up still doing its job. Open-drain matters. A push-pull output driving high into a target that is driving low is a short.
  3. Read SCL. If it is low, stop. Clocking will not help. Go to the hardware reset path.
  4. Pulse SCL up to nine times, with a delay on each half-cycle that keeps the rate at or below your bus speed. After each pulse, read SDA. If it is high, stop early.
  5. Generate a STOP: with SCL high, drive SDA low then release it. (Pulling SDA low while SCL is high is a START, then releasing it creates the STOP.)
  6. Read both lines. If both are high, re-enable the peripheral and continue.
  7. If SDA is still low after nine clocks, escalate: pulse the target’s reset pin, switch its power rail off and on, or report a fault.

Run this at boot, before the first I2C transaction, every time. It costs a few hundred microseconds when the bus is already fine. Also run it when a transaction times out, not only at start-up. A bus can hang at runtime from a noise event as easily as from a reset.

Check the peripheral’s own error flags and timeout handling too. Some I2C blocks need to be explicitly reset after an arbitration-lost or bus-error state before they will start again. That behavior differs by part and silicon revision, so read the reference manual and the errata for the specific chip, not a general forum answer.

What to build into the board

Firmware recovery is the second line of defense. The first is a board that can recover without it.

  • A reset line to each target that holds state. Many sensors and gauges have a reset pin. Route it to a GPIO rather than tying it high. Where a part has no reset pin, consider whether its supply can be switched by a load switch under firmware control.
  • Pull-ups that match the bus. Missing, doubled or oversized pull-ups cause slow edges, and slow edges make glitches more likely to be read as clocks. The specification defines the rise-time and sink-current limits, and the resistor value follows from bus capacitance and supply voltage. Do the calculation instead of copying a value from a previous board.
  • Keep it short and quiet. Run I2C away from switching regulator inductors and fast digital lines. A glitch on SCL that a target reads as an extra clock is one of the ways a bus ends up out of step in the first place.
  • Test points on SDA and SCL. If you can clip a probe on the bus, you can see who is holding it. If you cannot, you are guessing. See what test points a board needs for the broader list.
  • Level translators and muxes. An I2C switch or translator between the controller and the target adds another place the line can be held, and some have their own reset pins. Account for them in the recovery routine.

Proving the fix

Do not trust a fix you cannot trigger. The fault depends on timing, so make it deterministic.

  • Reset the microcontroller at random points during a long read from the suspect target. A debugger script or a GPIO-controlled reset line does this. Run it overnight and count hangs before and after the recovery code.
  • Watch SDA and SCL on a logic analyzer or scope with a trigger on SDA low for longer than a normal byte. You will see exactly which transaction was cut off.
  • Test with the watchdog, not only a manual reset, and with a supply dip that resets the microcontroller but not the targets. That last case is the field failure. Reproduce it on a bench supply. It ties closely to the broader question of finding why a device resets in the field, because a reset loop will hit this bug again and again.

Do not treat “it recovered once” as passing. The test is many cut-off transactions, each followed by a clean restart with no power cycle.

When it still hangs

If the bus is clean after nine clocks but a particular target still does not answer, the target itself is stuck. Its datasheet will say whether it has a reset command, a reset pin or a minimum power-off time. If SCL is stuck low and no reset exists, the only option is to remove power from the target. That is a board change, which is why the reset line or switched supply belongs in the first spin.

How we approach it

We treat bus recovery as part of the first firmware bring-up rather than a patch after field returns, and we ask for the hardware reset or power switching on every state-holding I2C target during schematic review. If your product has a bus that occasionally dies, send us the schematic, the firmware’s I2C driver and a description of when it fails, and we will reproduce it before changing anything.

If your product occasionally boots with a dead I2C bus, we would reproduce the hang on the bench and add bus recovery and a reset path to the firmware and board.

ENGINEERING NOTES

One note a week on PCB design, firmware and getting hardware built. No sales email.

Double opt-in. Unsubscribe in one click.