Skip to main content

bmi323_driver/
driver_impl.rs

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