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}