ms5611_spi/
lib.rs

1//! no_std driver for the MS5611 (barometric pressure sensor)
2//!
3//!
4//! # Usage
5//!
6//! Use embedded-hal implementation to get SPI, NCS, and delay, then create
7//! a ms5611 handle
8//!
9//! ```ignore
10//! // Create handle
11//! let ms5611 = Ms5611::new(spi, ncs, Delay)?;
12//! // Get a second order compensated pressure (and thermo) sample,
13//! let sample = ms5611.get_second_order_sample(Oversampling::OS_2048)?;
14//! println!("{:?}", sample);
15//! ```
16//!
17//! # References
18//!
19//! - [Product specification][1]
20//!
21//! [1]: https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036
22
23#![cfg_attr(not(test), no_std)]
24extern crate embedded_hal as hal;
25
26// Feature gate for MS5607
27#[cfg(all(feature = "ms5611", feature = "ms5607"))]
28compile_error!("Cannot specify both ms5611 and ms5607 flags. Try setting default-features = false");
29
30use hal::blocking::delay::DelayMs;
31use hal::blocking::spi::{Transfer, Write};
32use hal::digital::v2::OutputPin;
33
34/// MS5611 driver
35pub struct Ms5611<SPI, NCS> {
36    spi: SPI,
37    ncs: NCS,
38    coeffs: Coefficients,
39}
40
41impl<SPI, NCS, E> Ms5611<SPI, NCS>
42where
43    SPI: Transfer<u8, Error = E> + Write<u8, Error = E>,
44    NCS: OutputPin,
45{
46    /// Creates a new MS5611 driver from a SPI peripheral and a NCS pin
47    pub fn new(spi: SPI, ncs: NCS, delay: &mut impl DelayMs<u8>) -> Result<Ms5611<SPI, NCS>, E> {
48        let mut ms5611 = Ms5611 {
49            spi,
50            ncs,
51            coeffs: Coefficients::default(),
52        };
53
54        ms5611.reset(delay)?;
55        ms5611.coeffs = ms5611.read_coefficients()?;
56        assert!(ms5611.coeffs.check_crc());
57
58        Ok(ms5611)
59    }
60
61    /// Return the temperature compensation variables `dt`, `sens` and
62    /// `off` from the raw sample temperature and the coefficients.
63    fn get_temperature_compensation(&self, raw_sample: Sample) -> (i64, i64, i64) {
64        let dt: i64 = ((raw_sample.temperature as i32)
65            - ((self.coeffs.get_data(CoefficientsAddr::COEFF_5) as i32) << 8))
66            as i64;
67
68        #[cfg(any(feature = "ms5611"))]
69        let (offset_sh, sens_sh) = ((16, 7), (15, 8));
70        #[cfg(any(feature = "ms5607"))]
71        let (offset_sh, sens_sh) = ((17, 6), (16, 7));
72
73        let off = ((self.coeffs.get_data(CoefficientsAddr::COEFF_2) as i64) << offset_sh.0)
74            + (((self.coeffs.get_data(CoefficientsAddr::COEFF_4) as i64) * dt) >> offset_sh.1);
75
76        let sens = ((self.coeffs.get_data(CoefficientsAddr::COEFF_1) as i64) << sens_sh.0)
77            + (((self.coeffs.get_data(CoefficientsAddr::COEFF_3) as i64) * dt) >> sens_sh.1);
78
79        (dt, sens, off)
80    }
81
82    /// Reads and returns Pressure and Thermometer measurement
83    pub fn get_compensated_sample(&mut self, osr: Oversampling, delay_source: &mut impl DelayMs<u8>) -> Result<Sample, E> {
84        let raw_sample = self.read_raw_sample(osr, delay_source)?;
85
86        // Get temperature compensation constants
87        let (dt, sens, off) = self.get_temperature_compensation(raw_sample);
88
89        let temperature = (2000i64
90            + ((dt as i64) * (self.coeffs.get_data(CoefficientsAddr::COEFF_6) as i64) >> 23))
91            as i32;
92        let pressure = (((raw_sample.pressure as i64) * sens >> 21) - off) >> 15;
93
94        let sample = Sample {
95            pressure: pressure as i32,
96            temperature: temperature as i32,
97        };
98        Ok(sample)
99    }
100
101    /// Reads and returns a second order compensated Pressure and Thermometer
102    /// measurement as defined in datasheet.
103    pub fn get_second_order_sample(&mut self, osr: Oversampling, delay_source: &mut impl DelayMs<u8>) -> Result<Sample, E> {
104        let raw_sample = self.read_raw_sample(osr, delay_source)?;
105
106        // Get temperature compensation constants
107        let (dt, mut sens, mut off) = self.get_temperature_compensation(raw_sample);
108
109        let mut temperature = (2000i64
110            + ((dt as i64) * (self.coeffs.get_data(CoefficientsAddr::COEFF_6) as i64) >> 23))
111            as i32;
112
113        // Check low temp
114        let mut offsets = if temperature < 2000 {
115            (
116                ((dt as i64 * dt as i64) >> 31) as i32,
117                (5 * (temperature - 2000) * (temperature - 2000)) >> 1 as i32,
118                (5 * (temperature - 2000) * (temperature - 2000)) >> 2 as i32,
119            )
120        } else {
121            (0, 0, 0)
122        };
123
124        // Check very low temp
125        if temperature < -1500 {
126            offsets.0 = offsets.0 + 7 * (temperature + 1500) * (temperature + 1500);
127            offsets.1 = offsets.1 + 11 * ((temperature + 1500) * (temperature + 1500) >> 1);
128        }
129
130        off -= offsets.1 as i64;
131        sens -= offsets.2 as i64;
132
133        temperature = temperature - offsets.0;
134        let pressure = (((raw_sample.pressure as i64) * sens >> 21) - off) >> 15;
135
136        let sample = Sample {
137            pressure: pressure as i32,
138            temperature: temperature as i32,
139        };
140        Ok(sample)
141    }
142
143    fn send(&mut self, addr: u8) -> Result<(), E> {
144        let _ = self.ncs.set_low();
145        self.spi.write(&[addr])?;
146        let _ = self.ncs.set_high();
147        Ok(())
148    }
149
150    fn read_raw(&mut self, addr: u8) -> Result<u32, E> {
151        let mut buffer = [0; 4];
152        buffer[0] = addr;
153        let _ = self.ncs.set_low();
154        self.spi.transfer(&mut buffer)?;
155        let _ = self.ncs.set_high();
156
157        let r = ((buffer[1] as u32) << 16) | ((buffer[2] as u32) << 8) | (buffer[3] as u32);
158
159        Ok(r)
160    }
161
162    fn read_raw_u16(&mut self, addr: u8) -> Result<u16, E> {
163        let mut buffer = [0; 3];
164        buffer[0] = addr;
165        let _ = self.ncs.set_low();
166        self.spi.transfer(&mut buffer)?;
167        let _ = self.ncs.set_high();
168
169        let r = ((buffer[1] as u16) << 8) | (buffer[2] as u16);
170
171        Ok(r)
172    }
173
174    fn read_raw_sample(&mut self, osr: Oversampling, delay_source: &mut impl  DelayMs<u8>) -> Result<Sample, E> {
175        // Start convertion of D1 (pressure)
176        self.send(Command::CONV_D1.address() + osr.offset())?;
177        delay_source.delay_ms(osr.delay());
178        let raw_pressure = self.read_raw(Command::ADC_READ.address())?;
179
180        // Start convertion of D2 (temperature)
181        self.send(Command::CONV_D2.address() + osr.offset())?;
182        delay_source.delay_ms(osr.delay());
183        let raw_temperature = self.read_raw(Command::ADC_READ.address())?;
184
185        let sample = Sample {
186            pressure: raw_pressure as i32,
187            temperature: raw_temperature as i32,
188        };
189
190        Ok(sample)
191    }
192
193    fn reset(&mut self, delay_source: &mut impl DelayMs<u8>) -> Result<(), E> {
194        self.send(Command::RESET.address())?;
195        delay_source.delay_ms(3);
196        Ok(())
197    }
198
199    fn read_coefficients(&mut self) -> Result<Coefficients, E> {
200        let mut buffer = [0x00u16; 8];
201
202        buffer[CoefficientsAddr::MANUFACTURER as usize >> 1] =
203            self.read_raw_u16(Command::prom_address(CoefficientsAddr::MANUFACTURER))?;
204        buffer[CoefficientsAddr::COEFF_1 as usize >> 1] =
205            self.read_raw_u16(Command::prom_address(CoefficientsAddr::COEFF_1))?;
206        buffer[CoefficientsAddr::COEFF_2 as usize >> 1] =
207            self.read_raw_u16(Command::prom_address(CoefficientsAddr::COEFF_2))?;
208        buffer[CoefficientsAddr::COEFF_3 as usize >> 1] =
209            self.read_raw_u16(Command::prom_address(CoefficientsAddr::COEFF_3))?;
210        buffer[CoefficientsAddr::COEFF_4 as usize >> 1] =
211            self.read_raw_u16(Command::prom_address(CoefficientsAddr::COEFF_4))?;
212        buffer[CoefficientsAddr::COEFF_5 as usize >> 1] =
213            self.read_raw_u16(Command::prom_address(CoefficientsAddr::COEFF_5))?;
214        buffer[CoefficientsAddr::COEFF_6 as usize >> 1] =
215            self.read_raw_u16(Command::prom_address(CoefficientsAddr::COEFF_6))?;
216        buffer[CoefficientsAddr::CRC as usize >> 1] =
217            self.read_raw_u16(Command::prom_address(CoefficientsAddr::CRC))?;
218
219        Ok(Coefficients { data: buffer })
220    }
221}
222
223/// Pressure and Thermometer measurement
224#[derive(Debug, Clone, Copy, PartialEq)]
225pub struct Sample {
226    pub pressure: i32,
227    pub temperature: i32,
228}
229
230#[allow(non_camel_case_types)]
231#[derive(Clone, Copy)]
232enum Command {
233    RESET = 0x1E,
234    CONV_D1 = 0x40,
235    CONV_D2 = 0x50,
236    ADC_READ = 0x00,
237    PROM_BASE = 0xA0,
238}
239
240impl Command {
241    pub fn address(self) -> u8 {
242        self as u8
243    }
244
245    pub fn prom_address(offset: CoefficientsAddr) -> u8 {
246        Command::PROM_BASE.address() + offset as u8
247    }
248}
249
250
251/// Oversampling rates as defined in datasheet
252/// defines for how long reading a sample will block
253#[allow(non_camel_case_types)]
254#[derive(Clone, Copy)]
255pub enum Oversampling {
256    OS_256,
257    OS_512,
258    OS_1024,
259    OS_2048,
260    OS_4096,
261}
262
263impl Oversampling {
264    pub fn offset(self) -> u8 {
265        match self {
266            Oversampling::OS_256 => 0,
267            Oversampling::OS_512 => 2,
268            Oversampling::OS_1024 => 4,
269            Oversampling::OS_2048 => 6,
270            Oversampling::OS_4096 => 8,
271        }
272    }
273
274    pub fn delay(self) -> u8 {
275        match self {
276            Oversampling::OS_256 => 1,
277            Oversampling::OS_512 => 2,
278            Oversampling::OS_1024 => 3,
279            Oversampling::OS_2048 => 5,
280            Oversampling::OS_4096 => 10,
281        }
282    }
283}
284
285/// Default factory coefficients
286#[derive(Debug, Default)]
287struct Coefficients {
288    data: [u16; 8],
289}
290
291#[allow(non_camel_case_types)]
292enum CoefficientsAddr {
293    MANUFACTURER = 0x0,
294    COEFF_1 = 0x2,
295    COEFF_2 = 0x4,
296    COEFF_3 = 0x6,
297    COEFF_4 = 0x8,
298    COEFF_5 = 0xA,
299    COEFF_6 = 0xC,
300    CRC = 0xE,
301}
302
303impl Coefficients {
304    fn get_data(&self, addr: CoefficientsAddr) -> u16 {
305        (self.data[addr as usize >> 1])
306    }
307
308    fn get_crc(&self) -> u8 {
309        ((self.get_data(CoefficientsAddr::CRC) & 0xF) as u8)
310    }
311
312    pub fn check_crc(&self) -> bool {
313        let mut crc: u16 = 0;
314        let data_crc = self.get_crc() as u16;
315        for item in self.data[..self.data.len() - 1].iter() {
316            crc = Self::crc_coefficient(crc, item);
317        }
318        crc = Self::crc_coefficient(crc, &(self.get_data(CoefficientsAddr::CRC) & 0xFF00));
319
320        crc = (crc >> 12) & 0xF;
321        (crc == data_crc)
322    }
323
324    fn crc_coefficient(crc: u16, coefficient: &u16) -> u16 {
325        let mut crc = crc;
326        crc ^= (coefficient >> 8) & 0xFFu16;
327        crc = Self::crc_round(crc);
328        crc ^= coefficient & 0xFF;
329        crc = Self::crc_round(crc);
330        (crc)
331    }
332
333    fn crc_round(crc: u16) -> u16 {
334        let mut crc = crc;
335        for _ in (1..9).rev() {
336            crc = if (crc & 0x8000) > 0 {
337                (crc << 1) ^ 0x3000
338            } else {
339                (crc << 1)
340            }
341        }
342        (crc)
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    extern crate embedded_hal_mock;
349
350    use self::embedded_hal_mock::delay::MockNoop;
351    use self::embedded_hal_mock::spi::{Mock as SpiMock, Transaction as SpiTransaction};
352    use super::*;
353
354    struct Pin;
355
356    impl hal::digital::v2::OutputPin for Pin {
357	type Error = u32;
358        fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) }
359        fn set_high(&mut self) -> Result<(), Self::Error> { Ok(()) }
360    }
361
362    #[test]
363    fn get_coeffs() {
364        let data = [
365            0x3132, 0x3334, 0x3536, 0x3738, 0x3940, 0x4142, 0x4344, 0x450b,
366        ];
367        let coeffs = Coefficients { data };
368
369        assert_eq!(coeffs.get_data(CoefficientsAddr::MANUFACTURER), 0x3132);
370        assert_eq!(coeffs.get_data(CoefficientsAddr::COEFF_1), 0x3334);
371        assert_eq!(coeffs.get_data(CoefficientsAddr::COEFF_2), 0x3536);
372        assert_eq!(coeffs.get_data(CoefficientsAddr::COEFF_3), 0x3738);
373        assert_eq!(coeffs.get_data(CoefficientsAddr::COEFF_4), 0x3940);
374        assert_eq!(coeffs.get_data(CoefficientsAddr::COEFF_5), 0x4142);
375        assert_eq!(coeffs.get_data(CoefficientsAddr::COEFF_6), 0x4344);
376        assert_eq!(coeffs.get_data(CoefficientsAddr::CRC), 0x450b);
377
378        assert_eq!(coeffs.get_crc(), 0xb);
379    }
380
381    #[test]
382    fn check_crc() {
383        let mut data = [
384            0x0024, 0xB3D8, 0xBD83, 0x6E00, 0x628A, 0x8063, 0x6ADB, 0x947B,
385        ];
386        let coeffs = Coefficients { data };
387        assert_eq!(coeffs.check_crc(), true);
388
389        data[7] = 0x460b;
390        let coeffs = Coefficients { data };
391        assert_eq!(coeffs.check_crc(), false);
392    }
393
394    #[test]
395    #[cfg(feature = "ms5611")]
396    fn read_compensated_samples_ms5611() {
397        /* The following values are taken from the example in the datasheet */
398        let expectations = [
399            SpiTransaction::write(vec![0x1E]),
400            SpiTransaction::transfer(vec![0xA0, 0, 0], vec![0, 0x00, 0x00]),
401            SpiTransaction::transfer(vec![0xA2, 0, 0], vec![0, 0x9C, 0xBF]), // 40127
402            SpiTransaction::transfer(vec![0xA4, 0, 0], vec![0, 0x90, 0x3C]), // 36924
403            SpiTransaction::transfer(vec![0xA6, 0, 0], vec![0, 0x5B, 0x15]), // 23317
404            SpiTransaction::transfer(vec![0xA8, 0, 0], vec![0, 0x5A, 0xF2]), // 23282
405            SpiTransaction::transfer(vec![0xAA, 0, 0], vec![0, 0x82, 0xB8]), // 33464
406            SpiTransaction::transfer(vec![0xAC, 0, 0], vec![0, 0x6E, 0x98]), // 28312
407            SpiTransaction::transfer(vec![0xAE, 0, 0], vec![0, 0x00, 0x00]),
408            SpiTransaction::write(vec![0x46]), // Convert D1 OSR=2048
409            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x8A, 0xA2, 0x1A]), // 9085466
410            SpiTransaction::write(vec![0x56]), // Convert D2 OSR=2048
411            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x82, 0xC1, 0x3E]), // 9085466
412            SpiTransaction::write(vec![0x46]), // Convert D1 OSR=2048
413            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x8A, 0xA2, 0x1A]), // 9085466
414            SpiTransaction::write(vec![0x56]), // Convert D2 OSR=2048
415            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x82, 0xC1, 0x3E]), // 9085466
416        ];
417
418        let spi = SpiMock::new(&expectations);
419        let pin = Pin;
420        let mut delay_source = MockNoop::new();
421        let mut ms5611 = Ms5611::new(spi, pin, &mut delay_source).unwrap();
422        let sample1 = ms5611
423            .get_compensated_sample(Oversampling::OS_2048, &mut delay_source)
424            .unwrap();
425        let sample2 = ms5611
426            .get_second_order_sample(Oversampling::OS_2048, &mut delay_source)
427            .unwrap();
428
429        assert_eq!(
430            Sample {
431                pressure: 100009,
432                temperature: 2007
433            },
434            sample1
435        );
436        assert_eq!(sample1, sample2);
437    }
438
439    #[test]
440    #[cfg(feature = "ms5607")]
441    fn read_compensated_samples_ms5607() {
442        /* The following values are taken from the example in the datasheet */
443        let expectations = [
444            SpiTransaction::write(vec![0x1E]),
445            SpiTransaction::transfer(vec![0xA0, 0, 0], vec![0, 0x00, 0x00]),
446            SpiTransaction::transfer(vec![0xA2, 0, 0], vec![0, 0xB5, 0x24]), // 46372
447            SpiTransaction::transfer(vec![0xA4, 0, 0], vec![0, 0xAB, 0xCD]), // 43981
448            SpiTransaction::transfer(vec![0xA6, 0, 0], vec![0, 0x71, 0x83]), // 29059
449            SpiTransaction::transfer(vec![0xA8, 0, 0], vec![0, 0x6C, 0xC2]), // 27842
450            SpiTransaction::transfer(vec![0xAA, 0, 0], vec![0, 0x7B, 0x41]), // 31553
451            SpiTransaction::transfer(vec![0xAC, 0, 0], vec![0, 0x6E, 0x05]), // 28165
452            SpiTransaction::transfer(vec![0xAE, 0, 0], vec![0, 0x00, 0x08]),
453            SpiTransaction::write(vec![0x46]), // Convert D1 OSR=2048
454            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x62, 0xA7, 0xA4]), // 6465444
455            SpiTransaction::write(vec![0x56]), // Convert D2 OSR=2048
456            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x7B, 0x41, 0x44]), // 8077636
457            SpiTransaction::write(vec![0x46]), // Convert D1 OSR=2048
458            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x62, 0xA7, 0xA4]), // 6465444
459            SpiTransaction::write(vec![0x56]), // Convert D2 OSR=2048
460            SpiTransaction::transfer(vec![0, 0, 0, 0], vec![0, 0x7B, 0x41, 0x44]), // 8077636
461        ];
462
463        let spi = SpiMock::new(&expectations);
464        let pin = Pin;
465        let mut delay_source = MockNoop::new();
466        let mut ms5611 = Ms5611::new(spi, pin, &mut delay_source).unwrap();
467        let sample1 = ms5611
468            .get_compensated_sample(Oversampling::OS_2048, &mut delay_source)
469            .unwrap();
470        let sample2 = ms5611
471            .get_second_order_sample(Oversampling::OS_2048, &mut delay_source)
472            .unwrap();
473
474        assert_eq!(
475            Sample {
476                pressure: 110002,
477                temperature: 2000
478            },
479            sample1
480        );
481        assert_eq!(sample1, sample2);
482    }
483}