bmi323_driver/lib.rs
1//! Generic `no_std` driver for the Bosch BMI323 IMU.
2//!
3//! This crate provides:
4//!
5//! - blocking and async drivers
6//! - I2C and SPI transport support
7//! - accelerometer and gyroscope configuration
8//! - burst sample reads
9//! - FIFO configuration and reads
10//! - interrupt pin electrical configuration and interrupt routing
11//! - feature-engine enable flow
12//! - any-motion and no-motion configuration
13//! - tap and orientation/flat configuration
14//! - significant-motion and tilt configuration
15//! - step detector and step counter support
16//! - alternate accel/gyro configuration switching
17//! - built-in accelerometer and gyroscope self-test
18//!
19//! The API is built on top of `embedded-hal` 1.0 and `embedded-hal-async` 1.0.
20//! It does not own the external interrupt GPIO. This keeps the driver generic and
21//! makes it easy to use with Embassy or with a platform-specific interrupt layer.
22//!
23//! # Driver variants
24//!
25//! - [`Bmi323`] is the blocking driver.
26//! - [`Bmi323Async`] is the async driver.
27//!
28//! Both expose the same high-level BMI323 operations where practical.
29//!
30//! # Transport model
31//!
32//! The BMI323 uses 8-bit register addresses with 16-bit register payloads.
33//! Reads include interface-specific dummy bytes, which this crate handles
34//! internally for I2C and SPI.
35//!
36//! # Interrupt model
37//!
38//! BMI323 interrupt sources are routed to `INT1`, `INT2`, or I3C IBI inside the
39//! sensor. The driver configures the sensor-side routing, but the external GPIO
40//! line is managed by the application:
41//!
42//! - in blocking applications, poll the GPIO or an MCU interrupt flag yourself,
43//! then call [`Bmi323::read_interrupt_status`]
44//! - in async applications, either wait on the GPIO yourself or use
45//! [`Bmi323Async::wait_for_interrupt`] with a pin implementing
46//! [`embedded_hal_async::digital::Wait`]
47//!
48//! # Feature engine note
49//!
50//! Advanced features such as any-motion and no-motion depend on the BMI323
51//! feature engine. The datasheet requires the feature engine to be enabled
52//! before sensors are re-enabled for these features. The helper methods in this
53//! crate follow that model, but application code should still keep the order in
54//! mind when building its configuration sequence.
55//!
56//! For motion-feature timing and threshold fields, prefer the conversion
57//! helpers on [`AnyMotionConfig`] and [`NoMotionConfig`] instead of hand-coding
58//! raw register values.
59//!
60//! The `report_mode` and `interrupt_hold` fields are written to a single shared
61//! BMI323 register (`EXT_GEN_SET_1`). When multiple feature-engine blocks are
62//! configured, the last `configure_*` call's values win for both fields. Use
63//! the same values across all `configure_*` calls, or set them in the intended
64//! final order.
65//!
66//! # Startup configuration
67//!
68//! [`Bmi323::init`] and [`Bmi323Async::init`] perform a soft reset so the
69//! sensor starts from a known state. After that reset, this driver does not
70//! assume the accelerometer or gyroscope are configured for your application.
71//! In practice, you should call [`Bmi323::set_accel_config`] and
72//! [`Bmi323::set_gyro_config`] or their async equivalents before relying on
73//! accelerometer or gyroscope sample reads.
74//!
75//! The driver tracks local range fields initialized to `AccelRange::G8` and
76//! `GyroRange::Dps2000`, but those are only fallback bookkeeping values until
77//! you explicitly configure the sensor through the driver.
78//!
79//! # Example: blocking I2C
80//!
81//! ```no_run
82//! use bmi323_driver::{
83//! AccelConfig, AccelMode, AccelRange, AverageSamples, Bandwidth, Bmi323,
84//! GyroConfig, GyroMode, GyroRange, I2C_ADDRESS_PRIMARY, OutputDataRate,
85//! };
86//! use embedded_hal::delay::DelayNs;
87//! use embedded_hal::i2c::I2c;
88//!
89//! fn example<I2C, D>(i2c: I2C, delay: &mut D) -> Result<(), bmi323_driver::Error<I2C::Error>>
90//! where
91//! I2C: I2c,
92//! D: DelayNs,
93//! {
94//! let mut imu = Bmi323::new_i2c(i2c, I2C_ADDRESS_PRIMARY);
95//! let state = imu.init(delay)?;
96//! let _ = state;
97//!
98//! imu.set_accel_config(AccelConfig {
99//! odr: OutputDataRate::Hz100,
100//! ..Default::default()
101//! })?;
102//!
103//! imu.set_gyro_config(GyroConfig {
104//! odr: OutputDataRate::Hz100,
105//! ..Default::default()
106//! })?;
107//!
108//! let sample = imu.read_imu_data()?;
109//! let accel_g = sample.accel.as_g(imu.accel_range());
110//! let gyro_dps = sample.gyro.as_dps(imu.gyro_range());
111//! let _ = (accel_g, gyro_dps);
112//! Ok(())
113//! }
114//! ```
115//!
116//! # Example: async interrupt-driven usage
117//!
118//! ```no_run
119//! use bmi323_driver::{
120//! AccelConfig, AccelMode, AccelRange, ActiveLevel, AnyMotionConfig,
121//! AverageSamples, Bandwidth, Bmi323Async, EventReportMode,
122//! I2C_ADDRESS_PRIMARY, InterruptChannel, InterruptPinConfig,
123//! InterruptRoute, InterruptSource, MotionAxes, OutputDataRate, OutputMode,
124//! ReferenceUpdate,
125//! };
126//! use embedded_hal_async::delay::DelayNs;
127//! use embedded_hal_async::digital::Wait;
128//! use embedded_hal_async::i2c::I2c;
129//!
130//! async fn example<I2C, D, P>(
131//! i2c: I2C,
132//! delay: &mut D,
133//! int1_pin: &mut P,
134//! ) -> Result<(), bmi323_driver::Error<I2C::Error>>
135//! where
136//! I2C: I2c,
137//! D: DelayNs,
138//! P: Wait,
139//! {
140//! let mut imu = Bmi323Async::new_i2c(i2c, I2C_ADDRESS_PRIMARY);
141//! imu.init(delay).await?;
142//! imu.enable_feature_engine().await?;
143//! imu.set_accel_config(AccelConfig {
144//! mode: AccelMode::HighPerformance,
145//! odr: OutputDataRate::Hz100,
146//! ..Default::default()
147//! }).await?;
148//! imu.configure_any_motion(AnyMotionConfig {
149//! axes: MotionAxes::XYZ,
150//! threshold: AnyMotionConfig::threshold_from_g(0.08),
151//! hysteresis: AnyMotionConfig::hysteresis_from_g(0.02),
152//! duration: 5, // 5 / 50 s = 100 ms above threshold before event
153//! wait_time: 1, // 1 / 50 s = 20 ms clear delay after slope drops
154//! reference_update: ReferenceUpdate::EverySample,
155//! report_mode: EventReportMode::AllEvents,
156//! interrupt_hold: 3, // 0.625 ms * 2^3 = 5 ms interrupt hold
157//! }).await?;
158//! imu.set_interrupt_latching(true).await?;
159//! imu.configure_interrupt_pin(
160//! InterruptChannel::Int1,
161//! InterruptPinConfig {
162//! active_level: ActiveLevel::High,
163//! output_mode: OutputMode::PushPull,
164//! enabled: true,
165//! },
166//! ).await?;
167//! imu.map_interrupt(InterruptSource::AnyMotion, InterruptRoute::Int1).await?;
168//!
169//! let status = imu.wait_for_interrupt(int1_pin, InterruptChannel::Int1).await?;
170//! if status.any_motion() {
171//! let accel = imu.read_accel().await?;
172//! let _ = accel;
173//! }
174//! Ok(())
175//! }
176//! ```
177#![no_std]
178
179#[cfg(test)]
180extern crate std;
181
182mod async_driver;
183mod blocking_driver;
184mod driver;
185mod registers;
186mod transport;
187mod types;
188
189pub use driver::{Bmi323, Bmi323Async};
190pub use transport::{
191 AsyncAccess, AsyncI2cTransport, AsyncSpiTransport, SyncAccess, SyncI2cTransport,
192 SyncSpiTransport,
193};
194pub use types::*;
195
196#[cfg(test)]
197mod tests;