Skip to main content

embedded_qmp6988/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs)]
3#![no_std]
4
5#[allow(unused_imports)]
6use micromath::F32Ext;
7
8#[cfg(not(feature = "async"))]
9use embedded_hal as hal;
10#[cfg(feature = "async")]
11use embedded_hal_async as hal;
12
13use hal::i2c::{Operation, SevenBitAddress};
14
15pub use weather_utils::Temperature;
16use weather_utils::{BarometricPressure, Celsius, TemperatureAndBarometricPressure};
17
18/// The I2C address when the SDO pin is connected to logic low
19pub const I2C_ADDRESS_LOGIC_LOW: SevenBitAddress = 0x70;
20/// The I2C address when the SDO pin is connected to logic high
21pub const I2C_ADDRESS_LOGIC_HIGH: SevenBitAddress = 0x56;
22/// The default I2C address (SDO pin connected to low)
23pub const DEFAULT_I2C_ADDRESS: SevenBitAddress = I2C_ADDRESS_LOGIC_LOW;
24
25const CHIP_ID_REGISTER: u8 = 0xd1;
26const COE_B00_1_REGISTER: u8 = 0xa0;
27const CTRL_MEAS_REGISTER: u8 = 0xf4;
28const IIR_CNT_REGISTER: u8 = 0xf1;
29const PRESS_TXD2: u8 = 0xf7;
30const RESET_REGISTER: u8 = 0xe0;
31
32/// All possible errors generated when using the Qmp6988 struct
33#[derive(Debug)]
34pub enum Error<I2cE>
35where
36    I2cE: hal::i2c::Error,
37{
38    /// I²C bus error
39    I2c(I2cE),
40    /// The QMP6988 chip has not been detected
41    ChipNotDetected,
42    /// The computed CRC and the one sent by the device mismatch
43    BadCrc,
44}
45
46impl<I2cE> From<I2cE> for Error<I2cE>
47where
48    I2cE: hal::i2c::Error,
49{
50    fn from(value: I2cE) -> Self {
51        Error::I2c(value)
52    }
53}
54
55/// IIR (Infinite Impulse Response) filter.
56///
57/// It chooses the amount of noise reduction being performed on the pressure
58/// measurement. The greater the coeff, the higher the noise reduction.
59#[derive(Clone, Copy, Debug, Default)]
60#[repr(u8)]
61pub enum IirFilter {
62    /// No filter is applied
63    Off = 0x00,
64    /// A coefficient 2 filter is applied
65    Coeff2 = 0x01,
66    /// A coefficient 4 filter is applied
67    #[default]
68    Coeff4 = 0x02,
69    /// A coefficient 8 filter is applied
70    Coeff8 = 0x03,
71    /// A coefficient 16 filter is applied
72    Coeff16 = 0x04,
73    /// A coefficient 32 filter is applied
74    Coeff32 = 0x05,
75}
76
77/// The oversampling setting.
78///
79/// It chooses the accuracy of the measurement, with an impact on the
80/// duration of the measurement. The greater the accuracy, the longer the
81/// duration of the measurement, and the higher the current consumption.
82#[derive(Clone, Copy, Debug, Default)]
83#[repr(u8)]
84pub enum OverSamplingSetting {
85    /// The shorter measurement, with the lowest accuracy. This is typically
86    /// used for weather monitoring.
87    HighSpeed,
88    /// A measurement with a litle more accuracy, but still a low current
89    /// consumption. This might be used for drop detection.
90    LowPower,
91    /// The standard setting, providing a compromise between the accuracy
92    /// of the measurement and its duration. This might be used for elevator
93    /// detection.
94    #[default]
95    Standard,
96    /// A high accuracy measurement, with a quite long duration. This might be
97    /// used for stair detection.
98    HighAccuracy,
99    /// The best accuracy measurement, with the longer duration and higher
100    /// current consumption. This is typically used for indoor navigation.
101    UltraHighAccuracy,
102}
103
104#[derive(Clone, Copy, Debug, Default)]
105#[repr(u8)]
106enum PowerMode {
107    #[default]
108    Sleep = 0x00,
109    Forced = 0x01,
110    #[allow(dead_code)]
111    Normal = 0x03,
112}
113
114#[derive(Clone, Copy, Debug, Default)]
115#[repr(u8)]
116enum OverSampling {
117    // Skipped = 0x00,
118    #[default]
119    X1 = 0x01,
120    X2 = 0x02,
121    X4 = 0x03,
122    X8 = 0x04,
123    X16 = 0x05,
124    X32 = 0x06,
125    // X64 = 0x07,
126}
127
128#[derive(Debug, Default)]
129struct Coe {
130    a0: i32,
131    a1: i16,
132    a2: i16,
133    b00: i32,
134    bt1: i16,
135    bt2: i16,
136    bp1: i16,
137    b11: i16,
138    bp2: i16,
139    b12: i16,
140    b21: i16,
141    bp3: i16,
142}
143
144impl From<&[u8; 25]> for Coe {
145    fn from(value: &[u8; 25]) -> Self {
146        Coe {
147            a0: ((((value[18] as u32) << 12 | (value[19] as u32) << 4 | (value[24] as u32) & 0x0f)
148                << 12) as i32)
149                >> 12,
150            a1: ((value[20] as u16) << 8 | (value[21] as u16)) as i16,
151            a2: ((value[22] as u16) << 8 | (value[23] as u16)) as i16,
152            b00: ((((value[0] as u32) << 12
153                | (value[1] as u32) << 4
154                | ((value[24] as u32) & 0xf0) >> 4)
155                << 12) as i32)
156                >> 12,
157            bt1: ((value[2] as u16) << 8 | (value[3] as u16)) as i16,
158            bt2: ((value[4] as u16) << 8 | (value[5] as u16)) as i16,
159            bp1: ((value[6] as u16) << 8 | (value[7] as u16)) as i16,
160            b11: ((value[8] as u16) << 8 | (value[9] as u16)) as i16,
161            bp2: ((value[10] as u16) << 8 | (value[11] as u16)) as i16,
162            b12: ((value[12] as u16) << 8 | (value[13] as u16)) as i16,
163            b21: ((value[14] as u16) << 8 | (value[15] as u16)) as i16,
164            bp3: ((value[16] as u16) << 8 | (value[17] as u16)) as i16,
165        }
166    }
167}
168
169#[derive(Debug, Default)]
170struct K {
171    a0: f32,
172    a1: f32,
173    a2: f32,
174    b00: f32,
175    bt1: f32,
176    bt2: f32,
177    bp1: f32,
178    b11: f32,
179    bp2: f32,
180    b12: f32,
181    b21: f32,
182    bp3: f32,
183}
184
185impl From<&Coe> for K {
186    fn from(value: &Coe) -> Self {
187        K {
188            a0: value.a0 as f32 / 16.0,
189            a1: -6.30E-03 + ((4.30E-04 * value.a1 as f32) / 32_767.0),
190            a2: -1.90E-11 + ((1.20E-10 * value.a2 as f32) / 32_767.0),
191            b00: value.b00 as f32 / 16.0,
192            bt1: 1.00E-01 + ((9.10E-02 * value.bt1 as f32) / 32_767.0),
193            bt2: 1.20E-08 + ((1.20E-06 * value.bt2 as f32) / 32_767.0),
194            bp1: 3.30E-02 + ((1.90E-02 * value.bp1 as f32) / 32_767.0),
195            b11: 2.10E-07 + ((1.40E-07 * value.b11 as f32) / 32_767.0),
196            bp2: -6.30E-10 + ((3.50E-10 * value.bp2 as f32) / 32_767.0),
197            b12: 2.90E-13 + ((7.60E-13 * value.b12 as f32) / 32_767.0),
198            b21: 2.10E-15 + ((1.20E-14 * value.b21 as f32) / 32_767.0),
199            bp3: 1.30E-16 + ((7.90E-17 * value.bp3 as f32) / 32_767.0),
200        }
201    }
202}
203
204/// QMP6988 device driver
205#[derive(Debug)]
206pub struct Qmp6988<I2C, D> {
207    address: SevenBitAddress,
208    coe: Coe,
209    delay: D,
210    filter: IirFilter,
211    i2c: I2C,
212    k: K,
213    oversampling_setting: OverSamplingSetting,
214}
215
216impl<I2C, D> Qmp6988<I2C, D>
217where
218    I2C: hal::i2c::I2c,
219    D: hal::delay::DelayNs,
220{
221    /// Perform a measurement of pressure and temperature.
222    ///
223    /// This uses the forced power mode to perform a single measurement and
224    /// automatically go back to the sleep power mode where the sensor has the
225    /// lowest current consumption.
226    #[maybe_async_cfg::maybe(
227        sync(not(feature = "async"), keep_self),
228        async(feature = "async", keep_self)
229    )]
230    pub async fn measure(
231        &mut self,
232    ) -> Result<TemperatureAndBarometricPressure<Celsius>, Error<I2C::Error>> {
233        self.apply_power_mode(PowerMode::Forced).await?;
234        self.delay.delay_ms(self.get_measurement_duration()).await;
235        let mut data = [0u8; 6];
236        let mut operations = [Operation::Write(&[PRESS_TXD2]), Operation::Read(&mut data)];
237        self.i2c.transaction(self.address, &mut operations).await?;
238        let dp: &[u8; 3] = &data[0..3].try_into().unwrap();
239        let dt: &[u8; 3] = &data[3..6].try_into().unwrap();
240        let dp = Self::get_i32_value(dp) - 8_388_608;
241        let dt = Self::get_i32_value(dt) - 8_388_608;
242        let temperature = self.compensate_temperature(dt);
243        let pressure = self.compensate_pressure(dp, temperature);
244        Ok(TemperatureAndBarometricPressure {
245            temperature: Celsius(temperature / 256.0),
246            barometric_pressure: BarometricPressure(pressure / 100.0),
247        })
248    }
249
250    /// Create a new instance of the QMP6988 device.
251    #[maybe_async_cfg::maybe(
252        sync(not(feature = "async"), keep_self),
253        async(feature = "async", keep_self)
254    )]
255    pub async fn new(
256        i2c: I2C,
257        address: SevenBitAddress,
258        delay: D,
259    ) -> Result<Self, Error<I2C::Error>> {
260        let mut device = Self {
261            address,
262            coe: Coe::default(),
263            delay,
264            filter: IirFilter::default(),
265            i2c,
266            k: K::default(),
267            oversampling_setting: OverSamplingSetting::default(),
268        };
269        device.check_device().await?;
270        device.get_calibration_data().await?;
271        device.apply_filter().await?;
272        device.apply_measure_control_parameters().await?;
273        Ok(device)
274    }
275
276    /// Perform a soft reset.
277    #[maybe_async_cfg::maybe(
278        sync(not(feature = "async"), keep_self),
279        async(feature = "async", keep_self)
280    )]
281    pub async fn reset(&mut self) -> Result<(), Error<I2C::Error>> {
282        self.i2c.write(self.address, &[RESET_REGISTER]).await?;
283        self.delay.delay_ms(10).await;
284        Ok(())
285    }
286
287    /// Define the IIR (Infinite Impulse Response) filter to use during the
288    /// measurements.
289    #[maybe_async_cfg::maybe(
290        sync(not(feature = "async"), keep_self),
291        async(feature = "async", keep_self)
292    )]
293    pub async fn set_filter(&mut self, filter: IirFilter) -> Result<(), Error<I2C::Error>> {
294        self.filter = filter;
295        self.apply_filter().await
296    }
297
298    /// Define the oversampling setting to use during the measurements.
299    #[maybe_async_cfg::maybe(
300        sync(not(feature = "async"), keep_self),
301        async(feature = "async", keep_self)
302    )]
303    pub async fn set_oversampling_setting(
304        &mut self,
305        oversampling_setting: OverSamplingSetting,
306    ) -> Result<(), Error<I2C::Error>> {
307        self.oversampling_setting = oversampling_setting;
308        self.apply_measure_control_parameters().await
309    }
310
311    #[maybe_async_cfg::maybe(
312        sync(not(feature = "async"), keep_self),
313        async(feature = "async", keep_self)
314    )]
315    async fn apply_filter(&mut self) -> Result<(), Error<I2C::Error>> {
316        let data = [IIR_CNT_REGISTER, self.filter as u8];
317        self.i2c.write(self.address, &data).await?;
318        self.delay.delay_ms(20).await;
319        Ok(())
320    }
321
322    #[maybe_async_cfg::maybe(
323        sync(not(feature = "async"), keep_self),
324        async(feature = "async", keep_self)
325    )]
326    async fn apply_measure_control_parameters(&mut self) -> Result<(), Error<I2C::Error>> {
327        let (pressure_oversampling, temperature_oversampling) = self.get_oversamplings();
328        let data = [
329            CTRL_MEAS_REGISTER,
330            (temperature_oversampling as u8) << 5
331                | (pressure_oversampling as u8) << 2
332                | (PowerMode::Sleep as u8),
333        ];
334        self.i2c.write(self.address, &data).await?;
335        self.delay.delay_ms(20).await;
336        Ok(())
337    }
338
339    #[maybe_async_cfg::maybe(
340        sync(not(feature = "async"), keep_self),
341        async(feature = "async", keep_self)
342    )]
343    async fn apply_power_mode(&mut self, power_mode: PowerMode) -> Result<(), Error<I2C::Error>> {
344        let mut data = [0u8; 1];
345        let mut operations = [
346            Operation::Write(&[CTRL_MEAS_REGISTER]),
347            Operation::Read(&mut data),
348        ];
349        self.i2c.transaction(self.address, &mut operations).await?;
350        let data = [CTRL_MEAS_REGISTER, (data[0] & 0xfc) | power_mode as u8];
351        self.i2c.write(self.address, &data).await?;
352        self.delay.delay_ms(20).await;
353        Ok(())
354    }
355
356    #[maybe_async_cfg::maybe(
357        sync(not(feature = "async"), keep_self),
358        async(feature = "async", keep_self)
359    )]
360    async fn check_device(&mut self) -> Result<(), Error<I2C::Error>> {
361        let mut chip_id = [0u8; 1];
362        let mut operations = [
363            Operation::Write(&[CHIP_ID_REGISTER]),
364            Operation::Read(&mut chip_id),
365        ];
366        self.i2c.transaction(self.address, &mut operations).await?;
367        if chip_id[0] == 0x5c {
368            Ok(())
369        } else {
370            Err(Error::ChipNotDetected)
371        }
372    }
373
374    fn compensate_pressure(&self, dp: i32, temperature: f32) -> f32 {
375        let dp = dp as f32;
376        self.k.b00
377            + self.k.bt1 * temperature
378            + self.k.bp1 * dp
379            + self.k.b11 * temperature * dp
380            + self.k.bt2 * temperature.powf(2.0)
381            + self.k.bp2 * dp.powf(2.0)
382            + self.k.b12 * dp * temperature.powf(2.0)
383            + self.k.b21 * dp.powf(2.0) * temperature
384            + self.k.bp3 * dp.powf(3.0)
385    }
386
387    fn compensate_temperature(&self, dt: i32) -> f32 {
388        let dt = dt as f32;
389        self.k.a0 + self.k.a1 * dt + self.k.a2 * dt.powf(2.0)
390    }
391
392    #[maybe_async_cfg::maybe(
393        sync(not(feature = "async"), keep_self),
394        async(feature = "async", keep_self)
395    )]
396    async fn get_calibration_data(&mut self) -> Result<(), Error<I2C::Error>> {
397        let mut coe = [0u8; 25];
398        let mut operations = [
399            Operation::Write(&[COE_B00_1_REGISTER]),
400            Operation::Read(&mut coe),
401        ];
402        self.i2c.transaction(self.address, &mut operations).await?;
403        self.coe = (&coe).into();
404        self.k = (&self.coe).into();
405        Ok(())
406    }
407
408    fn get_measurement_duration(&self) -> u32 {
409        match self.oversampling_setting {
410            OverSamplingSetting::HighSpeed => 6,
411            OverSamplingSetting::LowPower => 8,
412            OverSamplingSetting::Standard => 11,
413            OverSamplingSetting::HighAccuracy => 19,
414            OverSamplingSetting::UltraHighAccuracy => 34,
415        }
416    }
417
418    fn get_oversamplings(&self) -> (OverSampling, OverSampling) {
419        match self.oversampling_setting {
420            OverSamplingSetting::HighSpeed => (OverSampling::X2, OverSampling::X1),
421            OverSamplingSetting::LowPower => (OverSampling::X4, OverSampling::X1),
422            OverSamplingSetting::Standard => (OverSampling::X8, OverSampling::X1),
423            OverSamplingSetting::HighAccuracy => (OverSampling::X16, OverSampling::X2),
424            OverSamplingSetting::UltraHighAccuracy => (OverSampling::X32, OverSampling::X4),
425        }
426    }
427
428    #[inline]
429    fn get_i32_value(data: &[u8; 3]) -> i32 {
430        ((data[0] as u32) << 16 | (data[1] as u32) << 8 | (data[2] as u32)) as i32
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use embedded_hal::i2c::ErrorKind;
437    use embedded_hal_mock::eh1::delay::StdSleep as Delay;
438    use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
439    use rstest::rstest;
440
441    use weather_utils::Altitude;
442
443    use crate::*;
444
445    fn create_device() -> Qmp6988<I2cMock, Delay> {
446        let expectations = [
447            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
448            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CHIP_ID_REGISTER].to_vec()),
449            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x5c].to_vec()),
450            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
451            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
452            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [COE_B00_1_REGISTER].to_vec()),
453            I2cTransaction::read(
454                DEFAULT_I2C_ADDRESS,
455                [
456                    0x48, 0xE3, 0xF7, 0xD5, 0x04, 0x50, 0xFD, 0x02, 0xF3, 0xCB, 0x0A, 0x5D, 0x1F,
457                    0x8C, 0x09, 0x13, 0xF9, 0xB6, 0xF7, 0x68, 0xD1, 0x62, 0xEB, 0xF2, 0x4E,
458                ]
459                .to_vec(),
460            ),
461            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
462            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [IIR_CNT_REGISTER, 0x02].to_vec()),
463            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CTRL_MEAS_REGISTER, 0x30].to_vec()),
464        ];
465        let i2c = I2cMock::new(&expectations);
466        let mut device = Qmp6988::new(i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
467        device.i2c.done();
468        device
469    }
470
471    #[test]
472    fn chip_not_detected() {
473        let expectations = [
474            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
475            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CHIP_ID_REGISTER].to_vec()),
476            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x2a].to_vec()),
477            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
478        ];
479        let mut i2c = I2cMock::new(&expectations);
480        assert!(matches!(
481            Qmp6988::new(i2c.by_ref(), DEFAULT_I2C_ADDRESS, Delay {}),
482            Err(Error::ChipNotDetected)
483        ));
484        i2c.done();
485    }
486
487    #[test]
488    fn measure() {
489        let expectations = [
490            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
491            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CTRL_MEAS_REGISTER].to_vec()),
492            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x30].to_vec()),
493            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
494            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CTRL_MEAS_REGISTER, 0x31].to_vec()),
495            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
496            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [PRESS_TXD2].to_vec()),
497            I2cTransaction::read(
498                DEFAULT_I2C_ADDRESS,
499                [0xA4, 0x92, 0xF1, 0x6E, 0x0D, 0x98].to_vec(),
500            ),
501            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
502        ];
503        let mut device = create_device();
504        device.i2c.update_expectations(&expectations);
505        let measure = device.measure();
506        assert!(matches!(measure, Ok(_)));
507        let measure = measure.unwrap();
508        assert_eq!(
509            measure,
510            TemperatureAndBarometricPressure {
511                temperature: Celsius(20.87),
512                barometric_pressure: BarometricPressure(981.19),
513            }
514        );
515        assert_eq!(measure.altitude(), Altitude(277.31));
516        device.i2c.done();
517    }
518
519    #[test]
520    fn reset() {
521        let expectations = [I2cTransaction::write(
522            DEFAULT_I2C_ADDRESS,
523            [RESET_REGISTER].to_vec(),
524        )];
525        let mut device = create_device();
526        device.i2c.update_expectations(&expectations);
527        assert!(matches!(device.reset(), Ok(())));
528        device.i2c.done();
529    }
530
531    #[test]
532    fn reset_with_arbitration_loss_error() {
533        let expectations = [
534            I2cTransaction::write(DEFAULT_I2C_ADDRESS, [RESET_REGISTER].to_vec())
535                .with_error(ErrorKind::ArbitrationLoss),
536        ];
537        let mut device = create_device();
538        device.i2c.update_expectations(&expectations);
539        assert!(matches!(device.reset(), Err(Error::I2c(_))));
540        device.i2c.done();
541    }
542
543    #[test]
544    fn set_filter() {
545        let expectations = [I2cTransaction::write(
546            DEFAULT_I2C_ADDRESS,
547            [IIR_CNT_REGISTER, 0x05].to_vec(),
548        )];
549        let mut device = create_device();
550        device.i2c.update_expectations(&expectations);
551        assert!(matches!(device.set_filter(IirFilter::Coeff32), Ok(())));
552        device.i2c.done();
553    }
554
555    #[rstest]
556    #[case(OverSamplingSetting::HighSpeed, 0x28, 6)]
557    #[case(OverSamplingSetting::LowPower, 0x2C, 8)]
558    #[case(OverSamplingSetting::Standard, 0x30, 11)]
559    #[case(OverSamplingSetting::HighAccuracy, 0x54, 19)]
560    #[case(OverSamplingSetting::UltraHighAccuracy, 0x78, 34)]
561    fn set_oversampling_setting(
562        #[case] oversampling_setting: OverSamplingSetting,
563        #[case] expected_ctrl_meas_value: u8,
564        #[case] expected_measurement_duration: u32,
565    ) {
566        let expectations = [I2cTransaction::write(
567            DEFAULT_I2C_ADDRESS,
568            [CTRL_MEAS_REGISTER, expected_ctrl_meas_value].to_vec(),
569        )];
570        let mut device = create_device();
571        device.i2c.update_expectations(&expectations);
572        assert!(matches!(
573            device.set_oversampling_setting(oversampling_setting),
574            Ok(())
575        ));
576        assert_eq!(
577            device.get_measurement_duration(),
578            expected_measurement_duration
579        );
580        device.i2c.done();
581    }
582}