bmi323_driver/types.rs
1/// Driver error type.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4pub enum Error<E> {
5 /// Transport-level bus error returned by the underlying HAL.
6 Bus(E),
7 /// The chip ID register did not contain the expected BMI323 identifier.
8 InvalidChipId(u8),
9 /// The BMI323 reported a fatal internal error.
10 FatalError,
11 /// The feature engine did not become ready after the expected init sequence.
12 FeatureEngineNotReady(u8),
13 /// The BMI323 self-test did not complete within the expected timeout.
14 SelfTestTimeout,
15 /// The temperature sensor returned the invalid-data sentinel (0x8000).
16 ///
17 /// The BMI323 datasheet defines raw value 0x8000 as "invalid temperature";
18 /// it is produced when no valid sample is available yet.
19 InvalidTemperature,
20 /// The GPIO pin returned an error while waiting for an interrupt edge.
21 ///
22 /// Only produced by [`Bmi323::wait_for_interrupt`](crate::Bmi323::wait_for_interrupt).
23 /// The underlying GPIO error value is not preserved because the GPIO error
24 /// type is independent of the bus error type `E`.
25 GpioError,
26}
27
28impl<E: core::fmt::Display> core::fmt::Display for Error<E> {
29 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30 match self {
31 Error::Bus(e) => write!(f, "bus error: {e}"),
32 Error::InvalidChipId(id) => write!(f, "invalid chip ID: {id:#x}"),
33 Error::FatalError => write!(f, "BMI323 fatal internal error"),
34 Error::FeatureEngineNotReady(status) => {
35 write!(f, "feature engine not ready: status={status:#x}")
36 }
37 Error::SelfTestTimeout => write!(f, "self-test timeout"),
38 Error::InvalidTemperature => write!(f, "invalid temperature sentinel"),
39 Error::GpioError => write!(f, "GPIO error while waiting for interrupt"),
40 }
41 }
42}
43
44impl<E: core::error::Error + 'static> core::error::Error for Error<E> {
45 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
46 match self {
47 Error::Bus(e) => Some(e),
48 _ => None,
49 }
50 }
51}
52
53/// Primary 7-bit BMI323 I2C address (§7.2.4.2).
54///
55/// Use this when the sensor address-selection pin is strapped for the default
56/// address (SDO pulled to GND).
57pub const I2C_ADDRESS_PRIMARY: u8 = 0x68;
58
59/// Alternate 7-bit BMI323 I2C address (§7.2.4.2).
60///
61/// Use this when the sensor address-selection pin is strapped for the
62/// alternate address (SDO pulled to VDDIO).
63pub const I2C_ADDRESS_ALTERNATE: u8 = 0x69;
64
65/// Decoded contents of the BMI323 `STATUS` register (§6.1.2, Register (0x02) status).
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68pub struct StatusWord(pub u16);
69
70impl StatusWord {
71 /// Returns true if the sensor reports a power-on reset condition.
72 pub const fn por_detected(self) -> bool {
73 self.0 & 0x0001 != 0
74 }
75
76 /// Returns true if a new temperature sample is available.
77 pub const fn drdy_temp(self) -> bool {
78 self.0 & (1 << 5) != 0
79 }
80
81 /// Returns true if a new gyroscope sample is available.
82 pub const fn drdy_gyro(self) -> bool {
83 self.0 & (1 << 6) != 0
84 }
85
86 /// Returns true if a new accelerometer sample is available.
87 pub const fn drdy_accel(self) -> bool {
88 self.0 & (1 << 7) != 0
89 }
90}
91
92/// Decoded contents of the BMI323 `ERR_REG` register (§6.1.2, Register (0x01) err_reg).
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94#[cfg_attr(feature = "defmt", derive(defmt::Format))]
95pub struct ErrorWord(pub u16);
96
97impl ErrorWord {
98 /// Returns true if the BMI323 reports a fatal internal error.
99 pub const fn fatal(self) -> bool {
100 self.0 & 0x0001 != 0
101 }
102
103 /// Returns true if the current accelerometer configuration is invalid.
104 pub const fn accel_conf_error(self) -> bool {
105 self.0 & (1 << 5) != 0
106 }
107
108 /// Returns true if the current gyroscope configuration is invalid.
109 pub const fn gyro_conf_error(self) -> bool {
110 self.0 & (1 << 6) != 0
111 }
112}
113
114/// Decoded interrupt status for `INT1`, `INT2`, or I3C IBI
115/// (§6.1.2, Registers (0x0D-0x0F) int_status_int1/int2/ibi).
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118pub struct InterruptStatus(pub u16);
119
120impl InterruptStatus {
121 /// Returns true if no-motion is asserted.
122 pub const fn no_motion(self) -> bool {
123 self.0 & (1 << 0) != 0
124 }
125 /// Returns true if any-motion is asserted.
126 pub const fn any_motion(self) -> bool {
127 self.0 & (1 << 1) != 0
128 }
129 /// Returns true if flat detection is asserted.
130 pub const fn flat(self) -> bool {
131 self.0 & (1 << 2) != 0
132 }
133 /// Returns true if orientation change is asserted.
134 pub const fn orientation(self) -> bool {
135 self.0 & (1 << 3) != 0
136 }
137 /// Returns true if the step detector event is asserted.
138 pub const fn step_detector(self) -> bool {
139 self.0 & (1 << 4) != 0
140 }
141 /// Returns true if the step counter event is asserted.
142 pub const fn step_counter(self) -> bool {
143 self.0 & (1 << 5) != 0
144 }
145 /// Returns true if significant motion is asserted.
146 pub const fn significant_motion(self) -> bool {
147 self.0 & (1 << 6) != 0
148 }
149 /// Returns true if tilt is asserted.
150 pub const fn tilt(self) -> bool {
151 self.0 & (1 << 7) != 0
152 }
153 /// Returns true if tap detection is asserted.
154 pub const fn tap(self) -> bool {
155 self.0 & (1 << 8) != 0
156 }
157 /// Returns true if the I3C synchronization interrupt is asserted.
158 pub const fn i3c_sync(self) -> bool {
159 self.0 & (1 << 9) != 0
160 }
161 /// Returns true if the error-status interrupt is asserted.
162 ///
163 /// This bit corresponds to `err_status` in the BMI323 `INT_STATUS_INT1/2/IBI`
164 /// register (bit 10). It is mapped via [`InterruptSource::ErrorStatus`].
165 pub const fn error_status(self) -> bool {
166 self.0 & (1 << 10) != 0
167 }
168 /// Returns true if temperature data-ready is asserted.
169 pub const fn temp_data_ready(self) -> bool {
170 self.0 & (1 << 11) != 0
171 }
172 /// Returns true if gyroscope data-ready is asserted.
173 pub const fn gyro_data_ready(self) -> bool {
174 self.0 & (1 << 12) != 0
175 }
176 /// Returns true if accelerometer data-ready is asserted.
177 pub const fn accel_data_ready(self) -> bool {
178 self.0 & (1 << 13) != 0
179 }
180 /// Returns true if FIFO watermark is asserted.
181 pub const fn fifo_watermark(self) -> bool {
182 self.0 & (1 << 14) != 0
183 }
184 /// Returns true if FIFO full is asserted.
185 pub const fn fifo_full(self) -> bool {
186 self.0 & (1 << 15) != 0
187 }
188}
189
190/// Self-test selection written to the BMI323 `st_select` extended register
191/// (§6.2.2, Register (0x25) st_select; §5.14).
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193#[cfg_attr(feature = "defmt", derive(defmt::Format))]
194pub enum SelfTestSelection {
195 /// Run accelerometer self-test only.
196 Accelerometer,
197 /// Run gyroscope self-test only.
198 Gyroscope,
199 /// Run both accelerometer and gyroscope self-tests.
200 Both,
201}
202
203impl SelfTestSelection {
204 /// Encode the selection into the BMI323 `st_select` bit layout.
205 pub const fn to_word(self) -> u16 {
206 match self {
207 Self::Accelerometer => 0x0001,
208 Self::Gyroscope => 0x0002,
209 Self::Both => 0x0003,
210 }
211 }
212
213 /// Returns true if the accelerometer self-test is enabled.
214 pub const fn tests_accelerometer(self) -> bool {
215 matches!(self, Self::Accelerometer | Self::Both)
216 }
217
218 /// Returns true if the gyroscope self-test is enabled.
219 pub const fn tests_gyroscope(self) -> bool {
220 matches!(self, Self::Gyroscope | Self::Both)
221 }
222}
223
224/// Detailed self-test result bits read from the BMI323 `st_result` extended
225/// register (§6.2.2, Register (0x24) st_result; §5.14).
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227#[cfg_attr(feature = "defmt", derive(defmt::Format))]
228pub struct SelfTestDetail(pub u16);
229
230impl SelfTestDetail {
231 /// Returns true if the accelerometer X-axis self-test passed.
232 pub const fn acc_sens_x_ok(self) -> bool {
233 self.0 & (1 << 0) != 0
234 }
235
236 /// Returns true if the accelerometer Y-axis self-test passed.
237 pub const fn acc_sens_y_ok(self) -> bool {
238 self.0 & (1 << 1) != 0
239 }
240
241 /// Returns true if the accelerometer Z-axis self-test passed.
242 pub const fn acc_sens_z_ok(self) -> bool {
243 self.0 & (1 << 2) != 0
244 }
245
246 /// Returns true if the gyroscope X-axis self-test passed.
247 pub const fn gyr_sens_x_ok(self) -> bool {
248 self.0 & (1 << 3) != 0
249 }
250
251 /// Returns true if the gyroscope Y-axis self-test passed.
252 pub const fn gyr_sens_y_ok(self) -> bool {
253 self.0 & (1 << 4) != 0
254 }
255
256 /// Returns true if the gyroscope Z-axis self-test passed.
257 pub const fn gyr_sens_z_ok(self) -> bool {
258 self.0 & (1 << 5) != 0
259 }
260
261 /// Returns true if the gyroscope drive self-test passed.
262 pub const fn gyr_drive_ok(self) -> bool {
263 self.0 & (1 << 6) != 0
264 }
265
266 /// Returns true when all requested accelerometer axes passed.
267 pub const fn accelerometer_ok(self) -> bool {
268 self.acc_sens_x_ok() && self.acc_sens_y_ok() && self.acc_sens_z_ok()
269 }
270
271 /// Returns true when all requested gyroscope checks passed.
272 pub const fn gyroscope_ok(self) -> bool {
273 self.gyr_sens_x_ok() && self.gyr_sens_y_ok() && self.gyr_sens_z_ok() && self.gyr_drive_ok()
274 }
275}
276
277/// Result of a completed BMI323 self-test run.
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279#[cfg_attr(feature = "defmt", derive(defmt::Format))]
280pub struct SelfTestResult {
281 /// Which sensor blocks were included in the self-test run.
282 pub selection: SelfTestSelection,
283 /// Overall pass/fail bit reported by `FEATURE_IO1.st_result`.
284 pub passed: bool,
285 /// Indicates that the required accelerometer sample rate precondition was
286 /// not met.
287 pub sample_rate_error: bool,
288 /// Low-level feature-engine status code from `FEATURE_IO1.error_status`.
289 ///
290 /// On the BMI323 this field uses non-obvious encodings. In particular,
291 /// `0x5` is the normal "no error" state after the feature engine is active.
292 pub error_status: u8,
293 /// Per-axis and gyroscope-drive detailed result bits from `st_result`.
294 pub detail: SelfTestDetail,
295}
296
297impl SelfTestResult {
298 /// Returns true if the requested accelerometer self-test portion passed.
299 pub const fn accelerometer_ok(self) -> bool {
300 if self.selection.tests_accelerometer() {
301 self.detail.accelerometer_ok()
302 } else {
303 true
304 }
305 }
306
307 /// Returns true if the requested gyroscope self-test portion passed.
308 pub const fn gyroscope_ok(self) -> bool {
309 if self.selection.tests_gyroscope() {
310 self.detail.gyroscope_ok()
311 } else {
312 true
313 }
314 }
315}
316
317/// Raw 3-axis sample from the accelerometer or gyroscope.
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319#[cfg_attr(feature = "defmt", derive(defmt::Format))]
320pub struct AxisData {
321 /// X-axis sample in raw sensor counts.
322 pub x: i16,
323 /// Y-axis sample in raw sensor counts.
324 pub y: i16,
325 /// Z-axis sample in raw sensor counts.
326 pub z: i16,
327}
328
329impl AxisData {
330 /// Convert a raw accelerometer sample into `g` for the selected range.
331 pub fn as_g(self, range: AccelRange) -> [f32; 3] {
332 let scale = range.scale_g_per_lsb();
333 [
334 self.x as f32 * scale,
335 self.y as f32 * scale,
336 self.z as f32 * scale,
337 ]
338 }
339
340 /// Convert a raw gyroscope sample into `deg/s` for the selected range.
341 pub fn as_dps(self, range: GyroRange) -> [f32; 3] {
342 let scale = range.scale_dps_per_lsb();
343 [
344 self.x as f32 * scale,
345 self.y as f32 * scale,
346 self.z as f32 * scale,
347 ]
348 }
349}
350
351/// Combined accelerometer and gyroscope sample read in one burst.
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
353#[cfg_attr(feature = "defmt", derive(defmt::Format))]
354pub struct ImuData {
355 /// Accelerometer sample.
356 pub accel: AxisData,
357 /// Gyroscope sample.
358 pub gyro: AxisData,
359}
360
361/// Result returned by [`Bmi323::init`](crate::Bmi323::init).
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363#[cfg_attr(feature = "defmt", derive(defmt::Format))]
364pub struct DeviceState {
365 /// Value read from the `CHIP_ID` register.
366 pub chip_id: u8,
367 /// Snapshot of the `STATUS` register taken during init.
368 pub status: StatusWord,
369 /// Snapshot of the `ERR_REG` register taken during init.
370 pub error: ErrorWord,
371}
372
373/// Output data rate selection used by accel and gyro configuration
374/// (§6.1.2, Register (0x20) acc_conf `acc_odr`; Register (0x21) gyr_conf `gyr_odr`).
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376#[cfg_attr(feature = "defmt", derive(defmt::Format))]
377pub enum OutputDataRate {
378 Hz0_78125 = 0x1,
379 Hz1_5625 = 0x2,
380 Hz3_125 = 0x3,
381 Hz6_25 = 0x4,
382 Hz12_5 = 0x5,
383 Hz25 = 0x6,
384 Hz50 = 0x7,
385 Hz100 = 0x8,
386 Hz200 = 0x9,
387 Hz400 = 0xA,
388 Hz800 = 0xB,
389 Hz1600 = 0xC,
390 Hz3200 = 0xD,
391 Hz6400 = 0xE,
392}
393
394/// Low-pass filter bandwidth relative to output data rate
395/// (§6.1.2, Register (0x20) acc_conf `acc_bwp`; Register (0x21) gyr_conf `gyr_bwp`).
396#[derive(Debug, Clone, Copy, PartialEq, Eq)]
397#[cfg_attr(feature = "defmt", derive(defmt::Format))]
398pub enum Bandwidth {
399 OdrOver2 = 0,
400 OdrOver4 = 1,
401}
402
403/// Sample averaging depth used in supported sensor modes
404/// (§6.1.2, Register (0x20) acc_conf `acc_avg_num`; Register (0x21) gyr_conf `gyr_avg_num`).
405#[derive(Debug, Clone, Copy, PartialEq, Eq)]
406#[cfg_attr(feature = "defmt", derive(defmt::Format))]
407pub enum AverageSamples {
408 Avg1 = 0,
409 Avg2 = 1,
410 Avg4 = 2,
411 Avg8 = 3,
412 Avg16 = 4,
413 Avg32 = 5,
414 Avg64 = 6,
415}
416
417/// Accelerometer operating mode (§6.1.2, Register (0x20) acc_conf `acc_mode`).
418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
419#[cfg_attr(feature = "defmt", derive(defmt::Format))]
420pub enum AccelMode {
421 Disabled = 0,
422 LowPower = 3,
423 Normal = 4,
424 HighPerformance = 7,
425}
426
427/// Gyroscope operating mode (§6.1.2, Register (0x21) gyr_conf `gyr_mode`).
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
429#[cfg_attr(feature = "defmt", derive(defmt::Format))]
430pub enum GyroMode {
431 Disabled = 0,
432 DriveEnabledOnly = 1,
433 LowPower = 3,
434 Normal = 4,
435 HighPerformance = 7,
436}
437
438/// Accelerometer full-scale measurement range (§6.1.2, Register (0x20) acc_conf `acc_range`).
439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
440#[cfg_attr(feature = "defmt", derive(defmt::Format))]
441pub enum AccelRange {
442 G2 = 0,
443 G4 = 1,
444 G8 = 2,
445 G16 = 3,
446}
447
448impl AccelRange {
449 /// Scale factor in `g/LSB` for raw accelerometer samples.
450 ///
451 /// Raw samples are 16-bit two's complement with full-scale ±range at ±32768
452 /// (§5.6.1).
453 pub const fn scale_g_per_lsb(self) -> f32 {
454 match self {
455 Self::G2 => 2.0 / 32768.0,
456 Self::G4 => 4.0 / 32768.0,
457 Self::G8 => 8.0 / 32768.0,
458 Self::G16 => 16.0 / 32768.0,
459 }
460 }
461}
462
463/// Gyroscope full-scale measurement range (§6.1.2, Register (0x21) gyr_conf `gyr_range`).
464#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465#[cfg_attr(feature = "defmt", derive(defmt::Format))]
466pub enum GyroRange {
467 Dps125 = 0,
468 Dps250 = 1,
469 Dps500 = 2,
470 Dps1000 = 3,
471 Dps2000 = 4,
472}
473
474impl GyroRange {
475 /// Scale factor in `deg/s per LSB` for raw gyroscope samples.
476 ///
477 /// Raw samples are 16-bit two's complement with full-scale ±range at ±32768
478 /// (§5.6.2).
479 pub const fn scale_dps_per_lsb(self) -> f32 {
480 match self {
481 Self::Dps125 => 125.0 / 32768.0,
482 Self::Dps250 => 250.0 / 32768.0,
483 Self::Dps500 => 500.0 / 32768.0,
484 Self::Dps1000 => 1000.0 / 32768.0,
485 Self::Dps2000 => 2000.0 / 32768.0,
486 }
487 }
488}
489
490/// High-level accelerometer configuration written to `ACC_CONF`
491/// (§6.1.2, Register (0x20) acc_conf).
492///
493/// `Default::default()` yields a ready-to-use active configuration (not the
494/// BMI323 power-on reset state, which has the sensor disabled):
495/// - mode: [`AccelMode::Normal`]
496/// - average: [`AverageSamples::Avg1`]
497/// - bandwidth: [`Bandwidth::OdrOver2`]
498/// - range: [`AccelRange::G8`]
499/// - odr: [`OutputDataRate::Hz50`]
500#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501#[cfg_attr(feature = "defmt", derive(defmt::Format))]
502pub struct AccelConfig {
503 /// Sensor operating mode.
504 pub mode: AccelMode,
505 /// Sample averaging depth.
506 pub average: AverageSamples,
507 /// Low-pass filter bandwidth.
508 pub bandwidth: Bandwidth,
509 /// Full-scale range.
510 pub range: AccelRange,
511 /// Output data rate.
512 pub odr: OutputDataRate,
513}
514
515impl Default for AccelConfig {
516 fn default() -> Self {
517 Self {
518 mode: AccelMode::Normal,
519 average: AverageSamples::Avg1,
520 bandwidth: Bandwidth::OdrOver2,
521 range: AccelRange::G8,
522 odr: OutputDataRate::Hz50,
523 }
524 }
525}
526
527impl AccelConfig {
528 /// Encode the configuration into the BMI323 register bit layout.
529 pub const fn to_word(self) -> u16 {
530 ((self.mode as u16) << 12)
531 | ((self.average as u16) << 8)
532 | ((self.bandwidth as u16) << 7)
533 | ((self.range as u16) << 4)
534 | self.odr as u16
535 }
536}
537
538/// High-level gyroscope configuration written to `GYR_CONF`
539/// (§6.1.2, Register (0x21) gyr_conf).
540///
541/// `Default::default()` yields a ready-to-use active configuration (not the
542/// BMI323 power-on reset state, which has the sensor disabled):
543/// - mode: [`GyroMode::Normal`]
544/// - average: [`AverageSamples::Avg1`]
545/// - bandwidth: [`Bandwidth::OdrOver2`]
546/// - range: [`GyroRange::Dps2000`]
547/// - odr: [`OutputDataRate::Hz50`]
548#[derive(Debug, Clone, Copy, PartialEq, Eq)]
549#[cfg_attr(feature = "defmt", derive(defmt::Format))]
550pub struct GyroConfig {
551 /// Sensor operating mode.
552 pub mode: GyroMode,
553 /// Sample averaging depth.
554 pub average: AverageSamples,
555 /// Low-pass filter bandwidth.
556 pub bandwidth: Bandwidth,
557 /// Full-scale range.
558 pub range: GyroRange,
559 /// Output data rate.
560 pub odr: OutputDataRate,
561}
562
563impl Default for GyroConfig {
564 fn default() -> Self {
565 Self {
566 mode: GyroMode::Normal,
567 average: AverageSamples::Avg1,
568 bandwidth: Bandwidth::OdrOver2,
569 range: GyroRange::Dps2000,
570 odr: OutputDataRate::Hz50,
571 }
572 }
573}
574
575impl GyroConfig {
576 /// Encode the configuration into the BMI323 register bit layout.
577 pub const fn to_word(self) -> u16 {
578 ((self.mode as u16) << 12)
579 | ((self.average as u16) << 8)
580 | ((self.bandwidth as u16) << 7)
581 | ((self.range as u16) << 4)
582 | self.odr as u16
583 }
584}
585
586/// FIFO enable and behavior configuration (§6.1.2, Register (0x36) fifo_conf).
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
588#[cfg_attr(feature = "defmt", derive(defmt::Format))]
589pub struct FifoConfig {
590 /// Stop writing new data once the FIFO is full.
591 pub stop_on_full: bool,
592 /// Include sensor time words in FIFO output.
593 pub include_time: bool,
594 /// Include accelerometer samples in FIFO output.
595 pub include_accel: bool,
596 /// Include gyroscope samples in FIFO output.
597 pub include_gyro: bool,
598 /// Include temperature samples in FIFO output.
599 pub include_temperature: bool,
600}
601
602impl FifoConfig {
603 /// Encode the configuration into the BMI323 register bit layout.
604 pub const fn to_word(self) -> u16 {
605 (self.stop_on_full as u16)
606 | ((self.include_time as u16) << 8)
607 | ((self.include_accel as u16) << 9)
608 | ((self.include_gyro as u16) << 10)
609 | ((self.include_temperature as u16) << 11)
610 }
611}
612
613/// Interrupt output channel inside the BMI323
614/// (§6.1.2, Registers (0x3A-0x3B) int_map1/int_map2).
615#[derive(Debug, Clone, Copy, PartialEq, Eq)]
616#[cfg_attr(feature = "defmt", derive(defmt::Format))]
617pub enum InterruptChannel {
618 /// Route or query the `INT1` output.
619 Int1,
620 /// Route or query the `INT2` output.
621 Int2,
622 /// Route or query the I3C in-band interrupt channel.
623 Ibi,
624}
625
626/// Mapping destination for an interrupt source
627/// (§6.1.2, Registers (0x3A-0x3B) int_map1/int_map2, 2-bit routing fields).
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629#[cfg_attr(feature = "defmt", derive(defmt::Format))]
630pub enum InterruptRoute {
631 /// Do not route the interrupt source anywhere.
632 Disabled = 0,
633 /// Route the source to `INT1`.
634 Int1 = 1,
635 /// Route the source to `INT2`.
636 Int2 = 2,
637 /// Route the source to I3C in-band interrupt.
638 Ibi = 3,
639}
640
641impl From<InterruptRoute> for u16 {
642 fn from(value: InterruptRoute) -> Self {
643 value as u16
644 }
645}
646
647/// Electrical active level for an interrupt output pin
648/// (§6.1.2, Register (0x38) io_int_ctrl `int1_lvl`/`int2_lvl`).
649#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
650#[cfg_attr(feature = "defmt", derive(defmt::Format))]
651pub enum ActiveLevel {
652 /// Active-low signaling.
653 #[default]
654 Low = 0,
655 /// Active-high signaling.
656 High = 1,
657}
658
659/// Electrical driver mode for an interrupt output pin
660/// (§6.1.2, Register (0x38) io_int_ctrl `int1_od`/`int2_od`).
661#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
662#[cfg_attr(feature = "defmt", derive(defmt::Format))]
663pub enum OutputMode {
664 /// Push-pull output driver.
665 #[default]
666 PushPull = 0,
667 /// Open-drain output driver.
668 OpenDrain = 1,
669}
670
671/// Electrical configuration for `INT1` or `INT2` (§6.1.2, Register (0x38) io_int_ctrl).
672#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
673#[cfg_attr(feature = "defmt", derive(defmt::Format))]
674pub struct InterruptPinConfig {
675 /// Active signaling level.
676 pub active_level: ActiveLevel,
677 /// Output driver type.
678 pub output_mode: OutputMode,
679 /// Whether the selected output is enabled.
680 pub enabled: bool,
681}
682
683/// Interrupt source that can be mapped to an output channel
684/// (§6.1.2, Registers (0x3A-0x3B) int_map1/int_map2).
685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
686#[cfg_attr(feature = "defmt", derive(defmt::Format))]
687pub enum InterruptSource {
688 /// No-motion feature interrupt.
689 NoMotion,
690 /// Any-motion feature interrupt.
691 AnyMotion,
692 /// Flat detection interrupt.
693 Flat,
694 /// Orientation change interrupt.
695 Orientation,
696 /// Step detector interrupt.
697 StepDetector,
698 /// Step counter interrupt.
699 StepCounter,
700 /// Significant motion interrupt.
701 SignificantMotion,
702 /// Tilt interrupt.
703 Tilt,
704 /// Tap interrupt.
705 Tap,
706 /// I3C synchronization interrupt.
707 I3cSync,
708 /// Error-status interrupt (`err_status` in the BMI323 datasheet).
709 ErrorStatus,
710 /// Temperature data-ready interrupt.
711 TempDataReady,
712 /// Gyroscope data-ready interrupt.
713 GyroDataReady,
714 /// Accelerometer data-ready interrupt.
715 AccelDataReady,
716 /// FIFO watermark interrupt.
717 FifoWatermark,
718 /// FIFO full interrupt.
719 FifoFull,
720}
721
722/// Event reporting policy for supported feature-engine motion detectors
723/// (§6.2.2, Register (0x02) gen_set_1 `event_report_mode` bit 0).
724#[derive(Debug, Clone, Copy, PartialEq, Eq)]
725#[cfg_attr(feature = "defmt", derive(defmt::Format))]
726pub enum EventReportMode {
727 /// Report every qualifying event.
728 AllEvents,
729 /// Report only the first event until cleared internally by the sensor.
730 FirstEventOnly,
731}
732
733/// Configuration for the BMI323 feature-engine step counter block
734/// (§6.2.2, Register (0x10) sc_1; §5.8.5).
735///
736/// The counter itself accumulates the total detected step count inside the
737/// sensor. This configuration controls two separate behaviors:
738///
739/// - `watermark_level`: generates a step-counter interrupt whenever the number
740/// of newly counted steps since the last step-counter event reaches the
741/// programmed value
742/// - `reset_counter`: requests a reset of the accumulated internal step count
743///
744/// A `watermark_level` of `0` disables the step-counter interrupt source.
745#[derive(Debug, Clone, Copy, PartialEq, Eq)]
746#[cfg_attr(feature = "defmt", derive(defmt::Format))]
747pub struct StepCounterConfig {
748 /// Step-counter interrupt watermark.
749 ///
750 /// Unit: steps
751 /// Scaling: 1 LSB = 1 step event increment
752 /// Range: `0 ..= 1023`
753 ///
754 /// When this is nonzero, the BMI323 raises the `StepCounter` interrupt
755 /// each time this many additional steps have been accumulated since the
756 /// last step-counter event. A value of `0` disables the step-counter
757 /// interrupt.
758 pub watermark_level: u16,
759 /// Reset request for the accumulated step count.
760 ///
761 /// When set to `true`, the driver writes the feature-engine reset bit for
762 /// the step counter. This is useful when re-arming an application that
763 /// wants a fresh accumulated count after initialization or after reading a
764 /// previous session's result.
765 pub reset_counter: bool,
766}
767
768impl Default for StepCounterConfig {
769 fn default() -> Self {
770 Self::disabled()
771 }
772}
773
774impl StepCounterConfig {
775 /// Create a configuration with the step-counter interrupt disabled.
776 pub const fn disabled() -> Self {
777 Self {
778 watermark_level: 0,
779 reset_counter: false,
780 }
781 }
782
783 /// Create a configuration that raises an interrupt every `steps` steps.
784 ///
785 /// Values above `1023` are saturated to the BMI323 register field width.
786 pub const fn with_watermark(steps: u16) -> Self {
787 Self {
788 watermark_level: if steps > 1023 { 1023 } else { steps },
789 reset_counter: false,
790 }
791 }
792
793 /// Encode the configuration into the BMI323 `sc_1` feature word.
794 pub const fn to_word(self) -> u16 {
795 (self.watermark_level & 0x03FF) | ((self.reset_counter as u16) << 10)
796 }
797}
798
799/// Shared blocking behavior used by flat and orientation detection
800/// (§6.2.2, Register (0x0B) flat_1 `blocking`; Register (0x1C) orient_1 `blocking`).
801#[derive(Debug, Clone, Copy, PartialEq, Eq)]
802#[cfg_attr(feature = "defmt", derive(defmt::Format))]
803pub enum FeatureBlockingMode {
804 /// Do not block state changes during large movements.
805 Disabled = 0,
806 /// Block when acceleration on any axis exceeds `1.5g`.
807 AccelOver1p5g = 1,
808 /// Block when acceleration exceeds `1.5g` or slope exceeds half of the
809 /// configured slope threshold.
810 AccelOver1p5gOrHalfSlope = 2,
811 /// Block when acceleration exceeds `1.5g` or slope exceeds the configured
812 /// slope threshold.
813 AccelOver1p5gOrFullSlope = 3,
814}
815
816/// Configuration mode for orientation spread between portrait and landscape
817/// (§6.2.2, Register (0x1C) orient_1 `mode`).
818#[derive(Debug, Clone, Copy, PartialEq, Eq)]
819#[cfg_attr(feature = "defmt", derive(defmt::Format))]
820pub enum OrientationMode {
821 /// Symmetrical spread for portrait and landscape orientations.
822 Symmetrical = 0,
823 /// Larger landscape area than portrait area.
824 LandscapeWide = 1,
825 /// Larger portrait area than landscape area.
826 PortraitWide = 2,
827}
828
829/// Dominant accelerometer axis used for tap detection
830/// (§6.2.2, Register (0x1E) tap_1 `axis_sel`).
831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
832#[cfg_attr(feature = "defmt", derive(defmt::Format))]
833pub enum TapAxis {
834 /// Detect taps along the X axis.
835 X = 0,
836 /// Detect taps along the Y axis.
837 Y = 1,
838 /// Detect taps along the Z axis.
839 Z = 2,
840}
841
842/// Reporting policy for tap gesture confirmation
843/// (§6.2.2, Register (0x1E) tap_1 `wait_for_timeout`).
844#[derive(Debug, Clone, Copy, PartialEq, Eq)]
845#[cfg_attr(feature = "defmt", derive(defmt::Format))]
846pub enum TapReportingMode {
847 /// Report the gesture as soon as it is detected.
848 Immediate = 0,
849 /// Delay reporting until the configured gesture timeout confirms it.
850 Confirmed = 1,
851}
852
853/// Detection profile for tap recognition
854/// (§6.2.2, Register (0x1E) tap_1 `mode`).
855#[derive(Debug, Clone, Copy, PartialEq, Eq)]
856#[cfg_attr(feature = "defmt", derive(defmt::Format))]
857pub enum TapDetectionMode {
858 /// Most sensitive profile. Useful for stable devices.
859 Sensitive = 0,
860 /// Balanced default profile.
861 Normal = 1,
862 /// More robust profile with reduced false detections in noisy scenarios.
863 Robust = 2,
864}
865
866/// Configuration for the BMI323 flat-detection feature
867/// (§6.2.2, Registers (0x0B-0x0C) flat_1/flat_2; §5.8.6).
868#[derive(Debug, Clone, Copy, PartialEq, Eq)]
869#[cfg_attr(feature = "defmt", derive(defmt::Format))]
870pub struct FlatConfig {
871 /// Maximum allowed tilt angle for the device to be considered flat.
872 ///
873 /// Unit: degrees
874 /// Encoding: nonlinear
875 /// Field width: 6 bits
876 /// Valid raw range: `0 ..= 63`
877 ///
878 /// The datasheet interpretation is `(tan(theta)^2) * 64`.
879 pub theta: u8,
880 /// Blocking behavior to suppress flat-state changes during large movement.
881 pub blocking: FeatureBlockingMode,
882 /// Minimum time the device must remain flat before the event is asserted.
883 ///
884 /// Unit: seconds
885 /// Scaling: `raw / 50`
886 /// Range: `0 ..= 255`, corresponding to approximately `0.0s ..= 5.10s`
887 pub hold_time: u8,
888 /// Minimum acceleration slope that blocks flat-status changes during large
889 /// movement.
890 ///
891 /// Unit: `g`
892 /// Scaling: `raw / 512`
893 /// Range: `0 ..= 255`, corresponding to approximately `0.0g ..= 0.498g`
894 pub slope_threshold: u8,
895 /// Angular hysteresis for flat detection.
896 ///
897 /// Unit: degrees
898 /// Encoding: nonlinear
899 /// Field width: 8 bits
900 ///
901 /// The datasheet expresses this field relative to the configured
902 /// `theta`. The raw value can be programmed directly when precise control
903 /// is needed.
904 pub hysteresis: u8,
905 /// Event reporting behavior shared across feature-engine interrupts.
906 pub report_mode: EventReportMode,
907 /// Shared feature-engine interrupt hold-time exponent.
908 ///
909 /// Effective hold time in non-latched mode:
910 /// `0.625ms * 2^interrupt_hold`
911 pub interrupt_hold: u8,
912}
913
914impl FlatConfig {
915 /// Convert a hold time in seconds to the BMI323 field encoding.
916 pub fn hold_time_from_seconds(seconds: f32) -> u8 {
917 (seconds * 50.0).clamp(0.0, 255.0) as u8
918 }
919
920 /// Convert a raw hold-time field value back to seconds.
921 pub fn hold_time_to_seconds(raw: u8) -> f32 {
922 raw as f32 / 50.0
923 }
924
925 /// Convert a slope threshold in `g` to the BMI323 field encoding.
926 pub fn slope_threshold_from_g(g: f32) -> u8 {
927 (g * 512.0).clamp(0.0, 255.0) as u8
928 }
929
930 /// Convert a raw slope-threshold field value back to `g`.
931 pub fn slope_threshold_to_g(raw: u8) -> f32 {
932 raw as f32 / 512.0
933 }
934
935 /// Convert an interrupt hold time in milliseconds to the BMI323 field
936 /// encoding.
937 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
938 AnyMotionConfig::interrupt_hold_from_millis(millis)
939 }
940
941 /// Convert a raw interrupt-hold field value back to milliseconds.
942 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
943 AnyMotionConfig::interrupt_hold_to_millis(raw)
944 }
945}
946
947/// Configuration for the BMI323 orientation-detection feature
948/// (§6.2.2, Registers (0x1C-0x1D) orient_1/orient_2; §5.8.7).
949#[derive(Debug, Clone, Copy, PartialEq, Eq)]
950#[cfg_attr(feature = "defmt", derive(defmt::Format))]
951pub struct OrientationConfig {
952 /// Whether upside-down orientation detection is enabled.
953 pub upside_down_enabled: bool,
954 /// Portrait/landscape spread mode.
955 pub mode: OrientationMode,
956 /// Blocking behavior to suppress orientation changes during large movement.
957 pub blocking: FeatureBlockingMode,
958 /// Maximum allowed tilt angle for orientation classification.
959 ///
960 /// Unit: degrees
961 /// Encoding: nonlinear
962 /// Field width: 6 bits
963 ///
964 /// The datasheet interpretation is `(tan(theta)^2) * 64`.
965 pub theta: u8,
966 /// Minimum time the device must remain in the new orientation before an
967 /// event is asserted.
968 ///
969 /// Unit: seconds
970 /// Scaling: `raw / 50`
971 /// Range: `0 ..= 31`, corresponding to approximately `0.0s ..= 0.62s`
972 pub hold_time: u8,
973 /// Minimum slope between consecutive acceleration samples used for blocking
974 /// orientation changes during large movement.
975 ///
976 /// Unit: `g`
977 /// Scaling: `raw / 512`
978 /// Range: `0 ..= 255`, corresponding to approximately `0.0g ..= 0.498g`
979 pub slope_threshold: u8,
980 /// Hysteresis of acceleration for orientation change detection.
981 ///
982 /// Unit: `g`
983 /// Scaling: `raw / 512`
984 /// Range: `0 ..= 255`, corresponding to approximately `0.0g ..= 0.498g`
985 pub hysteresis: u8,
986 /// Event reporting behavior shared across feature-engine interrupts.
987 pub report_mode: EventReportMode,
988 /// Shared feature-engine interrupt hold-time exponent.
989 pub interrupt_hold: u8,
990}
991
992impl OrientationConfig {
993 /// Convert a hold time in seconds to the BMI323 field encoding.
994 pub fn hold_time_from_seconds(seconds: f32) -> u8 {
995 (seconds * 50.0).clamp(0.0, 31.0) as u8
996 }
997
998 /// Convert a raw hold-time field value back to seconds.
999 pub fn hold_time_to_seconds(raw: u8) -> f32 {
1000 f32::from(raw & 0x1F) / 50.0
1001 }
1002
1003 /// Convert a slope threshold in `g` to the BMI323 field encoding.
1004 pub fn slope_threshold_from_g(g: f32) -> u8 {
1005 FlatConfig::slope_threshold_from_g(g)
1006 }
1007
1008 /// Convert a raw slope-threshold field value back to `g`.
1009 pub fn slope_threshold_to_g(raw: u8) -> f32 {
1010 FlatConfig::slope_threshold_to_g(raw)
1011 }
1012
1013 /// Convert a hysteresis in `g` to the BMI323 field encoding.
1014 pub fn hysteresis_from_g(g: f32) -> u8 {
1015 (g * 512.0).clamp(0.0, 255.0) as u8
1016 }
1017
1018 /// Convert a raw hysteresis field value back to `g`.
1019 pub fn hysteresis_to_g(raw: u8) -> f32 {
1020 raw as f32 / 512.0
1021 }
1022
1023 /// Convert an interrupt hold time in milliseconds to the BMI323 field
1024 /// encoding.
1025 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
1026 AnyMotionConfig::interrupt_hold_from_millis(millis)
1027 }
1028
1029 /// Convert a raw interrupt-hold field value back to milliseconds.
1030 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
1031 AnyMotionConfig::interrupt_hold_to_millis(raw)
1032 }
1033}
1034
1035/// Configuration for the BMI323 tap-detection feature
1036/// (§6.2.2, Registers (0x1E-0x20) tap_1/tap_2/tap_3; §5.8.8).
1037#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1038#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1039pub struct TapConfig {
1040 /// Dominant axis used for tap detection.
1041 pub axis: TapAxis,
1042 /// Whether tap gestures should be reported immediately or only after
1043 /// timeout-based confirmation.
1044 pub reporting_mode: TapReportingMode,
1045 /// Maximum number of threshold crossings expected around a tap.
1046 ///
1047 /// Range: `0 ..= 7`
1048 pub max_peaks_for_tap: u8,
1049 /// Tap detection profile.
1050 pub mode: TapDetectionMode,
1051 /// Whether single-tap reporting is enabled.
1052 pub single_tap_enabled: bool,
1053 /// Whether double-tap reporting is enabled.
1054 pub double_tap_enabled: bool,
1055 /// Whether triple-tap reporting is enabled.
1056 pub triple_tap_enabled: bool,
1057 /// Minimum peak threshold caused by the tap.
1058 ///
1059 /// Unit: `g`
1060 /// Scaling: `raw / 512`
1061 /// Range: `0 ..= 1023`, corresponding to approximately `0.0g ..= 1.998g`
1062 pub tap_peak_threshold: u16,
1063 /// Maximum duration from the first tap until the second and/or third tap is
1064 /// expected.
1065 ///
1066 /// Unit: seconds
1067 /// Scaling: `raw / 25`
1068 /// Range: `0 ..= 63`, corresponding to approximately `0.0s ..= 2.52s`
1069 pub max_gesture_duration: u8,
1070 /// Maximum duration between positive and negative peaks belonging to a tap.
1071 ///
1072 /// Unit: seconds
1073 /// Scaling: `raw / 200`
1074 /// Range: `0 ..= 15`, corresponding to approximately `0.0s ..= 0.075s`
1075 pub max_duration_between_peaks: u8,
1076 /// Maximum duration for which the tap impact is observed.
1077 ///
1078 /// Unit: seconds
1079 /// Scaling: `raw / 200`
1080 /// Range: `0 ..= 15`, corresponding to approximately `0.0s ..= 0.075s`
1081 pub tap_shock_settling_duration: u8,
1082 /// Minimum quiet duration between consecutive tap impacts.
1083 ///
1084 /// Unit: seconds
1085 /// Scaling: `raw / 200`
1086 /// Range: `0 ..= 15`, corresponding to approximately `0.0s ..= 0.075s`
1087 pub min_quiet_duration_between_taps: u8,
1088 /// Minimum quiet duration between two gestures.
1089 ///
1090 /// Unit: seconds
1091 /// Scaling: `raw / 25`
1092 /// Range: `0 ..= 15`, corresponding to approximately `0.0s ..= 0.6s`
1093 pub quiet_time_after_gesture: u8,
1094 /// Event reporting behavior shared across feature-engine interrupts.
1095 pub report_mode: EventReportMode,
1096 /// Shared feature-engine interrupt hold-time exponent.
1097 ///
1098 /// Note: the BMI323 datasheet warns that tap detection must not use the
1099 /// 5ms hold-time setting unless Bosch's enhanced flexibility configuration
1100 /// has been applied externally.
1101 pub interrupt_hold: u8,
1102}
1103
1104impl TapConfig {
1105 /// Convert a tap threshold in `g` to the BMI323 field encoding.
1106 pub fn tap_peak_threshold_from_g(g: f32) -> u16 {
1107 (g * 512.0).clamp(0.0, 1023.0) as u16
1108 }
1109
1110 /// Convert a raw tap-threshold field value back to `g`.
1111 pub fn tap_peak_threshold_to_g(raw: u16) -> f32 {
1112 (raw & 0x03FF) as f32 / 512.0
1113 }
1114
1115 /// Convert a gesture duration in seconds to the BMI323 field encoding.
1116 pub fn max_gesture_duration_from_seconds(seconds: f32) -> u8 {
1117 (seconds * 25.0).clamp(0.0, 63.0) as u8
1118 }
1119
1120 /// Convert a raw gesture-duration field value back to seconds.
1121 pub fn max_gesture_duration_to_seconds(raw: u8) -> f32 {
1122 f32::from(raw & 0x3F) / 25.0
1123 }
1124
1125 /// Convert a short tap timing parameter in seconds to the BMI323 field
1126 /// encoding used by `max_duration_between_peaks`,
1127 /// `tap_shock_settling_duration`, and `min_quiet_duration_between_taps`.
1128 pub fn short_duration_from_seconds(seconds: f32) -> u8 {
1129 (seconds * 200.0).clamp(0.0, 15.0) as u8
1130 }
1131
1132 /// Convert a raw short-duration field value back to seconds.
1133 pub fn short_duration_to_seconds(raw: u8) -> f32 {
1134 f32::from(raw & 0x0F) / 200.0
1135 }
1136
1137 /// Convert a post-gesture quiet time in seconds to the BMI323 field
1138 /// encoding.
1139 pub fn quiet_time_after_gesture_from_seconds(seconds: f32) -> u8 {
1140 (seconds * 25.0).clamp(0.0, 15.0) as u8
1141 }
1142
1143 /// Convert a raw post-gesture quiet-time field value back to seconds.
1144 pub fn quiet_time_after_gesture_to_seconds(raw: u8) -> f32 {
1145 f32::from(raw & 0x0F) / 25.0
1146 }
1147
1148 /// Convert an interrupt hold time in milliseconds to the BMI323 field
1149 /// encoding.
1150 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
1151 AnyMotionConfig::interrupt_hold_from_millis(millis)
1152 }
1153
1154 /// Convert a raw interrupt-hold field value back to milliseconds.
1155 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
1156 AnyMotionConfig::interrupt_hold_to_millis(raw)
1157 }
1158}
1159
1160/// Configuration for the BMI323 significant-motion feature
1161/// (§6.2.2, Registers (0x0D-0x0F) sigmo_1/sigmo_2/sigmo_3; §5.8.4).
1162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1163#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1164pub struct SignificantMotionConfig {
1165 /// Segment size used by the significant-motion detector.
1166 ///
1167 /// Unit: seconds
1168 /// Scaling: `raw / 50`
1169 /// Range: `0 ..= 65535`, corresponding to approximately
1170 /// `0.0s ..= 1310.7s`
1171 pub block_size: u16,
1172 /// Minimum peak-to-peak acceleration magnitude.
1173 ///
1174 /// Unit: `g`
1175 /// Scaling: `raw / 512`
1176 /// Range: `0 ..= 1023`, corresponding to approximately
1177 /// `0.0g ..= 1.998g`
1178 pub peak_to_peak_min: u16,
1179 /// Minimum mean-crossing rate in acceleration magnitude.
1180 ///
1181 /// Unit: crossings per second
1182 /// Range: `0 ..= 63`
1183 pub mean_crossing_rate_min: u8,
1184 /// Maximum peak-to-peak acceleration magnitude.
1185 ///
1186 /// Unit: `g`
1187 /// Scaling: `raw / 512`
1188 /// Range: `0 ..= 1023`, corresponding to approximately
1189 /// `0.0g ..= 1.998g`
1190 pub peak_to_peak_max: u16,
1191 /// Maximum mean-crossing rate in acceleration magnitude.
1192 ///
1193 /// Unit: crossings per second
1194 /// Range: `0 ..= 63`
1195 pub mean_crossing_rate_max: u8,
1196 /// Event reporting behavior shared across feature-engine interrupts.
1197 pub report_mode: EventReportMode,
1198 /// Shared feature-engine interrupt hold-time exponent.
1199 pub interrupt_hold: u8,
1200}
1201
1202impl SignificantMotionConfig {
1203 /// Convert a segment size in seconds to the BMI323 field encoding.
1204 pub fn block_size_from_seconds(seconds: f32) -> u16 {
1205 (seconds * 50.0).clamp(0.0, 65535.0) as u16
1206 }
1207
1208 /// Convert a raw segment-size field value back to seconds.
1209 pub fn block_size_to_seconds(raw: u16) -> f32 {
1210 raw as f32 / 50.0
1211 }
1212
1213 /// Convert a peak-to-peak acceleration magnitude in `g` to the BMI323
1214 /// field encoding.
1215 pub fn peak_to_peak_from_g(g: f32) -> u16 {
1216 (g * 512.0).clamp(0.0, 1023.0) as u16
1217 }
1218
1219 /// Convert a raw peak-to-peak field value back to `g`.
1220 pub fn peak_to_peak_to_g(raw: u16) -> f32 {
1221 (raw & 0x03FF) as f32 / 512.0
1222 }
1223
1224 /// Convert an interrupt hold time in milliseconds to the BMI323 field
1225 /// encoding.
1226 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
1227 AnyMotionConfig::interrupt_hold_from_millis(millis)
1228 }
1229
1230 /// Convert a raw interrupt-hold field value back to milliseconds.
1231 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
1232 AnyMotionConfig::interrupt_hold_to_millis(raw)
1233 }
1234}
1235
1236/// Configuration for the BMI323 tilt-detection feature
1237/// (§6.2.2, Registers (0x21-0x22) tilt_1/tilt_2; §5.8.9).
1238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1239#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1240pub struct TiltConfig {
1241 /// Averaging duration of the acceleration reference vector.
1242 ///
1243 /// Unit: seconds
1244 /// Scaling: `raw / 50`
1245 /// Range: `0 ..= 255`, corresponding to approximately `0.0s ..= 5.10s`
1246 pub segment_size: u8,
1247 /// Minimum tilt angle raw field.
1248 ///
1249 /// Unit: degrees
1250 /// Encoding: nonlinear
1251 /// Field width: 8 bits
1252 ///
1253 /// The datasheet interpretation is `cos(angle) * 256`. The raw value is
1254 /// exposed directly to avoid adding a floating-point math dependency in
1255 /// this `no_std` crate.
1256 pub min_tilt_angle: u8,
1257 /// Exponential smoothing coefficient for the low-pass mean of the
1258 /// acceleration vector.
1259 ///
1260 /// This is the raw `beta_acc_mean` field from the datasheet.
1261 pub beta_acc_mean: u16,
1262 /// Event reporting behavior shared across feature-engine interrupts.
1263 pub report_mode: EventReportMode,
1264 /// Shared feature-engine interrupt hold-time exponent.
1265 pub interrupt_hold: u8,
1266}
1267
1268impl TiltConfig {
1269 /// Convert a reference-vector averaging duration in seconds to the BMI323
1270 /// field encoding.
1271 pub fn segment_size_from_seconds(seconds: f32) -> u8 {
1272 (seconds * 50.0).clamp(0.0, 255.0) as u8
1273 }
1274
1275 /// Convert a raw segment-size field value back to seconds.
1276 pub fn segment_size_to_seconds(raw: u8) -> f32 {
1277 raw as f32 / 50.0
1278 }
1279
1280 /// Convert an interrupt hold time in milliseconds to the BMI323 field
1281 /// encoding.
1282 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
1283 AnyMotionConfig::interrupt_hold_from_millis(millis)
1284 }
1285
1286 /// Convert a raw interrupt-hold field value back to milliseconds.
1287 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
1288 AnyMotionConfig::interrupt_hold_to_millis(raw)
1289 }
1290}
1291
1292/// Reduced accelerometer configuration used by the BMI323 alternate mode
1293/// (§6.1.2, Register (0x28) alt_acc_conf; §5.10).
1294///
1295/// Unlike [`AccelConfig`], the alternate configuration does not contain
1296/// bandwidth or range fields. The hardware only exposes mode, averaging, and
1297/// output data rate in `ALT_ACC_CONF`.
1298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1299#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1300pub struct AltAccelConfig {
1301 /// Sensor operating mode while the alternate configuration is active.
1302 pub mode: AccelMode,
1303 /// Sample averaging depth used in the alternate configuration.
1304 pub average: AverageSamples,
1305 /// Alternate output data rate.
1306 pub odr: OutputDataRate,
1307}
1308
1309impl AltAccelConfig {
1310 /// Encode the alternate configuration into the BMI323 register bit layout.
1311 pub const fn to_word(self) -> u16 {
1312 ((self.mode as u16) << 12) | ((self.average as u16) << 8) | self.odr as u16
1313 }
1314}
1315
1316/// Reduced gyroscope configuration used by the BMI323 alternate mode
1317/// (§6.1.2, Register (0x29) alt_gyr_conf; §5.10).
1318///
1319/// Unlike [`GyroConfig`], the alternate configuration does not contain
1320/// bandwidth or range fields. The hardware only exposes mode, averaging, and
1321/// output data rate in `ALT_GYR_CONF`.
1322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1323#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1324pub struct AltGyroConfig {
1325 /// Sensor operating mode while the alternate configuration is active.
1326 pub mode: GyroMode,
1327 /// Sample averaging depth used in the alternate configuration.
1328 pub average: AverageSamples,
1329 /// Alternate output data rate.
1330 pub odr: OutputDataRate,
1331}
1332
1333impl AltGyroConfig {
1334 /// Encode the alternate configuration into the BMI323 register bit layout.
1335 pub const fn to_word(self) -> u16 {
1336 ((self.mode as u16) << 12) | ((self.average as u16) << 8) | self.odr as u16
1337 }
1338}
1339
1340/// Supported feature-engine sources that can switch between user and alternate
1341/// sensor configurations (§6.1.2, Register (0x2A) alt_conf; §5.10).
1342///
1343/// The BMI323 datasheet describes these as switch sources `A..I`. This API
1344/// names them by their corresponding feature interrupts instead.
1345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1346#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1347pub enum AltConfigSwitchSource {
1348 /// Disable automatic switching for this direction.
1349 None = 0,
1350 /// Source `A`: no-motion.
1351 NoMotion = 1,
1352 /// Source `B`: any-motion.
1353 AnyMotion = 2,
1354 /// Source `C`: flat.
1355 Flat = 3,
1356 /// Source `D`: orientation.
1357 Orientation = 4,
1358 /// Source `E`: step detector.
1359 StepDetector = 5,
1360 /// Source `F`: step-counter watermark.
1361 StepCounter = 6,
1362 /// Source `G`: significant motion.
1363 SignificantMotion = 7,
1364 /// Source `H`: tilt.
1365 Tilt = 8,
1366 /// Source `I`: tap.
1367 Tap = 9,
1368}
1369
1370/// Automatic switching policy between user and alternate accel/gyro
1371/// configurations (§6.1.2, Register (0x2A) alt_conf; §5.10).
1372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1373#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1374pub struct AltConfigControl {
1375 /// Whether the accelerometer may switch to its alternate configuration.
1376 pub accel_enabled: bool,
1377 /// Whether the gyroscope may switch to its alternate configuration.
1378 pub gyro_enabled: bool,
1379 /// If enabled, any later write to `ACC_CONF` or `GYR_CONF` immediately
1380 /// returns the affected sensor to its user configuration.
1381 pub reset_on_user_config_write: bool,
1382 /// Feature-engine source that switches sensors into the alternate
1383 /// configuration.
1384 pub switch_to_alternate: AltConfigSwitchSource,
1385 /// Feature-engine source that switches sensors back to the user
1386 /// configuration.
1387 pub switch_to_user: AltConfigSwitchSource,
1388}
1389
1390/// Current active accel/gyro configuration selection reported by the BMI323
1391/// (§6.1.2, Register (0x2B) alt_status).
1392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1393#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1394pub struct AltStatus(pub u16);
1395
1396impl AltStatus {
1397 /// Returns true when the accelerometer is currently using `ALT_ACC_CONF`.
1398 pub const fn accel_uses_alternate(self) -> bool {
1399 self.0 & 0x0001 != 0
1400 }
1401
1402 /// Returns true when the gyroscope is currently using `ALT_GYR_CONF`.
1403 pub const fn gyro_uses_alternate(self) -> bool {
1404 self.0 & (1 << 4) != 0
1405 }
1406}
1407
1408/// Convenience bundle for a common accel-only alternate-configuration setup.
1409///
1410/// This represents a practical battery-oriented pattern:
1411/// - a low-power user accelerometer configuration for normal operation
1412/// - a high-performance alternate accelerometer configuration for higher-fidelity
1413/// sampling after a feature-engine event
1414/// - an [`AltConfigControl`] value that enables accel switching and keeps gyro
1415/// on the user configuration
1416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1417#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1418pub struct AltAccelSwitchProfile {
1419 /// User accelerometer configuration written to `ACC_CONF`.
1420 pub user_accel: AccelConfig,
1421 /// Alternate accelerometer configuration written to `ALT_ACC_CONF`.
1422 pub alternate_accel: AltAccelConfig,
1423 /// Matching alternate-configuration control policy.
1424 pub control: AltConfigControl,
1425}
1426
1427impl AltAccelSwitchProfile {
1428 /// Build a recommended accel-only low-power to high-performance switching
1429 /// profile.
1430 ///
1431 /// This helper keeps:
1432 /// - accelerometer range at [`AccelRange::G8`]
1433 /// - user accel mode at [`AccelMode::LowPower`] with `Avg2`
1434 /// - alternate accel mode at [`AccelMode::HighPerformance`] with `Avg1`
1435 /// - gyro alternate switching disabled
1436 /// - `reset_on_user_config_write` enabled
1437 ///
1438 /// The caller still needs to configure the feature-engine event that will
1439 /// generate the chosen switch sources, for example any-motion or
1440 /// no-motion.
1441 pub const fn low_power_to_high_performance(
1442 user_odr: OutputDataRate,
1443 alternate_odr: OutputDataRate,
1444 switch_to_alternate: AltConfigSwitchSource,
1445 switch_to_user: AltConfigSwitchSource,
1446 ) -> Self {
1447 Self {
1448 user_accel: AccelConfig {
1449 mode: AccelMode::LowPower,
1450 average: AverageSamples::Avg2,
1451 bandwidth: Bandwidth::OdrOver2,
1452 range: AccelRange::G8,
1453 odr: user_odr,
1454 },
1455 alternate_accel: AltAccelConfig {
1456 mode: AccelMode::HighPerformance,
1457 average: AverageSamples::Avg1,
1458 odr: alternate_odr,
1459 },
1460 control: AltConfigControl {
1461 accel_enabled: true,
1462 gyro_enabled: false,
1463 reset_on_user_config_write: true,
1464 switch_to_alternate,
1465 switch_to_user,
1466 },
1467 }
1468 }
1469}
1470
1471/// Reference update policy for supported motion detectors
1472/// (§6.2.2, Register (0x05) anymo_1 `acc_ref_up`; Register (0x08) nomo_1 `acc_ref_up`).
1473#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1474#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1475pub enum ReferenceUpdate {
1476 /// Update the internal reference only when an event is detected.
1477 OnDetection,
1478 /// Continuously update the internal reference.
1479 EverySample,
1480}
1481
1482/// Per-axis enable mask for motion features
1483/// (§6.2.2, Register (0x07) anymo_3 `axis_sel`; Register (0x0A) nomo_3 `axis_sel`).
1484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1485#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1486pub struct MotionAxes {
1487 /// Enable X-axis contribution.
1488 pub x: bool,
1489 /// Enable Y-axis contribution.
1490 pub y: bool,
1491 /// Enable Z-axis contribution.
1492 pub z: bool,
1493}
1494
1495impl MotionAxes {
1496 /// Convenience constant enabling X, Y, and Z axes.
1497 pub const XYZ: Self = Self {
1498 x: true,
1499 y: true,
1500 z: true,
1501 };
1502}
1503
1504impl Default for MotionAxes {
1505 /// Defaults to all axes enabled (`XYZ`), the only meaningful starting point for motion detection.
1506 fn default() -> Self {
1507 Self::XYZ
1508 }
1509}
1510
1511/// Configuration for the BMI323 any-motion feature
1512/// (§6.2.2, Registers (0x05-0x07) anymo_1/anymo_2/anymo_3; §5.8.2).
1513#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1514#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1515pub struct AnyMotionConfig {
1516 /// Axis selection for detection.
1517 pub axes: MotionAxes,
1518 /// Minimum acceleration slope for motion detection.
1519 ///
1520 /// Unit: `g`
1521 /// Scaling: `raw / 512`
1522 /// Range: `0 ..= 4095`, corresponding to approximately `0.0g ..= 7.998g`
1523 pub threshold: u16,
1524 /// Hysteresis for the acceleration slope comparator.
1525 ///
1526 /// Unit: `g`
1527 /// Scaling: `raw / 512`
1528 /// Range: `0 ..= 1023`, corresponding to approximately `0.0g ..= 1.998g`
1529 pub hysteresis: u16,
1530 /// Minimum duration for which the slope must stay above `threshold`.
1531 ///
1532 /// Unit: seconds
1533 /// Scaling: `raw / 50`
1534 /// Range: `0 ..= 8191`, corresponding to approximately `0.0s ..= 163.82s`
1535 pub duration: u16,
1536 /// Wait time before the event is cleared after the slope drops below
1537 /// `threshold`.
1538 ///
1539 /// Unit: seconds
1540 /// Scaling: `raw / 50`
1541 /// Range: `0 ..= 7`, corresponding to `0.00s ..= 0.14s` in `20ms` steps
1542 pub wait_time: u8,
1543 /// Reference update policy.
1544 pub reference_update: ReferenceUpdate,
1545 /// Event reporting behavior.
1546 pub report_mode: EventReportMode,
1547 /// Interrupt hold-time exponent used by the feature engine.
1548 ///
1549 /// Effective hold time in non-latched mode:
1550 /// `0.625ms * 2^interrupt_hold`
1551 ///
1552 /// Valid raw range is `0 ..= 13`. Larger values are clamped to `13` by the
1553 /// driver before programming the register. This setting is only applicable
1554 /// to non-latched feature-engine interrupts.
1555 pub interrupt_hold: u8,
1556}
1557
1558/// Maximum valid value for the interrupt hold-time exponent field shared across feature-engine
1559/// blocks (`gen_set_1` bits[4:1]). Values 14–15 are undefined; the field is clamped to this
1560/// maximum before being written to the register (§6.2.2, Register (0x02) gen_set_1).
1561pub(crate) const INTERRUPT_HOLD_MAX: u8 = 13;
1562
1563impl AnyMotionConfig {
1564 /// Convert a physical threshold in `g` to the BMI323 field encoding.
1565 pub fn threshold_from_g(g: f32) -> u16 {
1566 (g * 512.0).clamp(0.0, 4095.0) as u16
1567 }
1568
1569 /// Convert a raw threshold field value back to `g`.
1570 pub fn threshold_to_g(raw: u16) -> f32 {
1571 (raw & 0x0FFF) as f32 / 512.0
1572 }
1573
1574 /// Convert a physical hysteresis in `g` to the BMI323 field encoding.
1575 pub fn hysteresis_from_g(g: f32) -> u16 {
1576 (g * 512.0).clamp(0.0, 1023.0) as u16
1577 }
1578
1579 /// Convert a raw hysteresis field value back to `g`.
1580 pub fn hysteresis_to_g(raw: u16) -> f32 {
1581 (raw & 0x03FF) as f32 / 512.0
1582 }
1583
1584 /// Convert a duration in seconds to the BMI323 field encoding.
1585 ///
1586 /// The datasheet specifies a `1/50s` step size.
1587 pub fn duration_from_seconds(seconds: f32) -> u16 {
1588 (seconds * 50.0).clamp(0.0, 8191.0) as u16
1589 }
1590
1591 /// Convert a raw duration field value back to seconds.
1592 pub fn duration_to_seconds(raw: u16) -> f32 {
1593 (raw & 0x1FFF) as f32 / 50.0
1594 }
1595
1596 /// Convert a wait time in seconds to the BMI323 field encoding.
1597 ///
1598 /// The datasheet specifies a `1/50s` step size and a 3-bit field.
1599 pub fn wait_time_from_seconds(seconds: f32) -> u8 {
1600 (seconds * 50.0).clamp(0.0, 7.0) as u8
1601 }
1602
1603 /// Convert a raw wait-time field value back to seconds.
1604 pub fn wait_time_to_seconds(raw: u8) -> f32 {
1605 (raw & 0x07) as f32 / 50.0
1606 }
1607
1608 /// Convert an interrupt hold time in milliseconds to the BMI323 field
1609 /// encoding.
1610 ///
1611 /// The encoded hold time is `0.625ms * 2^n`. This helper returns the
1612 /// smallest `n` that is greater than or equal to the requested duration and
1613 /// saturates at `13`.
1614 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
1615 let mut encoded = 0u8;
1616 let mut hold_ms = 0.625f32;
1617 while hold_ms < millis && encoded < INTERRUPT_HOLD_MAX {
1618 encoded += 1;
1619 hold_ms *= 2.0;
1620 }
1621 encoded
1622 }
1623
1624 /// Convert a raw interrupt-hold field value back to milliseconds.
1625 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
1626 let mut hold_ms = 0.625f32;
1627 let mut encoded = 0u8;
1628 while encoded < raw.min(INTERRUPT_HOLD_MAX) {
1629 hold_ms *= 2.0;
1630 encoded += 1;
1631 }
1632 hold_ms
1633 }
1634}
1635
1636/// Configuration for the BMI323 no-motion feature
1637/// (§6.2.2, Registers (0x08-0x0A) nomo_1/nomo_2/nomo_3; §5.8.3).
1638#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1639#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1640pub struct NoMotionConfig {
1641 /// Axis selection for detection.
1642 pub axes: MotionAxes,
1643 /// Maximum acceleration slope allowed for stationary detection.
1644 ///
1645 /// Unit: `g`
1646 /// Scaling: `raw / 512`
1647 /// Range: `0 ..= 4095`, corresponding to approximately `0.0g ..= 7.998g`
1648 pub threshold: u16,
1649 /// Hysteresis for the acceleration slope comparator.
1650 ///
1651 /// Unit: `g`
1652 /// Scaling: `raw / 512`
1653 /// Range: `0 ..= 1023`, corresponding to approximately `0.0g ..= 1.998g`
1654 pub hysteresis: u16,
1655 /// Minimum duration for which the slope must stay below `threshold`.
1656 ///
1657 /// Unit: seconds
1658 /// Scaling: `raw / 50`
1659 /// Range: `0 ..= 8191`, corresponding to approximately `0.0s ..= 163.82s`
1660 pub duration: u16,
1661 /// Wait time before the event is cleared after the slope remains below
1662 /// `threshold`.
1663 ///
1664 /// Unit: seconds
1665 /// Scaling: `raw / 50`
1666 /// Range: `0 ..= 7`, corresponding to `0.00s ..= 0.14s` in `20ms` steps
1667 pub wait_time: u8,
1668 /// Reference update policy.
1669 pub reference_update: ReferenceUpdate,
1670 /// Event reporting behavior.
1671 pub report_mode: EventReportMode,
1672 /// Interrupt hold-time exponent used by the feature engine.
1673 ///
1674 /// Effective hold time in non-latched mode:
1675 /// `0.625ms * 2^interrupt_hold`
1676 ///
1677 /// Valid raw range is `0 ..= 13`. Larger values are clamped to `13` by the
1678 /// driver before programming the register. This setting is only applicable
1679 /// to non-latched feature-engine interrupts.
1680 pub interrupt_hold: u8,
1681}
1682
1683impl NoMotionConfig {
1684 /// Convert a physical threshold in `g` to the BMI323 field encoding.
1685 pub fn threshold_from_g(g: f32) -> u16 {
1686 (g * 512.0).clamp(0.0, 4095.0) as u16
1687 }
1688
1689 /// Convert a raw threshold field value back to `g`.
1690 pub fn threshold_to_g(raw: u16) -> f32 {
1691 (raw & 0x0FFF) as f32 / 512.0
1692 }
1693
1694 /// Convert a physical hysteresis in `g` to the BMI323 field encoding.
1695 pub fn hysteresis_from_g(g: f32) -> u16 {
1696 (g * 512.0).clamp(0.0, 1023.0) as u16
1697 }
1698
1699 /// Convert a raw hysteresis field value back to `g`.
1700 pub fn hysteresis_to_g(raw: u16) -> f32 {
1701 (raw & 0x03FF) as f32 / 512.0
1702 }
1703
1704 /// Convert a duration in seconds to the BMI323 field encoding.
1705 ///
1706 /// The datasheet specifies a `1/50s` step size.
1707 pub fn duration_from_seconds(seconds: f32) -> u16 {
1708 (seconds * 50.0).clamp(0.0, 8191.0) as u16
1709 }
1710
1711 /// Convert a raw duration field value back to seconds.
1712 pub fn duration_to_seconds(raw: u16) -> f32 {
1713 (raw & 0x1FFF) as f32 / 50.0
1714 }
1715
1716 /// Convert a wait time in seconds to the BMI323 field encoding.
1717 ///
1718 /// The datasheet specifies a `1/50s` step size and a 3-bit field.
1719 pub fn wait_time_from_seconds(seconds: f32) -> u8 {
1720 (seconds * 50.0).clamp(0.0, 7.0) as u8
1721 }
1722
1723 /// Convert a raw wait-time field value back to seconds.
1724 pub fn wait_time_to_seconds(raw: u8) -> f32 {
1725 (raw & 0x07) as f32 / 50.0
1726 }
1727
1728 /// Convert an interrupt hold time in milliseconds to the BMI323 field
1729 /// encoding.
1730 ///
1731 /// The encoded hold time is `0.625ms * 2^n`. This helper returns the
1732 /// smallest `n` that is greater than or equal to the requested duration and
1733 /// saturates at `13`.
1734 pub fn interrupt_hold_from_millis(millis: f32) -> u8 {
1735 AnyMotionConfig::interrupt_hold_from_millis(millis)
1736 }
1737
1738 /// Convert a raw interrupt-hold field value back to milliseconds.
1739 pub fn interrupt_hold_to_millis(raw: u8) -> f32 {
1740 AnyMotionConfig::interrupt_hold_to_millis(raw)
1741 }
1742}