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