Skip to main content

bmi323_driver/
async_driver.rs

1use embedded_hal_async::delay::DelayNs as AsyncDelayNs;
2use embedded_hal_async::digital::Wait;
3
4use crate::registers::{
5    ACC_CONF, ACC_DATA_X, ALT_ACC_CONF, ALT_CONF, ALT_GYR_CONF, ALT_STATUS, BMI323_CHIP_ID,
6    CHIP_ID, CMD, ERR_REG, EXT_ALT_CONFIG_CHG, EXT_ANYMO_1, EXT_ANYMO_2, EXT_ANYMO_3, EXT_FLAT_1,
7    EXT_FLAT_2, EXT_GEN_SET_1, EXT_NOMO_1, EXT_NOMO_2, EXT_NOMO_3, EXT_ORIENT_1, EXT_ORIENT_2,
8    EXT_SC_1, EXT_SIGMO_1, EXT_SIGMO_2, EXT_SIGMO_3, EXT_ST_RESULT, EXT_ST_SELECT, EXT_TAP_1,
9    EXT_TAP_2, EXT_TAP_3, EXT_TILT_1, EXT_TILT_2, FEATURE_CTRL, FEATURE_DATA_ADDR, FEATURE_DATA_TX,
10    FEATURE_IO_STATUS, FEATURE_IO0, FEATURE_IO1, FEATURE_IO2, FEATURE_IO3, FIFO_CONF, FIFO_CTRL,
11    FIFO_DATA, FIFO_FILL_LEVEL, FIFO_WATERMARK, GYR_CONF, GYR_DATA_X, INT_CONF, IO_INT_CTRL,
12    SELF_TEST, SENSOR_TIME_0, SOFT_RESET, STATUS, TEMP_DATA, TransportKind, interrupt_map_location,
13    words_to_axis,
14};
15use crate::{
16    AccelConfig, ActiveLevel, AltAccelConfig, AltConfigControl, AltGyroConfig, AltStatus,
17    AnyMotionConfig, AsyncAccess, AxisData, Bmi323Async, DeviceState, Error, ErrorWord,
18    EventReportMode, FifoConfig, FlatConfig, GyroConfig, ImuData, InterruptChannel,
19    InterruptPinConfig, InterruptRoute, InterruptSource, InterruptStatus, NoMotionConfig,
20    OrientationConfig, OutputDataRate, OutputMode, ReferenceUpdate, SelfTestDetail, SelfTestResult,
21    SelfTestSelection, SignificantMotionConfig, StatusWord, StepCounterConfig, TapConfig,
22    TiltConfig,
23};
24
25impl<T> Bmi323Async<T>
26where
27    Self: AsyncAccess,
28{
29    /// Reset the sensor and verify communication, chip ID, and error state.
30    ///
31    /// This method performs a soft reset on every call so startup begins from a
32    /// known device state. After `init`, configure the accelerometer and
33    /// gyroscope explicitly with
34    /// [`set_accel_config`](Self::set_accel_config) and
35    /// [`set_gyro_config`](Self::set_gyro_config) before depending on sample
36    /// reads in application code.
37    pub async fn init<D: AsyncDelayNs>(
38        &mut self,
39        delay: &mut D,
40    ) -> Result<DeviceState, Error<<Self as AsyncAccess>::BusError>> {
41        // Reset on every init so the driver starts from a known device state
42        // even if the MCU rebooted while the sensor stayed powered.
43        self.soft_reset(delay).await?;
44
45        if matches!(self.kind, TransportKind::Spi) {
46            self.read_word(CHIP_ID).await.map_err(Error::Bus)?;
47            delay.delay_us(250).await;
48        }
49
50        let chip_id = self.read_word(CHIP_ID).await.map_err(Error::Bus)? as u8;
51        if chip_id != BMI323_CHIP_ID {
52            return Err(Error::InvalidChipId(chip_id));
53        }
54
55        let error = self.error_word().await?;
56        if error.fatal() {
57            return Err(Error::FatalError);
58        }
59
60        Ok(DeviceState {
61            chip_id,
62            status: self.status_word().await?,
63            error,
64        })
65    }
66
67    /// Issue the BMI323 soft-reset command and wait for restart completion.
68    pub async fn soft_reset<D: AsyncDelayNs>(
69        &mut self,
70        delay: &mut D,
71    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
72        self.write_word(CMD, SOFT_RESET).await.map_err(Error::Bus)?;
73        delay.delay_ms(2).await;
74        Ok(())
75    }
76
77    /// Run the BMI323 built-in self-test and return the detailed result.
78    ///
79    /// For gyroscope self-test, the BMI323 requires the accelerometer to be in
80    /// high-performance mode with an output data rate between `12.5 Hz` and
81    /// `200 Hz`. This method enforces that prerequisite temporarily when needed
82    /// and restores the previous accel/gyro and alternate-configuration
83    /// registers afterwards.
84    ///
85    /// The returned [`SelfTestResult::error_status`] comes from
86    /// `FEATURE_IO1.error_status`. On the BMI323, `0x5` is the normal
87    /// "no error" value after the feature engine is active.
88    pub async fn run_self_test<D: AsyncDelayNs>(
89        &mut self,
90        delay: &mut D,
91        selection: SelfTestSelection,
92    ) -> Result<SelfTestResult, Error<<Self as AsyncAccess>::BusError>> {
93        let saved_acc_conf = self.read_word(ACC_CONF).await.map_err(Error::Bus)?;
94        let saved_gyr_conf = self.read_word(GYR_CONF).await.map_err(Error::Bus)?;
95        let saved_alt_acc_conf = self.read_word(ALT_ACC_CONF).await.map_err(Error::Bus)?;
96        let saved_alt_gyr_conf = self.read_word(ALT_GYR_CONF).await.map_err(Error::Bus)?;
97        let saved_st_select = self.read_feature_word(EXT_ST_SELECT).await?;
98        let saved_accel_range = self.accel_range;
99        let saved_gyro_range = self.gyro_range;
100
101        let result = self.run_self_test_inner(delay, selection).await;
102        let restore = self
103            .restore_self_test_configuration(
104                saved_acc_conf,
105                saved_gyr_conf,
106                saved_alt_acc_conf,
107                saved_alt_gyr_conf,
108                saved_st_select,
109            )
110            .await;
111
112        self.accel_range = saved_accel_range;
113        self.gyro_range = saved_gyro_range;
114
115        match (result, restore) {
116            (Err(err), _) => Err(err),
117            (Ok(_), Err(err)) => Err(err),
118            (Ok(report), Ok(())) => Ok(report),
119        }
120    }
121
122    /// Read and decode the `STATUS` register.
123    pub async fn status_word(
124        &mut self,
125    ) -> Result<StatusWord, Error<<Self as AsyncAccess>::BusError>> {
126        self.read_word(STATUS)
127            .await
128            .map(StatusWord)
129            .map_err(Error::Bus)
130    }
131
132    /// Read and decode the `ERR_REG` register.
133    pub async fn error_word(
134        &mut self,
135    ) -> Result<ErrorWord, Error<<Self as AsyncAccess>::BusError>> {
136        self.read_word(ERR_REG)
137            .await
138            .map(ErrorWord)
139            .map_err(Error::Bus)
140    }
141
142    /// Configure the accelerometer and remember the selected range locally.
143    ///
144    /// Call this after [`init`](Self::init) before relying on accelerometer or
145    /// combined IMU sample reads.
146    pub async fn set_accel_config(
147        &mut self,
148        config: AccelConfig,
149    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
150        self.write_word(ACC_CONF, config.to_word())
151            .await
152            .map_err(Error::Bus)?;
153        self.accel_range = config.range;
154        Ok(())
155    }
156
157    /// Configure the gyroscope and remember the selected range locally.
158    ///
159    /// Call this after [`init`](Self::init) before relying on gyroscope or
160    /// combined IMU sample reads.
161    pub async fn set_gyro_config(
162        &mut self,
163        config: GyroConfig,
164    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
165        self.write_word(GYR_CONF, config.to_word())
166            .await
167            .map_err(Error::Bus)?;
168        self.gyro_range = config.range;
169        Ok(())
170    }
171
172    /// Return the last accelerometer range configured through this driver.
173    ///
174    /// Before any explicit accelerometer configuration, this returns the
175    /// driver's local startup value of `AccelRange::G8`.
176    pub fn accel_range(&self) -> crate::AccelRange {
177        self.accel_range
178    }
179
180    /// Return the last gyroscope range configured through this driver.
181    ///
182    /// Before any explicit gyroscope configuration, this returns the driver's
183    /// local startup value of `GyroRange::Dps2000`.
184    pub fn gyro_range(&self) -> crate::GyroRange {
185        self.gyro_range
186    }
187
188    /// Read a single accelerometer sample.
189    ///
190    /// For predictable results, configure the accelerometer first with
191    /// [`set_accel_config`](Self::set_accel_config).
192    pub async fn read_accel(&mut self) -> Result<AxisData, Error<<Self as AsyncAccess>::BusError>> {
193        let mut words = [0u16; 3];
194        self.read_words(ACC_DATA_X, &mut words)
195            .await
196            .map_err(Error::Bus)?;
197        Ok(words_to_axis(words))
198    }
199
200    /// Read a single gyroscope sample.
201    ///
202    /// For predictable results, configure the gyroscope first with
203    /// [`set_gyro_config`](Self::set_gyro_config).
204    pub async fn read_gyro(&mut self) -> Result<AxisData, Error<<Self as AsyncAccess>::BusError>> {
205        let mut words = [0u16; 3];
206        self.read_words(GYR_DATA_X, &mut words)
207            .await
208            .map_err(Error::Bus)?;
209        Ok(words_to_axis(words))
210    }
211
212    /// Read accelerometer and gyroscope data in one burst transaction.
213    ///
214    /// For predictable results, configure both sensors first with
215    /// [`set_accel_config`](Self::set_accel_config) and
216    /// [`set_gyro_config`](Self::set_gyro_config).
217    pub async fn read_imu_data(
218        &mut self,
219    ) -> Result<ImuData, Error<<Self as AsyncAccess>::BusError>> {
220        let mut words = [0u16; 6];
221        self.read_words(ACC_DATA_X, &mut words)
222            .await
223            .map_err(Error::Bus)?;
224        Ok(ImuData {
225            accel: words_to_axis([words[0], words[1], words[2]]),
226            gyro: words_to_axis([words[3], words[4], words[5]]),
227        })
228    }
229
230    /// Read the temperature sensor and convert it to degrees Celsius.
231    pub async fn read_temperature_celsius(
232        &mut self,
233    ) -> Result<f32, Error<<Self as AsyncAccess>::BusError>> {
234        let raw = self.read_word(TEMP_DATA).await.map_err(Error::Bus)? as i16;
235        Ok(raw as f32 / 512.0 + 23.0)
236    }
237
238    /// Read the 24-bit sensor time counter.
239    pub async fn read_sensor_time(
240        &mut self,
241    ) -> Result<u32, Error<<Self as AsyncAccess>::BusError>> {
242        let mut words = [0u16; 2];
243        self.read_words(SENSOR_TIME_0, &mut words)
244            .await
245            .map_err(Error::Bus)?;
246        Ok(words[0] as u32 | ((words[1] as u32) << 16))
247    }
248
249    /// Configure the electrical behavior of `INT1` or `INT2`.
250    ///
251    /// Passing [`InterruptChannel::Ibi`] is a no-op: I3C in-band interrupts
252    /// have no pin electrical configuration in the BMI323.
253    pub async fn configure_interrupt_pin(
254        &mut self,
255        channel: InterruptChannel,
256        config: InterruptPinConfig,
257    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
258        let mut word = self.read_word(IO_INT_CTRL).await.map_err(Error::Bus)?;
259        let shift = match channel {
260            InterruptChannel::Int1 => 0,
261            InterruptChannel::Int2 => 8,
262            InterruptChannel::Ibi => return Ok(()),
263        };
264        word &= !(0b111 << shift);
265        word |= ((matches!(config.active_level, ActiveLevel::High) as u16) << shift)
266            | ((matches!(config.output_mode, OutputMode::OpenDrain) as u16) << (shift + 1))
267            | ((config.enabled as u16) << (shift + 2));
268        self.write_word(IO_INT_CTRL, word).await.map_err(Error::Bus)
269    }
270
271    /// Enable or disable latched interrupt behavior inside the sensor.
272    pub async fn set_interrupt_latching(
273        &mut self,
274        latched: bool,
275    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
276        self.write_word(INT_CONF, latched as u16)
277            .await
278            .map_err(Error::Bus)
279    }
280
281    /// Route an interrupt source to `INT1`, `INT2`, or I3C IBI.
282    pub async fn map_interrupt(
283        &mut self,
284        source: InterruptSource,
285        route: InterruptRoute,
286    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
287        let (register, shift) = interrupt_map_location(source);
288        let mut word = self.read_word(register).await.map_err(Error::Bus)?;
289        word &= !(0b11 << shift);
290        word |= u16::from(route) << shift;
291        self.write_word(register, word).await.map_err(Error::Bus)
292    }
293
294    /// Read the interrupt status register for the selected output channel.
295    pub async fn read_interrupt_status(
296        &mut self,
297        channel: InterruptChannel,
298    ) -> Result<InterruptStatus, Error<<Self as AsyncAccess>::BusError>> {
299        let reg = match channel {
300            InterruptChannel::Int1 => 0x0D,
301            InterruptChannel::Int2 => 0x0E,
302            InterruptChannel::Ibi => 0x0F,
303        };
304        self.read_word(reg)
305            .await
306            .map(InterruptStatus)
307            .map_err(Error::Bus)
308    }
309
310    /// Wait for an external GPIO interrupt line to assert, then read status.
311    ///
312    /// Any error from `pin.wait_for_high()` is silently discarded. The
313    /// interrupt status register is read regardless, so if the pin wait fails
314    /// the status read still proceeds. If you need to handle GPIO errors, call
315    /// `pin.wait_for_high()` yourself and then call
316    /// [`read_interrupt_status`](Self::read_interrupt_status) directly.
317    pub async fn wait_for_interrupt<P: Wait>(
318        &mut self,
319        pin: &mut P,
320        channel: InterruptChannel,
321    ) -> Result<InterruptStatus, Error<<Self as AsyncAccess>::BusError>> {
322        pin.wait_for_high().await.ok();
323        self.read_interrupt_status(channel).await
324    }
325
326    /// Configure FIFO contents and watermark level.
327    pub async fn set_fifo_config(
328        &mut self,
329        config: FifoConfig,
330        watermark_words: u16,
331    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
332        self.write_word(FIFO_WATERMARK, watermark_words & 0x03FF)
333            .await
334            .map_err(Error::Bus)?;
335        self.write_word(FIFO_CONF, config.to_word())
336            .await
337            .map_err(Error::Bus)
338    }
339
340    /// Read the current FIFO fill level in 16-bit words.
341    pub async fn fifo_fill_level(&mut self) -> Result<u16, Error<<Self as AsyncAccess>::BusError>> {
342        Ok(self.read_word(FIFO_FILL_LEVEL).await.map_err(Error::Bus)? & 0x07FF)
343    }
344
345    /// Flush all currently buffered FIFO contents.
346    pub async fn flush_fifo(&mut self) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
347        self.write_word(FIFO_CTRL, 0x0001).await.map_err(Error::Bus)
348    }
349
350    /// Read raw FIFO words into the provided output slice.
351    ///
352    /// `words.len()` must not exceed 64 due to the fixed internal transfer
353    /// buffer. Use [`fifo_fill_level`](Self::fifo_fill_level) first and split
354    /// larger reads into chunks of at most 64 words.
355    ///
356    /// # Panics
357    ///
358    /// Panics if `words.len() > 64`.
359    pub async fn read_fifo_words(
360        &mut self,
361        words: &mut [u16],
362    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
363        assert!(
364            words.len() <= 64,
365            "read_fifo_words: words.len() must not exceed 64"
366        );
367        self.read_words(FIFO_DATA, words).await.map_err(Error::Bus)
368    }
369
370    /// Enable the BMI323 feature engine required by advanced motion features.
371    ///
372    /// This method temporarily disables the accelerometer and gyroscope by
373    /// writing to `ACC_CONF` and `GYR_CONF` as part of the enable sequence.
374    /// After calling this method, re-configure the sensors with
375    /// [`set_accel_config`](Self::set_accel_config) and
376    /// [`set_gyro_config`](Self::set_gyro_config) before reading samples.
377    pub async fn enable_feature_engine(
378        &mut self,
379    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
380        self.write_word(ACC_CONF, 0).await.map_err(Error::Bus)?;
381        self.write_word(GYR_CONF, 0).await.map_err(Error::Bus)?;
382        self.write_word(FEATURE_IO2, 0x012C)
383            .await
384            .map_err(Error::Bus)?;
385        self.write_word(FEATURE_IO_STATUS, 0x0001)
386            .await
387            .map_err(Error::Bus)?;
388        self.write_word(FEATURE_CTRL, 0x0001)
389            .await
390            .map_err(Error::Bus)?;
391
392        for _ in 0..32 {
393            let io1 = self.read_word(FEATURE_IO1).await.map_err(Error::Bus)?;
394            let status = (io1 & 0x000F) as u8;
395            if status == 0x1 || status == 0x5 {
396                return Ok(());
397            }
398        }
399        let io1 = self.read_word(FEATURE_IO1).await.map_err(Error::Bus)?;
400        Err(Error::FeatureEngineNotReady((io1 & 0x000F) as u8))
401    }
402
403    /// Configure the BMI323 any-motion feature-engine block.
404    pub async fn configure_any_motion(
405        &mut self,
406        config: AnyMotionConfig,
407    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
408        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
409            .await?;
410        self.write_feature_word(
411            EXT_ANYMO_1,
412            (config.threshold & 0x0FFF)
413                | (match config.reference_update {
414                    ReferenceUpdate::OnDetection => 0,
415                    ReferenceUpdate::EverySample => 1,
416                } << 12),
417        )
418        .await?;
419        self.write_feature_word(EXT_ANYMO_2, config.hysteresis & 0x03FF)
420            .await?;
421        self.write_feature_word(
422            EXT_ANYMO_3,
423            (config.duration & 0x1FFF) | (((config.wait_time as u16) & 0x07) << 13),
424        )
425        .await?;
426        self.modify_word(FEATURE_IO0, |mut word| {
427            word &= !(0b111 << 3);
428            word |= (config.axes.x as u16) << 3;
429            word |= (config.axes.y as u16) << 4;
430            word |= (config.axes.z as u16) << 5;
431            word
432        })
433        .await
434    }
435
436    /// Configure the BMI323 no-motion feature-engine block.
437    pub async fn configure_no_motion(
438        &mut self,
439        config: NoMotionConfig,
440    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
441        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
442            .await?;
443        self.write_feature_word(
444            EXT_NOMO_1,
445            (config.threshold & 0x0FFF)
446                | (match config.reference_update {
447                    ReferenceUpdate::OnDetection => 0,
448                    ReferenceUpdate::EverySample => 1,
449                } << 12),
450        )
451        .await?;
452        self.write_feature_word(EXT_NOMO_2, config.hysteresis & 0x03FF)
453            .await?;
454        self.write_feature_word(
455            EXT_NOMO_3,
456            (config.duration & 0x1FFF) | (((config.wait_time as u16) & 0x07) << 13),
457        )
458        .await?;
459        self.modify_word(FEATURE_IO0, |mut word| {
460            word &= !0b111;
461            word |= config.axes.x as u16;
462            word |= (config.axes.y as u16) << 1;
463            word |= (config.axes.z as u16) << 2;
464            word
465        })
466        .await
467    }
468
469    /// Configure the BMI323 flat-detection feature-engine block and enable the
470    /// flat detector.
471    pub async fn configure_flat(
472        &mut self,
473        config: FlatConfig,
474    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
475        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
476            .await?;
477        self.write_feature_word(
478            EXT_FLAT_1,
479            u16::from(config.theta & 0x3F)
480                | ((config.blocking as u16) << 6)
481                | (u16::from(config.hold_time) << 8),
482        )
483        .await?;
484        self.write_feature_word(
485            EXT_FLAT_2,
486            u16::from(config.slope_threshold) | (u16::from(config.hysteresis) << 8),
487        )
488        .await?;
489        self.set_flat_enabled(true).await
490    }
491
492    /// Enable or disable the flat-detection feature.
493    pub async fn set_flat_enabled(
494        &mut self,
495        enabled: bool,
496    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
497        self.modify_word(FEATURE_IO0, |mut word| {
498            if enabled {
499                word |= 1 << 6;
500            } else {
501                word &= !(1 << 6);
502            }
503            word
504        })
505        .await
506    }
507
508    /// Configure the BMI323 orientation-detection feature-engine block and
509    /// enable the orientation detector.
510    pub async fn configure_orientation(
511        &mut self,
512        config: OrientationConfig,
513    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
514        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
515            .await?;
516        self.write_feature_word(
517            EXT_ORIENT_1,
518            (config.upside_down_enabled as u16)
519                | ((config.mode as u16) << 1)
520                | ((config.blocking as u16) << 3)
521                | ((u16::from(config.theta & 0x3F)) << 5)
522                | ((u16::from(config.hold_time & 0x1F)) << 11),
523        )
524        .await?;
525        self.write_feature_word(
526            EXT_ORIENT_2,
527            u16::from(config.slope_threshold) | (u16::from(config.hysteresis) << 8),
528        )
529        .await?;
530        self.set_orientation_enabled(true).await
531    }
532
533    /// Enable or disable the orientation-detection feature.
534    pub async fn set_orientation_enabled(
535        &mut self,
536        enabled: bool,
537    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
538        self.modify_word(FEATURE_IO0, |mut word| {
539            if enabled {
540                word |= 1 << 7;
541            } else {
542                word &= !(1 << 7);
543            }
544            word
545        })
546        .await
547    }
548
549    /// Configure the BMI323 tap-detection feature-engine block.
550    ///
551    /// The interrupt status register exposes a single tap bit, even when
552    /// single-, double-, and triple-tap detection are enabled together.
553    pub async fn configure_tap(
554        &mut self,
555        config: TapConfig,
556    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
557        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
558            .await?;
559        self.write_feature_word(
560            EXT_TAP_1,
561            (config.axis as u16)
562                | ((config.reporting_mode as u16) << 2)
563                | ((u16::from(config.max_peaks_for_tap & 0x07)) << 3)
564                | ((config.mode as u16) << 6),
565        )
566        .await?;
567        self.write_feature_word(
568            EXT_TAP_2,
569            (config.tap_peak_threshold & 0x03FF)
570                | ((u16::from(config.max_gesture_duration & 0x3F)) << 10),
571        )
572        .await?;
573        self.write_feature_word(
574            EXT_TAP_3,
575            u16::from(config.max_duration_between_peaks & 0x0F)
576                | (u16::from(config.tap_shock_settling_duration & 0x0F) << 4)
577                | (u16::from(config.min_quiet_duration_between_taps & 0x0F) << 8)
578                | (u16::from(config.quiet_time_after_gesture & 0x0F) << 12),
579        )
580        .await?;
581        self.modify_word(FEATURE_IO0, |mut word| {
582            word &= !((1 << 12) | (1 << 13) | (1 << 14));
583            word |= (config.single_tap_enabled as u16) << 12;
584            word |= (config.double_tap_enabled as u16) << 13;
585            word |= (config.triple_tap_enabled as u16) << 14;
586            word
587        })
588        .await
589    }
590
591    /// Configure the BMI323 significant-motion feature-engine block and enable
592    /// the significant-motion detector.
593    pub async fn configure_significant_motion(
594        &mut self,
595        config: SignificantMotionConfig,
596    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
597        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
598            .await?;
599        self.write_feature_word(EXT_SIGMO_1, config.block_size)
600            .await?;
601        self.write_feature_word(
602            EXT_SIGMO_2,
603            (config.peak_to_peak_min & 0x03FF)
604                | ((u16::from(config.mean_crossing_rate_min & 0x3F)) << 10),
605        )
606        .await?;
607        self.write_feature_word(
608            EXT_SIGMO_3,
609            (config.peak_to_peak_max & 0x03FF)
610                | ((u16::from(config.mean_crossing_rate_max & 0x3F)) << 10),
611        )
612        .await?;
613        self.set_significant_motion_enabled(true).await
614    }
615
616    /// Enable or disable the significant-motion feature.
617    pub async fn set_significant_motion_enabled(
618        &mut self,
619        enabled: bool,
620    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
621        self.modify_word(FEATURE_IO0, |mut word| {
622            if enabled {
623                word |= 1 << 10;
624            } else {
625                word &= !(1 << 10);
626            }
627            word
628        })
629        .await
630    }
631
632    /// Configure the BMI323 tilt-detection feature-engine block and enable the
633    /// tilt detector.
634    pub async fn configure_tilt(
635        &mut self,
636        config: TiltConfig,
637    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
638        self.apply_common_feature_settings(config.report_mode, config.interrupt_hold)
639            .await?;
640        self.write_feature_word(
641            EXT_TILT_1,
642            u16::from(config.segment_size) | (u16::from(config.min_tilt_angle) << 8),
643        )
644        .await?;
645        self.write_feature_word(EXT_TILT_2, config.beta_acc_mean)
646            .await?;
647        self.set_tilt_enabled(true).await
648    }
649
650    /// Enable or disable the tilt-detection feature.
651    pub async fn set_tilt_enabled(
652        &mut self,
653        enabled: bool,
654    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
655        self.modify_word(FEATURE_IO0, |mut word| {
656            if enabled {
657                word |= 1 << 11;
658            } else {
659                word &= !(1 << 11);
660            }
661            word
662        })
663        .await
664    }
665
666    /// Enable or disable the feature-engine step detector.
667    ///
668    /// When enabled, the BMI323 can assert the
669    /// [`InterruptSource::StepDetector`] interrupt source after the feature
670    /// engine has been enabled with [`enable_feature_engine`](Self::enable_feature_engine).
671    pub async fn set_step_detector_enabled(
672        &mut self,
673        enabled: bool,
674    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
675        self.modify_word(FEATURE_IO0, |mut word| {
676            if enabled {
677                word |= 1 << 8;
678            } else {
679                word &= !(1 << 8);
680            }
681            word
682        })
683        .await
684    }
685
686    /// Enable or disable the feature-engine step counter.
687    ///
688    /// This controls the BMI323 step-count accumulation path and the
689    /// [`InterruptSource::StepCounter`] interrupt source. The feature engine
690    /// must already be enabled with [`enable_feature_engine`](Self::enable_feature_engine).
691    pub async fn set_step_counter_enabled(
692        &mut self,
693        enabled: bool,
694    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
695        self.modify_word(FEATURE_IO0, |mut word| {
696            if enabled {
697                word |= 1 << 9;
698            } else {
699                word &= !(1 << 9);
700            }
701            word
702        })
703        .await
704    }
705
706    /// Configure the BMI323 step-counter watermark and optional reset request.
707    ///
708    /// A watermark of `0` disables the step-counter interrupt source while
709    /// still allowing the accumulated step count to be read.
710    pub async fn configure_step_counter(
711        &mut self,
712        config: StepCounterConfig,
713    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
714        self.write_feature_word(EXT_SC_1, config.to_word()).await
715    }
716
717    /// Request a reset of the accumulated step count.
718    pub async fn reset_step_counter(
719        &mut self,
720    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
721        self.configure_step_counter(StepCounterConfig {
722            reset_counter: true,
723            ..StepCounterConfig::disabled()
724        })
725        .await
726    }
727
728    /// Read the 32-bit accumulated step count from the feature engine.
729    pub async fn read_step_count(&mut self) -> Result<u32, Error<<Self as AsyncAccess>::BusError>> {
730        let low = self.read_word(FEATURE_IO2).await.map_err(Error::Bus)? as u32;
731        let high = self.read_word(FEATURE_IO3).await.map_err(Error::Bus)? as u32;
732        Ok(low | (high << 16))
733    }
734
735    /// Program the alternate accelerometer configuration in `ALT_ACC_CONF`.
736    pub async fn set_alt_accel_config(
737        &mut self,
738        config: AltAccelConfig,
739    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
740        self.write_word(ALT_ACC_CONF, config.to_word())
741            .await
742            .map_err(Error::Bus)
743    }
744
745    /// Program the alternate gyroscope configuration in `ALT_GYR_CONF`.
746    pub async fn set_alt_gyro_config(
747        &mut self,
748        config: AltGyroConfig,
749    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
750        self.write_word(ALT_GYR_CONF, config.to_word())
751            .await
752            .map_err(Error::Bus)
753    }
754
755    /// Configure automatic switching between user and alternate sensor
756    /// configurations.
757    pub async fn configure_alt_config_control(
758        &mut self,
759        config: AltConfigControl,
760    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
761        self.write_word(
762            ALT_CONF,
763            (config.accel_enabled as u16)
764                | ((config.gyro_enabled as u16) << 4)
765                | ((config.reset_on_user_config_write as u16) << 8),
766        )
767        .await
768        .map_err(Error::Bus)?;
769        self.write_feature_word(
770            EXT_ALT_CONFIG_CHG,
771            (config.switch_to_alternate as u16) | ((config.switch_to_user as u16) << 4),
772        )
773        .await
774    }
775
776    /// Read which configuration set is currently active for accel and gyro.
777    pub async fn alt_status(
778        &mut self,
779    ) -> Result<AltStatus, Error<<Self as AsyncAccess>::BusError>> {
780        self.read_word(ALT_STATUS)
781            .await
782            .map(AltStatus)
783            .map_err(Error::Bus)
784    }
785
786    async fn modify_word<F>(
787        &mut self,
788        reg: u8,
789        f: F,
790    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>>
791    where
792        F: FnOnce(u16) -> u16,
793    {
794        let value = self.read_word(reg).await.map_err(Error::Bus)?;
795        self.write_word(reg, f(value)).await.map_err(Error::Bus)?;
796        if reg == FEATURE_IO0 {
797            self.write_word(FEATURE_IO_STATUS, 1)
798                .await
799                .map_err(Error::Bus)?;
800        }
801        Ok(())
802    }
803
804    async fn run_self_test_inner<D: AsyncDelayNs>(
805        &mut self,
806        delay: &mut D,
807        selection: SelfTestSelection,
808    ) -> Result<SelfTestResult, Error<<Self as AsyncAccess>::BusError>> {
809        self.enable_feature_engine().await?;
810
811        if selection.tests_gyroscope() {
812            self.write_word(
813                ACC_CONF,
814                AccelConfig {
815                    mode: crate::AccelMode::HighPerformance,
816                    odr: OutputDataRate::Hz200,
817                    ..Default::default()
818                }
819                .to_word(),
820            )
821            .await
822            .map_err(Error::Bus)?;
823        }
824
825        self.write_word(ALT_ACC_CONF, 0).await.map_err(Error::Bus)?;
826        self.write_word(ALT_GYR_CONF, 0).await.map_err(Error::Bus)?;
827        self.write_feature_word(EXT_ST_SELECT, selection.to_word())
828            .await?;
829        self.write_word(CMD, SELF_TEST).await.map_err(Error::Bus)?;
830
831        for _ in 0..50 {
832            delay.delay_ms(10).await;
833            let feature_io1 = self.read_word(FEATURE_IO1).await.map_err(Error::Bus)?;
834            if feature_io1 & (1 << 4) != 0 {
835                let detail = SelfTestDetail(self.read_feature_word(EXT_ST_RESULT).await?);
836                return Ok(SelfTestResult {
837                    selection,
838                    passed: feature_io1 & (1 << 6) != 0,
839                    sample_rate_error: feature_io1 & (1 << 7) != 0,
840                    error_status: (feature_io1 & 0x000F) as u8,
841                    detail,
842                });
843            }
844        }
845
846        Err(Error::SelfTestTimeout)
847    }
848
849    async fn restore_self_test_configuration(
850        &mut self,
851        acc_conf: u16,
852        gyr_conf: u16,
853        alt_acc_conf: u16,
854        alt_gyr_conf: u16,
855        st_select: u16,
856    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
857        self.write_word(ACC_CONF, acc_conf)
858            .await
859            .map_err(Error::Bus)?;
860        self.write_word(GYR_CONF, gyr_conf)
861            .await
862            .map_err(Error::Bus)?;
863        self.write_word(ALT_ACC_CONF, alt_acc_conf)
864            .await
865            .map_err(Error::Bus)?;
866        self.write_word(ALT_GYR_CONF, alt_gyr_conf)
867            .await
868            .map_err(Error::Bus)?;
869        self.write_feature_word(EXT_ST_SELECT, st_select).await
870    }
871
872    async fn write_feature_word(
873        &mut self,
874        ext_addr: u16,
875        value: u16,
876    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
877        self.write_word(FEATURE_DATA_ADDR, ext_addr)
878            .await
879            .map_err(Error::Bus)?;
880        self.write_word(FEATURE_DATA_TX, value)
881            .await
882            .map_err(Error::Bus)
883    }
884
885    async fn read_feature_word(
886        &mut self,
887        ext_addr: u16,
888    ) -> Result<u16, Error<<Self as AsyncAccess>::BusError>> {
889        self.write_word(FEATURE_DATA_ADDR, ext_addr)
890            .await
891            .map_err(Error::Bus)?;
892        self.read_word(FEATURE_DATA_TX).await.map_err(Error::Bus)
893    }
894
895    async fn modify_feature_word<F>(
896        &mut self,
897        ext_addr: u16,
898        f: F,
899    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>>
900    where
901        F: FnOnce(u16) -> u16,
902    {
903        let word = self.read_feature_word(ext_addr).await?;
904        self.write_feature_word(ext_addr, f(word)).await
905    }
906
907    /// Write `report_mode` and `interrupt_hold` into `EXT_GEN_SET_1`.
908    ///
909    /// `EXT_GEN_SET_1` is a single shared register used by all
910    /// feature-engine blocks. Every call to a `configure_*` method overwrites
911    /// the same two fields, so the last call's values take effect across all
912    /// features. If your application uses multiple feature-engine blocks,
913    /// configure them all with consistent `report_mode` and `interrupt_hold`
914    /// values, or be aware that later calls overwrite earlier ones.
915    async fn apply_common_feature_settings(
916        &mut self,
917        report_mode: EventReportMode,
918        interrupt_hold: u8,
919    ) -> Result<(), Error<<Self as AsyncAccess>::BusError>> {
920        self.modify_feature_word(EXT_GEN_SET_1, |word| {
921            let mut updated = word & !0x003F;
922            updated |= match report_mode {
923                EventReportMode::AllEvents => 0,
924                EventReportMode::FirstEventOnly => 1,
925            };
926            updated |= ((interrupt_hold.min(13) as u16) & 0x0F) << 1;
927            updated
928        })
929        .await
930    }
931}