Skip to main content

stm_32/peripherals/
iis2mdc.rs

1// ******************************************************************************
2// * File     : platforms/stm_32/src/peripherals/iis2mdc.rs
3// * Date     : June 28, 2026
4// ******************************************************************************
5// *
6// * Copyright (c) 2023, AeroVironment, Inc.
7// * All rights reserved.
8// *
9// * Redistribution and use in source and binary forms, with or without
10// * modification, are permitted provided that the following conditions are met:
11// *
12// * 1.Redistributions of source code must retain the above copyright notice, this
13// * list of conditions and the following disclaimer.
14// *
15// * 2.Redistributions in binary form must reproduce the above copyright notice,
16// * this list of conditions and the following disclaimer in the documentation
17// * and/or other materials provided with the distribution.
18// *
19// * 3.Neither the name of the copyright holder nor the names of its
20// * contributors may be used to endorse or promote products derived from
21// * this software without specific prior written permission.
22// *
23// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24// * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25// * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26// * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27// * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28// * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30// * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31// * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33// *
34// ******************************************************************************
35
36use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
37use embassy_stm32::exti::ExtiInput;
38use embassy_stm32::gpio::Output;
39use embassy_stm32::mode::Async;
40use embassy_stm32::spi;
41use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
42use embassy_sync::signal::Signal;
43use embassy_time::Duration;
44use embassy_time::Timer;
45use embassy_time::with_timeout;
46use embedded_hal_async::spi::SpiDevice as _;
47
48use crate::synch_at;
49use veloxity_core::errors;
50use veloxity_core::packets;
51
52// Device dependent
53const SPI_READ: u8 = 0x80;
54const SPI_WRITE: u8 = 0x00;
55
56// Registers
57const OFFSET_REG: u8 = 0x45;
58const WHO_AM_I_REG: u8 = 0x4F;
59
60const CFG_REG_A: u8 = 0x60;
61const CFG_REG_B: u8 = 0x61;
62const CFG_REG_C: u8 = 0x62;
63const INT_CTRL_REG: u8 = 0x63;
64const INT_SOURCE_REG: u8 = 0x64;
65const INT_THS_L_REG: u8 = 0x65;
66const INT_THS_H_REG: u8 = 0x66;
67const STATUS_REG: u8 = 0x67;
68const OUT_TEMP: u8 = 0x6E;
69
70// Chip ID
71const WHO_AM_I: u8 = 0x40;
72
73pub static MAG_SIGNAL: Signal<
74    CriticalSectionRawMutex,
75    Result<packets::MagPacket, errors::SensorError>,
76> = Signal::<CriticalSectionRawMutex, Result<packets::MagPacket, errors::SensorError>>::new();
77
78pub struct Iis2mdcSensor {
79    pub dev: SpiDevice<
80        'static,
81        CriticalSectionRawMutex,
82        spi::Spi<'static, Async, spi::mode::Master>,
83        Output<'static>,
84    >,
85    pub drdy: ExtiInput<'static, Async>,
86}
87
88impl Iis2mdcSensor {
89    async fn read_register(&mut self, reg_addr: u8) -> Result<u8, errors::SensorError> {
90        let tx = [reg_addr | SPI_READ, 0x00];
91        let mut rx = [0u8; 2];
92        self.dev.transfer(&mut rx, &tx).await.map_err(|e| match e {
93            _ => errors::SensorError::GenericSensorError("SPI failed: read_register"),
94        })?;
95        Ok(rx[1])
96    }
97
98    async fn write_register(&mut self, reg_addr: u8, value: u8) -> Result<(), errors::SensorError> {
99        let tx = [reg_addr | SPI_WRITE, value];
100        // Soft Reset
101        self.dev.write(&tx).await.map_err(|e| match e {
102            _ => errors::SensorError::GenericSensorError("SPI failed: write_register"),
103        })?;
104        Ok(())
105    }
106
107    async fn initialize_sensor(&mut self) -> Result<u8, errors::SensorError> {
108        // Check Chip ID
109        let who_am_i = self.read_register(WHO_AM_I_REG).await?;
110        if who_am_i == WHO_AM_I {
111        } else {
112            return Err(errors::SensorError::GenericSensorError(
113                "IIS2MDC Chip ID mismatch",
114            ));
115        }
116
117        // Reset the sensor:
118        // Register A (0x60)
119        // 7:  = 0 COMP_TEMP_EN Temp comp enable
120        // 6:  = X REBOOT
121        // 5:  = X SOFT_RST
122        // 4:  = 0 High resolution Mode (LP=0)
123        // 3:2 = 00 10 Hz Data Rate (ODR)
124        // 1:0 = 00 for continuous Mode,  10 or 11 = idle mode
125        self.write_register(CFG_REG_A, 0x20).await?;
126        Timer::after_micros(10).await; // Wait at least 5 us after reset
127        self.write_register(CFG_REG_A, 0x40).await?;
128        Timer::after_micros(21).await; // wait at least 20 ms for reboot to complete
129
130        // Configure sensor:
131        // Set to idle (awaiting read command)
132        // Register A (0x60)
133        // 7:  = 1 COMP_TEMP_EN Temp comp enable
134        // 6:  = 0 REBOOT
135        // 5:  = 0 SOFT_RST
136        // 4:  = 0 High resolution Mode (High resolution = 0, Low power =1)
137        //
138        // 3:2 = 11 = 100 Hz Data Rate (ODR)
139        // 1:0 = 00 Continuous Mode, 01 = single mode, 11 or 10 is idle mode.
140        //	write_register(0x60,0x81); // 1000 0001 = 0x81 For Single Acq
141        // 1000 1100 = 0x8C For 100 Hz.
142        // writeRegister(CFG_REG_A,0x80|(odr_mode<<2)); // continuous mode
143
144        self.write_register(CFG_REG_A, 0x83).await?;
145
146        // Set Single Measurement Mode
147        // Register B (0x61)
148        // [7:5] 000
149        // [4]  1 OFF_CANC_ONE_SHOT 1=Offset Cancellation in single mode
150        // [3]  0
151        // [2]  0 Set Freq of Set pulse to 63 ODR
152        // [1]  1, OFF_CANC 1= enable offset cancellation in single mode
153        // [0]  0 LPF disable offset filter (1- enabled)
154
155        self.write_register(CFG_REG_B, 0x12).await?;
156
157        // Enable DRDY Pin and set SPI only mode (no I2C)
158        // Register C (0x62)
159        // 7: =0 Unused
160        // 6: =0 INT_on_PIN Enable event interrupts
161        // 5: =1 I2C_DIS (Disable I2C interface use only SPI)
162        // 4: =1 BDU
163        //
164        // 3: =0 BLE do not swap data bytes
165        // 2: =0 Unused
166        // 1: =0 SELF_TEST
167        // 0: =1 DRDY_on_PIN Enable DRDY
168
169        self.write_register(CFG_REG_C, 0x31).await?;
170
171        // Disable Event Interrupts (this is not DRDY)
172
173        self.write_register(INT_CTRL_REG, 0x00).await?;
174        self.write_register(INT_SOURCE_REG, 0x00).await?;
175        self.write_register(INT_THS_L_REG, 0x00).await?;
176        self.write_register(INT_THS_H_REG, 0x00).await?;
177
178        // Read the status register
179        let status = self.read_register(STATUS_REG).await?;
180
181        // Read calibration registers
182        let mut offset = [0u8; 7];
183        self.dev
184            .transfer(&mut offset, &[OFFSET_REG | SPI_READ, 0, 0, 0, 0, 0, 0])
185            .await
186            .map_err(|e| match e {
187                _ => errors::SensorError::GenericSensorError("SPI failed: initialize_sensor"),
188            })?;
189
190        let _offset = [
191            (((offset[1] as u16) | (offset[2] as u16) << 8) as i16) * 3 / 2,
192            (((offset[3] as u16) | (offset[4] as u16) << 8) as i16) * 3 / 2,
193            (((offset[5] as u16) | (offset[6] as u16) << 8) as i16) * 3 / 2,
194        ];
195
196        Ok(status)
197    }
198
199    async fn get_flux_and_temperature_data(
200        &mut self,
201    ) -> Result<(u16, [u8; 8], [u8; 3]), errors::SensorError> {
202        // Start Read Cycle
203        self.write_register(CFG_REG_A, 0x81).await?;
204
205        // Use DRDY signal for better robustness? otherwise, timeout at 9.6ms.
206        let _drdy_result = with_timeout(
207            Duration::from_micros(9_600),
208            self.drdy.wait_for_rising_edge(),
209        )
210        .await
211        .is_ok();
212
213        let mut flux_data = [0u8; 8];
214        // note starting at STATUS_REG to capture both status and flux values
215        self.dev
216            .transfer(
217                &mut flux_data,
218                &[STATUS_REG | SPI_READ, 0, 0, 0, 0, 0, 0, 0],
219            )
220            .await
221            .map_err(|e| match e {
222                _ => errors::SensorError::GenericSensorError(
223                    "SPI failed: get_flux_and_temperature_data",
224                ),
225            })?;
226
227        let mut raw_temperature = [0u8; 3];
228        self.dev
229            .transfer(&mut raw_temperature, &[OUT_TEMP | SPI_READ, 0, 0])
230            .await
231            .map_err(|e| match e {
232                _ => errors::SensorError::GenericSensorError(
233                    "SPI failed: get_flux_and_temperature_data",
234                ),
235            })?;
236
237        let status = flux_data[1] as u16;
238        Ok((status, flux_data, raw_temperature))
239    }
240
241    fn process_flux_data(&self, flux_data: [u8; 8], flux_previous: &mut [i16; 3]) -> [f32; 3] {
242        let raw_flux = [
243            ((flux_data[2] as u16) | ((flux_data[3] as u16) << 8)) as i16,
244            ((flux_data[4] as u16) | ((flux_data[5] as u16) << 8)) as i16,
245            ((flux_data[6] as u16) | ((flux_data[7] as u16) << 8)) as i16,
246        ];
247
248        let flux = [
249            -f32::from(raw_flux[0] + flux_previous[0]) / 2. * 1.5e-7, // convert to Tesla
250            f32::from(raw_flux[1] + flux_previous[1]) / 2. * 1.5e-7,
251            f32::from(raw_flux[2] + flux_previous[2]) / 2. * 1.5e-7,
252        ];
253
254        *flux_previous = raw_flux;
255
256        flux
257    }
258
259    fn process_temperature_data(&self, raw_temperature: [u8; 3]) -> f32 {
260        let temperature =
261            f32::from(((raw_temperature[1] as u16) | ((raw_temperature[2] as u16) << 8)) as i16)
262                / 8.0
263                + 25.0;
264        temperature
265    }
266
267    pub async fn run(&mut self) {
268        let _status = match self.initialize_sensor().await {
269            Ok(status) => status,
270            Err(e) => {
271                MAG_SIGNAL.signal(Err(e));
272                return;
273            }
274        };
275
276        let mut flux_previous = [0_i16; 3];
277        let sample_period = Duration::from_hz(100);
278
279        loop {
280            let timestamp = synch_at(sample_period);
281            Timer::at(timestamp).await;
282
283            // Read Flux Data
284            let (status, flux_data, raw_temperature) =
285                match self.get_flux_and_temperature_data().await {
286                    Ok(data) => data,
287                    Err(e) => {
288                        MAG_SIGNAL.signal(Err(e));
289                        continue;
290                    }
291                };
292
293            if status == 0x000F {
294                let flux = self.process_flux_data(flux_data, &mut flux_previous);
295                let temperature = self.process_temperature_data(raw_temperature);
296
297                let header = packets::RosflightPacketHeader {
298                    timestamp: timestamp.as_micros(),
299                    status,
300                };
301                let mag_packet = packets::MagPacket {
302                    header,
303                    flux,
304                    temperature,
305                };
306                MAG_SIGNAL.signal(Ok(mag_packet)); // make data available for other tasks
307            } else {
308                MAG_SIGNAL.signal(Err(errors::SensorError::GenericSensorError("Bad Status")));
309            }
310        }
311    }
312}
313
314#[embassy_executor::task]
315pub async fn task(mut iis: Iis2mdcSensor) {
316    iis.run().await;
317}