bmp085_driver/
lib.rs

1//! A platform agnostic driver to interface the [Bosch Sensortec](https://www.bosch-sensortec.com) BMP085 pressure sensor, written in Rust.
2//!
3//! Please note that the BMP085 sensor has been [discontinued](https://media.digikey.com/pdf/PCNs/Bosch/BMP085_Disc.pdf) by Bosch Sensortec in 2013.
4//!
5//!This driver is build using the [embedded-hal](https://docs.rs/embedded-hal/) traits.
6//!
7//! [embedded-hal]: https://docs.rs/embedded-hal/0.2
8//!
9//! ## Features
10//!
11//! - Reading the calibration coefficients from the sensor eeprom
12//! - Reading the uncompensated temperature from the sensor
13//! - Converting the uncompensated temperature to d℃
14//! - Reading the uncompensated pressure from the sensor
15//! - Converting the uncompensated pressure to hPa
16//! - Converting the pressure in hPa to hPa relative to altitude normal null (optional feature)
17//! - Tests for the conversion functions according to the data-sheet of the sensor
18
19#![no_std]
20#![deny(missing_docs)]
21#![allow(clippy::trivially_copy_pass_by_ref, clippy::new_ret_no_self)]
22
23use core::mem;
24
25use cast::{i16, i32, u16, u32};
26use generic_array::typenum::consts::*;
27use generic_array::{ArrayLength, GenericArray};
28use hal::blocking::delay::DelayUs;
29use hal::blocking::i2c::{Write, WriteRead};
30
31/// BMP085 module address.
32// The LSB of the device address distinguishes between read (1) and
33// write (0) operation, corresponding to address 0xEF (read) and 0xEE
34// (write). Note: embedded-hal/blocking/i2c uses 7-bit addresses.
35#[allow(clippy::unreadable_literal)]
36const ADDRESS: u8 = 0b1110111; // 7-bit I²C address, missing least significant r/w bit
37
38/// BMP085 registers
39#[allow(non_camel_case_types)]
40#[derive(Copy, Clone)]
41enum Register {
42    COEFF_AC1 = 0xAA,
43    COEFF_AC2 = 0xAC,
44    COEFF_AC3 = 0xAE,
45    COEFF_AC4 = 0xB0,
46    COEFF_AC5 = 0xB2,
47    COEFF_AC6 = 0xB4,
48    COEFF_B1 = 0xB6,
49    COEFF_B2 = 0xB8,
50    COEFF_MB = 0xBA,
51    COEFF_MC = 0xBC,
52    COEFF_MD = 0xBE,
53    /// Control register
54    CONTROL_REG = 0xF4,
55    /// First value register, next is 0xF7, 0xF8
56    VALUE_REG = 0xF6,
57}
58
59impl Register {
60    pub fn addr(&self) -> u8 {
61        *self as u8
62    }
63}
64
65/// BMP085 control register values for different internal oversampling settings (osrs)
66// Instead of waiting for the maximum conversion time, the output pin
67// EOC (end of conversion) can be used to check if the conversion is
68// finished (logic 1) or still running (logic 0).
69#[allow(non_camel_case_types)]
70#[derive(Copy, Clone)]
71enum ControlRegisterValue {
72    CONTROL_VALUE_TEMPERATURE = 0x2E,     // Max. conversion time 4.5ms
73    CONTROL_VALUE_PRESSURE_OSRS_0 = 0x34, // Max. conversion time 4.5ms
74    CONTROL_VALUE_PRESSURE_OSRS_1 = 0x74, // Max. conversion time 7.5ms
75    CONTROL_VALUE_PRESSURE_OSRS_2 = 0xB4, // Max. conversion time 13.5ms
76    CONTROL_VALUE_PRESSURE_OSRS_3 = 0xF4, // Max. conversion time 25.5ms
77}
78
79impl ControlRegisterValue {
80    pub fn addr(&self) -> u8 {
81        *self as u8
82    }
83}
84
85impl From<Oversampling> for ControlRegisterValue {
86    fn from(oss: Oversampling) -> Self {
87        match oss {
88            Oversampling::UltraLowPower => ControlRegisterValue::CONTROL_VALUE_PRESSURE_OSRS_0,
89            Oversampling::Standard => ControlRegisterValue::CONTROL_VALUE_PRESSURE_OSRS_1,
90            Oversampling::HighResolution => ControlRegisterValue::CONTROL_VALUE_PRESSURE_OSRS_2,
91            Oversampling::UltraHighResolution => {
92                ControlRegisterValue::CONTROL_VALUE_PRESSURE_OSRS_3
93            }
94        }
95    }
96}
97
98/// Calibration coefficients from BMP085 eeprom. These are used to
99/// calculate the temperature and the pressure. They are
100/// calibrated by the manufacturer, individually for each silicon.
101struct Coefficients {
102    pub ac1: i16,
103    pub ac2: i16,
104    pub ac3: i16,
105    pub ac4: u16,
106    pub ac5: u16,
107    pub ac6: u16,
108    pub b1: i16,
109    pub b2: i16,
110    pub mb: i16,
111    pub mc: i16,
112    pub md: i16,
113}
114
115impl Coefficients {
116    fn new() -> Self {
117        Coefficients {
118            ac1: 0,
119            ac2: 0,
120            ac3: 0,
121            ac4: 0,
122            ac5: 0,
123            ac6: 0,
124            b1: 0,
125            b2: 0,
126            mb: 0,
127            mc: 0,
128            md: 0,
129        }
130    }
131}
132
133/// Oversampling modes
134#[derive(Copy, Clone)]
135pub enum Oversampling {
136    /// Number of samples 1, conversion time max 4.5ms, average current 3µA
137    UltraLowPower = 0,
138    /// Number of samples 2, conversion time max 7.5ms, average current 5µA
139    Standard = 1,
140    /// Number of samples 4, conversion time max 13.5ms, average current 7µA
141    HighResolution = 2,
142    /// Number of samples 8, conversion time max. 22.5ms, average current 12µA
143    UltraHighResolution = 3,
144}
145
146impl Oversampling {
147    fn value(&self) -> u8 {
148        *self as u8
149    }
150}
151
152/// BMP085 driver
153pub struct Bmp085<I2C, TIMER: DelayUs<u16>> {
154    i2c: I2C,
155    timer: TIMER,
156    coeff: Coefficients,
157    oss: Oversampling,
158}
159
160/// Temperature in deci degree Celsius (dC)
161pub type DeciCelcius = i32;
162
163/// Pressure in Pascal (Pa)
164pub type Pascal = i32;
165
166/// Result of the BMP085 sensor read-out
167pub struct PT {
168    /// Temperature in deci degree Celsius, e.g. 241 for 24.1 ℃
169    pub temperature: DeciCelcius,
170    /// Pressure in Pascal relative to the location of the sensor.
171    /// Note that meteorological pressures are given relative to
172    /// normal null sea level in order to be location independent and
173    /// comparable. The function `pressure_to_normal_null` can be used
174    /// to convert the location pressure to normal null.
175    pub pressure: Pascal,
176}
177
178impl<I2C, TIMER, E> Bmp085<I2C, TIMER>
179where
180    I2C: WriteRead<Error = E> + Write<Error = E>,
181    TIMER: DelayUs<u16>,
182{
183    /// Create a new driver from a I2C peripheral with given oversampling settings.
184    pub fn new(i2c: I2C, timer: TIMER, oss: Oversampling) -> Result<Self, E> {
185        let mut bmp085 = Bmp085 {
186            i2c,
187            timer,
188            coeff: Coefficients::new(),
189            oss,
190        };
191
192        // Get calibration coefficients from the BMP085 eeprom
193        bmp085.coeff.ac1 = bmp085.read_i16(Register::COEFF_AC1)?;
194        bmp085.coeff.ac2 = bmp085.read_i16(Register::COEFF_AC2)?;
195        bmp085.coeff.ac3 = bmp085.read_i16(Register::COEFF_AC3)?;
196        bmp085.coeff.ac4 = bmp085.read_u16(Register::COEFF_AC4)?;
197        bmp085.coeff.ac5 = bmp085.read_u16(Register::COEFF_AC5)?;
198        bmp085.coeff.ac6 = bmp085.read_u16(Register::COEFF_AC6)?;
199        bmp085.coeff.b1 = bmp085.read_i16(Register::COEFF_B1)?;
200        bmp085.coeff.b2 = bmp085.read_i16(Register::COEFF_B2)?;
201        bmp085.coeff.mb = bmp085.read_i16(Register::COEFF_MB)?;
202        bmp085.coeff.mc = bmp085.read_i16(Register::COEFF_MC)?;
203        bmp085.coeff.md = bmp085.read_i16(Register::COEFF_MD)?;
204
205        Ok(bmp085)
206    }
207
208    fn read_u16(&mut self, reg: Register) -> Result<u16, E> {
209        let buf: GenericArray<u8, U2> = self.read_register(reg)?;
210        Ok((u16(buf[0]) << 8) + u16(buf[1]))
211    }
212
213    fn read_i16(&mut self, reg: Register) -> Result<i16, E> {
214        let buf: GenericArray<u8, U2> = self.read_register(reg)?;
215        Ok((i16(buf[0]) << 8) + i16(buf[1]))
216    }
217
218    fn read_register<N>(&mut self, reg: Register) -> Result<GenericArray<u8, N>, E>
219    where
220        N: ArrayLength<u8>,
221    {
222        let mut buffer: GenericArray<u8, N> = unsafe { mem::uninitialized() };
223        {
224            let buffer: &mut [u8] = &mut buffer;
225            self.i2c.write_read(ADDRESS, &[reg.addr()], buffer)?;
226        }
227        Ok(buffer)
228    }
229
230    fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), E> {
231        self.i2c.write(ADDRESS, &[reg.addr(), byte])
232    }
233
234    /// Read temperature and pressure from sensor.
235    pub fn read(&mut self) -> Result<PT, E> {
236        // Read temperature, wait 4.5ms before reading the value
237        self.write_register(
238            Register::CONTROL_REG,
239            ControlRegisterValue::CONTROL_VALUE_TEMPERATURE.addr(),
240        )?;
241        self.timer.delay_us(4500);
242        let ut = self.read_i16(Register::VALUE_REG)?;
243        let (temperature, b5) = calculate_temperature(i32(ut), &self.coeff);
244
245        // Read pressure
246        let press_reg_value = ControlRegisterValue::from(self.oss);
247        self.write_register(Register::CONTROL_REG, press_reg_value.addr())?;
248        self.timer.delay_us(1000 * (2 + (3 << self.oss.value())));
249        let up: GenericArray<u8, U3> = self.read_register(Register::VALUE_REG)?;
250        let up: i32 =
251            ((i32(up[0]) << 16) + (i32(up[1]) << 8) + i32(up[2])) >> (8 - self.oss.value());
252        let pressure: Pascal = calculate_true_pressure(up, b5, self.oss, &self.coeff);
253
254        Ok(PT {
255            temperature,
256            pressure,
257        })
258    }
259}
260
261// Temperature calculation according data-sheet
262fn calculate_temperature(ut: i32, coeff: &Coefficients) -> (DeciCelcius, i32) {
263    let x1: i32 = ((ut - i32(coeff.ac6)) * i32(coeff.ac5)) >> 15;
264    let x2: i32 = (i32(coeff.mc) << 11) / (x1 + i32(coeff.md));
265    let b5: i32 = x1 + x2; // Value b5 is used in pressure calculation
266    let t: DeciCelcius = (b5 + 8) >> 4;
267    (t, b5)
268}
269
270// Pressure calculation according data-sheet
271fn calculate_true_pressure(up: i32, b5: i32, oss: Oversampling, coeff: &Coefficients) -> Pascal {
272    let b6: i32 = b5 - 4000;
273
274    // B3
275    let x1: i32 = ((i32(coeff.b2) * (b6 * b6)) >> 12) >> 11;
276    let x2: i32 = (i32(coeff.ac2) * b6) >> 11;
277    let x3: i32 = x1 + x2;
278    let b3: i32 = (((i32(coeff.ac1) * 4 + x3) << oss.value()) + 2) >> 2;
279
280    // B4
281    let x1: i32 = (i32(coeff.ac3) * b6) >> 13;
282    let x2: i32 = (i32(coeff.b1) * ((b6 * b6) >> 12)) >> 16;
283    let x3: i32 = (x1 + x2 + 2) >> 2;
284    let b4: u32 = (u32(coeff.ac4) * ((x3 as u32) + 32768)) >> 15;
285
286    // B7 (use u32 instead of long, differs from data-sheet)
287    let b7: u32 = ((up - b3) as u32) * (50000u32 >> oss.value());
288
289    let p: i32 = if b7 < 0x8000_0000 {
290        b7 * 2 / b4
291    } else {
292        b7 / b4 * 2
293    } as i32;
294
295    let x1: i32 = (p >> 8) * (p >> 8);
296    let x1: i32 = (x1 * 3038) >> 16;
297    let x2: i32 = (-7357 * p) >> 16;
298    p + ((x1 + x2 + 3791) >> 4)
299}
300
301/// Convert pressure from sensor (altitude dependant) to pressure in
302/// hecto Pascal (hPa) relative to sea level normal null.
303///
304/// # Arguments
305///
306/// * `p` - Pressure in Pascal
307/// * `altitude` - Altitude in Meters
308///
309#[cfg(feature = "default")]
310pub fn pressure_to_normal_null(p: Pascal, altitude: u16) -> u16 {
311    let z = (p as f32) / libm::powf(1f32 - (f32::from(altitude) / 44330f32), 5.255f32);
312    libm::roundf(z / 100f32) as u16
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn calculate_temp_pressure() {
321        // Example coefficient values according data-sheet
322        let coeff = Coefficients {
323            ac1: 408,
324            ac2: -72,
325            ac3: -14383,
326            ac4: 32741,
327            ac5: 32757,
328            ac6: 23153,
329            b1: 6190,
330            b2: 4,
331            mb: -32767,
332            mc: -8711,
333            md: 2868,
334        };
335
336        let ut: i32 = 27898; // Uncompensated temperature from sensor
337        let up: i32 = 23843; // Uncompensated pressure from sensor
338        let oss = Oversampling::UltraLowPower;
339        assert_eq!(oss.value(), 0);
340
341        let (deci_c, b5) = calculate_temperature(ut, &coeff);
342        assert_eq!(deci_c, 150);
343        assert_eq!(b5, 2400); // Differs from data-sheet example, IMHO OK
344
345        let pressure: Pascal = calculate_true_pressure(up, b5, oss, &coeff);
346        assert_eq!(pressure, 69964); // Value from data-sheet
347    }
348
349    #[test]
350    #[cfg(feature = "default")]
351    fn calculate_pressure_to_normal_null() {
352        let p: Pascal = 93728;
353        let alt: u16 = 691; // Holzkirchen, Germany
354        let pnn = pressure_to_normal_null(p, alt);
355        assert_eq!(1018, pnn);
356    }
357}