embedded_devices/devices/bosch/bmp390/
mod.rs

1//! The BMP390 is a digital sensor with pressure and temperature measurement based on proven sensing principles. The sensor
2//! module is housed in an extremely compact 10-pin metal-lid LGA package with a footprint of only 2.0 × 2.0 mm² and max 0.8
3//! mm package height. Its small dimensions and its low power consumption of 3.2 μA @1Hz allow the implementation in battery
4//! driven devices such as mobile phones, GPS modules or watches.
5//!
6//! ## Usage (sync)
7//!
8//! ```rust
9//! # #[cfg(feature = "sync")] mod test {
10//! # fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_devices::devices::bosch::bmp390::InitError<I::Error>>
11//! # where
12//! #   I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType,
13//! #   D: embedded_hal::delay::DelayNs
14//! # {
15//! use embedded_devices::devices::bosch::bmp390::{BMP390Sync, address::Address, Configuration};
16//! use embedded_devices::devices::bosch::bmp390::registers::{IIRFilter, Oversampling};
17//! use embedded_devices::sensor::OneshotSensorSync;
18//! use uom::si::pressure::pascal;
19//! use uom::si::thermodynamic_temperature::degree_celsius;
20//!
21//! // Create and initialize the device
22//! let mut bmp390 = BMP390Sync::new_i2c(delay, i2c, Address::Primary);
23//! bmp390.init()?;
24//! // Enable sensors
25//! bmp390.configure(Configuration {
26//!     temperature_oversampling: Some(Oversampling::X_32),
27//!     pressure_oversampling: Some(Oversampling::X_32),
28//!     iir_filter: IIRFilter::Disabled,
29//! })?;
30//!
31//! // Read the current temperature in °C
32//! let measurement = bmp390.measure().unwrap();
33//! let temp = measurement.temperature.expect("should be enabled").get::<degree_celsius>();
34//! let pressure = measurement.pressure.expect("should be enabled").get::<pascal>();
35//! println!("Current measurement: {:?}°C, {:?} Pa", temp, pressure);
36//! # Ok(())
37//! # }
38//! # }
39//! ```
40//!
41//! ## Usage (async)
42//!
43//! ```rust
44//! # #[cfg(feature = "async")] mod test {
45//! # async fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_devices::devices::bosch::bmp390::InitError<I::Error>>
46//! # where
47//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
48//! #   D: embedded_hal_async::delay::DelayNs
49//! # {
50//! use embedded_devices::devices::bosch::bmp390::{BMP390Async, address::Address, Configuration};
51//! use embedded_devices::devices::bosch::bmp390::registers::{IIRFilter, Oversampling};
52//! use embedded_devices::sensor::OneshotSensorAsync;
53//! use uom::si::pressure::pascal;
54//! use uom::si::thermodynamic_temperature::degree_celsius;
55//!
56//! // Create and initialize the device
57//! let mut bmp390 = BMP390Async::new_i2c(delay, i2c, Address::Primary);
58//! bmp390.init().await?;
59//! // Enable sensors
60//! bmp390.configure(Configuration {
61//!     temperature_oversampling: Some(Oversampling::X_32),
62//!     pressure_oversampling: Some(Oversampling::X_32),
63//!     iir_filter: IIRFilter::Disabled,
64//! }).await?;
65//!
66//! // Read the current temperature in °C
67//! let measurement = bmp390.measure().await.unwrap();
68//! let temp = measurement.temperature.expect("should be enabled").get::<degree_celsius>();
69//! let pressure = measurement.pressure.expect("should be enabled").get::<pascal>();
70//! println!("Current measurement: {:?}°C, {:?} Pa", temp, pressure);
71//! # Ok(())
72//! # }
73//! # }
74//! ```
75
76use embedded_devices_derive::{forward_register_fns, sensor};
77use embedded_interfaces::TransportError;
78use uom::si::f64::{Pressure, ThermodynamicTemperature};
79use uom::si::pressure::pascal;
80use uom::si::thermodynamic_temperature::degree_celsius;
81
82pub mod address;
83pub mod registers;
84
85use self::address::Address;
86use self::registers::{
87    BurstMeasurements, ChipId, Cmd, Config, DataRateControl, IIRFilter, Oversampling, OversamplingControl,
88    PowerControl, SensorMode, TrimmingCoefficients, TrimmingCoefficientsUnpacked,
89};
90
91#[cfg_attr(feature = "defmt", derive(defmt::Format))]
92#[derive(Debug, thiserror::Error)]
93pub enum InitError<BusError> {
94    /// Transport error
95    #[error("transport error")]
96    Transport(#[from] TransportError<(), BusError>),
97    /// Invalid chip id was encountered in `init`
98    #[error("invalid chip id {0:#02x}")]
99    InvalidChip(u8),
100}
101
102#[cfg_attr(feature = "defmt", derive(defmt::Format))]
103#[derive(Debug, thiserror::Error)]
104pub enum MeasurementError<BusError> {
105    /// Transport error
106    #[error("transport error")]
107    Transport(#[from] TransportError<(), BusError>),
108    /// The calibration data was not yet read from the device, but a measurement was requested. Call `init` or `calibrate` first.
109    #[error("not yet calibrated")]
110    NotCalibrated,
111}
112
113/// Measurement data
114#[derive(Debug, embedded_devices_derive::Measurement)]
115pub struct Measurement {
116    /// Current temperature
117    #[measurement(Temperature)]
118    pub temperature: Option<ThermodynamicTemperature>,
119    /// Current pressure or None if the sensor reported and invalid value
120    #[measurement(Pressure)]
121    pub pressure: Option<Pressure>,
122}
123
124/// Common configuration values for the BME280 sensor.
125/// The power-on-reset default is to set all oversampling settings to 1X
126/// and use no IIR filter.
127#[derive(Debug, Clone)]
128pub struct Configuration {
129    /// The oversampling rate for temperature mesurements or None to disable this measurement
130    pub temperature_oversampling: Option<Oversampling>,
131    /// The oversampling rate for pressure mesurements or None to disable this measurement
132    pub pressure_oversampling: Option<Oversampling>,
133    /// The iir filter to use
134    pub iir_filter: IIRFilter,
135}
136
137impl Default for Configuration {
138    fn default() -> Self {
139        Self {
140            temperature_oversampling: Some(Oversampling::X_1),
141            pressure_oversampling: Some(Oversampling::X_1),
142            iir_filter: IIRFilter::Disabled,
143        }
144    }
145}
146
147/// Fine temperature coefficient calculated when compensating temperature
148/// and required to compensate pressure
149#[derive(Debug, Clone, Copy)]
150pub(super) struct TFine(i32);
151
152#[derive(Debug)]
153pub(super) struct CalibrationData(TrimmingCoefficientsUnpacked);
154
155impl CalibrationData {
156    pub(super) fn compensate_temperature(&self, uncompensated: u32) -> (ThermodynamicTemperature, TFine) {
157        let v1: u64 = (uncompensated as u64) - ((self.0.par_t1 as u64) << 8);
158        let v2: u64 = self.0.par_t2 as u64 * v1;
159        let v3: u64 = v1 * v1;
160        let v4: i64 = (v3 as i64) * (self.0.par_t3 as i64);
161        let v5: i64 = ((v2 as i64) << 18) + v4;
162        let t_fine: i32 = (v5 >> 32) as i32;
163        let temperature = (t_fine * 25) as f64 / (100 << 14) as f64;
164
165        (
166            ThermodynamicTemperature::new::<degree_celsius>(temperature),
167            TFine(t_fine),
168        )
169    }
170
171    pub(super) fn compensate_pressure(&self, uncompensated: u32, t_fine: TFine) -> Pressure {
172        let t_fine = t_fine.0;
173
174        let v1 = t_fine as i64 * t_fine as i64;
175        let v3 = ((v1 >> 6) * t_fine as i64) >> 8;
176        let v4 = (self.0.par_p8 as i64 * v3) >> 5;
177        let v5 = (self.0.par_p7 as i64 * v1) << 4;
178        let v6 = (self.0.par_p6 as i64 * t_fine as i64) << 22;
179        let offset = ((self.0.par_p5 as i64) << 47) + v4 + v5 + v6;
180        let v2 = ((self.0.par_p4 as i64) * v3) >> 5;
181        let v4 = (self.0.par_p3 as i64 * v1) << 2;
182        let v5 = ((self.0.par_p2 as i64 - 16384) * t_fine as i64) << 21;
183        let sensitivity = (((self.0.par_p1 as i64 - 16384) << 46) + v2 + v4 + v5) >> 24;
184        let v1 = (sensitivity * uncompensated as i64) >> 13;
185        let v2 = (self.0.par_p10 as i64) * (t_fine as i64);
186        let v3 = v2 + ((self.0.par_p9 as i64) << 16);
187        let v4 = (v3 * uncompensated as i64) >> 13;
188        let v5 = (v4 * uncompensated as i64) >> 9;
189        let v6 = uncompensated as i64 * uncompensated as i64;
190        let v2 = ((self.0.par_p11 as i64) * v6) >> 16;
191        let v3 = (v2 * uncompensated as i64) >> 7;
192        let v4 = (offset / 4) + v1 + v5 + v3;
193        let pressure = ((v4 >> 32) * 25) as i32;
194        let pressure = pressure as f64 / (100 << 8) as f64;
195        Pressure::new::<pascal>(pressure)
196    }
197}
198
199/// The BMP390 is a digital sensor with pressure and temperature measurement based on proven sensing principles. The sensor
200/// module is housed in an extremely compact 10-pin metal-lid LGA package with a footprint of only 2.0 × 2.0 mm² and max 0.8
201/// mm package height. Its small dimensions and its low power consumption of 3.2 μA @1Hz allow the implementation in battery
202/// driven devices such as mobile phones, GPS modules or watches.
203#[maybe_async_cfg::maybe(
204    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface),
205    sync(feature = "sync"),
206    async(feature = "async")
207)]
208pub struct BMP390<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> {
209    /// The delay provider
210    delay: D,
211    /// The interface to communicate with the device
212    interface: I,
213    /// Calibration data
214    pub(super) calibration_data: Option<CalibrationData>,
215}
216
217pub trait BMP390Register {}
218
219#[maybe_async_cfg::maybe(
220    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
221    sync(feature = "sync"),
222    async(feature = "async")
223)]
224impl<D, I> BMP390<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
225where
226    I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
227    D: hal::delay::DelayNs,
228{
229    /// Initializes a new device with the given address on the specified bus.
230    /// This consumes the I2C bus `I`.
231    ///
232    /// Before using this device, you must call the [`Self::init`] method which
233    /// initializes the device and ensures that it is working correctly.
234    #[inline]
235    pub fn new_i2c(delay: D, interface: I, address: Address) -> Self {
236        Self {
237            delay,
238            interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
239            calibration_data: None,
240        }
241    }
242}
243
244#[maybe_async_cfg::maybe(
245    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), SpiDevice),
246    sync(feature = "sync"),
247    async(feature = "async")
248)]
249impl<D, I> BMP390<D, embedded_interfaces::spi::SpiDevice<I>>
250where
251    I: hal::spi::r#SpiDevice,
252    D: hal::delay::DelayNs,
253{
254    /// Initializes a new device from the specified SPI device.
255    /// This consumes the SPI device `I`.
256    ///
257    /// Before using this device, you must call the [`Self::init`] method which
258    /// initializes the device and ensures that it is working correctly.
259    #[inline]
260    pub fn new_spi(delay: D, interface: I) -> Self {
261        Self {
262            delay,
263            interface: embedded_interfaces::spi::SpiDevice::new(interface),
264            calibration_data: None,
265        }
266    }
267}
268
269#[forward_register_fns]
270#[sensor(Temperature, Pressure)]
271#[maybe_async_cfg::maybe(
272    idents(
273        hal(sync = "embedded_hal", async = "embedded_hal_async"),
274        RegisterInterface,
275        ResettableDevice
276    ),
277    sync(feature = "sync"),
278    async(feature = "async")
279)]
280impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> BMP390<D, I> {
281    /// Initialize the sensor by performing a soft-reset, verifying its chip id
282    /// and reading calibration data.
283    ///
284    /// Beware that by default all internal sensors are disabled. Please
285    /// call [`Self::configure`] after initialization to enable the sensors,
286    /// otherwise measurement may return valid-looking but static values.
287    pub async fn init(&mut self) -> Result<(), InitError<I::BusError>> {
288        use crate::device::ResettableDevice;
289
290        // Soft-reset device
291        self.reset().await?;
292
293        // Verify chip id
294        let chip = self.read_register::<ChipId>().await?.read_chip();
295        if let self::registers::Chip::Invalid(x) = chip {
296            return Err(InitError::InvalidChip(x));
297        }
298
299        // Read calibration data
300        self.calibrate().await?;
301        Ok(())
302    }
303
304    /// Reads the calibration registers from the device to
305    /// compensate measurements. It is required to call this once
306    /// before taking any measurements. Calling [`Self::init`] will
307    /// automatically do this.
308    pub async fn calibrate(&mut self) -> Result<(), TransportError<(), I::BusError>> {
309        let coefficients = self.read_register::<TrimmingCoefficients>().await?.unpack();
310        self.calibration_data = Some(CalibrationData(coefficients));
311
312        Ok(())
313    }
314
315    /// Configures common sensor settings. Sensor must be in sleep mode for this to work. To
316    /// configure advanced settings, please directly update the respective registers.
317    pub async fn configure(&mut self, config: Configuration) -> Result<(), TransportError<(), I::BusError>> {
318        self.write_register(
319            PowerControl::default()
320                .with_temperature_enable(config.temperature_oversampling.is_some())
321                .with_pressure_enable(config.pressure_oversampling.is_some()),
322        )
323        .await?;
324
325        let mut oversampling = OversamplingControl::default();
326        if let Some(ov) = config.temperature_oversampling {
327            oversampling.write_temperature_oversampling(ov);
328        }
329        if let Some(ov) = config.pressure_oversampling {
330            oversampling.write_pressure_oversampling(ov);
331        }
332        self.write_register(oversampling).await?;
333        self.write_register(Config::default().with_iir_filter(config.iir_filter))
334            .await?;
335
336        Ok(())
337    }
338}
339
340#[maybe_async_cfg::maybe(
341    idents(
342        hal(sync = "embedded_hal", async = "embedded_hal_async"),
343        RegisterInterface,
344        ResettableDevice
345    ),
346    sync(feature = "sync"),
347    async(feature = "async")
348)]
349impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::device::ResettableDevice
350    for BMP390<D, I>
351{
352    type Error = TransportError<(), I::BusError>;
353
354    /// Performs a soft-reset of the device. The datasheet specifies a start-up time
355    /// of 10ms, which is automatically awaited before allowing further communication.
356    async fn reset(&mut self) -> Result<(), Self::Error> {
357        self.write_register(self::registers::Command::default().with_command(Cmd::Reset))
358            .await?;
359        self.delay.delay_ms(10).await;
360        Ok(())
361    }
362}
363
364#[maybe_async_cfg::maybe(
365    idents(
366        hal(sync = "embedded_hal", async = "embedded_hal_async"),
367        RegisterInterface,
368        OneshotSensor
369    ),
370    sync(feature = "sync"),
371    async(feature = "async")
372)]
373impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::OneshotSensor
374    for BMP390<D, I>
375{
376    type Error = MeasurementError<I::BusError>;
377    type Measurement = Measurement;
378
379    /// Performs a one-shot measurement. This will transition the device into forced mode,
380    /// which will cause it to take a measurement and return to sleep mode afterwards.
381    ///
382    /// This function will wait until the data is acquired, perform a burst read
383    /// and compensate the returned raw data using the calibration data.
384    async fn measure(&mut self) -> Result<Self::Measurement, Self::Error> {
385        let power_ctrl = self.read_register::<PowerControl>().await?;
386        self.write_register(power_ctrl.with_sensor_mode(SensorMode::Forced))
387            .await?;
388
389        let temperature_enable = power_ctrl.read_temperature_enable();
390        let pressure_enable = power_ctrl.read_pressure_enable();
391
392        if !temperature_enable && !pressure_enable {
393            return Ok(Measurement {
394                temperature: None,
395                pressure: None,
396            });
397        }
398
399        // Read current oversampling config to determine required measurement delay
400        let reg_ctrl_m = self.read_register::<OversamplingControl>().await?;
401        let o_t = reg_ctrl_m.read_temperature_oversampling();
402        let o_p = reg_ctrl_m.read_pressure_oversampling();
403
404        // Maximum time required to perform the measurement.
405        // See section 3.9.2 of the datasheet for more information.
406        let max_measurement_delay_us = 234 + (392 + 2020 * o_p.factor()) + (163 + o_t.factor() * 2020);
407        self.delay.delay_us(max_measurement_delay_us).await;
408
409        let raw_data = self.read_register::<BurstMeasurements>().await?;
410        let Some(ref cal) = self.calibration_data else {
411            return Err(MeasurementError::NotCalibrated);
412        };
413
414        let temperature_enable = power_ctrl.read_temperature_enable();
415        let pressure_enable = power_ctrl.read_pressure_enable();
416
417        let (temperature, pressure) = if temperature_enable {
418            let (temp, t_fine) = cal.compensate_temperature(raw_data.read_temperature_value());
419            let press = pressure_enable.then(|| cal.compensate_pressure(raw_data.read_pressure_value(), t_fine));
420            (Some(temp), press)
421        } else {
422            (None, None)
423        };
424
425        Ok(Measurement { temperature, pressure })
426    }
427}
428
429#[maybe_async_cfg::maybe(
430    idents(
431        hal(sync = "embedded_hal", async = "embedded_hal_async"),
432        RegisterInterface,
433        ContinuousSensor
434    ),
435    sync(feature = "sync"),
436    async(feature = "async")
437)]
438impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::ContinuousSensor
439    for BMP390<D, I>
440{
441    type Error = MeasurementError<I::BusError>;
442    type Measurement = Measurement;
443
444    /// Starts continuous measurement.
445    async fn start_measuring(&mut self) -> Result<(), Self::Error> {
446        let power_ctrl = self.read_register::<PowerControl>().await?;
447        self.write_register(power_ctrl.with_sensor_mode(SensorMode::Normal))
448            .await?;
449        Ok(())
450    }
451
452    /// Stops continuous measurement.
453    async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
454        let power_ctrl = self.read_register::<PowerControl>().await?;
455        self.write_register(power_ctrl.with_sensor_mode(SensorMode::Sleep))
456            .await?;
457        Ok(())
458    }
459
460    /// Expected amount of time between measurements in microseconds.
461    async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
462        // Read current oversampling config to determine required measurement delay
463        let reg_ctrl_m = self.read_register::<OversamplingControl>().await?;
464        let o_t = reg_ctrl_m.read_temperature_oversampling();
465        let o_p = reg_ctrl_m.read_pressure_oversampling();
466
467        // Maximum time required to perform the measurement.
468        // See section 3.9.2 of the datasheet for more information.
469        let t_measure_us = 234 + (392 + 2020 * o_p.factor()) + (163 + o_t.factor() * 2020);
470
471        let data_rate_ctrl = self.read_register::<DataRateControl>().await?;
472        let t_standby_us = data_rate_ctrl.read_data_rate().interval_us();
473        Ok(t_measure_us + t_standby_us)
474    }
475
476    /// Returns the most recent measurement. Will never return None.
477    async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
478        let raw_data = self.read_register::<BurstMeasurements>().await?;
479        let power_ctrl = self.read_register::<PowerControl>().await?;
480        let Some(ref cal) = self.calibration_data else {
481            return Err(MeasurementError::NotCalibrated);
482        };
483
484        let temperature_enable = power_ctrl.read_temperature_enable();
485        let pressure_enable = power_ctrl.read_pressure_enable();
486
487        let (temperature, pressure) = if temperature_enable {
488            let (temp, t_fine) = cal.compensate_temperature(raw_data.read_temperature_value());
489            let press = pressure_enable.then(|| cal.compensate_pressure(raw_data.read_pressure_value(), t_fine));
490            (Some(temp), press)
491        } else {
492            (None, None)
493        };
494
495        Ok(Some(Measurement { temperature, pressure }))
496    }
497
498    /// Not supported, always returns true.
499    async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
500        Ok(true)
501    }
502
503    /// Opportunistically waits one conversion interval and returns the measurement.
504    async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
505        let interval = self.measurement_interval_us().await?;
506        self.delay.delay_us(interval).await;
507        self.current_measurement().await?.ok_or_else(|| {
508            MeasurementError::Transport(TransportError::Unexpected(
509                "measurement was not ready even though we expected it to be",
510            ))
511        })
512    }
513}