Skip to main content

stm_32/peripherals/
dps310.rs

1// ******************************************************************************
2// * File     : platforms/stm_32/src/peripherals/dps310.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
56pub static BARO_SIGNAL: Signal<
57    CriticalSectionRawMutex,
58    Result<packets::BaroPacket, errors::SensorError>,
59> = Signal::<CriticalSectionRawMutex, Result<packets::BaroPacket, errors::SensorError>>::new();
60
61fn publish_baro(result: Result<packets::BaroPacket, errors::SensorError>) {
62    #[cfg(feature = "runtime-diagnostics")]
63    crate::runtime_diagnostics::record_signal_publish(
64        crate::runtime_diagnostics::SensorKind::Baro,
65        BARO_SIGNAL.signaled(),
66        result.is_err(),
67    );
68    BARO_SIGNAL.signal(result);
69}
70
71pub struct Dps310Sensor {
72    pub dev: SpiDevice<
73        'static,
74        CriticalSectionRawMutex,
75        spi::Spi<'static, Async, spi::mode::Master>,
76        Output<'static>,
77    >,
78    pub drdy: ExtiInput<'static, Async>,
79    pub three_wire: bool,
80}
81
82fn compliment(x: u32, bits: u32) -> f64 {
83    let mut x = x as i32;
84    if (x & (1i32 << (bits - 1))) != 0 {
85        x -= 1i32 << bits;
86    }
87    f64::from(x)
88}
89
90const MEAS_CFG_REG: u8 = 0x08;
91const ISR_REG: u8 = 0x0A;
92const DPS310_READ_P_CMD: u8 = 0x00;
93const DPS310_READ_T_CMD: u8 = 0x03;
94
95const K1: f64 = 524288.0;
96const K8: f64 = 7864320.0; //
97const CELSIUS_TO_KELVIN: f64 = 273.15;
98
99impl Dps310Sensor {
100    async fn read_register(&mut self, reg_addr: u8) -> Result<u8, errors::SensorError> {
101        let tx = [reg_addr | SPI_READ, 0x00];
102        let mut rx = [0u8; 2];
103        self.dev.transfer(&mut rx, &tx).await.map_err(|e| match e {
104            _ => errors::SensorError::GenericSensorError("SPI failed: read_register"),
105        })?;
106        Ok(rx[1])
107    }
108
109    async fn write_register(&mut self, reg_addr: u8, value: u8) -> Result<(), errors::SensorError> {
110        let tx = [reg_addr | SPI_WRITE, value];
111        // Soft Reset
112        self.dev.write(&tx).await.map_err(|e| match e {
113            _ => errors::SensorError::GenericSensorError("SPI failed: write_register"),
114        })?;
115        Ok(())
116    }
117
118    async fn initialize_sensor(&mut self) -> Result<[f64; 9], errors::SensorError> {
119        // SOFT RESET
120        const RESET_REG: u8 = 0x0C;
121        self.write_register(RESET_REG, 0x09).await?;
122        Timer::after_millis(52).await; // Wait reset (12ms) and for Coefficients to be ready (40ms).
123
124        // 3-WIRE MODE & DRDY interrupts
125        // Set to 3-wire or 4-wire SPI mode so we can read registers.
126        // Interrupt and FIFO Config 0x09
127        // 7 - 	1, DRDY active high
128        // 6 - 	0, Disable FIFO full interrupt
129        // 5 - 	1, Int on temp
130        // 4 - 	1, Int on pressure
131        // 3 - 	0, no Temp data shift
132        // 2 - 	0, no Press data shift
133        // 1 - 	0, Disable FIFO
134        // 0 - 	1, 3-wire SPI interface
135        const CFG_REG: u8 = 0x09;
136        let three_wire_mode: u8 = if self.three_wire { 0x01 } else { 0x00 };
137        self.write_register(CFG_REG, three_wire_mode | 0xB0).await?;
138
139        // CHECK PRODUCT ID
140        // there's a more concise way to do the if else, but I'm leaving it for now...
141        const PRODUCT_ID_REG: u8 = 0x0D;
142        const PRODUCT_ID: u8 = 0x10;
143        let id = self.read_register(PRODUCT_ID_REG).await?;
144        if id != PRODUCT_ID {
145            //    "Failure: ID = {:#02x} failure. Should be {:#02x}",
146            //    id,
147            //    PRODUCT_ID
148            //);
149            return Err(errors::SensorError::GenericSensorError("ID mismatch"));
150        }
151
152        // CHECK IF CALIBRATION COEFFICIENTS ARE READY
153        // again, better way to do if else is for future work
154        const COEF_READY: u8 = 0x80;
155        let coef_rdy = self.read_register(MEAS_CFG_REG).await?;
156        if (coef_rdy & COEF_READY) == 0x00 {
157            return Err(errors::SensorError::GenericSensorError(
158                "Calibration coefficients not ready",
159            ));
160        }
161
162        let cal = self.read_calibration_coefficients().await?;
163        Ok(cal)
164    }
165
166    async fn read_calibration_coefficients(&mut self) -> Result<[f64; 9], errors::SensorError> {
167        const COEF_REG: u8 = 0x10;
168        let mut tx = [0u8; 19];
169        tx[0] = COEF_REG | SPI_READ;
170
171        let mut rx = [0u8; 19];
172        self.dev.transfer(&mut rx, &tx).await.map_err(|e| match e {
173            _ => {
174                errors::SensorError::GenericSensorError("SPI failed: read_calibration_coefficients")
175            }
176        })?;
177
178        // move u8 date into u32 data for bit manipulation
179        let buf = rx.map(|x| x as u32);
180        // compute coefficint values in f64
181        let mut cal = [0f64; 9];
182        cal[0] = compliment((buf[1] << 4) | ((buf[2] >> 4) & 0x0F), 12); // C0
183        cal[1] = compliment(((buf[2] & 0x0F) << 8) | buf[3], 12); // C1
184        cal[2] = compliment((buf[4] << 12) | (buf[5] << 4) | ((buf[6] >> 4) & 0x0F), 20); // C00
185        cal[3] = compliment(((buf[6] & 0x0F) << 16) | (buf[7] << 8) | buf[8], 20); // C10
186        cal[6] = compliment((buf[9] << 8) | buf[10], 16); // C01
187        cal[7] = compliment((buf[11] << 8) | buf[12], 16); // C11
188        cal[4] = compliment((buf[13] << 8) | buf[14], 16); // C20
189        cal[8] = compliment((buf[15] << 8) | buf[16], 16); // C21
190        cal[5] = compliment((buf[17] << 8) | buf[18], 16); // C30
191
192        Ok(cal)
193    }
194
195    async fn pressure_config(&mut self) -> Result<(), errors::SensorError> {
196        // PRESSURE CONFIG
197        const PRS_CFG_REG: u8 = 0x06;
198        self.write_register(PRS_CFG_REG, 0x03).await?; // 8x oversampling
199
200        Ok(())
201    }
202
203    async fn temperature_config(&mut self) -> Result<(), errors::SensorError> {
204        // CHECK TEMPERATURE SOURCE
205        const COEF_SRCE_REG: u8 = 0x28;
206        let temp_source = self.read_register(COEF_SRCE_REG).await? & 0x80;
207
208        // TEMPERATURE CONFIG
209        const TMP_CFG_REG: u8 = 0x07;
210        self.write_register(TMP_CFG_REG, temp_source | 0x00).await?; //no oversampling
211
212        Ok(())
213    }
214
215    async fn measurement_configuration(&mut self) -> Result<(), errors::SensorError> {
216        // Measurement Configuration
217        // 7 - 	0, read only
218        // 6 - 	0, read only
219        // 5 - 	0, read only
220        // 4 - 	0, read only
221        // 3 - 	0, reserved
222        // 2:0 - 	111, pressure and temperature continuous mode
223        // Set to idle
224        self.write_register(MEAS_CFG_REG, 0x00).await?;
225
226        Ok(())
227    }
228
229    async fn get_sensor_data(&mut self, cmd: u8) -> Result<i32, errors::SensorError> {
230        let mut rx = [0u8; 4];
231        self.dev
232            .transfer(&mut rx, &[cmd | SPI_READ, 0, 0, 0])
233            .await
234            .map_err(|e| match e {
235                _ => errors::SensorError::GenericSensorError("SPI failed: get_sensor_data"),
236            })?;
237
238        // Clear the ISR
239        self.read_register(ISR_REG).await?;
240
241        let raw = (((rx[1] as u32) << 24 | (rx[2] as u32) << 16 | (rx[3] as u32) << 8) as i32) >> 8;
242
243        Ok(raw)
244    }
245
246    async fn get_pressure_data(&mut self) -> Result<(i32, u16), errors::SensorError> {
247        // Start the Pressure read
248        self.write_register(MEAS_CFG_REG, 0x01).await?;
249
250        // wait for data ready...
251        // Use DRDY signal for better robustness? otherwise, timeout at 14ms.
252        let _drdy_result = with_timeout(
253            Duration::from_micros(14_000),
254            self.drdy.wait_for_rising_edge(),
255        )
256        .await
257        .is_ok();
258        Timer::after_micros(20).await; // We need at least 14us delay here if running at 2 MHz, maybe because of the messy harness?
259
260        // read status (highest 8 bits)
261        let status = (self.read_register(MEAS_CFG_REG).await? as u16) << 8;
262
263        // read Pressure data
264        let raw_p = self.get_sensor_data(DPS310_READ_P_CMD).await?;
265        Ok((raw_p, status))
266    }
267
268    async fn get_temperature_data(&mut self) -> Result<(i32, u16), errors::SensorError> {
269        // Start Temperature read
270        self.write_register(MEAS_CFG_REG, 0x02).await?;
271
272        // wait for data ready...
273        // Use DRDY signal if available, otherwise let it timeout
274        let _drdy_result = with_timeout(
275            Duration::from_micros(3_000),
276            self.drdy.wait_for_rising_edge(),
277        )
278        .await
279        .is_ok();
280
281        // read status (modify lowest 8 bits)
282        let status_low = self.read_register(MEAS_CFG_REG).await? as u16;
283
284        // read Temperature data
285        let raw_t = self.get_sensor_data(DPS310_READ_T_CMD).await?;
286        Ok((raw_t, status_low))
287    }
288
289    fn process_temperature_data(
290        &mut self,
291        raw_t: i32,
292        raw_t_previous: &mut i32,
293        cal: &[f64; 9],
294    ) -> (f64, f64) {
295        *raw_t_previous += (raw_t - *raw_t_previous) / 16; // filter temperature a bit (1/127 is cutoff frequenc of 100Hz * (1/16)/(2*pi) around 1 sec to 1/e)
296        let raw_t_f64 = f64::from(*raw_t_previous) / K1;
297        // The DPS310 compensation formula returns degrees Celsius. ROSflight's
298        // BaroPacket and SMALL_BARO telemetry carry absolute temperature.
299        let temperature = cal[0] * 0.5 + cal[1] * raw_t_f64 + CELSIUS_TO_KELVIN;
300
301        (raw_t_f64, temperature)
302    }
303
304    fn process_pressure_data(&mut self, raw_p: i32, raw_t_f64: f64, cal: &[f64; 9]) -> (f64, f64) {
305        let raw_p_f64 = f64::from(raw_p) / K8;
306        let pressure = cal[2]
307            + raw_p_f64 * (cal[3] + raw_p_f64 * (cal[4] + raw_p_f64 * cal[5]))
308            + raw_t_f64 * (cal[6] + raw_p_f64 * (cal[7] + raw_p_f64 * cal[8])); // Pa
309        (raw_p_f64, pressure)
310    }
311
312    pub async fn run(&mut self) {
313        // initialize the sensor
314        let mut cal = match self.initialize_sensor().await {
315            Ok(cal) => cal,
316            Err(e) => {
317                publish_baro(Err(e));
318                return;
319            }
320        };
321        if let Err(e) = self.pressure_config().await {
322            publish_baro(Err(e));
323            return;
324        }
325        if let Err(e) = self.temperature_config().await {
326            publish_baro(Err(e));
327            return;
328        }
329        if let Err(e) = self.measurement_configuration().await {
330            publish_baro(Err(e));
331            return;
332        }
333        //////////////////////////////////////////////////////////////////////////////////////
334        // Periodic Data Acquisition
335        let mut raw_t_previous = 0_i32;
336        let sample_period = Duration::from_hz(50);
337
338        loop {
339            let timestamp = synch_at(sample_period);
340            Timer::at(timestamp).await;
341
342            // process pressure data
343            let (raw_p, status_high) = match self.get_pressure_data().await {
344                Ok(data) => data,
345                Err(e) => {
346                    publish_baro(Err(e));
347                    continue;
348                }
349            };
350            let (raw_t, status_low) = match self.get_temperature_data().await {
351                Ok(data) => data,
352                Err(e) => {
353                    publish_baro(Err(e));
354                    continue;
355                }
356            };
357
358            // combine status bits
359            let status_combined = (status_high & 0xFF00) | (status_low & 0x00FF);
360
361            let (raw_t_f64, temperature) =
362                self.process_temperature_data(raw_t, &mut raw_t_previous, &mut cal);
363            let (_raw_p_f64, pressure) = self.process_pressure_data(raw_p, raw_t_f64, &mut cal);
364
365            if status_combined == 0xD0E0 {
366                let header = packets::RosflightPacketHeader {
367                    timestamp: timestamp.as_micros(),
368                    status: status_combined,
369                };
370                let baro_packet = packets::BaroPacket {
371                    header,
372                    pressure: pressure as f32,
373                    temperature: temperature as f32,
374                    ..Default::default()
375                };
376                publish_baro(Ok(baro_packet)); // make data available for other tasks.
377            } else {
378                publish_baro(Err(errors::SensorError::GenericSensorError("Bad status")));
379            }
380        }
381    }
382}
383
384#[embassy_executor::task]
385pub async fn task(mut dps: Dps310Sensor) {
386    dps.run().await;
387}