microbit_bsp/motion/
mod.rs

1//! Motion sensor for the micro:bit.
2//!
3//! The sensor is an LSM303AGR, a 3D accelerometer and 3D magnetometer combined in a single package.
4
5use embassy_nrf::{
6    interrupt::typelevel::{self, Binding},
7    peripherals::{P0_08, P0_16, TWISPI0},
8    twim::{self, InterruptHandler},
9    Peri,
10};
11use embassy_sync::channel::DynamicSender;
12use embassy_time::{Duration, Ticker};
13use lsm303agr::{
14    interface::I2cInterface, mode::MagOneShot, AccelMode, AccelOutputDataRate, Acceleration, Error as LsmError,
15    Lsm303agr, MagMode, MagOutputDataRate, MagneticField, Status,
16};
17use static_cell::ConstStaticCell;
18
19type I2C<'d> = twim::Twim<'d, TWISPI0>;
20
21/// Accelerometer error
22pub type Error = LsmError<twim::Error>;
23
24/// Accelerometer and magnetometer chip present on the microbit
25pub struct Sensor<'d> {
26    sensor: Lsm303agr<I2cInterface<I2C<'d>>, MagOneShot>,
27}
28
29/// Create a new lsm303agr sensor
30///
31/// As an alternative to the [`Sensor`] struct, you can create a new [`Lsm303agr`] sensor using this
32/// function. No initialization is performed, which means you will need to perform initialization
33/// and configuration yourself.
34///
35/// # Examples
36///
37/// ```no_run
38/// use microbit_bsp::{motion, Microbit};
39/// use embassy_nrf::{bind_interrupts, peripherals::TWISPI0, twim::InterruptHandler};
40///
41/// bind_interrupts!(
42///     struct InterruptRequests {
43///         SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => InterruptHandler<TWISPI0>;
44///     }
45/// );
46/// let irq = InterruptRequests{};
47/// let board = Microbit::default();
48/// let mut lsm = motion::new_lsm303agr(board.twispi0, irq, board.p23, board.p22);
49/// lsm.init().unwrap();
50/// lsm
51///     .set_accel_mode_and_odr(
52///         &mut embassy_time::Delay,
53///         AccelMode::Normal,
54///         AccelOutputDataRate::Hz10,
55///     )
56///     .unwrap();
57/// lsm
58///     .set_mag_mode_and_odr(
59///         &mut embassy_time::Delay,
60///         lsm303agr::MagMode::HighResolution,
61///         lsm303agr::MagOutputDataRate::Hz10,
62///     )
63///     .unwrap();
64/// lsm.mag_enable_low_pass_filter().unwrap();
65/// lsm.enable_mag_offset_cancellation().unwrap();
66/// ```
67pub fn new_lsm303agr<'d>(
68    twispi0: Peri<'static, TWISPI0>,
69    irq: impl Binding<typelevel::TWISPI0, InterruptHandler<TWISPI0>> + 'd,
70    sda: Peri<'static, P0_16>,
71    scl: Peri<'static, P0_08>,
72) -> Lsm303agr<I2cInterface<I2C<'d>>, MagOneShot> {
73    let config = twim::Config::default();
74    static RAM_BUFFER: ConstStaticCell<[u8; 16]> = ConstStaticCell::new([0; 16]);
75    let twi = twim::Twim::new(twispi0, irq, sda, scl, config, RAM_BUFFER.take());
76    Lsm303agr::new_with_i2c(twi)
77}
78
79impl<'d> Sensor<'d> {
80    /// Create and initialize the motion sensor
81    ///
82    /// # Errors
83    ///
84    /// If there is a problem communicating with the sensor, an error is returned.
85    pub async fn new(
86        twispi0: Peri<'static, TWISPI0>,
87        irq: impl Binding<typelevel::TWISPI0, InterruptHandler<TWISPI0>> + 'd,
88        sda: Peri<'static, P0_16>,
89        scl: Peri<'static, P0_08>,
90    ) -> Result<Self, Error> {
91        let mut sensor = new_lsm303agr(twispi0, irq, sda, scl);
92        sensor.init().await?;
93        sensor
94            .set_accel_mode_and_odr(&mut embassy_time::Delay, AccelMode::Normal, AccelOutputDataRate::Hz10)
95            .await?;
96
97        sensor
98            .set_mag_mode_and_odr(
99                &mut embassy_time::Delay,
100                MagMode::HighResolution,
101                MagOutputDataRate::Hz10,
102            )
103            .await?;
104        sensor.mag_enable_low_pass_filter().await?;
105        sensor.enable_mag_offset_cancellation().await?;
106
107        Ok(Self { sensor })
108    }
109
110    /// Return status of accelerometer
111    ///
112    /// # Errors
113    ///
114    /// If there is a problem communicating with the sensor, an error is returned.
115    pub async fn accel_status(&mut self) -> Result<Status, Error> {
116        self.sensor.accel_status().await
117    }
118
119    /// Return accelerometer data
120    ///
121    /// Returned in mg (milli-g) where 1g is 9.8m/s².
122    ///
123    /// # Errors
124    ///
125    /// If there is a problem communicating with the sensor, an error is returned.
126    pub async fn accel_data(&mut self) -> Result<Acceleration, Error> {
127        self.sensor.acceleration().await
128    }
129
130    /// Run a continuous task outputing accelerometer data at the configured data rate
131    ///
132    /// # Errors
133    ///
134    /// If there is a problem communicating with the sensor, an error is returned.
135    pub async fn accel_run(
136        &mut self,
137        rate: AccelOutputDataRate,
138        sender: DynamicSender<'_, Acceleration>,
139    ) -> Result<(), Error> {
140        let delay = match rate {
141            AccelOutputDataRate::Hz1 => Duration::from_millis(1000),
142            AccelOutputDataRate::Hz10 => Duration::from_millis(100),
143            AccelOutputDataRate::Hz25 => Duration::from_millis(40),
144            AccelOutputDataRate::Hz50 => Duration::from_millis(20),
145            AccelOutputDataRate::Hz100 => Duration::from_millis(10),
146            AccelOutputDataRate::Hz200 => Duration::from_millis(5),
147            AccelOutputDataRate::Hz400 => Duration::from_micros(2500),
148            AccelOutputDataRate::Khz1_344 => Duration::from_micros(744),
149            AccelOutputDataRate::Khz1_620LowPower => Duration::from_micros(617),
150            AccelOutputDataRate::Khz5_376LowPower => Duration::from_micros(186),
151        };
152        let mut ticker = Ticker::every(delay);
153        loop {
154            ticker.next().await;
155            let data = self.accel_data().await?;
156            let _ = sender.try_send(data);
157        }
158    }
159
160    /// Returns data from the magnetometer.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if the magnetometer is not ready to provide data, or if there is an error
165    /// communicating with the sensor.
166    pub async fn mag_data(&mut self) -> Result<MagneticField, Error> {
167        self.sensor.magnetic_field().await
168    }
169
170    /// Returns the status of the magnetometer.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error if there is an error communicating with the sensor.
175    pub async fn mag_status(&mut self) -> Result<Status, Error> {
176        self.sensor.mag_status().await
177    }
178}