HomeAbout UsContact Us

Fixing Zephyr BMI160 I2C Timeout on STM32

By Jithin Tom
Published in Embedded Concepts
September 20, 2026
5 min read
Fixing Zephyr BMI160 I2C Timeout on STM32

Table Of Contents

01
Architecture & I2C Timeout Debug Flow
02
Problem Statement
03
Root Cause Analysis
04
Solution Approaches
05
Complete, Production-Ready Implementation
06
Verification & Signal Integrity Analysis
07
Summary
08
Related Reading
09
References
10
Frequently Asked Questions

Architecture & I2C Timeout Debug Flow

+----------------------------------------------------------------------------+
| STM32 MCU (Master) I2C Physical Bus BMI160 Sensor (Slave)|
+----------------------------------------------------------------------------+
| |
| +------------------------+ VDDIO (3.3V) +------------------------+ |
| | Zephyr RTOS Driver | | | Interface Selection | |
| | - drivers/sensor/ | +---+---+ | - CSB -> VDDIO (I2C) | |
| | bosch/bmi160 | | | | | - SDO -> GND (0x68) | |
| | - POST_KERNEL (pri 90) | [Rp][Rp] 2.2k | - Protocol: I2C Fast | |
| +-----------+------------+ | | +-----------+------------+ |
| | | | | |
| +-----------v------------+ | | +-----------v------------+ |
| | STM32 I2C Peripheral | | | | Serial Interface Core | |
| | - SCL (PB6) -----------+------+---|---------->| Pin 13: SCL | |
| | - SDA (PB7) <----------+----------+---------->| Pin 14: SDA | |
| | - BUSY Flag Monitor | | | +-----------+------------+ |
| +-----------+------------+ Cb Cb | |
| | 50pF 50pF +-----------v------------+ |
| +-----------v------------+ | | | Power-On Reset Circuit | |
| | GPIO Bus Recovery | GND GND | - t_POR >= 10 ms delay | |
| | - 9 SCL Pulses on Hang | tr <= 300 ns | - CMD 0xB6 Soft-Reset | |
| +------------------------+ (400 kHz Fast) +------------------------+ |
| |
+----------------------------------------------------------------------------+
| TIMING PITFALLS & FIRMWARE REMEDIATIONS |
+----------------------------------------------------------------------------+
| 1. Power Race: MCU boots (<1ms) -> Sensor in POR (10ms) -> I2C NACK / -EIO |
| 2. CSB Pin: Floating CSB drops into SPI -> Must strap CSB to VDDIO |
| 3. Pull-ups: Internal 40k weak pull-ups cause tr > 3us -> Add 2.2k ext |
| 4. Bus Lockup: Reset during read leaves SDA low -> Zephyr 9-pulse recovery |
+----------------------------------------------------------------------------+

Problem Statement

When integrating the Bosch BMI160 6-axis Inertial Measurement Unit (IMU) with an STM32 microcontroller (such as STM32F4, STM32L4, or STM32H7) running the Zephyr Real-Time Operating System (RTOS), developers frequently encounter intermittent or persistent I2C bus timeouts during device boot or runtime sampling.

In Zephyr, this issue manifests through console logs such as:

[00:00:00.005,000] <err> i2c_ll_stm32_v1: i2c_stm32_msg_write: timeout waiting for master state
[00:00:00.005,000] <err> BMI160: unexpected chip id (0)
[00:00:00.005,000] <err> BMI160: device bmi160@68 init failed: -5

Because Zephyr executes driver initialization routines during kernel boot (POST_KERNEL priority CONFIG_SENSOR_INIT_PRIORITY), an initialization failure causes device_is_ready(bmi160) to evaluate to false when user space or application threads launch. The failure may occur sporadically across cold starts, warm software reboots, or debugger attach cycles, leaving engineers guessing whether the culprit is firmware configuration, bus timing violations, or hardware layout defects.


Root Cause Analysis

Systematic debugging reveals five distinct hardware and driver-level mechanisms that induce I2C timeouts between STM32 and the BMI160 under Zephyr RTOS.

1. Sensor Power-On Reset (POR) Race Condition

The Bosch BMI160 contains an internal Power-On Reset (POR) circuit. According to Section 2.11.1 of the BMI160 Datasheet, after supply voltages (VDD and VDDIO) reach their minimum operating thresholds (1.71V and 1.2V respectively), the internal digital core requires up to:

t_POR >= 10 ms

before its register matrix and I2C serial interface become responsive.

In Zephyr, peripheral initialization occurs deterministically in early kernel boot phases via DEVICE_DT_INST_DEFINE:

DEVICE_DT_INST_DEFINE(0, bmi160_init, NULL, &bmi160_data, &bmi160_cfg,
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,
&bmi160_api);

High-performance ARM Cortex-M MCUs (such as an STM32F401 running at 84 MHz or an STM32H7 running at 480 MHz) initialize clocks and execute through z_cstart() and POST_KERNEL drivers in less than 1 ms from power rail stabilization. When bmi160_init() executes its first transaction - reading the CHIP_ID register (0x00) - the sensor’s digital state machine has not completed internal calibration. The BMI160 fails to generate an Acknowledge (ACK) on its slave address (0x68 or 0x69), triggering a -EIO (-5) or -ETIMEDOUT (-116) in the STM32 I2C driver.

2. The Floating CSB Interface Auto-Selection Pin Trap

The BMI160 supports both SPI (3-wire/4-wire) and I2C interfaces over multiplexed physical pins. Pin 12 (CSB / Chip Select Bar) acts as the protocol selection strap:

  • CSB = VDDIO (Logic High): Protocol is configured for I2C.
  • CSB = GND (Logic Low): Protocol is configured for SPI.

If CSB is left floating on the PCB or breadboard breakout, transient capacitive coupling or internal gate leakage during power-up can pull CSB low. The BMI160 latches into SPI mode. In SPI mode, the sensor completely ignores all I2C START conditions and addressing bytes on SDA/SCL, leaving the bus lines pulled high. The STM32 hardware controller detects no ACK, waits for the transaction to complete, and generates an acknowledgment timeout.

VDDIO (3.3V)
|
[ ] 10k Ohm Pull-up (Mandatory if not tied directly)
|
Pin 12 -----+------------> CSB (BMI160)
[Must be HIGH for I2C protocol]

3. Physical Bus RC Time Constant & Pull-Up Resistor Mismatch

The I2C bus is an open-drain topology where high logic levels are achieved passively via pull-up resistors (R_p). At the default Zephyr fast-mode bitrate:

f_SCL = 400 kHz (Clock period T = 2.5 us)

The NXP I2C Bus Specification (UM10204) Table 10 mandates:

  • Maximum Rise Time (t_r): t_r <= 300 ns
  • Minimum SCL High Time (t_HIGH): t_HIGH >= 600 ns

The rise time is governed by the RC time constant between pull-up resistance R_p and total bus capacitance C_b (parasitic capacitance of PCB traces, pin input capacitance, and sensor pads):

t_r = R_p * C_b * ln((VDDIO - 0.3*VDDIO) / (VDDIO - 0.7*VDDIO))
= R_p * C_b * ln(2.333)
= 0.8473 * R_p * C_b

Solving for maximum permissible pull-up resistance:

R_p(max) = t_r / (0.8473 * C_b)
= (300 * 10^-9) / (0.8473 * C_b)

Assuming a typical bus capacitance C_b = 100 pF:

R_p(max) = (300 * 10^-9) / (0.8473 * 100 * 10^-12)
= 3540 Ohm = 3.54 kOhm

A common engineering error is relying on the STM32’s internal GPIO pull-up resistors (GPIO_PULL_UP). On STM32 devices, the internal weak pull-up resistance R_pu ranges between 30 kOhm and 50 kOhm (nominal 40 kOhm). With R_p = 40 kOhm and C_b = 100 pF:

t_r = 0.8473 * (40 * 10^3 Ohm) * (100 * 10^-12 F)
= 3.39 us

Because 3.39 us exceeds the entire 2.5 us clock cycle, the waveform degenerates into a low-amplitude sawtooth wave that never reaches the input high threshold V_IH = 0.7 * VDDIO. The master detects clock stretching violations, missing ACK bits, or false START/STOP triggers, timing out immediately.

Conversely, minimum pull-up resistance R_p(min) is limited by the maximum sink current capability (I_OL = 3 mA) at maximum output low voltage (V_OL = 0.4 V):

R_p(min) = (VDDIO - V_OL) / I_OL
= (3.3 V - 0.4 V) / 3 mA
= 967 Ohm

Hence, external resistors between 2.2 kOhm and 3.3 kOhm are required for reliable 400 kHz operation.

4. STM32 I2C Hardware Errata and SDA Line Lockup

On STM32 microcontrollers utilizing the I2C V1 IP block (e.g., STM32F1, STM32F2, STM32F4), the peripheral exhibits well-documented hardware errata concerning the BUSY flag in the I2C_SR2 register (ST RM0090).

When an MCU reset (watchdog reset, software reset, or SWD debug reset) occurs while the BMI160 is actively transmitting a data byte or pulling SDA low for an ACK, the MCU resets, but the BMI160 remains powered and continues holding SDA low, waiting for the remaining SCL clock transitions.

SCL: --+ +---+ +---+ +---+ +---+ +---+ +---+
+---+ +---+ +---+ +---+ +---+ +---+ +---... (Clock stops on reset)
SDA: ------+
+-----------------------------------------------... [STUCK LOW by BMI160]
MCU detects BUSY!

When the STM32 reboots, its I2C peripheral samples SDA = 0 and sets I2C_SR2_BUSY = 1. In this state, the STM32 hardware state machine refuses to generate a START condition. Without intervention, Zephyr’s driver spins in a timeout loop waiting for BUSY to clear:

/* Zephyr i2c_ll_stm32_v1 driver waiting for idle bus */
while (LL_I2C_IsActiveFlag_BUSY(i2c)) {
if (k_uptime_get_32() - start > timeout) {
return -ETIMEDOUT;
}
}

The only mechanism to release the bus is I2C Bus Recovery: switching the SCL pin to GPIO open-drain mode, generating 9 clock pulses to clock out the slave’s internal shift register, and asserting an explicit STOP condition.

5. Soft-Reset (0xB6) Timing & PMU Mode Transitions

When Zephyr’s bmi160_init() initializes the chip, it sends command 0xB6 (BMI160_CMD_SOFT_RESET) to register 0x7E (BMI160_REG_CMD).

Per Section 2.11.1 of the datasheet:

  1. After issuing soft reset, the sensor requires at least 1 ms before any serial communication can occur.
  2. When commanding the Power Management Unit (PMU) to transition from Suspend to Normal mode:
    • Accelerometer: requires 3.2 ms to stabilize.
    • Gyroscope: requires up to 55 ms for the PLL and drive circuitry to reach steady state.

If application code or modified driver trees execute register reads prior to these delays, the sensor NACKs the address or yields corrupted register reads (0x00 or 0xFF).


Solution Approaches

To permanently resolve I2C timeouts, execute the following four-tier remediation strategy:

Remediation AreaRequired ActionHardware/Firmware Target
1. Pin StrappingConnect CSB directly to VDDIOHardwires I2C protocol, eliminating SPI mode lockout
2. Bus Pull-upsInstall external 2.2 kOhm resistors on SDA & SCLGuarantees t_r <= 300 ns for 400 kHz Fast-Mode
3. Bus RecoveryDefine scl-gpios and sda-gpios in DevicetreeEnables automated 9-clock recovery if SDA is stuck low
4. Power SequencingEnforce pre-init settle delay (> 10 ms)Prevents MCU from querying BMI160 during internal POR

Complete, Production-Ready Implementation

The following code illustrates a complete, modern Zephyr (v3.x / v4.x compliant) devicetree overlay, kernel configuration, and driver consumer application.

1. Devicetree Overlay (app.overlay)

Configure the I2C bus with Fast-Mode bitrate, open-drain pin control, and dedicated GPIO recovery pins:

/* app.overlay: STM32 I2C1 with BMI160 and Bus Recovery */
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/dt-bindings/gpio/gpio.h>
&i2c1 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>; /* 400 kHz */
pinctrl-0 = <&i2c1_scl_pb6 &i2c1_sda_pb7>;
pinctrl-names = "default";
/* Dedicated GPIO pins for automatic 9-clock I2C bus recovery */
scl-gpios = <&gpiob 6 (GPIO_OPEN_DRAIN | GPIO_PULL_UP)>;
sda-gpios = <&gpiob 7 (GPIO_OPEN_DRAIN | GPIO_PULL_UP)>;
bmi160: bmi160@68 {
compatible = "bosch,bmi160";
reg = <0x68>; /* SDO connected to GND. (Use 0x69 if SDO connected to VDDIO) */
status = "okay";
/* Optional interrupt line for data-ready triggers */
int-gpios = <&gpiod 12 GPIO_ACTIVE_HIGH>;
};
};

2. Kernel Configuration (prj.conf)

Enable I2C, Sensor subsystems, STM32 bus recovery, and detailed debugging logs:

# Enable Core Subsystems
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_GPIO=y
CONFIG_REBOOT=y
# Enable STM32 I2C Driver & Automated Bus Recovery
CONFIG_I2C_STM32=y
CONFIG_I2C_STM32_BUS_RECOVERY=y
# Enable Bosch BMI160 Sensor Driver
CONFIG_BMI160=y
CONFIG_BMI160_TRIGGER_NONE=y
# Enable Logging for Diagnostic Tracing
CONFIG_LOG=y
CONFIG_SENSOR_LOG_LEVEL_DBG=y
CONFIG_I2C_LOG_LEVEL_INF=y

3. Application C Implementation (src/main.c)

This application checks device readiness, handles bus recovery if needed, queries chip metadata, and streams accelerometer and gyroscope samples:

/*
* Copyright (c) 2026 EmbeddedSoft.net
* Zephyr BMI160 I2C Diagnostics and Data Acquisition on STM32
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
/* Get device binding via Devicetree node label */
static const struct device *const bmi160_dev = DEVICE_DT_GET(DT_NODELABEL(bmi160));
static const struct device *const i2c_bus = DEVICE_DT_GET(DT_NODELABEL(i2c1));
static void recover_i2c_bus_if_stuck(void)
{
if (!device_is_ready(i2c_bus)) {
LOG_ERR("I2C bus controller %s is not ready!", i2c_bus->name);
return;
}
/* Trigger explicit GPIO bus recovery sequence if supported */
int ret = i2c_recover_bus(i2c_bus);
if (ret == 0) {
LOG_INF("I2C bus recovery routine completed successfully");
} else if (ret == -ENOTSUP) {
LOG_WRN("I2C bus recovery not supported or recovery pins omitted");
} else {
LOG_ERR("I2C bus recovery failed with error: %d", ret);
}
}
int main(void)
{
LOG_INF("Starting STM32 BMI160 I2C Diagnostics...");
/* Allow power rail stabilization on cold boot */
k_msleep(20);
/*
* Verify whether the sensor driver initialized during early boot.
* Note: In Zephyr, device drivers execute during POST_KERNEL initialization.
* If the physical bus was locked or the sensor was still in POR during boot,
* device_is_ready() will return false.
*/
if (!device_is_ready(bmi160_dev)) {
LOG_ERR("BMI160 device not ready! Driver init failed during kernel boot.");
LOG_INF("Executing I2C bus recovery to clear stuck SDA/SCL lines...");
recover_i2c_bus_if_stuck();
/*
* In Zephyr's device model, device_is_ready() checks the static boot status.
* Once the physical bus has been un-wedged by sending 9 SCL recovery pulses,
* perform a controlled warm reboot so the Zephyr driver probe succeeds.
*/
LOG_WRN("Rebooting MCU to re-execute driver initialization on clean bus...");
k_msleep(100);
sys_reboot(SYS_REBOOT_WARM);
return -ENODEV;
}
LOG_INF("BMI160 device [%s] initialized and ready for communication", bmi160_dev->name);
/* Main sensor sampling loop */
while (1) {
struct sensor_value accel[3];
struct sensor_value gyro[3];
int rc = sensor_sample_fetch(bmi160_dev);
if (rc != 0) {
LOG_ERR("sensor_sample_fetch failed: %d (I2C timeout/NACK)", rc);
k_msleep(1000);
continue;
}
/* Read 3-axis Acceleration (m/s^2) */
sensor_channel_get(bmi160_dev, SENSOR_CHAN_ACCEL_XYZ, accel);
/* Read 3-axis Angular Velocity (rad/s) */
sensor_channel_get(bmi160_dev, SENSOR_CHAN_GYRO_XYZ, gyro);
LOG_INF("Accel [m/s^2]: X=%d.%06d Y=%d.%06d Z=%d.%06d | Gyro [rad/s]: X=%d.%06d Y=%d.%06d Z=%d.%06d",
accel[0].val1, accel[0].val2,
accel[1].val1, accel[1].val2,
accel[2].val1, accel[2].val2,
gyro[0].val1, gyro[0].val2,
gyro[1].val1, gyro[1].val2,
gyro[2].val1, gyro[2].val2);
k_msleep(100);
}
return 0;
}

Verification & Signal Integrity Analysis

Validate the physical bus and driver state with the following diagnostic steps:

1. Oscilloscope Rise-Time & Voltage Threshold Verification

Attach high-impedance 10:1 passive probes to SDA (PB7) and SCL (PB6). Measure signal edges during a 400 kHz burst read:

  • Rise Time (t_r): Measure from 0.3 * VDDIO (0.99 V) to 0.7 * VDDIO (2.31 V). The measured interval must satisfy t_r <= 300 ns.
  • Low Voltage Level (V_OL): Ensure logic-low dips below 0.4 V when the BMI160 drives an ACK. If V_OL > 0.6 V, the pull-up resistor is too strong (value too low), exceeding the driver’s sink capacity.
  • Clock Stretch Window: Measure the duration SCL remains held low by the BMI160. Clock stretch duration should not exceed 30 us.
3.30V --+ +---
| t_r <= 300ns ^|
2.31V --+----------------------/ /-+--- (VIH = 0.7 VDDIO)
| / / |
0.99V --+--------------------/ /---+--- (VIL = 0.3 VDDIO)
| / / |
0.40V --+------------------/ t_LOW >= 1.3us /-----+--- (VOL max = 0.4V)
+--+ / / +---
+-------------/----------------------/

2. Zephyr Shell Interactive Bus Inspection

If CONFIG_SHELL=y and CONFIG_I2C_SHELL=y are enabled in prj.conf, query the bus directly via the UART serial console:

# 1. Scan the I2C1 peripheral for active slave addresses
uart:~$ i2c scan i2c@40005400
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
1 devices found on i2c@40005400
# 2. Read the CHIP_ID register (Register 0x00) from address 0x68
uart:~$ i2c read_byte i2c@40005400 0x68 0x00
Output: 0xd1

If address 0x68 (or 0x69) does not appear in the matrix scan, check the CSB pin voltage level and measure VDDIO rails directly at the BMI160 package pins.


Summary

Resolving I2C timeouts between STM32 and the Bosch BMI160 in Zephyr RTOS requires synchronizing physical bus physics with driver timing semantics:

  1. Strap Pin 12 (CSB) to VDDIO to lock the sensor in I2C mode and prevent spurious fallbacks into SPI.
  2. Install external 2.2 kOhm pull-up resistors on both SDA and SCL to guarantee t_r <= 300 ns under 400 kHz Fast-Mode capacitance loads.
  3. Configure GPIO bus recovery (scl-gpios, sda-gpios) in the Devicetree overlay so the STM32 can automatically clock out stuck slaves holding SDA low after warm reboots.
  4. Respect the 10 ms Power-On Reset delay (t_POR >= 10 ms) prior to issuing early serial register transactions.


References

  1. Bosch Sensortec. BMI160 Small, Low Power Inertial Measurement Unit (IMU) Datasheet, Document BST-BMI160-DS000-08, Rev 0.8. https://www.bosch-sensortec.com/
  2. STMicroelectronics. STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 Advanced Arm-based 32-bit MCUs Reference Manual, RM0090, Rev 19.
  3. STMicroelectronics. Using the hardware I2C peripheral on STM32 microcontrollers, Application Note AN4759, Rev 3.
  4. NXP Semiconductors. I2C-bus specification and user manual, User Manual UM10204, Rev 7.0. https://www.nxp.com/docs/en/user-guide/UM10204.pdf
  5. Zephyr Project. Sensor Subsystem Documentation & Drivers, Release 3.7.0 / 4.0.0. https://docs.zephyrproject.org/latest/hardware/peripherals/sensor/index.html
  6. Texas Instruments. I2C Bus Pull-Up Resistor Calculation, Application Report SLVA689. https://www.ti.com/lit/an/slva689/slva689.pdf

Frequently Asked Questions

What causes I2C timeout in Zephyr's BMI160 driver on STM32?

I2C timeouts are primarily caused by sensor startup race conditions during Zephyr's early POST_KERNEL boot, floating CSB interface selection pins forcing SPI mode, insufficient pull-up resistors causing excessive rise times at 400kHz Fast-Mode, and bus lockups where SDA is stuck low without GPIO bus recovery.

Why does the STM32 I2C peripheral get stuck with the BUSY flag set?

On STM32 I2C V1 IP (such as on the STM32F4 series), if the MCU resets while the BMI160 is pulling SDA low during an ACK or data transfer, the hardware detects a busy bus and refuses to generate a START condition. Enabling Zephyr's GPIO-based bus recovery sends 9 clock pulses on SCL to clear the line.

How to properly size I2C pull-up resistors for 400kHz Fast Mode?

According to the I2C specification UM10204, Fast Mode requires a rise time under 300ns (t_r <= 300 ns). Given a typical bus capacitance of 50pF-100pF, external pull-up resistors of 2.2 kOhm to 3.3 kOhm are required. Internal STM32 pull-ups (~40 kOhm) are far too weak and cause severe waveform distortion and timeouts.

Tags

zephyrbmi160i2cstm32debugging

Share


Previous Article
UART Overrun Errors in STM32: Fixing with DMA
Jithin Tom

Jithin Tom

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

Related Posts

Zephyr Workqueue API for Deferred Work in Embedded Systems
Zephyr Workqueue API for Deferred Work in Embedded Systems
September 27, 2026
6 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media