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