Skip to main content

pamoja_sensors/bme280/
driver.rs

1//! The BME280 driven over a bus: reset, identify, calibrate, and measure on demand.
2
3use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use embedded_hal::spi::SpiDevice;
6use pamoja_core::Sensor;
7
8use super::{
9    image_updating, max_measurement_micros, measuring, register, Calibration, Config, CtrlHum,
10    CtrlMeas, Filter, Measurement, Mode, Oversampling, RawMeasurement, CALIB_HUMIDITY_LEN,
11    CALIB_TEMP_PRESS_LEN, CHIP_ID, DATA_LEN, RESET_WORD, STARTUP_MICROS,
12};
13use crate::driver::{I2cRegisters, RegisterBus, SpiRegisters};
14use crate::error::{DriverError, SensorError};
15
16/// How many times the status register is polled, 1 ms apart, before a conversion or
17/// a reset is called overdue.
18pub const STATUS_POLLS: u8 = 20;
19
20/// A BME280 on an I2C or SPI bus, measuring on demand in forced mode.
21///
22/// [`Bme280::i2c`] or [`Bme280::spi`] wraps the bus. [`init`](Bme280::init) resets the
23/// part, checks its chip id, reads its calibration, and writes the oversampling and
24/// filter settings, in the order the datasheet requires (`ctrl_hum` takes effect only
25/// after the following `ctrl_meas` write). [`measure`](Bme280::measure) runs one
26/// forced-mode conversion, waits the datasheet's maximum measurement time for the
27/// settings in use, confirms the part is idle, and returns the compensated
28/// [`Measurement`]. As a [`Sensor`] its reading is the whole measurement; one channel
29/// is selected with [`Sensor::map`].
30///
31/// # Examples
32///
33/// A weather station's reading, taken from a BME280 that is not plugged in yet: the
34/// simulated part holds 18.5 °C, 1013.2 hPa, and 55 % relative humidity, and the driver
35/// compensates its raw readings with the calibration the part carries.
36///
37/// ```
38/// use pamoja_core::Sensor;
39/// use pamoja_hal::script::{block_on, DelayLog};
40/// use pamoja_sensors::bme280::{sim, Bme280, I2C_ADDRESS_PRIMARY};
41///
42/// let part = sim::reporting(I2C_ADDRESS_PRIMARY, 18.5, 1013.2, 55.0);
43/// let mut station = Bme280::i2c(part, I2C_ADDRESS_PRIMARY, DelayLog::new());
44/// let weather = block_on(station.read())?;
45/// assert!((weather.celsius() - 18.5).abs() < 0.01);
46/// assert!((weather.hectopascals() - 1013.2).abs() < 0.1);
47/// assert!((weather.relative_humidity_percent() - 55.0).abs() < 0.1);
48/// # Ok::<(), pamoja_core::Error>(())
49/// ```
50#[derive(Debug)]
51pub struct Bme280<B, D> {
52    bus: B,
53    delay: D,
54    temperature: Oversampling,
55    pressure: Oversampling,
56    humidity: Oversampling,
57    filter: Filter,
58    calibration: Option<Calibration>,
59}
60
61impl<I2C: I2c, D> Bme280<I2cRegisters<I2C>, D> {
62    /// Wraps an I2C bus and the part's address.
63    ///
64    /// # Arguments
65    ///
66    /// * `bus` - the I2C bus the part is on.
67    /// * `address` - [`I2C_ADDRESS_PRIMARY`](super::I2C_ADDRESS_PRIMARY) with SDO
68    ///   low, [`I2C_ADDRESS_SECONDARY`](super::I2C_ADDRESS_SECONDARY) with SDO high.
69    /// * `delay` - the timer that paces the reset and the conversions.
70    ///
71    /// # Returns
72    ///
73    /// The driver, with every measurement at oversampling x1 and the filter off.
74    pub fn i2c(bus: I2C, address: u8, delay: D) -> Self {
75        Bme280::new(I2cRegisters::new(bus, address), delay)
76    }
77}
78
79impl<SPI: SpiDevice, D> Bme280<SpiRegisters<SPI>, D> {
80    /// Wraps an SPI device, the bus plus the part's chip-select line.
81    ///
82    /// # Arguments
83    ///
84    /// * `device` - the SPI device, in mode 0 or 3.
85    /// * `delay` - the timer that paces the reset and the conversions.
86    ///
87    /// # Returns
88    ///
89    /// The driver, with every measurement at oversampling x1 and the filter off.
90    pub fn spi(device: SPI, delay: D) -> Self {
91        Bme280::new(SpiRegisters::new(device), delay)
92    }
93}
94
95impl<B, D> Bme280<B, D> {
96    /// Wraps any register bus.
97    ///
98    /// # Arguments
99    ///
100    /// * `bus` - the part's register map, over whichever bus reaches it.
101    /// * `delay` - the timer that paces the reset and the conversions.
102    ///
103    /// # Returns
104    ///
105    /// The driver, with every measurement at oversampling x1 and the filter off.
106    pub fn new(bus: B, delay: D) -> Self {
107        Bme280 {
108            bus,
109            delay,
110            temperature: Oversampling::X1,
111            pressure: Oversampling::X1,
112            humidity: Oversampling::X1,
113            filter: Filter::Off,
114            calibration: None,
115        }
116    }
117
118    /// Sets the oversampling of each measurement; `Skipped` leaves one out.
119    ///
120    /// The settings are written to the part by [`init`](Bme280::init).
121    ///
122    /// # Arguments
123    ///
124    /// * `temperature` - temperature oversampling.
125    /// * `pressure` - pressure oversampling.
126    /// * `humidity` - humidity oversampling.
127    ///
128    /// # Returns
129    ///
130    /// The driver.
131    pub fn with_oversampling(
132        mut self,
133        temperature: Oversampling,
134        pressure: Oversampling,
135        humidity: Oversampling,
136    ) -> Self {
137        self.temperature = temperature;
138        self.pressure = pressure;
139        self.humidity = humidity;
140        self
141    }
142
143    /// Sets the IIR filter that smooths pressure and temperature.
144    ///
145    /// The setting is written to the part by [`init`](Bme280::init).
146    ///
147    /// # Arguments
148    ///
149    /// * `filter` - the filter coefficient.
150    ///
151    /// # Returns
152    ///
153    /// The driver.
154    pub fn with_filter(mut self, filter: Filter) -> Self {
155        self.filter = filter;
156        self
157    }
158
159    /// Returns the calibration read at initialization, if it has happened.
160    pub fn calibration(&self) -> Option<&Calibration> {
161        self.calibration.as_ref()
162    }
163
164    /// Gives back the bus and the delay.
165    ///
166    /// # Returns
167    ///
168    /// The bus and delay the driver was built from.
169    pub fn release(self) -> (B, D) {
170        (self.bus, self.delay)
171    }
172}
173
174impl<B: RegisterBus, D: DelayNs> Bme280<B, D> {
175    /// Resets the part, checks its identity, reads its calibration, and writes the
176    /// settings.
177    ///
178    /// The sequence is the datasheet's: the soft-reset word, the start-up time, a wait
179    /// for the calibration image to finish loading, the chip id, the two calibration
180    /// blocks, then `config`, `ctrl_hum`, and `ctrl_meas` in that order, leaving the
181    /// part asleep.
182    ///
183    /// # Errors
184    ///
185    /// Returns [`DriverError::Bus`] if the bus fails, [`DriverError::Timeout`] if the
186    /// calibration image never finishes loading, and
187    /// [`DriverError::Sensor`] with [`SensorError::Identity`] if the chip id is not a
188    /// BME280's.
189    pub fn init(&mut self) -> Result<(), DriverError<B::Error>> {
190        self.write(register::RESET, RESET_WORD)?;
191        self.delay.delay_us(STARTUP_MICROS);
192        self.wait(image_updating)?;
193
194        if self.read(register::CHIP_ID)? != CHIP_ID {
195            return Err(SensorError::Identity.into());
196        }
197
198        let mut temp_press = [0u8; CALIB_TEMP_PRESS_LEN];
199        let mut humidity = [0u8; CALIB_HUMIDITY_LEN];
200        self.bus
201            .read_registers(register::CALIB_TEMP_PRESS, &mut temp_press)
202            .map_err(DriverError::Bus)?;
203        self.bus
204            .read_registers(register::CALIB_HUMIDITY, &mut humidity)
205            .map_err(DriverError::Bus)?;
206        self.calibration = Some(Calibration::from_registers(&temp_press, &humidity));
207
208        let config = Config {
209            filter: self.filter,
210            ..Config::default()
211        };
212        self.write(register::CONFIG, config.bits())?;
213        let ctrl_hum = CtrlHum {
214            humidity: self.humidity,
215        };
216        self.write(register::CTRL_HUM, ctrl_hum.bits())?;
217        self.write(register::CTRL_MEAS, self.ctrl_meas(Mode::Sleep).bits())?;
218        Ok(())
219    }
220
221    /// Runs one forced-mode conversion and returns the compensated measurement.
222    ///
223    /// Initializes the part first if [`init`](Bme280::init) has not run. The wait is
224    /// the datasheet's maximum measurement time for the oversampling in use, after
225    /// which the status register is polled until the part reports idle.
226    ///
227    /// # Returns
228    ///
229    /// The compensated measurement.
230    ///
231    /// # Errors
232    ///
233    /// Returns [`DriverError::Bus`] if the bus fails, [`DriverError::Timeout`] if the
234    /// part is still measuring after the polls, or any error of
235    /// [`init`](Bme280::init).
236    pub fn measure(&mut self) -> Result<Measurement, DriverError<B::Error>> {
237        if self.calibration.is_none() {
238            self.init()?;
239        }
240        self.write(register::CTRL_MEAS, self.ctrl_meas(Mode::Forced).bits())?;
241        self.delay.delay_us(max_measurement_micros(
242            self.temperature,
243            self.pressure,
244            self.humidity,
245        ));
246        self.wait(measuring)?;
247
248        let mut data = [0u8; DATA_LEN];
249        self.bus
250            .read_registers(register::DATA, &mut data)
251            .map_err(DriverError::Bus)?;
252        let raw = RawMeasurement::from_registers(&data);
253        let calibration = self.calibration.as_ref().expect("read at initialization");
254        Ok(calibration.compensate(&raw))
255    }
256
257    fn ctrl_meas(&self, mode: Mode) -> CtrlMeas {
258        CtrlMeas {
259            temperature: self.temperature,
260            pressure: self.pressure,
261            mode,
262        }
263    }
264
265    fn read(&mut self, register: u8) -> Result<u8, DriverError<B::Error>> {
266        self.bus.read_register(register).map_err(DriverError::Bus)
267    }
268
269    fn write(&mut self, register: u8, value: u8) -> Result<(), DriverError<B::Error>> {
270        self.bus
271            .write_register(register, value)
272            .map_err(DriverError::Bus)
273    }
274
275    fn wait(&mut self, busy: fn(u8) -> bool) -> Result<(), DriverError<B::Error>> {
276        for _ in 0..STATUS_POLLS {
277            if !busy(self.read(register::STATUS)?) {
278                return Ok(());
279            }
280            self.delay.delay_ms(1);
281        }
282        Err(DriverError::Timeout)
283    }
284}
285
286impl<B, D> Sensor for Bme280<B, D>
287where
288    B: RegisterBus + Send,
289    B::Error: core::fmt::Debug,
290    D: DelayNs + Send,
291{
292    type Reading = Measurement;
293
294    async fn read(&mut self) -> pamoja_core::Result<Measurement> {
295        self.measure().map_err(pamoja_core::Error::from)
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use crate::bme280::{Standby, I2C_ADDRESS_PRIMARY, I2C_ADDRESS_SECONDARY};
303    use alloc::vec;
304    use alloc::vec::Vec;
305    use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
306    use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep, SpiScript, SpiStep};
307
308    const CALIBRATION_A: [u8; CALIB_TEMP_PRESS_LEN] = [
309        0x45, 0x6F, 0x6F, 0x68, 0x32, 0x00, 0x46, 0x91, 0x6A, 0xD6, 0xD0, 0x0B, 0x4E, 0x1E, 0x88,
310        0xFF, 0xF9, 0xFF, 0xAC, 0x26, 0x0A, 0xD8, 0xBD, 0x10, 0x00, 0x4B,
311    ];
312    const CALIBRATION_B: [u8; CALIB_HUMIDITY_LEN] = [0x62, 0x01, 0x00, 0x15, 0x23, 0x03, 0x1E];
313    const DATA: [u8; DATA_LEN] = [0x65, 0x5A, 0xC0, 0x7E, 0xED, 0x00, 0x75, 0x30];
314
315    fn init_steps(address: u8, config: u8, ctrl_hum: u8, ctrl_meas: u8) -> Vec<I2cStep> {
316        vec![
317            I2cStep::write(address, [register::RESET, RESET_WORD]),
318            I2cStep::write_read(address, [register::STATUS], [0x00]),
319            I2cStep::write_read(address, [register::CHIP_ID], [CHIP_ID]),
320            I2cStep::write_read(address, [register::CALIB_TEMP_PRESS], CALIBRATION_A),
321            I2cStep::write_read(address, [register::CALIB_HUMIDITY], CALIBRATION_B),
322            I2cStep::write(address, [register::CONFIG, config]),
323            I2cStep::write(address, [register::CTRL_HUM, ctrl_hum]),
324            I2cStep::write(address, [register::CTRL_MEAS, ctrl_meas]),
325        ]
326    }
327
328    fn measure_steps(address: u8, ctrl_meas: u8) -> Vec<I2cStep> {
329        vec![
330            I2cStep::write(address, [register::CTRL_MEAS, ctrl_meas]),
331            I2cStep::write_read(address, [register::STATUS], [0x00]),
332            I2cStep::write_read(address, [register::DATA], DATA),
333        ]
334    }
335
336    #[test]
337    fn init_follows_the_datasheet_sequence_and_keeps_the_calibration() {
338        let mut steps = init_steps(I2C_ADDRESS_PRIMARY, 0x00, 0x01, 0x24);
339        steps.extend(measure_steps(I2C_ADDRESS_PRIMARY, 0x25));
340        let mut sensor = Bme280::i2c(I2cScript::new(steps), I2C_ADDRESS_PRIMARY, DelayLog::new());
341        sensor.init().unwrap();
342        let expected = Calibration::from_registers(&CALIBRATION_A, &CALIBRATION_B);
343        assert_eq!(sensor.calibration(), Some(&expected));
344
345        let measurement = sensor.measure().unwrap();
346        assert_eq!(
347            measurement,
348            expected.compensate(&RawMeasurement::from_registers(&DATA))
349        );
350        assert_eq!(measurement.temperature_centi_celsius, 2044);
351
352        let (bus, delay) = sensor.release();
353        assert!(bus.release().done());
354        assert_eq!(delay.waits_ns()[0], STARTUP_MICROS * 1_000);
355        assert_eq!(
356            delay.waits_ns()[1],
357            9_300 * 1_000,
358            "x1, x1, x1 is 9.3 ms at most"
359        );
360    }
361
362    #[test]
363    fn oversampling_and_filter_settings_reach_the_control_registers() {
364        let config = Config {
365            standby: Standby::Ms0_5,
366            filter: Filter::X16,
367            spi_3wire: false,
368        }
369        .bits();
370        let ctrl_meas_sleep = CtrlMeas {
371            temperature: Oversampling::X2,
372            pressure: Oversampling::X16,
373            mode: Mode::Sleep,
374        }
375        .bits();
376        let mut steps = init_steps(I2C_ADDRESS_SECONDARY, config, 0x00, ctrl_meas_sleep);
377        steps.extend(measure_steps(I2C_ADDRESS_SECONDARY, ctrl_meas_sleep | 0x01));
378        let mut sensor = Bme280::i2c(
379            I2cScript::new(steps),
380            I2C_ADDRESS_SECONDARY,
381            DelayLog::new(),
382        )
383        .with_oversampling(Oversampling::X2, Oversampling::X16, Oversampling::Skipped)
384        .with_filter(Filter::X16);
385        sensor.measure().unwrap();
386        let (bus, delay) = sensor.release();
387        assert!(bus.release().done());
388        let expected =
389            max_measurement_micros(Oversampling::X2, Oversampling::X16, Oversampling::Skipped);
390        assert_eq!(delay.waits_ns()[1], expected * 1_000);
391    }
392
393    #[test]
394    fn a_part_that_is_not_a_bme280_is_refused_before_anything_else() {
395        let steps = [
396            I2cStep::write(0x76, [register::RESET, RESET_WORD]),
397            I2cStep::write_read(0x76, [register::STATUS], [0x00]),
398            I2cStep::write_read(0x76, [register::CHIP_ID], [0x58]),
399        ];
400        let mut sensor = Bme280::i2c(I2cScript::new(steps), 0x76, DelayLog::new());
401        assert_eq!(
402            sensor.init(),
403            Err(DriverError::Sensor(SensorError::Identity))
404        );
405        assert!(sensor.calibration().is_none());
406        assert!(sensor.release().0.release().done());
407    }
408
409    #[test]
410    fn a_conversion_that_never_finishes_is_a_timeout() {
411        let mut steps = init_steps(0x76, 0x00, 0x01, 0x24);
412        steps.push(I2cStep::write(0x76, [register::CTRL_MEAS, 0x25]));
413        for _ in 0..STATUS_POLLS {
414            steps.push(I2cStep::write_read(0x76, [register::STATUS], [0x08]));
415        }
416        let mut sensor = Bme280::i2c(I2cScript::new(steps), 0x76, DelayLog::new());
417        assert_eq!(sensor.measure(), Err(DriverError::Timeout));
418        assert!(sensor.release().0.release().done());
419    }
420
421    #[test]
422    fn a_bus_fault_surfaces_as_a_bus_error_and_maps_to_the_core_io_error() {
423        let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
424        let steps = [I2cStep::fault(0x76, kind)];
425        let mut sensor = Bme280::i2c(I2cScript::new(steps), 0x76, DelayLog::new());
426        let error = sensor.init().unwrap_err();
427        assert!(matches!(error, DriverError::Bus(_)));
428        let core: pamoja_core::Error = error.into();
429        assert!(matches!(core, pamoja_core::Error::Io(_)));
430        let identity: pamoja_core::Error =
431            DriverError::<ErrorKind>::Sensor(SensorError::Identity).into();
432        assert!(matches!(identity, pamoja_core::Error::Io(_)));
433        let crc: pamoja_core::Error = DriverError::<ErrorKind>::Sensor(SensorError::Crc).into();
434        assert!(matches!(crc, pamoja_core::Error::Codec(_)));
435    }
436
437    #[test]
438    fn as_a_sensor_it_reads_and_maps_to_one_channel() {
439        let mut steps = init_steps(0x76, 0x00, 0x01, 0x24);
440        steps.extend(measure_steps(0x76, 0x25));
441        let sensor = Bme280::i2c(I2cScript::new(steps), 0x76, DelayLog::new());
442        let mut celsius = sensor.map(|measurement| measurement.celsius());
443        let reading = block_on(celsius.read()).unwrap();
444        assert!((reading - 20.44).abs() < 0.001);
445        assert!(celsius.into_inner().release().0.release().done());
446    }
447
448    #[test]
449    fn over_spi_the_same_sequence_uses_the_bosch_control_bytes() {
450        let steps = [
451            SpiStep::write([register::RESET & 0x7F, RESET_WORD]),
452            SpiStep::write([register::STATUS | 0x80]),
453            SpiStep::read([0x00]),
454            SpiStep::write([register::CHIP_ID | 0x80]),
455            SpiStep::read([CHIP_ID]),
456            SpiStep::write([register::CALIB_TEMP_PRESS | 0x80]),
457            SpiStep::read(CALIBRATION_A),
458            SpiStep::write([register::CALIB_HUMIDITY | 0x80]),
459            SpiStep::read(CALIBRATION_B),
460            SpiStep::write([register::CONFIG & 0x7F, 0x00]),
461            SpiStep::write([register::CTRL_HUM & 0x7F, 0x01]),
462            SpiStep::write([register::CTRL_MEAS & 0x7F, 0x24]),
463            SpiStep::write([register::CTRL_MEAS & 0x7F, 0x25]),
464            SpiStep::write([register::STATUS | 0x80]),
465            SpiStep::read([0x00]),
466            SpiStep::write([register::DATA | 0x80]),
467            SpiStep::read(DATA),
468        ];
469        let mut sensor = Bme280::spi(SpiScript::new(steps), DelayLog::new());
470        let measurement = sensor.measure().unwrap();
471        assert_eq!(measurement.temperature_centi_celsius, 2044);
472        assert!(sensor.release().0.release().done());
473    }
474
475    #[test]
476    fn init_and_a_forced_measurement_make_the_transfers_the_datasheet_gives() {
477        const PART: u8 = crate::bme280::I2C_ADDRESS_PRIMARY;
478        let calibration_a = [
479            0x45, 0x6F, 0x6F, 0x68, 0x32, 0x00, 0x46, 0x91, 0x6A, 0xD6, 0xD0, 0x0B, 0x4E, 0x1E,
480            0x88, 0xFF, 0xF9, 0xFF, 0xAC, 0x26, 0x0A, 0xD8, 0xBD, 0x10, 0x00, 0x4B,
481        ];
482        let calibration_b = [0x62, 0x01, 0x00, 0x15, 0x23, 0x03, 0x1E];
483        let bus = I2cScript::new([
484            I2cStep::write(PART, [0xE0, 0xB6]),
485            I2cStep::write_read(PART, [0xF3], [0x00]),
486            I2cStep::write_read(PART, [0xD0], [0x60]),
487            I2cStep::write_read(PART, [0x88], calibration_a),
488            I2cStep::write_read(PART, [0xE1], calibration_b),
489            I2cStep::write(PART, [0xF5, 0x00]),
490            I2cStep::write(PART, [0xF2, 0x01]),
491            I2cStep::write(PART, [0xF4, 0x24]),
492            I2cStep::write(PART, [0xF4, 0x25]),
493            I2cStep::write_read(PART, [0xF3], [0x00]),
494            I2cStep::write_read(
495                PART,
496                [0xF7],
497                [0x65, 0x5A, 0xC0, 0x7E, 0xED, 0x00, 0x75, 0x30],
498            ),
499        ]);
500
501        let mut sensor = Bme280::i2c(bus, PART, DelayLog::new());
502        let measurement = block_on(Sensor::read(&mut sensor)).expect("the scripted part answers");
503        assert_eq!(measurement.temperature_centi_celsius, 2044);
504
505        let (registers, delay) = sensor.release();
506        assert!(registers.release().done());
507        assert!(delay.total_micros() >= 2_000 + 9_300);
508    }
509}