Skip to main content

pamoja_sensors/
bme280.rs

1//! Bosch BME280 temperature, pressure, and humidity sensor.
2//!
3//! The BME280 ships raw, uncompensated readings plus a block of per-chip calibration
4//! coefficients; the real measurement only appears after running those raw values
5//! through Bosch's compensation formulas. Those formulas are the classic place a
6//! from-memory port goes subtly wrong, so the integer arithmetic here is ported from
7//! Bosch's published reference code, and the tests cross-check it against the
8//! reference floating-point form and the datasheet's worked temperature example.
9//!
10//! A caller reads the calibration registers once with [`Calibration::from_registers`],
11//! then reads the data registers each cycle into a [`RawMeasurement`] and calls
12//! [`Calibration::compensate`]. The control registers ([`CtrlHum`], [`CtrlMeas`],
13//! [`Config`]) and the measurement-time formula are here too, and the [`Bme280`]
14//! driver (the `embedded-hal` feature) runs the whole sequence over an I2C or SPI bus.
15
16#[cfg(feature = "embedded-hal")]
17mod driver;
18#[cfg(feature = "embedded-hal")]
19pub mod sim;
20
21#[cfg(feature = "embedded-hal")]
22pub use driver::{Bme280, STATUS_POLLS};
23
24/// The I2C address with the SDO pin tied low.
25pub const I2C_ADDRESS_PRIMARY: u8 = 0x76;
26/// The I2C address with the SDO pin tied high.
27pub const I2C_ADDRESS_SECONDARY: u8 = 0x77;
28/// The value the chip-id register (0xD0) returns for a BME280.
29pub const CHIP_ID: u8 = 0x60;
30
31/// The BME280 register addresses used to read calibration and measurements.
32pub mod register {
33    /// Chip-id register; reads [`super::CHIP_ID`] for a BME280.
34    pub const CHIP_ID: u8 = 0xD0;
35    /// Soft-reset register.
36    pub const RESET: u8 = 0xE0;
37    /// First of the 26 temperature and pressure calibration bytes (0x88..=0xA1).
38    pub const CALIB_TEMP_PRESS: u8 = 0x88;
39    /// First of the 7 humidity calibration bytes (0xE1..=0xE7).
40    pub const CALIB_HUMIDITY: u8 = 0xE1;
41    /// First of the 8 burst-read data bytes: pressure, temperature, humidity
42    /// (0xF7..=0xFE).
43    pub const DATA: u8 = 0xF7;
44    /// Humidity acquisition options; effective only after a write to `CTRL_MEAS`.
45    pub const CTRL_HUM: u8 = 0xF2;
46    /// The `measuring` and `im_update` flags.
47    pub const STATUS: u8 = 0xF3;
48    /// Temperature and pressure acquisition options and the power mode.
49    pub const CTRL_MEAS: u8 = 0xF4;
50    /// Standby time, IIR filter, and the 3-wire SPI switch.
51    pub const CONFIG: u8 = 0xF5;
52}
53
54/// The soft-reset word: writing it to [`register::RESET`] runs the power-on sequence.
55pub const RESET_WORD: u8 = 0xB6;
56/// The value a skipped temperature or pressure measurement leaves in its registers.
57pub const SKIPPED_OUTPUT: u32 = 0x80000;
58/// The value a skipped humidity measurement leaves in its registers.
59pub const SKIPPED_HUMIDITY: u16 = 0x8000;
60/// The number of bytes in the temperature and pressure calibration block.
61pub const CALIB_TEMP_PRESS_LEN: usize = 26;
62/// The number of bytes in the humidity calibration block.
63pub const CALIB_HUMIDITY_LEN: usize = 7;
64/// The number of bytes in the burst-read data block.
65pub const DATA_LEN: usize = 8;
66/// The start-up time from power-on or reset until the part accepts a transfer, in
67/// microseconds.
68pub const STARTUP_MICROS: u32 = 2_000;
69
70/// Reads the `measuring` flag of a status register value.
71///
72/// # Arguments
73///
74/// * `status` - the byte read from [`register::STATUS`].
75///
76/// # Returns
77///
78/// `true` while a conversion is running and its results are not yet in the data
79/// registers.
80pub fn measuring(status: u8) -> bool {
81    status & 0x08 != 0
82}
83
84/// Reads the `im_update` flag of a status register value.
85///
86/// # Arguments
87///
88/// * `status` - the byte read from [`register::STATUS`].
89///
90/// # Returns
91///
92/// `true` while the calibration data is being copied from non-volatile memory, at
93/// power-on reset and before every conversion.
94pub fn image_updating(status: u8) -> bool {
95    status & 0x01 != 0
96}
97
98/// The per-chip calibration coefficients read from the sensor's calibration registers.
99///
100/// These are factory-programmed and constant for a given device, so they are read
101/// once at start-up and reused for every measurement.
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct Calibration {
104    dig_t1: u16,
105    dig_t2: i16,
106    dig_t3: i16,
107    dig_p1: u16,
108    dig_p2: i16,
109    dig_p3: i16,
110    dig_p4: i16,
111    dig_p5: i16,
112    dig_p6: i16,
113    dig_p7: i16,
114    dig_p8: i16,
115    dig_p9: i16,
116    dig_h1: u8,
117    dig_h2: i16,
118    dig_h3: u8,
119    dig_h4: i16,
120    dig_h5: i16,
121    dig_h6: i8,
122}
123
124impl Calibration {
125    /// Parses the calibration coefficients from the two register blocks.
126    ///
127    /// The split-and-shifted packing of `dig_h4` and `dig_h5`, which share a byte, is
128    /// done exactly as the datasheet specifies.
129    ///
130    /// # Arguments
131    ///
132    /// * `temp_press` - the 26 bytes read from `0x88..=0xA1`.
133    /// * `humidity` - the 7 bytes read from `0xE1..=0xE7`.
134    ///
135    /// # Returns
136    ///
137    /// The decoded calibration.
138    pub fn from_registers(temp_press: &[u8; 26], humidity: &[u8; 7]) -> Calibration {
139        let tp = temp_press;
140        let h = humidity;
141        Calibration {
142            dig_t1: u16::from_le_bytes([tp[0], tp[1]]),
143            dig_t2: i16::from_le_bytes([tp[2], tp[3]]),
144            dig_t3: i16::from_le_bytes([tp[4], tp[5]]),
145            dig_p1: u16::from_le_bytes([tp[6], tp[7]]),
146            dig_p2: i16::from_le_bytes([tp[8], tp[9]]),
147            dig_p3: i16::from_le_bytes([tp[10], tp[11]]),
148            dig_p4: i16::from_le_bytes([tp[12], tp[13]]),
149            dig_p5: i16::from_le_bytes([tp[14], tp[15]]),
150            dig_p6: i16::from_le_bytes([tp[16], tp[17]]),
151            dig_p7: i16::from_le_bytes([tp[18], tp[19]]),
152            dig_p8: i16::from_le_bytes([tp[20], tp[21]]),
153            dig_p9: i16::from_le_bytes([tp[22], tp[23]]),
154            dig_h1: tp[25],
155            dig_h2: i16::from_le_bytes([h[0], h[1]]),
156            dig_h3: h[2],
157            dig_h4: ((h[3] as i8 as i16) * 16) | (h[4] & 0x0F) as i16,
158            dig_h5: ((h[5] as i8 as i16) * 16) | (h[4] >> 4) as i16,
159            dig_h6: h[6] as i8,
160        }
161    }
162
163    /// Compensates a raw measurement into temperature, pressure, and humidity.
164    ///
165    /// Temperature is computed first because its intermediate `t_fine` term feeds the
166    /// pressure and humidity formulas, exactly as the reference code shares it.
167    ///
168    /// # Arguments
169    ///
170    /// * `raw` - the uncompensated readings from the data registers.
171    ///
172    /// # Returns
173    ///
174    /// The compensated [`Measurement`].
175    pub fn compensate(&self, raw: &RawMeasurement) -> Measurement {
176        let t_fine = self.t_fine(raw.temperature);
177        Measurement {
178            temperature_centi_celsius: compensate_temperature(t_fine),
179            pressure_centi_pascals: self.compensate_pressure(raw.pressure, t_fine),
180            humidity_q22_10: self.compensate_humidity(raw.humidity, t_fine),
181        }
182    }
183
184    // The shared fine-temperature term, in the reference code's fixed-point form.
185    fn t_fine(&self, adc_t: i32) -> i32 {
186        let var1 = ((adc_t / 8) - (self.dig_t1 as i32 * 2)) * self.dig_t2 as i32 / 2048;
187        let near = (adc_t / 16) - self.dig_t1 as i32;
188        let var2 = (((near * near) / 4096) * self.dig_t3 as i32) / 16384;
189        var1 + var2
190    }
191
192    // Pressure in hundredths of a pascal, via the 64-bit reference path.
193    fn compensate_pressure(&self, adc_p: i32, t_fine: i32) -> u32 {
194        let mut var1 = t_fine as i64 - 128000;
195        let mut var2 = var1 * var1 * self.dig_p6 as i64;
196        var2 += (var1 * self.dig_p5 as i64) * 131072;
197        var2 += self.dig_p4 as i64 * 34359738368;
198        var1 = (var1 * var1 * self.dig_p3 as i64) / 256 + (var1 * self.dig_p2 as i64 * 4096);
199        var1 = (140737488355328 + var1) * self.dig_p1 as i64 / 8589934592;
200        if var1 == 0 {
201            return 3_000_000;
202        }
203        let mut var4 = 1048576 - adc_p as i64;
204        var4 = (((var4 * 2147483648) - var2) * 3125) / var1;
205        var1 = (self.dig_p9 as i64 * (var4 / 8192) * (var4 / 8192)) / 33554432;
206        var2 = (self.dig_p8 as i64 * var4) / 524288;
207        var4 = ((var4 + var1 + var2) / 256) + (self.dig_p7 as i64 * 16);
208        let pressure = ((var4 / 2) * 100) / 128;
209        pressure.clamp(3_000_000, 11_000_000) as u32
210    }
211
212    // Humidity in Q22.10 fixed-point (units of 1/1024 %), via the reference path.
213    fn compensate_humidity(&self, adc_h: i32, t_fine: i32) -> u32 {
214        let var1 = t_fine - 76800;
215        let var2 = adc_h * 16384;
216        let var3 = self.dig_h4 as i32 * 1048576;
217        let var4 = self.dig_h5 as i32 * var1;
218        let var5 = (((var2 - var3) - var4) + 16384) / 32768;
219        let var2 = (var1 * self.dig_h6 as i32) / 1024;
220        let var3 = (var1 * self.dig_h3 as i32) / 2048;
221        let var4 = ((var2 * (var3 + 32768)) / 1024) + 2097152;
222        let var2 = ((var4 * self.dig_h2 as i32) + 8192) / 16384;
223        let var3 = var5 * var2;
224        let var4 = ((var3 / 32768) * (var3 / 32768)) / 128;
225        let var5 = (var3 - ((var4 * self.dig_h1 as i32) / 16)).clamp(0, 419430400);
226        ((var5 / 4096) as u32).min(102400)
227    }
228}
229
230// The integer temperature in hundredths of a degree Celsius, clamped to range.
231fn compensate_temperature(t_fine: i32) -> i32 {
232    ((t_fine * 5 + 128) / 256).clamp(-4000, 8500)
233}
234
235/// The raw, uncompensated readings from the BME280 data registers.
236#[derive(Clone, Copy, Debug, PartialEq, Eq)]
237pub struct RawMeasurement {
238    /// The 20-bit uncompensated temperature.
239    pub temperature: i32,
240    /// The 20-bit uncompensated pressure.
241    pub pressure: i32,
242    /// The 16-bit uncompensated humidity.
243    pub humidity: i32,
244}
245
246impl RawMeasurement {
247    /// Parses the eight data-register bytes burst-read from `0xF7..=0xFE`.
248    ///
249    /// The order on the wire is pressure (20 bits), temperature (20 bits), then
250    /// humidity (16 bits), each most significant byte first.
251    ///
252    /// # Arguments
253    ///
254    /// * `data` - the eight bytes read from the data registers.
255    ///
256    /// # Returns
257    ///
258    /// The unpacked raw measurement.
259    pub fn from_registers(data: &[u8; 8]) -> RawMeasurement {
260        let pressure =
261            (i32::from(data[0]) << 12) | (i32::from(data[1]) << 4) | (i32::from(data[2]) >> 4);
262        let temperature =
263            (i32::from(data[3]) << 12) | (i32::from(data[4]) << 4) | (i32::from(data[5]) >> 4);
264        let humidity = (i32::from(data[6]) << 8) | i32::from(data[7]);
265        RawMeasurement {
266            temperature,
267            pressure,
268            humidity,
269        }
270    }
271}
272
273/// A compensated BME280 measurement.
274///
275/// The fields are the exact integer outputs of the compensation formulas; the
276/// methods present them in conventional units.
277#[derive(Clone, Copy, Debug, PartialEq, Eq)]
278pub struct Measurement {
279    /// Temperature in hundredths of a degree Celsius.
280    pub temperature_centi_celsius: i32,
281    /// Pressure in hundredths of a pascal.
282    pub pressure_centi_pascals: u32,
283    /// Relative humidity in Q22.10 fixed-point, that is units of 1/1024 percent.
284    pub humidity_q22_10: u32,
285}
286
287impl Measurement {
288    /// Returns the temperature in degrees Celsius.
289    pub fn celsius(&self) -> f32 {
290        self.temperature_centi_celsius as f32 / 100.0
291    }
292
293    /// Returns the pressure in pascals.
294    pub fn pascals(&self) -> u32 {
295        self.pressure_centi_pascals / 100
296    }
297
298    /// Returns the pressure in hectopascals (millibars).
299    pub fn hectopascals(&self) -> f32 {
300        self.pressure_centi_pascals as f32 / 10_000.0
301    }
302
303    /// Returns the relative humidity in percent.
304    pub fn relative_humidity_percent(&self) -> f32 {
305        self.humidity_q22_10 as f32 / 1024.0
306    }
307}
308
309/// An oversampling setting for one of the three measurements.
310///
311/// Each doubling of the sample count adds one bit of resolution to temperature and
312/// pressure, up to the 20 bits the data registers hold.
313#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
314pub enum Oversampling {
315    /// The measurement is skipped; its data registers read [`SKIPPED_OUTPUT`] or
316    /// [`SKIPPED_HUMIDITY`].
317    #[default]
318    Skipped,
319    /// A single sample.
320    X1,
321    /// Two samples.
322    X2,
323    /// Four samples.
324    X4,
325    /// Eight samples.
326    X8,
327    /// Sixteen samples.
328    X16,
329}
330
331impl Oversampling {
332    /// Returns the three-bit `osrs_t`, `osrs_p`, or `osrs_h` field value.
333    pub fn code(self) -> u8 {
334        match self {
335            Oversampling::Skipped => 0b000,
336            Oversampling::X1 => 0b001,
337            Oversampling::X2 => 0b010,
338            Oversampling::X4 => 0b011,
339            Oversampling::X8 => 0b100,
340            Oversampling::X16 => 0b101,
341        }
342    }
343
344    /// Decodes a three-bit oversampling field value.
345    ///
346    /// The codes `0b101`, `0b110`, and `0b111` all select 16x oversampling.
347    ///
348    /// # Arguments
349    ///
350    /// * `code` - the field value; only the low three bits are used.
351    ///
352    /// # Returns
353    ///
354    /// The oversampling setting.
355    pub fn from_code(code: u8) -> Oversampling {
356        match code & 0b111 {
357            0b000 => Oversampling::Skipped,
358            0b001 => Oversampling::X1,
359            0b010 => Oversampling::X2,
360            0b011 => Oversampling::X4,
361            0b100 => Oversampling::X8,
362            _ => Oversampling::X16,
363        }
364    }
365
366    /// Returns the number of samples averaged, or `0` when skipped.
367    pub fn factor(self) -> u8 {
368        match self {
369            Oversampling::Skipped => 0,
370            Oversampling::X1 => 1,
371            Oversampling::X2 => 2,
372            Oversampling::X4 => 4,
373            Oversampling::X8 => 8,
374            Oversampling::X16 => 16,
375        }
376    }
377}
378
379/// The power mode selected by the `mode[1:0]` field.
380#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
381pub enum Mode {
382    /// No measurements; the power-on default. Every register stays readable.
383    #[default]
384    Sleep,
385    /// One measurement, then back to sleep.
386    Forced,
387    /// Continuous cycling between a measurement and a standby period.
388    Normal,
389}
390
391impl Mode {
392    /// Returns the two-bit `mode` field value.
393    pub fn code(self) -> u8 {
394        match self {
395            Mode::Sleep => 0b00,
396            Mode::Forced => 0b01,
397            Mode::Normal => 0b11,
398        }
399    }
400
401    /// Decodes a two-bit `mode` field value; both `0b01` and `0b10` mean forced.
402    ///
403    /// # Arguments
404    ///
405    /// * `code` - the field value; only the low two bits are used.
406    ///
407    /// # Returns
408    ///
409    /// The power mode.
410    pub fn from_code(code: u8) -> Mode {
411        match code & 0b11 {
412            0b00 => Mode::Sleep,
413            0b11 => Mode::Normal,
414            _ => Mode::Forced,
415        }
416    }
417}
418
419/// The inactive period between measurements in normal mode, the `t_sb[2:0]` field.
420///
421/// The two longest BMP280 settings become the two shortest here: codes `0b110` and
422/// `0b111` mean 10 and 20 ms on a BME280.
423#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
424pub enum Standby {
425    /// 0.5 ms.
426    #[default]
427    Ms0_5,
428    /// 62.5 ms.
429    Ms62_5,
430    /// 125 ms.
431    Ms125,
432    /// 250 ms.
433    Ms250,
434    /// 500 ms.
435    Ms500,
436    /// 1000 ms.
437    Ms1000,
438    /// 10 ms.
439    Ms10,
440    /// 20 ms.
441    Ms20,
442}
443
444impl Standby {
445    /// Returns the three-bit `t_sb` field value.
446    pub fn code(self) -> u8 {
447        match self {
448            Standby::Ms0_5 => 0b000,
449            Standby::Ms62_5 => 0b001,
450            Standby::Ms125 => 0b010,
451            Standby::Ms250 => 0b011,
452            Standby::Ms500 => 0b100,
453            Standby::Ms1000 => 0b101,
454            Standby::Ms10 => 0b110,
455            Standby::Ms20 => 0b111,
456        }
457    }
458
459    /// Decodes a three-bit `t_sb` field value.
460    ///
461    /// # Arguments
462    ///
463    /// * `code` - the field value; only the low three bits are used.
464    ///
465    /// # Returns
466    ///
467    /// The standby period.
468    pub fn from_code(code: u8) -> Standby {
469        match code & 0b111 {
470            0b000 => Standby::Ms0_5,
471            0b001 => Standby::Ms62_5,
472            0b010 => Standby::Ms125,
473            0b011 => Standby::Ms250,
474            0b100 => Standby::Ms500,
475            0b101 => Standby::Ms1000,
476            0b110 => Standby::Ms10,
477            _ => Standby::Ms20,
478        }
479    }
480
481    /// Returns the standby period in microseconds.
482    pub fn microseconds(self) -> u32 {
483        match self {
484            Standby::Ms0_5 => 500,
485            Standby::Ms62_5 => 62_500,
486            Standby::Ms125 => 125_000,
487            Standby::Ms250 => 250_000,
488            Standby::Ms500 => 500_000,
489            Standby::Ms1000 => 1_000_000,
490            Standby::Ms10 => 10_000,
491            Standby::Ms20 => 20_000,
492        }
493    }
494}
495
496/// The IIR filter coefficient, the `filter[2:0]` field.
497///
498/// The filter smooths pressure and temperature across measurements and raises their
499/// resolution to 20 bits; humidity is never filtered.
500#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
501pub enum Filter {
502    /// No filtering.
503    #[default]
504    Off,
505    /// Coefficient 2.
506    X2,
507    /// Coefficient 4.
508    X4,
509    /// Coefficient 8.
510    X8,
511    /// Coefficient 16.
512    X16,
513}
514
515impl Filter {
516    /// Returns the three-bit `filter` field value.
517    pub fn code(self) -> u8 {
518        match self {
519            Filter::Off => 0b000,
520            Filter::X2 => 0b001,
521            Filter::X4 => 0b010,
522            Filter::X8 => 0b011,
523            Filter::X16 => 0b100,
524        }
525    }
526
527    /// Decodes a three-bit `filter` field value; `0b100` and above mean 16.
528    ///
529    /// # Arguments
530    ///
531    /// * `code` - the field value; only the low three bits are used.
532    ///
533    /// # Returns
534    ///
535    /// The filter setting.
536    pub fn from_code(code: u8) -> Filter {
537        match code & 0b111 {
538            0b000 => Filter::Off,
539            0b001 => Filter::X2,
540            0b010 => Filter::X4,
541            0b011 => Filter::X8,
542            _ => Filter::X16,
543        }
544    }
545
546    /// Returns the filter coefficient, or `0` when off.
547    pub fn coefficient(self) -> u8 {
548        match self {
549            Filter::Off => 0,
550            Filter::X2 => 2,
551            Filter::X4 => 4,
552            Filter::X8 => 8,
553            Filter::X16 => 16,
554        }
555    }
556}
557
558/// The humidity control register (0xF2, `ctrl_hum`).
559///
560/// A write takes effect only after the next write to `ctrl_meas`. The default is the
561/// reset state, `0x00`: humidity skipped.
562#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
563pub struct CtrlHum {
564    /// Humidity oversampling, `osrs_h[2:0]` in bits 2:0.
565    pub humidity: Oversampling,
566}
567
568impl CtrlHum {
569    /// Packs the setting into the register byte.
570    ///
571    /// # Returns
572    ///
573    /// The byte to write to [`register::CTRL_HUM`].
574    pub fn bits(self) -> u8 {
575        self.humidity.code()
576    }
577
578    /// Decodes a register byte.
579    ///
580    /// # Arguments
581    ///
582    /// * `bits` - the byte read from [`register::CTRL_HUM`].
583    ///
584    /// # Returns
585    ///
586    /// The decoded setting.
587    pub fn from_bits(bits: u8) -> CtrlHum {
588        CtrlHum {
589            humidity: Oversampling::from_code(bits),
590        }
591    }
592}
593
594/// The measurement control register (0xF4, `ctrl_meas`).
595///
596/// The default is the reset state, `0x00`: temperature and pressure skipped, the
597/// part asleep.
598#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
599pub struct CtrlMeas {
600    /// Temperature oversampling, `osrs_t[2:0]` in bits 7:5.
601    pub temperature: Oversampling,
602    /// Pressure oversampling, `osrs_p[2:0]` in bits 4:2.
603    pub pressure: Oversampling,
604    /// Power mode, `mode[1:0]` in bits 1:0.
605    pub mode: Mode,
606}
607
608impl CtrlMeas {
609    /// Packs the settings into the register byte.
610    ///
611    /// # Returns
612    ///
613    /// The byte to write to [`register::CTRL_MEAS`].
614    pub fn bits(self) -> u8 {
615        (self.temperature.code() << 5) | (self.pressure.code() << 2) | self.mode.code()
616    }
617
618    /// Decodes a register byte.
619    ///
620    /// # Arguments
621    ///
622    /// * `bits` - the byte read from [`register::CTRL_MEAS`].
623    ///
624    /// # Returns
625    ///
626    /// The decoded settings.
627    pub fn from_bits(bits: u8) -> CtrlMeas {
628        CtrlMeas {
629            temperature: Oversampling::from_code(bits >> 5),
630            pressure: Oversampling::from_code(bits >> 2),
631            mode: Mode::from_code(bits),
632        }
633    }
634}
635
636/// The configuration register (0xF5, `config`).
637///
638/// Writes may be ignored in normal mode; write it in sleep mode. The default is the
639/// reset state, `0x00`.
640#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
641pub struct Config {
642    /// Normal-mode standby period, `t_sb[2:0]` in bits 7:5.
643    pub standby: Standby,
644    /// IIR filter coefficient, `filter[2:0]` in bits 4:2.
645    pub filter: Filter,
646    /// Enables the 3-wire SPI interface, `spi3w_en` in bit 0.
647    pub spi_3wire: bool,
648}
649
650impl Config {
651    /// Packs the settings into the register byte.
652    ///
653    /// # Returns
654    ///
655    /// The byte to write to [`register::CONFIG`].
656    pub fn bits(self) -> u8 {
657        (self.standby.code() << 5) | (self.filter.code() << 2) | u8::from(self.spi_3wire)
658    }
659
660    /// Decodes a register byte.
661    ///
662    /// # Arguments
663    ///
664    /// * `bits` - the byte read from [`register::CONFIG`].
665    ///
666    /// # Returns
667    ///
668    /// The decoded settings.
669    pub fn from_bits(bits: u8) -> Config {
670        Config {
671            standby: Standby::from_code(bits >> 5),
672            filter: Filter::from_code(bits >> 2),
673            spi_3wire: bits & 0x01 != 0,
674        }
675    }
676}
677
678/// The longest one measurement cycle can take, in microseconds.
679///
680/// This is the datasheet's maximum: 1.25 ms, plus 2.3 ms per temperature sample,
681/// plus 2.3 ms per pressure sample and 0.575 ms, plus 2.3 ms per humidity sample and
682/// 0.575 ms, each term only for a measurement that is not skipped. A driver waits
683/// this long after forcing a measurement before it polls the status register.
684///
685/// # Arguments
686///
687/// * `temperature` - temperature oversampling.
688/// * `pressure` - pressure oversampling.
689/// * `humidity` - humidity oversampling.
690///
691/// # Returns
692///
693/// The maximum measurement time in microseconds.
694///
695/// # Examples
696///
697/// ```
698/// use pamoja_sensors::bme280::{max_measurement_micros, Oversampling};
699///
700/// // The datasheet's worked example: temperature x1, pressure x4, no humidity.
701/// let micros = max_measurement_micros(Oversampling::X1, Oversampling::X4, Oversampling::Skipped);
702/// assert_eq!(micros, 13_325);
703/// ```
704pub fn max_measurement_micros(
705    temperature: Oversampling,
706    pressure: Oversampling,
707    humidity: Oversampling,
708) -> u32 {
709    let mut micros = 1_250;
710    if temperature != Oversampling::Skipped {
711        micros += 2_300 * u32::from(temperature.factor());
712    }
713    if pressure != Oversampling::Skipped {
714        micros += 2_300 * u32::from(pressure.factor()) + 575;
715    }
716    if humidity != Oversampling::Skipped {
717        micros += 2_300 * u32::from(humidity.factor()) + 575;
718    }
719    micros
720}
721
722/// The typical time one measurement cycle takes, in microseconds.
723///
724/// The datasheet's typical figure: 1 ms, plus 2 ms per temperature sample, plus 2 ms
725/// per pressure sample and 0.5 ms, plus 2 ms per humidity sample and 0.5 ms, each
726/// term only for a measurement that is not skipped.
727///
728/// # Arguments
729///
730/// * `temperature` - temperature oversampling.
731/// * `pressure` - pressure oversampling.
732/// * `humidity` - humidity oversampling.
733///
734/// # Returns
735///
736/// The typical measurement time in microseconds.
737pub fn typical_measurement_micros(
738    temperature: Oversampling,
739    pressure: Oversampling,
740    humidity: Oversampling,
741) -> u32 {
742    let mut micros = 1_000;
743    if temperature != Oversampling::Skipped {
744        micros += 2_000 * u32::from(temperature.factor());
745    }
746    if pressure != Oversampling::Skipped {
747        micros += 2_000 * u32::from(pressure.factor()) + 500;
748    }
749    if humidity != Oversampling::Skipped {
750        micros += 2_000 * u32::from(humidity.factor()) + 500;
751    }
752    micros
753}
754
755impl Calibration {
756    /// Packs the coefficients back into the two register blocks.
757    ///
758    /// This is the inverse of [`from_registers`](Calibration::from_registers), so a
759    /// test can build what a part with these coefficients holds in its calibration
760    /// registers. Byte 24 of the first block (register 0xA0) is not a coefficient and
761    /// is written as zero.
762    ///
763    /// # Returns
764    ///
765    /// The 26 bytes of `0x88..=0xA1` and the 7 bytes of `0xE1..=0xE7`.
766    pub fn to_registers(&self) -> ([u8; CALIB_TEMP_PRESS_LEN], [u8; CALIB_HUMIDITY_LEN]) {
767        let mut tp = [0u8; CALIB_TEMP_PRESS_LEN];
768        let words = [
769            self.dig_t1,
770            self.dig_t2 as u16,
771            self.dig_t3 as u16,
772            self.dig_p1,
773            self.dig_p2 as u16,
774            self.dig_p3 as u16,
775            self.dig_p4 as u16,
776            self.dig_p5 as u16,
777            self.dig_p6 as u16,
778            self.dig_p7 as u16,
779            self.dig_p8 as u16,
780            self.dig_p9 as u16,
781        ];
782        for (index, word) in words.into_iter().enumerate() {
783            let [low, high] = word.to_le_bytes();
784            tp[2 * index] = low;
785            tp[2 * index + 1] = high;
786        }
787        tp[25] = self.dig_h1;
788
789        let h2 = (self.dig_h2 as u16).to_le_bytes();
790        let h = [
791            h2[0],
792            h2[1],
793            self.dig_h3,
794            (self.dig_h4 >> 4) as u8,
795            (((self.dig_h5 & 0x0F) as u8) << 4) | (self.dig_h4 & 0x0F) as u8,
796            (self.dig_h5 >> 4) as u8,
797            self.dig_h6 as u8,
798        ];
799        (tp, h)
800    }
801}
802
803impl RawMeasurement {
804    /// Packs the raw readings back into the eight data-register bytes.
805    ///
806    /// This is the inverse of [`from_registers`](RawMeasurement::from_registers), so
807    /// a test can build what a part holding these readings sends in a burst read.
808    ///
809    /// # Returns
810    ///
811    /// The eight bytes of `0xF7..=0xFE`.
812    pub fn to_registers(&self) -> [u8; DATA_LEN] {
813        let pressure = self.pressure as u32;
814        let temperature = self.temperature as u32;
815        let humidity = self.humidity as u16;
816        [
817            (pressure >> 12) as u8,
818            (pressure >> 4) as u8,
819            ((pressure & 0x0F) << 4) as u8,
820            (temperature >> 12) as u8,
821            (temperature >> 4) as u8,
822            ((temperature & 0x0F) << 4) as u8,
823            (humidity >> 8) as u8,
824            humidity as u8,
825        ]
826    }
827}
828
829#[cfg(test)]
830mod control_tests {
831    use super::*;
832
833    #[test]
834    fn oversampling_codes_follow_the_datasheet_tables() {
835        assert_eq!(Oversampling::Skipped.code(), 0b000);
836        assert_eq!(Oversampling::X16.code(), 0b101);
837        assert_eq!(Oversampling::from_code(0b110), Oversampling::X16);
838        assert_eq!(Oversampling::from_code(0b100), Oversampling::X8);
839        assert_eq!(Oversampling::X8.factor(), 8);
840    }
841
842    #[test]
843    fn mode_codes_treat_both_forced_encodings_alike() {
844        assert_eq!(Mode::from_code(0b01), Mode::Forced);
845        assert_eq!(Mode::from_code(0b10), Mode::Forced);
846        assert_eq!(Mode::from_code(0b11), Mode::Normal);
847        assert_eq!(Mode::Normal.code(), 0b11);
848    }
849
850    #[test]
851    fn standby_codes_carry_the_bme280_reassignment_of_the_top_two() {
852        assert_eq!(Standby::Ms10.code(), 0b110);
853        assert_eq!(Standby::Ms20.code(), 0b111);
854        assert_eq!(Standby::from_code(0b110).microseconds(), 10_000);
855        assert_eq!(Standby::from_code(0b101).microseconds(), 1_000_000);
856        assert_eq!(Standby::from_code(0b000).microseconds(), 500);
857    }
858
859    #[test]
860    fn filter_codes_saturate_at_sixteen() {
861        assert_eq!(Filter::X16.code(), 0b100);
862        assert_eq!(Filter::from_code(0b111), Filter::X16);
863        assert_eq!(Filter::from_code(0b011).coefficient(), 8);
864        assert_eq!(Filter::Off.coefficient(), 0);
865    }
866
867    #[test]
868    fn control_registers_pack_their_fields_where_the_datasheet_puts_them() {
869        let ctrl_meas = CtrlMeas {
870            temperature: Oversampling::X2,
871            pressure: Oversampling::X16,
872            mode: Mode::Normal,
873        };
874        assert_eq!(ctrl_meas.bits(), 0b0101_0111);
875        assert_eq!(CtrlMeas::from_bits(0b0101_0111), ctrl_meas);
876        let config = Config {
877            standby: Standby::Ms1000,
878            filter: Filter::X4,
879            spi_3wire: true,
880        };
881        assert_eq!(config.bits(), 0b1010_1001);
882        assert_eq!(Config::from_bits(0b1010_1001), config);
883        assert_eq!(CtrlHum::from_bits(0x03).humidity, Oversampling::X4);
884        assert_eq!(CtrlMeas::default().bits(), 0x00);
885        assert_eq!(Config::default().bits(), 0x00);
886    }
887
888    #[test]
889    fn status_flags_sit_in_bits_three_and_zero() {
890        assert!(measuring(0x08));
891        assert!(!measuring(0x01));
892        assert!(image_updating(0x01));
893        assert!(!image_updating(0x08));
894    }
895
896    #[test]
897    fn measurement_times_match_the_datasheet_worked_example() {
898        let (t, p, h) = (Oversampling::X1, Oversampling::X4, Oversampling::Skipped);
899        assert_eq!(max_measurement_micros(t, p, h), 13_325);
900        assert_eq!(typical_measurement_micros(t, p, h), 11_500);
901        let all = Oversampling::X1;
902        assert_eq!(max_measurement_micros(all, all, all), 9_300);
903        assert_eq!(
904            max_measurement_micros(
905                Oversampling::Skipped,
906                Oversampling::Skipped,
907                Oversampling::Skipped
908            ),
909            1_250
910        );
911    }
912
913    #[test]
914    fn calibration_registers_round_trip_including_the_shared_h4_h5_byte() {
915        let tp = [
916            0x45, 0x6F, 0x6F, 0x68, 0x32, 0x00, 0x46, 0x91, 0x6A, 0xD6, 0xD0, 0x0B, 0x4E, 0x1E,
917            0x88, 0xFF, 0xF9, 0xFF, 0xAC, 0x26, 0x0A, 0xD8, 0xBD, 0x10, 0x00, 0x4B,
918        ];
919        let h = [0x62, 0x01, 0x00, 0x15, 0x23, 0x03, 0x1E];
920        let calibration = Calibration::from_registers(&tp, &h);
921        assert_eq!(calibration.dig_t1, 28485);
922        assert_eq!(calibration.dig_p2, -10646);
923        assert_eq!(calibration.dig_h4, 339);
924        assert_eq!(calibration.dig_h5, 50);
925        assert_eq!(calibration.to_registers(), (tp, h));
926
927        let negative = Calibration {
928            dig_h4: -100,
929            dig_h5: -7,
930            ..calibration
931        };
932        let (tp2, h2) = negative.to_registers();
933        assert_eq!(Calibration::from_registers(&tp2, &h2), negative);
934    }
935
936    #[test]
937    fn raw_measurement_registers_round_trip() {
938        let data = [0x53, 0xD0, 0xE0, 0x81, 0xD9, 0x00, 0x6E, 0x62];
939        let raw = RawMeasurement::from_registers(&data);
940        assert_eq!(raw.to_registers(), data);
941        let skipped = RawMeasurement {
942            temperature: SKIPPED_OUTPUT as i32,
943            pressure: SKIPPED_OUTPUT as i32,
944            humidity: i32::from(SKIPPED_HUMIDITY),
945        };
946        assert_eq!(
947            RawMeasurement::from_registers(&skipped.to_registers()),
948            skipped
949        );
950    }
951}
952
953#[cfg(test)]
954mod tests {
955    use super::*;
956
957    // A representative, internally consistent calibration set, used to exercise the
958    // formulas across a sweep of raw codes.
959    fn sample_calibration() -> Calibration {
960        Calibration {
961            dig_t1: 28485,
962            dig_t2: 26735,
963            dig_t3: 50,
964            dig_p1: 37190,
965            dig_p2: -10646,
966            dig_p3: 3024,
967            dig_p4: 7758,
968            dig_p5: -120,
969            dig_p6: -7,
970            dig_p7: 9900,
971            dig_p8: -10230,
972            dig_p9: 4285,
973            dig_h1: 75,
974            dig_h2: 354,
975            dig_h3: 0,
976            dig_h4: 339,
977            dig_h5: 50,
978            dig_h6: 30,
979        }
980    }
981
982    #[test]
983    fn temperature_matches_the_datasheet_worked_example() {
984        // Bosch's published worked example: dig_t1 = 27504, dig_t2 = 26435,
985        // dig_t3 = -1000, adc_t = 519888 gives T = 25.08 °C (2508 in hundredths). The
986        // shared t_fine intermediate lands at 128423 with the current integer formula,
987        // one count off the 128422 quoted from the older BMP280 example; the published
988        // result, the 25.08 °C output, is what this anchors to.
989        let calib = Calibration {
990            dig_t1: 27504,
991            dig_t2: 26435,
992            dig_t3: -1000,
993            ..sample_calibration()
994        };
995        let t_fine = calib.t_fine(519888);
996        assert!((t_fine - 128422).abs() <= 1, "t_fine {t_fine}");
997        assert_eq!(compensate_temperature(t_fine), 2508);
998    }
999
1000    #[test]
1001    fn integer_compensation_tracks_the_floating_point_reference() {
1002        let calib = sample_calibration();
1003        // Sweep a range of raw codes and require the integer path to agree with the
1004        // structurally different floating-point reference to within rounding.
1005        for &adc_t in &[400_000, 500_000, 519_888, 540_000] {
1006            let t_fine = calib.t_fine(adc_t);
1007            let t_int = compensate_temperature(t_fine);
1008            let t_ref = reference_temperature(&calib, adc_t);
1009            assert!(
1010                ((t_int as f64) / 100.0 - t_ref).abs() < 0.02,
1011                "temperature {t_int} vs {t_ref}"
1012            );
1013
1014            for &adc_p in &[300_000, 415_148, 512_000] {
1015                let p_int = calib.compensate_pressure(adc_p, t_fine);
1016                let p_ref = reference_pressure(&calib, adc_p, adc_t);
1017                assert!(
1018                    ((p_int as f64) / 100.0 - p_ref).abs() < 2.0,
1019                    "pressure {p_int} (centi-Pa) vs {p_ref} Pa"
1020                );
1021            }
1022
1023            for &adc_h in &[20_000, 30_000, 45_000] {
1024                let h_int = calib.compensate_humidity(adc_h, t_fine);
1025                let h_ref = reference_humidity(&calib, adc_h, adc_t);
1026                assert!(
1027                    ((h_int as f64) / 1024.0 - h_ref).abs() < 0.05,
1028                    "humidity {h_int} (Q22.10) vs {h_ref} %"
1029                );
1030            }
1031        }
1032    }
1033
1034    #[test]
1035    fn raw_measurement_unpacks_the_data_registers() {
1036        // Pressure 0x53D0E, temperature 0x81D90, humidity 0x6E62.
1037        let data = [0x53, 0xD0, 0xE0, 0x81, 0xD9, 0x00, 0x6E, 0x62];
1038        let raw = RawMeasurement::from_registers(&data);
1039        assert_eq!(raw.pressure, 0x5_3D0E);
1040        assert_eq!(raw.temperature, 0x8_1D90);
1041        assert_eq!(raw.humidity, 0x6E62);
1042    }
1043
1044    #[test]
1045    fn measurement_unit_helpers_convert_correctly() {
1046        let measurement = Measurement {
1047            temperature_centi_celsius: 2508,
1048            pressure_centi_pascals: 10_065_300,
1049            humidity_q22_10: 47_104,
1050        };
1051        assert!((measurement.celsius() - 25.08).abs() < 0.001);
1052        assert_eq!(measurement.pascals(), 100_653);
1053        assert!((measurement.hectopascals() - 1006.53).abs() < 0.01);
1054        assert!((measurement.relative_humidity_percent() - 46.0).abs() < 0.001);
1055    }
1056
1057    // The reference floating-point compensation from Bosch's published code, used only
1058    // to validate the integer path above.
1059    fn reference_temperature(c: &Calibration, adc_t: i32) -> f64 {
1060        let var1 = (adc_t as f64 / 16384.0 - c.dig_t1 as f64 / 1024.0) * c.dig_t2 as f64;
1061        let var2 = {
1062            let v = adc_t as f64 / 131072.0 - c.dig_t1 as f64 / 8192.0;
1063            v * v * c.dig_t3 as f64
1064        };
1065        ((var1 + var2) / 5120.0).clamp(-40.0, 85.0)
1066    }
1067
1068    fn reference_t_fine(c: &Calibration, adc_t: i32) -> f64 {
1069        let var1 = (adc_t as f64 / 16384.0 - c.dig_t1 as f64 / 1024.0) * c.dig_t2 as f64;
1070        let var2 = {
1071            let v = adc_t as f64 / 131072.0 - c.dig_t1 as f64 / 8192.0;
1072            v * v * c.dig_t3 as f64
1073        };
1074        var1 + var2
1075    }
1076
1077    fn reference_pressure(c: &Calibration, adc_p: i32, adc_t: i32) -> f64 {
1078        let t_fine = reference_t_fine(c, adc_t);
1079        let mut var1 = (t_fine / 2.0) - 64000.0;
1080        let mut var2 = var1 * var1 * c.dig_p6 as f64 / 32768.0;
1081        var2 += var1 * c.dig_p5 as f64 * 2.0;
1082        var2 = (var2 / 4.0) + (c.dig_p4 as f64 * 65536.0);
1083        let var3 = c.dig_p3 as f64 * var1 * var1 / 524288.0;
1084        var1 = (var3 + c.dig_p2 as f64 * var1) / 524288.0;
1085        var1 = (1.0 + var1 / 32768.0) * c.dig_p1 as f64;
1086        if var1 <= 0.0 {
1087            return 30000.0;
1088        }
1089        let mut pressure = 1048576.0 - adc_p as f64;
1090        pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1;
1091        var1 = c.dig_p9 as f64 * pressure * pressure / 2147483648.0;
1092        var2 = pressure * c.dig_p8 as f64 / 32768.0;
1093        pressure += (var1 + var2 + c.dig_p7 as f64) / 16.0;
1094        pressure.clamp(30000.0, 110000.0)
1095    }
1096
1097    fn reference_humidity(c: &Calibration, adc_h: i32, adc_t: i32) -> f64 {
1098        let t_fine = reference_t_fine(c, adc_t);
1099        let var1 = t_fine - 76800.0;
1100        let var2 = c.dig_h4 as f64 * 64.0 + (c.dig_h5 as f64 / 16384.0) * var1;
1101        let var3 = adc_h as f64 - var2;
1102        let var4 = c.dig_h2 as f64 / 65536.0;
1103        let var5 = 1.0 + (c.dig_h3 as f64 / 67108864.0) * var1;
1104        let var6 = 1.0 + (c.dig_h6 as f64 / 67108864.0) * var1 * var5;
1105        let var6 = var3 * var4 * (var5 * var6);
1106        (var6 * (1.0 - c.dig_h1 as f64 * var6 / 524288.0)).clamp(0.0, 100.0)
1107    }
1108}