Skip to main content

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}