Skip to main content

bmi323_driver/
blocking_driver.rs

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