From 48c6b58fb6c6e85b9a8832644780724b4c0087ac Mon Sep 17 00:00:00 2001 From: Janne Snabb Date: Tue, 5 May 2026 22:35:10 +0300 Subject: [PATCH 1/2] Test README examples as crate docs --- README.md | 65 ++++++++++++++++-- src/lib.rs | 191 +---------------------------------------------------- 2 files changed, 60 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index 6491c27..49421e8 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ After `init()`, configure the accelerometer and gyroscope explicitly before depending on sample reads. The driver does not promise application-ready accel or gyro settings immediately after initialization. +The API is built on top of `embedded-hal` 1.0 and `embedded-hal-async` 1.0. +The driver does not own the external interrupt GPIO, which keeps it transport +agnostic and easy to integrate with Embassy or platform-specific interrupt +handling. + ## Features - `embedded-hal` 1.0 blocking API support @@ -46,9 +51,52 @@ causes a compile error. - `blocking`: synchronous driver using `embedded-hal` traits - `defmt`: derives `defmt::Format` for public value types +## Transport model + +The BMI323 uses 8-bit register addresses with 16-bit register payloads. Reads +include interface-specific dummy bytes, which this crate handles internally for +both I2C and SPI. + +## Interrupt model + +BMI323 interrupt sources are routed to `INT1`, `INT2`, or I3C IBI inside the +sensor. The driver configures the sensor-side routing, but the external GPIO +line is managed by the application: + +- in blocking applications, poll the GPIO or an MCU interrupt flag yourself, + then call `read_interrupt_status()` +- in async applications, either wait on the GPIO yourself or use + `wait_for_interrupt()` with a pin implementing + `embedded_hal_async::digital::Wait` + +## Feature engine notes + +Advanced features such as any-motion and no-motion depend on the BMI323 feature +engine. The datasheet requires the feature engine to be enabled before sensors +are re-enabled for these features. The helper methods in this crate follow that +model, but application code should still keep the order in mind when building +its configuration sequence. + +For motion-feature timing and threshold fields, prefer the conversion helpers +on `AnyMotionConfig` and `NoMotionConfig` instead of hand-coding raw register +values. + +The `report_mode` and `interrupt_hold` fields are written to a single shared +BMI323 register (`EXT_GEN_SET_1`). When multiple feature-engine blocks are +configured, the last `configure_*` call's values win for both fields. Use the +same values across all `configure_*` calls, or set them in the intended final +order. + +The driver tracks local range fields initialized to `AccelRange::G2` and +`GyroRange::Dps125` to match the BMI323 power-on reset defaults +(`ACC_CONF`/`GYR_CONF = 0x0000`). + ### Blocking I2C example ```rust,no_run +# #[cfg(not(feature = "blocking"))] fn main() {} +# #[cfg(feature = "blocking")] +# fn main() { use bmi323_driver::{ AccelConfig, AccelRange, Bmi323, GyroConfig, GyroRange, I2C_ADDRESS_PRIMARY, OutputDataRate, @@ -62,7 +110,8 @@ where D: DelayNs, { let mut imu = Bmi323::new_i2c(i2c, I2C_ADDRESS_PRIMARY); - imu.init(delay)?; + let state = imu.init(delay)?; + let _ = state; imu.set_accel_config(AccelConfig { odr: OutputDataRate::Hz100, @@ -80,16 +129,19 @@ where let _ = (accel_g, gyro_dps); Ok(()) } +# } ``` ### Async interrupt-driven advanced example ```rust,no_run +# #[cfg(feature = "blocking")] fn main() {} +# #[cfg(not(feature = "blocking"))] +# fn main() { use bmi323_driver::{ - AccelConfig, AccelRange, ActiveLevel, AnyMotionConfig, Bmi323Async, - EventReportMode, I2C_ADDRESS_PRIMARY, InterruptChannel, InterruptPinConfig, - InterruptRoute, InterruptSource, MotionAxes, OutputDataRate, OutputMode, - ReferenceUpdate, + AccelConfig, ActiveLevel, AnyMotionConfig, Bmi323, EventReportMode, + I2C_ADDRESS_PRIMARY, InterruptChannel, InterruptPinConfig, InterruptRoute, + InterruptSource, MotionAxes, OutputDataRate, OutputMode, ReferenceUpdate, }; use embedded_hal_async::delay::DelayNs; use embedded_hal_async::digital::Wait; @@ -105,7 +157,7 @@ where D: DelayNs, P: Wait, { - let mut imu = Bmi323Async::new_i2c(i2c, I2C_ADDRESS_PRIMARY); + let mut imu = Bmi323::new_i2c(i2c, I2C_ADDRESS_PRIMARY); imu.init(delay).await?; imu.enable_feature_engine(delay).await?; imu.set_accel_config(AccelConfig { @@ -147,6 +199,7 @@ where } Ok(()) } +# } ``` ## Repository examples diff --git a/src/lib.rs b/src/lib.rs index 28e7a3c..1a06b1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,194 +1,5 @@ -//! Generic `no_std` driver for the Bosch Sensortec BMI323 IMU. -//! -//! This crate provides: -//! -//! - blocking and async drivers (selected by Cargo feature) -//! - I2C and SPI transport support -//! - accelerometer and gyroscope configuration -//! - burst sample reads -//! - FIFO configuration and reads -//! - interrupt pin electrical configuration and interrupt routing -//! - feature-engine enable flow -//! - any-motion and no-motion configuration -//! - tap and orientation/flat configuration -//! - significant-motion and tilt configuration -//! - step detector and step counter support -//! - alternate accel/gyro configuration switching -//! - built-in accelerometer and gyroscope self-test -//! -//! The API is built on top of `embedded-hal` 1.0 and `embedded-hal-async` 1.0. -//! It does not own the external interrupt GPIO. This keeps the driver generic and -//! makes it easy to use with Embassy or with a platform-specific interrupt layer. -//! -//! # Async vs blocking -//! -//! Select exactly one Cargo feature: -//! -//! - `async` (default) — all [`Bmi323`] methods are `async fn`. -//! Uses `embedded-hal-async` 1.0 traits. Required for Embassy. -//! - `blocking` — all [`Bmi323`] methods are regular synchronous `fn`. -//! Uses `embedded-hal` 1.0 I2C/SPI/delay traits. -//! -//! Enabling both features simultaneously is a compile error. -//! -//! # Driver -//! -//! [`Bmi323`] exposes the same high-level BMI323 operations in both modes. -//! -//! # Transport model -//! -//! The BMI323 uses 8-bit register addresses with 16-bit register payloads. -//! Reads include interface-specific dummy bytes, which this crate handles -//! internally for I2C and SPI. -//! -//! # Interrupt model -//! -//! BMI323 interrupt sources are routed to `INT1`, `INT2`, or I3C IBI inside the -//! sensor. The driver configures the sensor-side routing, but the external GPIO -//! line is managed by the application: -//! -//! - in blocking applications, poll the GPIO or an MCU interrupt flag yourself, -//! then call [`Bmi323::read_interrupt_status`] -//! - in async applications, either wait on the GPIO yourself or use -//! [`Bmi323::wait_for_interrupt`] with a pin implementing -//! [`embedded_hal_async::digital::Wait`] -//! -//! # Feature engine note -//! -//! Advanced features such as any-motion and no-motion depend on the BMI323 -//! feature engine. The datasheet requires the feature engine to be enabled -//! before sensors are re-enabled for these features. The helper methods in this -//! crate follow that model, but application code should still keep the order in -//! mind when building its configuration sequence. -//! -//! For motion-feature timing and threshold fields, prefer the conversion -//! helpers on [`AnyMotionConfig`] and [`NoMotionConfig`] instead of hand-coding -//! raw register values. -//! -//! The `report_mode` and `interrupt_hold` fields are written to a single shared -//! BMI323 register (`EXT_GEN_SET_1`). When multiple feature-engine blocks are -//! configured, the last `configure_*` call's values win for both fields. Use -//! the same values across all `configure_*` calls, or set them in the intended -//! final order. -//! -//! # Startup configuration -//! -//! [`Bmi323::init`] performs a soft reset so the sensor starts from a known -//! state. After that reset, this driver does not assume the accelerometer or -//! gyroscope are configured for your application. In practice, you should call -//! [`Bmi323::set_accel_config`] and [`Bmi323::set_gyro_config`] before relying -//! on accelerometer or gyroscope sample reads. -//! -//! The driver tracks local range fields initialized to `AccelRange::G2` and -//! `GyroRange::Dps125` to match the BMI323 power-on reset defaults -//! (`ACC_CONF`/`GYR_CONF` = `0x0000`). -//! -//! # Example: blocking I2C -//! -//! Requires `default-features = false, features = ["blocking"]`. -//! -//! ```no_run -//! # #[cfg(not(feature = "blocking"))] fn main() {} -//! # #[cfg(feature = "blocking")] -//! # fn main() { -//! use bmi323_driver::{AccelConfig, Bmi323, GyroConfig, I2C_ADDRESS_PRIMARY, OutputDataRate}; -//! use embedded_hal::delay::DelayNs; -//! use embedded_hal::i2c::I2c; -//! -//! fn example(i2c: I2C, delay: &mut D) -> Result<(), bmi323_driver::Error> -//! where -//! I2C: I2c, -//! D: DelayNs, -//! { -//! let mut imu = Bmi323::new_i2c(i2c, I2C_ADDRESS_PRIMARY); -//! let state = imu.init(delay)?; -//! let _ = state; -//! -//! imu.set_accel_config(AccelConfig { -//! odr: OutputDataRate::Hz100, -//! ..Default::default() -//! })?; -//! -//! imu.set_gyro_config(GyroConfig { -//! odr: OutputDataRate::Hz100, -//! ..Default::default() -//! })?; -//! -//! let sample = imu.read_imu_data()?; -//! let accel_g = sample.accel.as_g(imu.accel_range()); -//! let gyro_dps = sample.gyro.as_dps(imu.gyro_range()); -//! let _ = (accel_g, gyro_dps); -//! Ok(()) -//! } -//! # } -//! ``` -//! -//! # Example: async interrupt-driven usage -//! -//! Works with default features (async is the default). -//! -//! ```no_run -//! # #[cfg(feature = "blocking")] fn main() {} -//! # #[cfg(not(feature = "blocking"))] -//! # fn main() { -//! use bmi323_driver::{ -//! AccelConfig, AccelMode, ActiveLevel, AnyMotionConfig, Bmi323, EventReportMode, -//! I2C_ADDRESS_PRIMARY, InterruptChannel, InterruptPinConfig, InterruptRoute, -//! InterruptSource, MotionAxes, OutputDataRate, OutputMode, ReferenceUpdate, -//! }; -//! use embedded_hal_async::delay::DelayNs; -//! use embedded_hal_async::digital::Wait; -//! use embedded_hal_async::i2c::I2c; -//! -//! async fn example( -//! i2c: I2C, -//! delay: &mut D, -//! int1_pin: &mut P, -//! ) -> Result<(), bmi323_driver::Error> -//! where -//! I2C: I2c, -//! D: DelayNs, -//! P: Wait, -//! { -//! let mut imu = Bmi323::new_i2c(i2c, I2C_ADDRESS_PRIMARY); -//! imu.init(delay).await?; -//! imu.enable_feature_engine(delay).await?; -//! imu.set_accel_config(AccelConfig { -//! mode: AccelMode::HighPerformance, -//! odr: OutputDataRate::Hz100, -//! ..Default::default() -//! }).await?; -//! imu.configure_any_motion(AnyMotionConfig { -//! axes: MotionAxes::XYZ, -//! threshold: AnyMotionConfig::threshold_from_g(0.08), -//! hysteresis: AnyMotionConfig::hysteresis_from_g(0.02), -//! duration: 5, // 5 / 50 s = 100 ms above threshold before event -//! wait_time: 1, // 1 / 50 s = 20 ms clear delay after slope drops -//! reference_update: ReferenceUpdate::EverySample, -//! report_mode: EventReportMode::AllEvents, -//! interrupt_hold: 3, // 0.625 ms * 2^3 = 5 ms interrupt hold -//! }).await?; -//! imu.set_interrupt_latching(true).await?; -//! imu.configure_interrupt_pin( -//! InterruptChannel::Int1, -//! InterruptPinConfig { -//! active_level: ActiveLevel::High, -//! output_mode: OutputMode::PushPull, -//! enabled: true, -//! }, -//! ).await?; -//! imu.map_interrupt(InterruptSource::AnyMotion, InterruptRoute::Int1).await?; -//! -//! let status = imu.wait_for_interrupt(int1_pin, InterruptChannel::Int1).await?; -//! if status.any_motion() { -//! let accel = imu.read_accel().await?; -//! let _ = accel; -//! } -//! Ok(()) -//! } -//! # } -//! ``` #![no_std] +#![doc = include_str!("../README.md")] #[cfg(all(feature = "blocking", feature = "async"))] compile_error!( From 9d928ebb60bb2bbc2343ac8832f0cc9c6c06d792 Mon Sep 17 00:00:00 2001 From: Janne Snabb Date: Tue, 5 May 2026 22:40:33 +0300 Subject: [PATCH 2/2] cargo fmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1a06b1a..b9ad25e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ mod transport; mod types; pub use driver::Bmi323; -pub use transport::{Access, I2cTransport, SpiTransport, MAX_WORDS_PER_READ}; +pub use transport::{Access, I2cTransport, MAX_WORDS_PER_READ, SpiTransport}; pub use types::*; #[cfg(test)]