Skip to main content

oxstack_cardputer/
imu.rs

1//! BMI270 IMU on the internal I²C bus (0x69).
2//!
3//! The chip's INT1/INT2 pins are not connected on the Cardputer ADV, so it
4//! can only be polled, and it cannot wake the ESP32 from sleep.
5//!
6//! # Axes
7//!
8//! [`Imu::read`] returns readings in the **board frame**. Hold the Cardputer
9//! in landscape, screen facing you, keyboard below:
10//!
11//! * **+X** points right,
12//! * **+Y** points up, toward the top edge (away from the keyboard),
13//! * **+Z** points out of the screen, toward you.
14//!
15//! Lying flat on a table, screen up, acceleration reads about `[0, 0, +1]` g.
16//! Standing on its keyboard edge, screen vertical, it reads `[0, +1, 0]`.
17//! The gyro follows the right-hand rule about the same axes.
18//!
19//! The chip is mounted rotated relative to the board; [`BOARD_AXES`] is the
20//! mapping applied. **Only its Z axis is confirmed on hardware so far.** The
21//! `imu_axes` example in the repository measures all three in under a
22//! minute and prints the constant to use; until then, [`Imu::set_axes`]
23//! overrides the default and [`Imu::read_chip`] gives untranslated readings.
24
25use core::marker::PhantomData;
26
27use embassy_time::Delay;
28pub use oxstack_drivers::axes::{Axis, AxisMap};
29use oxstack_drivers::bmi270::{self, ADDRESS_SDO_HIGH, Bmi270};
30pub use oxstack_drivers::bmi270::{AccelRange, Config, DataRate, GyroRange, RawSample, Sample};
31
32use crate::i2c::{SysI2cBus, SysI2cDevice, device};
33
34/// The raw BMI270 driver on the shared bus.
35pub type Chip = Bmi270<SysI2cDevice>;
36
37/// IMU errors.
38pub type Error =
39    bmi270::Error<embassy_embedded_hal::shared_bus::I2cDeviceError<esp_hal::i2c::master::Error>>;
40
41/// How the BMI270's axes map onto the board frame (see the module docs).
42///
43/// Z is verified: the chip's +Z points out of the screen. X and Y are the
44/// chip's own until measured; run the `imu_axes` example to check them.
45pub const BOARD_AXES: AxisMap = AxisMap {
46    x: Axis::PosX,
47    y: Axis::PosY,
48    z: Axis::PosZ,
49};
50
51/// Ownership token for the IMU. It has no pins of its own, but borrowing it
52/// keeps two drivers from initialising the chip at once.
53pub struct ImuResources {
54    pub(crate) _private: (),
55}
56
57impl ImuResources {
58    /// Resets the chip, uploads Bosch's config (~250 ms), and starts sampling
59    /// with readings in the board frame ([`BOARD_AXES`]).
60    pub async fn init(
61        &mut self,
62        bus: &'static SysI2cBus,
63        config: Config,
64    ) -> Result<Imu<'_>, Error> {
65        let mut chip = Bmi270::new(device(bus), ADDRESS_SDO_HIGH);
66        chip.init(config, &mut Delay).await?;
67        Ok(Imu {
68            chip,
69            axes: BOARD_AXES,
70            _token: PhantomData,
71        })
72    }
73}
74
75/// The IMU driver.
76pub struct Imu<'d> {
77    chip: Chip,
78    axes: AxisMap,
79    _token: PhantomData<&'d mut ()>,
80}
81
82impl Imu<'_> {
83    /// One sample in the board frame: acceleration in g, rate in °/s.
84    pub async fn read(&mut self) -> Result<Sample, Error> {
85        let s = self.read_chip().await?;
86        Ok(Sample {
87            accel: self.axes.apply(s.accel),
88            gyro: self.axes.apply(s.gyro),
89        })
90    }
91
92    /// One sample in the chip's own frame.
93    pub async fn read_chip(&mut self) -> Result<Sample, Error> {
94        self.chip.read().await.map_err(Error::I2c)
95    }
96
97    /// One unscaled sample in the chip's own frame.
98    pub async fn read_raw(&mut self) -> Result<RawSample, Error> {
99        self.chip.read_raw().await.map_err(Error::I2c)
100    }
101
102    /// Die temperature in °C, or `None` if the sensor reports it invalid.
103    pub async fn temperature(&mut self) -> Result<Option<f32>, Error> {
104        self.chip.temperature().await.map_err(Error::I2c)
105    }
106
107    /// Changes ranges and data rate.
108    pub async fn configure(&mut self, config: Config) -> Result<(), Error> {
109        self.chip.configure(config).await
110    }
111
112    /// The chip-to-board axis mapping [`read`](Self::read) applies.
113    pub fn axes(&self) -> AxisMap {
114        self.axes
115    }
116
117    /// Replaces the axis mapping, e.g. with one measured by `imu_axes`.
118    pub fn set_axes(&mut self, axes: AxisMap) {
119        self.axes = axes;
120    }
121
122    /// The underlying driver, for advanced control.
123    pub fn chip(&mut self) -> &mut Chip {
124        &mut self.chip
125    }
126}