i2cdev_bmp280/
lib.rs

1// Copyright 2017, Martin Deegan <mddeegan@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/license/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option.  This file may not be copied, modified, or distributed
7// except according to those terms.
8
9// This module is implementing userland library to interact with
10// Bosh BMP280 i2c temperature and pressure sensor.
11
12#![allow(dead_code)]
13
14extern crate i2cdev;
15extern crate i2csensors;
16extern crate byteorder;
17
18use i2csensors::{Barometer, Thermometer};
19use std::thread;
20use std::time::Duration;
21use std::error::Error;
22use i2cdev::core::I2CDevice;
23#[cfg(any(target_os = "linux", target_os = "android"))]
24use i2cdev::linux::{LinuxI2CDevice,LinuxI2CError};
25use byteorder::{ByteOrder, BigEndian, LittleEndian};
26
27pub const BMP280_I2C_ADDR: u16 = 0x77;
28
29const BMP280_PRESS_MSB: u8 = 0xF7;
30const BMP280_PRESS_LSB: u8 = 0xF8;
31const BMP280_PRESS_XLSB: u8 = 0xF9;
32
33const BMP280_TEMP_MSB: u8 = 0xFA;
34const BMP280_TEMP_LSB: u8 = 0xFB;
35const BMP280_TEMP_XLSB: u8 = 0xFC;
36
37/// Algorithm to process measurements.
38/// Float is significantly more expensive.
39#[derive(Copy,Clone)]
40pub enum BMP280CompensationAlgorithm{
41    B32,
42    B64,
43    Float
44}
45
46#[derive(Copy,Clone)]
47struct BMP280CalibrationCoefficients {
48    pub dig_t1: u16,
49    pub dig_t2: i16,
50    pub dig_t3: i16,
51    pub dig_p1: u16,
52    pub dig_p2: i16,
53    pub dig_p3: i16,
54    pub dig_p4: i16,
55    pub dig_p5: i16,
56    pub dig_p6: i16,
57    pub dig_p7: i16,
58    pub dig_p8: i16,
59    pub dig_p9: i16,
60}
61
62impl BMP280CalibrationCoefficients {
63    pub fn new<E: Error>(i2cdev: &mut I2CDevice<Error = E>) -> Result<BMP280CalibrationCoefficients, E> {
64//        /*
65        let mut buf = [0_u8; 26];
66        let mut register: u8 = 0x88;
67        try!(i2cdev.write(&[register]));
68        try!(i2cdev.read(&mut buf));
69
70        Ok(BMP280CalibrationCoefficients {
71            dig_t1: LittleEndian::read_u16(&buf[0..2]),
72            dig_t2: LittleEndian::read_i16(&buf[2..4]),
73            dig_t3: LittleEndian::read_i16(&buf[4..6]),
74            dig_p1: LittleEndian::read_u16(&buf[6..8]),
75            dig_p2: LittleEndian::read_i16(&buf[8..10]),
76            dig_p3: LittleEndian::read_i16(&buf[10..12]),
77            dig_p4: LittleEndian::read_i16(&buf[12..14]),
78            dig_p5: LittleEndian::read_i16(&buf[14..16]),
79            dig_p6: LittleEndian::read_i16(&buf[16..18]),
80            dig_p7: LittleEndian::read_i16(&buf[18..20]),
81            dig_p8: LittleEndian::read_i16(&buf[20..22]),
82            dig_p9: LittleEndian::read_i16(&buf[22..24])
83        })
84//        */
85    }
86}
87
88#[derive(Copy,Clone)]
89pub enum BMP280PowerMode {
90    SleepMode = 0b00000000,
91    NormalMode = 0b00000011,
92    ForcedMode = 0b00000001
93}
94
95/// Ultra low power: ×1 16 bit / 2.62 Pa
96/// Low power: ×2 17 bit / 1.31 Pa
97/// Standard resolution: ×4 18 bit / 0.66 Pa
98/// High resolution: ×8 19 bit / 0.33 Pa
99/// Ultra high resolution: ×16 20 bit / 0.16 Pa
100#[derive(Copy,Clone)]
101pub enum BMP280PressureOversampling {
102    Off = 0b00000000,
103    UltraLowPower = 0b00000100,
104    LowPower= 0b00001000,
105    StandardResolution = 0b00001100,
106    HighResolution = 0b00010000,
107    UltraHighResolution = 0b00010100
108}
109
110/// ×1: 16 bit / 0.0050 °C
111/// ×2: 17 bit / 0.0025 °C
112/// ×4: 18 bit / 0.0012 °C
113/// ×8: 19 bit / 0.0006 °C
114/// ×16: 20 bit / 0.0003 °C
115///
116/// Recommended:
117/// 1x for all UltraLowPower->HighResolution
118/// 2x for UltraHighResolution
119#[derive(Copy,Clone)]
120pub enum BMP280TemperatureOversampling {
121    Off,
122    x1 = 0b00100000,
123    x2 = 0b01000000,
124    x4 = 0b01100000,
125    x8 = 0b10000000,
126    x16 = 0b10100000
127}
128
129///Off = 1, Low = 2, Medium = 4, High = 8, UltraHigh = 16
130#[derive(Copy,Clone)]
131pub enum BMP280FilterCoefficient {
132    Off = 0b00000100,
133    Low = 0b00001000,
134    Medium = 0b00001100,
135    High = 0b00010000,
136    UltraHigh = 0b00010100
137}
138
139#[derive(Copy,Clone)]
140pub enum BMP280Timing {
141    ms0_5 = 0b00000000,
142    ms62_5 = 0b00100000,
143    ms125 = 0b01000000,
144    ms250 = 0b01100000,
145    ms500 = 0b10000000,
146    ms1000 = 0b10100000,
147    ms2000 = 0b11000000,
148    ms4000 = 0b11100000
149}
150
151/// [data sheet](https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP280-DS001-18.pdf)
152#[derive(Copy,Clone)]
153pub struct BMP280Settings {
154    pub compensation: BMP280CompensationAlgorithm,
155    pub t_sb: BMP280Timing,
156    pub iir_filter_coeff: BMP280FilterCoefficient,
157    pub osrs_t: BMP280TemperatureOversampling,
158    pub osrs_p: BMP280PressureOversampling,
159    pub power_mode: BMP280PowerMode
160}
161
162#[cfg(any(target_os = "linux", target_os = "android"))]
163pub fn get_linux_bmp280_i2c_device() -> Result<LinuxI2CDevice, LinuxI2CError> {
164    match LinuxI2CDevice::new("/dev/i2c-1", BMP280_I2C_ADDR) {
165        Ok(device) => Ok(device),
166        Err(e) => Err(e)
167    }
168}
169
170#[derive(Copy,Clone)]
171pub struct BMP280<T: I2CDevice + Sized> {
172    pub barometer: T,
173    coeff: BMP280CalibrationCoefficients,
174    t_fine: i32,
175    algorithm: BMP280CompensationAlgorithm
176}
177
178//unsafe impl<T> Send for BMP280<T> where T: I2CDevice + Sized {}
179
180impl<T> BMP280<T>
181    where T: I2CDevice + Sized
182{
183    #[cfg(any(target_os = "linux", target_os = "android"))]
184    pub fn new(mut i2cdev: T, settings: BMP280Settings) -> Result<BMP280<T>, T::Error> {
185        let id = try!(i2cdev.smbus_read_byte_data(0xD0));
186        assert!(id == 0x58);
187
188        let measurement_control = 0_u8 | settings.osrs_t as u8 | settings.osrs_p as u8 | settings.power_mode as u8;
189        let config = 0_u8 | settings.t_sb as u8 | settings.iir_filter_coeff as u8;
190
191        try!(i2cdev.smbus_write_byte_data(0xF4, measurement_control));
192        try!(i2cdev.smbus_write_byte_data(0xF5, config));
193
194        let coefficients = try!(BMP280CalibrationCoefficients::new(&mut i2cdev));
195
196        Ok(BMP280 {
197            barometer: i2cdev,
198            coeff: coefficients,
199            t_fine: 0,
200            algorithm: settings.compensation
201        })
202    }
203
204    #[cfg(any(target_os = "linux", target_os = "android"))]
205    pub fn reset(&mut self) -> Result<(), T::Error> {
206        try!(self.barometer.smbus_write_byte_data(0xE0, 0xB6));
207        Ok(())
208    }
209
210    #[cfg(any(target_os = "linux", target_os = "android"))]
211    pub fn set_mode(&mut self, mode: BMP280PowerMode) -> Result<(), T::Error> {
212        let mut ctrl_meas = try!(self.barometer.smbus_read_byte_data(0xF4));
213        ctrl_meas = ctrl_meas & 0b11111100;
214        ctrl_meas = ctrl_meas | mode as u8;
215        try!(self.barometer.smbus_write_byte_data(0xF4, ctrl_meas));
216        Ok(())
217    }
218
219    fn compensate_temperature_32b(&mut self, adc_t: i32) -> i32 {
220        let (mut var1, mut var2, mut t): (i32, i32, i32);
221        var1 = ((((adc_t >> 3) - ((self.coeff.dig_t1 as i32) <<1))) * (self.coeff.dig_t2 as i32)) >> 11;
222        var2 = (((((adc_t>>4) - (self.coeff.dig_t1 as i32)) * ((adc_t>>4) - (self.coeff.dig_t1 as i32))) >> 12) *
223            (self.coeff.dig_t3 as i32)) >> 14;
224        self.t_fine = var1 + var2;
225        t = (self.t_fine * 5 + 128) >> 8;
226        t
227    }
228
229    fn compensate_pressure_32b(&mut self, adc_p: i32) -> u32 {
230        let (mut var1, mut var2, mut p): (i32, i32, u32);
231
232        var1 = ((self.t_fine as i32) >> 1) - 64000;
233        var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * (self.coeff.dig_p6 as i32);
234        var2 = var2 + ((var1 * (self.coeff.dig_p5 as i32)) << 1);
235        var2 = (var2 >> 2) + ((self.coeff.dig_p4 as i32) << 16);
236        var1 = ((((self.coeff.dig_p3 as i32) * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + (((self.coeff.dig_p2 as i32) * var1) >> 1)) >> 18;
237        var1 = ((((32768 + var1)) * (self.coeff.dig_p1 as i32)) >> 15);
238        if var1 == 0 {
239            return 0; // avoid exception caused by division by zero
240        }
241        p = (((1048576 - adc_p) - (var2 >> 12)) as u32) * 3125;
242        if p < 0x80000000 {
243            p = (p << 1) / (var1 as u32);
244        } else {
245            p = (p / var1 as u32) * 2;
246        }
247        var1 = ((self.coeff.dig_p9 as i32) * ((((p >> 3) * (p >> 3)) >> 13) as i32)) >> 12;
248        var2 = (((p >> 2) as i32) * (self.coeff.dig_p8) as i32) >> 13;
249        p = ((p as i32) + ((var1 + var2 + (self.coeff.dig_p7 as i32)) >> 4)) as u32;
250        p
251    }
252
253    fn compensate_pressure_64b(&mut self, adc_p: i32) -> u32 {
254        let (mut var1, mut var2, mut p): (i64, i64, i64);
255        var1 = (self.t_fine as i64) - 128000;
256        var2 = var1 * var1 * (self.coeff.dig_p6 as i64);
257        var2 = var2 + ((var1 * (self.coeff.dig_p5 as i64)) << 17);
258        var2 = var2 + ((self.coeff.dig_p4 as i64) << 35);
259        var1 = ((var1 * var1 * (self.coeff.dig_p3 as i64))>>8) + ((var1 * (self.coeff.dig_p2 as i64)) << 12);
260        var1 = ((1_i64 << 47) + var1)*(self.coeff.dig_p1 as i64)>>33;
261        if var1 == 0 {
262            return 0; // avoid exception caused by division by zero
263        }
264        p = 1048576 - adc_p as i64;
265        p = (((p << 31)-var2) * 3125) / var1;
266        var1 = ((self.coeff.dig_p9 as i64) * (p >> 13) * (p >> 13)) >> 25;
267        var2 = ((self.coeff.dig_p8 as i64) * p) >> 19;
268        p = ((p + var1 + var2) >> 8) + ((self.coeff.dig_p7 as i64) << 4);
269        p as u32
270    }
271
272    fn compensate_temperature_float(&mut self, adc_t: i32) -> f64 {
273        let (mut var1, mut var2, mut t): (f64, f64, f64);
274        var1 = ((adc_t as f64)/16384.0 - (self.coeff.dig_t1 as f64)/1024.0) * (self.coeff.dig_t2 as f64);
275        var2 = (((adc_t as f64)/131072.0 - (self.coeff.dig_t1 as f64)/8192.0) *
276            ((adc_t as f64)/131072.0 - (self.coeff.dig_t1 as f64)/8192.0)) * (self.coeff.dig_t3 as f64);
277        self.t_fine = (var1 + var2) as i32;
278        t = (var1 + var2) / 5120.0;
279        t
280    }
281
282    fn compensate_pressure_float(&mut self, adc_p: i32) -> f64 {
283        let (mut var1, mut var2, mut p): (f64, f64, f64);
284
285        var1 = ((self.t_fine as f64)/2.0) - 64000.0_f64;
286        var2 = var1 * var1 * (self.coeff.dig_p6 as f64) / 32768.0_f64;
287        var2 = var2 + var1 * (self.coeff.dig_p5 as f64) * 2.0_f64;
288        var2 = (var2 / 4.0_f64) + ((self.coeff.dig_p4 as f64) * 65536.0_f64);
289        var1 = ((self.coeff.dig_p3 as f64) * var1 * var1 / 524288.0_f64 + (self.coeff.dig_p2 as f64) * var1) / 524288.0_f64;
290        var1 = (1.0 + var1 / 32768.0_f64) * (self.coeff.dig_p1 as f64);
291        if var1 == 0.0_f64 {
292            return 0.0; // avoid exception caused by division by zero
293        }
294        p = 1048576.0_f64 - (adc_p as f64);
295        p = (p - (var2 / 4096.0_f64)) * 6250.0_f64 / var1;
296        var1 = (self.coeff.dig_p9 as f64) * p * p / 2147483648.0_f64;
297        var2 = p * (self.coeff.dig_p8 as f64) / 32768.0_f64;
298        p = p + (var1 + var2 + (self.coeff.dig_p7 as f64)) / 16.0_f64;
299        p
300    }
301
302    fn compensate_temperature(&mut self, adc_t: i32) -> f32 {
303        match self.algorithm {
304            BMP280CompensationAlgorithm::B32 => {
305                let result = self.compensate_temperature_32b(adc_t);
306                return (result as f32) / 100.0
307            },
308            BMP280CompensationAlgorithm::B64 => {
309                let result = self.compensate_temperature_32b(adc_t);
310                return (result as f32) / 100.0
311            },
312            BMP280CompensationAlgorithm::Float => self.compensate_temperature_float(adc_t) as f32
313        }
314    }
315
316    //Returns Pa
317    fn compensate_pressure(&mut self, adc_p: i32) -> f32 {
318        match self.algorithm {
319            BMP280CompensationAlgorithm::B32 => {
320                let result = self.compensate_pressure_64b(adc_p);
321                return result as f32;
322            },
323            BMP280CompensationAlgorithm::B64 => {
324                let result = self.compensate_pressure_64b(adc_p);
325                return (result as f32) / 256.0;
326            },
327            BMP280CompensationAlgorithm::Float => {
328                self.compensate_pressure_float(adc_p) as f32
329            }
330        }
331    }
332
333    #[cfg(any(target_os = "linux", target_os = "android"))]
334    fn read_temp_raw(&mut self) -> Result<i32, T::Error> {
335        let mut buf = [0_u8; 3];
336        try!(self.barometer.write(&[BMP280_TEMP_MSB]));
337        try!(self.barometer.read(&mut buf));
338        let mut raw_temp: i32 = ((buf[0] as i32) << 12) + ((buf[1] as i32) << 4) + ((buf[2] as i32) >> 4);
339
340        Ok(raw_temp)
341    }
342
343    #[cfg(any(target_os = "linux", target_os = "android"))]
344    fn read_press_raw(&mut self) -> Result<i32, T::Error> {
345        let mut buf = [0_u8; 3];
346        try!(self.barometer.write(&[BMP280_PRESS_MSB]));
347        try!(self.barometer.read(&mut buf));
348        let raw_press = ((buf[0] as i32) << 12) | ((buf[1] as i32) << 4) | ((buf[2] as i32) >> 4);
349
350        Ok(raw_press)
351    }
352
353    #[doc(hidden)]
354    pub fn test_calculate_real_pressure(&mut self) {
355        self.coeff = BMP280CalibrationCoefficients {
356            dig_t1: 27504,
357            dig_t2: 26435,
358            dig_t3: -1000,
359            dig_p1: 36477,
360            dig_p2: -10685,
361            dig_p3: 3024,
362            dig_p4: 2855,
363            dig_p5: 140,
364            dig_p6: -7,
365            dig_p7: 15500,
366            dig_p8: -14600,
367            dig_p9: 6000,
368        };
369
370        let temp_reading = 519888;
371        let pressure_reading = 415148;
372
373        let tdiff_f = self.compensate_temperature_float(temp_reading) - 25.08;
374        assert!(tdiff_f.abs() < 1.0 && tdiff_f > -1.0);
375        println!("TempFloat PASS");
376        let pdiff_f = self.compensate_pressure_float(pressure_reading) - 100653.26;
377        assert!(pdiff_f.abs() < 1.0 || pdiff_f > -1.0);
378        println!("PressFloat PASS");
379
380        assert!(self.compensate_temperature_32b(temp_reading) == 2508);
381        println!("Temp32b PASS");
382
383        println!("{}", self.compensate_pressure_32b(pressure_reading));
384        //Off by 3 for some reason even though I've triple checked the algorithm
385        //        assert!(self.compensate_pressure_32b(pressure_reading) == 100653_u32);
386        println!("Press32b PASS");
387
388        println!("{}", self.compensate_pressure_64b(pressure_reading));
389        //Off by 3 for some reason even though I've triple checked the algorithm
390//        assert!(self.compensate_pressure_64b(pressure_reading) == 25767236_u32);
391        println!("Press64b PASS");
392
393        println!("Passed calibration test");
394    }
395}
396
397impl<T> Thermometer for BMP280<T>
398    where T: I2CDevice + Sized
399{
400    type Error = T::Error;
401
402    #[cfg(not(any(target_os = "linux", target_os = "android")))]
403    fn temperature_celsius(&mut self) -> Result<f32, T::Error> {
404        Ok(0.0)
405    }
406
407    #[cfg(any(target_os = "linux", target_os = "android"))]
408    fn temperature_celsius(&mut self) -> Result<f32, T::Error> {
409        match self.read_temp_raw() {
410            Ok(adc_t) => Ok(self.compensate_temperature(adc_t)),
411            Err(e) => Err(e)
412        }
413    }
414}
415
416impl<T> Barometer for BMP280<T>
417    where T: I2CDevice + Sized
418{
419    type Error = T::Error;
420
421    #[cfg(not(any(target_os = "linux", target_os = "android")))]
422    fn pressure_kpa(&mut self) -> Result<f32, T::Error> {
423        Ok(0.0)
424    }
425
426    #[cfg(any(target_os = "linux", target_os = "android"))]
427    fn pressure_kpa(&mut self) -> Result<f32, T::Error> {
428        self.temperature_celsius();
429
430        match self.read_press_raw() {
431            Ok(adc_p) => Ok(self.compensate_pressure(adc_p) / 1000.0),
432            Err(e) => Err(e)
433        }
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440    #[test]
441    fn test_algorithms() {
442        println!("BMP280 Barometer Thermometer.");
443        match get_linux_bmp280_i2c_device() {
444            Ok(device) => {
445                let settings = BMP280Settings {
446                    compensation: BMP280CompensationAlgorithm::B64,
447                    t_sb: BMP280Timing::ms0_5,
448                    iir_filter_coeff: BMP280FilterCoefficient::Medium,
449                    osrs_t: BMP280TemperatureOversampling::x1,
450                    osrs_p: BMP280PressureOversampling::StandardResolution,
451                    power_mode: BMP280PowerMode::NormalMode
452                };
453
454                match BMP280::new(device, settings) {
455                    Ok(mut bmp280) => {
456                        bmp280.test_calculate_real_pressure();
457                    },
458                    Err(e) => {}
459                }
460            },
461            Err(e) => {}
462        }
463    }
464}