
+----------------------------------------------------------------------------+| 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 |+----------------------------------------------------------------------------+
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.
Systematic debugging reveals five distinct hardware and driver-level mechanisms that induce I2C timeouts between STM32 and the BMI160 under Zephyr RTOS.
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.
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]
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:
t_r): t_r <= 300 nst_HIGH): t_HIGH >= 600 nsThe 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.
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.
0xB6) Timing & PMU Mode TransitionsWhen 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:
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).
To permanently resolve I2C timeouts, execute the following four-tier remediation strategy:
| Remediation Area | Required Action | Hardware/Firmware Target |
|---|---|---|
| 1. Pin Strapping | Connect CSB directly to VDDIO | Hardwires I2C protocol, eliminating SPI mode lockout |
| 2. Bus Pull-ups | Install external 2.2 kOhm resistors on SDA & SCL | Guarantees t_r <= 300 ns for 400 kHz Fast-Mode |
| 3. Bus Recovery | Define scl-gpios and sda-gpios in Devicetree | Enables automated 9-clock recovery if SDA is stuck low |
| 4. Power Sequencing | Enforce pre-init settle delay (> 10 ms) | Prevents MCU from querying BMI160 during internal POR |
The following code illustrates a complete, modern Zephyr (v3.x / v4.x compliant) devicetree overlay, kernel configuration, and driver consumer application.
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>;};};
prj.conf)Enable I2C, Sensor subsystems, STM32 bus recovery, and detailed debugging logs:
# Enable Core SubsystemsCONFIG_I2C=yCONFIG_SENSOR=yCONFIG_GPIO=yCONFIG_REBOOT=y# Enable STM32 I2C Driver & Automated Bus RecoveryCONFIG_I2C_STM32=yCONFIG_I2C_STM32_BUS_RECOVERY=y# Enable Bosch BMI160 Sensor DriverCONFIG_BMI160=yCONFIG_BMI160_TRIGGER_NONE=y# Enable Logging for Diagnostic TracingCONFIG_LOG=yCONFIG_SENSOR_LOG_LEVEL_DBG=yCONFIG_I2C_LOG_LEVEL_INF=y
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;}
Validate the physical bus and driver state with the following diagnostic steps:
Attach high-impedance 10:1 passive probes to SDA (PB7) and SCL (PB6). Measure signal edges during a 400 kHz burst read:
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.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.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)+--+ / / +---+-------------/----------------------/
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 addressesuart:~$ i2c scan i2c@400054000 1 2 3 4 5 6 7 8 9 a b c d e f00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --70: -- -- -- -- -- -- -- --1 devices found on i2c@40005400# 2. Read the CHIP_ID register (Register 0x00) from address 0x68uart:~$ i2c read_byte i2c@40005400 0x68 0x00Output: 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.
Resolving I2C timeouts between STM32 and the Bosch BMI160 in Zephyr RTOS requires synchronizing physical bus physics with driver timing semantics:
CSB) to VDDIO to lock the sensor in I2C mode and prevent spurious fallbacks into SPI.t_r <= 300 ns under 400 kHz Fast-Mode capacitance loads.scl-gpios, sda-gpios) in the Devicetree overlay so the STM32 can automatically clock out stuck slaves holding SDA low after warm reboots.t_POR >= 10 ms) prior to issuing early serial register transactions.Quick Links
Legal Stuff





