Skip to main content

pamoja_sensors/
bmp280.rs

1//! Bosch BMP280 digital pressure sensor.
2//!
3//! The BMP280 is the BME280's sibling without the humidity element: a barometric
4//! pressure and temperature sensor that ships 20-bit raw ADC codes plus a block of
5//! per-chip trimming coefficients, and leaves the compensation to the host. The
6//! integer arithmetic here is a line for line port of the `bmp280_compensate_T_int32`
7//! and `bmp280_compensate_P_int64` reference code in section 3.11.3 of the data
8//! sheet (BST-BMP280-DS001), and the tests cross-check it against the data sheet's
9//! floating-point form in appendix 8.1.
10//!
11//! A caller reads the 24 calibration bytes once with [`Calibration::parse`], then
12//! burst-reads the six data registers each cycle into a [`Measurement`] and calls
13//! [`Calibration::compensate`] for a [`Reading`]. [`CtrlMeas`] and [`Config`] build
14//! and decode the two control registers, and every decode has a matching builder so a
15//! node can be exercised with nothing wired.
16
17/// The I2C address with the SDO pin tied to ground.
18pub const I2C_ADDRESS_PRIMARY: u8 = 0x76;
19/// The I2C address with the SDO pin tied to VDDIO.
20pub const I2C_ADDRESS_SECONDARY: u8 = 0x77;
21/// The value the chip-id register (0xD0) returns for a BMP280.
22pub const CHIP_ID: u8 = 0x58;
23/// The word written to the reset register (0xE0) to run a full power-on reset.
24pub const RESET_WORD: u8 = 0xB6;
25/// The raw code a data register holds when its measurement is skipped.
26pub const SKIPPED_OUTPUT: u32 = 0x80000;
27/// The number of calibration bytes at [`register::CALIBRATION`].
28pub const CALIBRATION_LEN: usize = 24;
29/// The number of data bytes at [`register::DATA`].
30pub const DATA_LEN: usize = 6;
31
32/// The BMP280 register addresses.
33pub mod register {
34    /// First of the 24 trimming bytes, `calib00..=calib23` (0x88..=0x9F).
35    pub const CALIBRATION: u8 = 0x88;
36    /// Chip-id register; reads [`super::CHIP_ID`] for a BMP280.
37    pub const CHIP_ID: u8 = 0xD0;
38    /// Soft-reset register; write [`super::RESET_WORD`].
39    pub const RESET: u8 = 0xE0;
40    /// Status register: `measuring` in bit 3, `im_update` in bit 0.
41    pub const STATUS: u8 = 0xF3;
42    /// Measurement control register: oversampling and power mode.
43    pub const CTRL_MEAS: u8 = 0xF4;
44    /// Configuration register: standby time, IIR filter, and 3-wire SPI.
45    pub const CONFIG: u8 = 0xF5;
46    /// First of the 6 burst-read data bytes: pressure then temperature (0xF7..=0xFC).
47    pub const DATA: u8 = 0xF7;
48}
49
50/// Returns whether the status register reports a conversion in progress.
51///
52/// # Arguments
53///
54/// * `status` - the byte read from [`register::STATUS`].
55///
56/// # Returns
57///
58/// `true` while a conversion runs, `false` once its results have reached the data
59/// registers.
60pub fn measuring(status: u8) -> bool {
61    status & 0x08 != 0
62}
63
64/// Returns whether the status register reports the NVM image being copied.
65///
66/// # Arguments
67///
68/// * `status` - the byte read from [`register::STATUS`].
69///
70/// # Returns
71///
72/// `true` while the trimming data is being copied to the image registers, which
73/// happens at power-on reset and before every conversion.
74pub fn image_updating(status: u8) -> bool {
75    status & 0x01 != 0
76}
77
78/// An oversampling setting for the pressure or temperature measurement.
79///
80/// Each step adds one bit of output resolution, stored in the XLSB data register.
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
82pub enum Oversampling {
83    /// The measurement is skipped and its data registers read [`SKIPPED_OUTPUT`].
84    #[default]
85    Skipped,
86    /// A single sample, 16-bit output.
87    X1,
88    /// Two samples, 17-bit output.
89    X2,
90    /// Four samples, 18-bit output.
91    X4,
92    /// Eight samples, 19-bit output.
93    X8,
94    /// Sixteen samples, 20-bit output.
95    X16,
96}
97
98impl Oversampling {
99    /// Returns the three-bit `osrs_t` / `osrs_p` field value.
100    pub fn code(self) -> u8 {
101        match self {
102            Oversampling::Skipped => 0b000,
103            Oversampling::X1 => 0b001,
104            Oversampling::X2 => 0b010,
105            Oversampling::X4 => 0b011,
106            Oversampling::X8 => 0b100,
107            Oversampling::X16 => 0b101,
108        }
109    }
110
111    /// Decodes a three-bit `osrs_t` / `osrs_p` field value.
112    ///
113    /// The codes `0b101`, `0b110`, and `0b111` all select 16x oversampling.
114    ///
115    /// # Arguments
116    ///
117    /// * `code` - the field value; only the low three bits are used.
118    ///
119    /// # Returns
120    ///
121    /// The oversampling setting.
122    pub fn from_code(code: u8) -> Oversampling {
123        match code & 0b111 {
124            0b000 => Oversampling::Skipped,
125            0b001 => Oversampling::X1,
126            0b010 => Oversampling::X2,
127            0b011 => Oversampling::X4,
128            0b100 => Oversampling::X8,
129            _ => Oversampling::X16,
130        }
131    }
132
133    /// Returns the number of samples averaged, or `0` when skipped.
134    pub fn factor(self) -> u8 {
135        match self {
136            Oversampling::Skipped => 0,
137            Oversampling::X1 => 1,
138            Oversampling::X2 => 2,
139            Oversampling::X4 => 4,
140            Oversampling::X8 => 8,
141            Oversampling::X16 => 16,
142        }
143    }
144}
145
146/// The power mode selected by the `mode[1:0]` field.
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
148pub enum Mode {
149    /// No measurements; the power-on default. Registers stay readable.
150    #[default]
151    Sleep,
152    /// One measurement, then back to sleep.
153    Forced,
154    /// Continuous cycling between measurement and a standby period.
155    Normal,
156}
157
158impl Mode {
159    /// Returns the two-bit `mode` field value.
160    pub fn code(self) -> u8 {
161        match self {
162            Mode::Sleep => 0b00,
163            Mode::Forced => 0b01,
164            Mode::Normal => 0b11,
165        }
166    }
167
168    /// Decodes a two-bit `mode` field value; both `0b01` and `0b10` mean forced.
169    ///
170    /// # Arguments
171    ///
172    /// * `code` - the field value; only the low two bits are used.
173    ///
174    /// # Returns
175    ///
176    /// The power mode.
177    pub fn from_code(code: u8) -> Mode {
178        match code & 0b11 {
179            0b00 => Mode::Sleep,
180            0b11 => Mode::Normal,
181            _ => Mode::Forced,
182        }
183    }
184}
185
186/// The inactive period between measurements in normal mode, the `t_sb[2:0]` field.
187#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
188pub enum Standby {
189    /// 0.5 ms.
190    #[default]
191    Ms0_5,
192    /// 62.5 ms.
193    Ms62_5,
194    /// 125 ms.
195    Ms125,
196    /// 250 ms.
197    Ms250,
198    /// 500 ms.
199    Ms500,
200    /// 1000 ms.
201    Ms1000,
202    /// 2000 ms.
203    Ms2000,
204    /// 4000 ms.
205    Ms4000,
206}
207
208impl Standby {
209    /// Returns the three-bit `t_sb` field value.
210    pub fn code(self) -> u8 {
211        match self {
212            Standby::Ms0_5 => 0b000,
213            Standby::Ms62_5 => 0b001,
214            Standby::Ms125 => 0b010,
215            Standby::Ms250 => 0b011,
216            Standby::Ms500 => 0b100,
217            Standby::Ms1000 => 0b101,
218            Standby::Ms2000 => 0b110,
219            Standby::Ms4000 => 0b111,
220        }
221    }
222
223    /// Decodes a three-bit `t_sb` field value.
224    ///
225    /// # Arguments
226    ///
227    /// * `code` - the field value; only the low three bits are used.
228    ///
229    /// # Returns
230    ///
231    /// The standby period.
232    pub fn from_code(code: u8) -> Standby {
233        match code & 0b111 {
234            0b000 => Standby::Ms0_5,
235            0b001 => Standby::Ms62_5,
236            0b010 => Standby::Ms125,
237            0b011 => Standby::Ms250,
238            0b100 => Standby::Ms500,
239            0b101 => Standby::Ms1000,
240            0b110 => Standby::Ms2000,
241            _ => Standby::Ms4000,
242        }
243    }
244
245    /// Returns the standby period in microseconds.
246    pub fn microseconds(self) -> u32 {
247        match self {
248            Standby::Ms0_5 => 500,
249            Standby::Ms62_5 => 62_500,
250            Standby::Ms125 => 125_000,
251            Standby::Ms250 => 250_000,
252            Standby::Ms500 => 500_000,
253            Standby::Ms1000 => 1_000_000,
254            Standby::Ms2000 => 2_000_000,
255            Standby::Ms4000 => 4_000_000,
256        }
257    }
258}
259
260/// The measurement control register (0xF4, `ctrl_meas`).
261///
262/// The default is the register's reset state, `0x00`: both measurements skipped and
263/// the device asleep.
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
265pub struct CtrlMeas {
266    /// Temperature oversampling, `osrs_t[2:0]` in bits 7:5.
267    pub temperature: Oversampling,
268    /// Pressure oversampling, `osrs_p[2:0]` in bits 4:2.
269    pub pressure: Oversampling,
270    /// Power mode, `mode[1:0]` in bits 1:0.
271    pub mode: Mode,
272}
273
274impl CtrlMeas {
275    /// Packs the settings into the register byte.
276    ///
277    /// # Returns
278    ///
279    /// The byte to write to [`register::CTRL_MEAS`].
280    pub fn bits(self) -> u8 {
281        (self.temperature.code() << 5) | (self.pressure.code() << 2) | self.mode.code()
282    }
283
284    /// Decodes a register byte.
285    ///
286    /// # Arguments
287    ///
288    /// * `bits` - the byte read from [`register::CTRL_MEAS`].
289    ///
290    /// # Returns
291    ///
292    /// The decoded settings.
293    pub fn from_bits(bits: u8) -> CtrlMeas {
294        CtrlMeas {
295            temperature: Oversampling::from_code(bits >> 5),
296            pressure: Oversampling::from_code(bits >> 2),
297            mode: Mode::from_code(bits),
298        }
299    }
300}
301
302/// The configuration register (0xF5, `config`).
303///
304/// The data sheet lists the IIR filter coefficients (off, 2, 4, 8, 16) and places the
305/// field in bits 4:2, but does not publish which three-bit code selects which
306/// coefficient, so the field is carried here as its raw code. Writes to this register
307/// may be ignored in normal mode; write it in sleep mode.
308///
309/// The default is the register's reset state, `0x00`.
310#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
311pub struct Config {
312    /// Normal-mode standby period, `t_sb[2:0]` in bits 7:5.
313    pub standby: Standby,
314    /// IIR filter setting, the raw `filter[2:0]` code in bits 4:2.
315    pub filter: u8,
316    /// Enables the 3-wire SPI interface, `spi3w_en` in bit 0.
317    pub spi_3wire: bool,
318}
319
320impl Config {
321    /// Packs the settings into the register byte.
322    ///
323    /// # Returns
324    ///
325    /// The byte to write to [`register::CONFIG`]. Only the low three bits of `filter`
326    /// are used.
327    pub fn bits(self) -> u8 {
328        (self.standby.code() << 5) | ((self.filter & 0b111) << 2) | u8::from(self.spi_3wire)
329    }
330
331    /// Decodes a register byte.
332    ///
333    /// # Arguments
334    ///
335    /// * `bits` - the byte read from [`register::CONFIG`].
336    ///
337    /// # Returns
338    ///
339    /// The decoded settings.
340    pub fn from_bits(bits: u8) -> Config {
341        Config {
342            standby: Standby::from_code(bits >> 5),
343            filter: (bits >> 2) & 0b111,
344            spi_3wire: bits & 0x01 != 0,
345        }
346    }
347}
348
349/// The per-chip trimming coefficients read from `0x88..=0x9F`.
350///
351/// These are programmed into non-volatile memory during production and are constant
352/// for a given device, so they are read once at start-up and reused for every
353/// measurement.
354#[derive(Clone, Copy, Debug, PartialEq, Eq)]
355pub struct Calibration {
356    /// `dig_T1`, unsigned, at 0x88/0x89.
357    pub dig_t1: u16,
358    /// `dig_T2`, signed, at 0x8A/0x8B.
359    pub dig_t2: i16,
360    /// `dig_T3`, signed, at 0x8C/0x8D.
361    pub dig_t3: i16,
362    /// `dig_P1`, unsigned, at 0x8E/0x8F.
363    pub dig_p1: u16,
364    /// `dig_P2`, signed, at 0x90/0x91.
365    pub dig_p2: i16,
366    /// `dig_P3`, signed, at 0x92/0x93.
367    pub dig_p3: i16,
368    /// `dig_P4`, signed, at 0x94/0x95.
369    pub dig_p4: i16,
370    /// `dig_P5`, signed, at 0x96/0x97.
371    pub dig_p5: i16,
372    /// `dig_P6`, signed, at 0x98/0x99.
373    pub dig_p6: i16,
374    /// `dig_P7`, signed, at 0x9A/0x9B.
375    pub dig_p7: i16,
376    /// `dig_P8`, signed, at 0x9C/0x9D.
377    pub dig_p8: i16,
378    /// `dig_P9`, signed, at 0x9E/0x9F.
379    pub dig_p9: i16,
380}
381
382impl Calibration {
383    /// Parses the coefficients from the calibration block, each word LSB first.
384    ///
385    /// # Arguments
386    ///
387    /// * `bytes` - the 24 bytes read from `0x88..=0x9F`.
388    ///
389    /// # Returns
390    ///
391    /// The decoded calibration.
392    pub fn parse(bytes: &[u8; CALIBRATION_LEN]) -> Calibration {
393        let b = bytes;
394        Calibration {
395            dig_t1: u16::from_le_bytes([b[0], b[1]]),
396            dig_t2: i16::from_le_bytes([b[2], b[3]]),
397            dig_t3: i16::from_le_bytes([b[4], b[5]]),
398            dig_p1: u16::from_le_bytes([b[6], b[7]]),
399            dig_p2: i16::from_le_bytes([b[8], b[9]]),
400            dig_p3: i16::from_le_bytes([b[10], b[11]]),
401            dig_p4: i16::from_le_bytes([b[12], b[13]]),
402            dig_p5: i16::from_le_bytes([b[14], b[15]]),
403            dig_p6: i16::from_le_bytes([b[16], b[17]]),
404            dig_p7: i16::from_le_bytes([b[18], b[19]]),
405            dig_p8: i16::from_le_bytes([b[20], b[21]]),
406            dig_p9: i16::from_le_bytes([b[22], b[23]]),
407        }
408    }
409
410    /// Builds the calibration block a device holding these coefficients would return.
411    ///
412    /// # Returns
413    ///
414    /// The 24 bytes as they sit at `0x88..=0x9F`.
415    pub fn to_bytes(&self) -> [u8; CALIBRATION_LEN] {
416        let mut out = [0u8; CALIBRATION_LEN];
417        let words = [
418            self.dig_t1,
419            self.dig_t2 as u16,
420            self.dig_t3 as u16,
421            self.dig_p1,
422            self.dig_p2 as u16,
423            self.dig_p3 as u16,
424            self.dig_p4 as u16,
425            self.dig_p5 as u16,
426            self.dig_p6 as u16,
427            self.dig_p7 as u16,
428            self.dig_p8 as u16,
429            self.dig_p9 as u16,
430        ];
431        for (chunk, word) in out.as_chunks_mut::<2>().0.iter_mut().zip(words) {
432            *chunk = word.to_le_bytes();
433        }
434        out
435    }
436
437    /// Compensates a raw measurement into temperature and pressure.
438    ///
439    /// Temperature is computed first because its `t_fine` intermediate feeds the
440    /// pressure formula, exactly as the reference code shares it.
441    ///
442    /// # Arguments
443    ///
444    /// * `raw` - the uncompensated 20-bit codes from the data registers.
445    ///
446    /// # Returns
447    ///
448    /// The compensated [`Reading`].
449    pub fn compensate(&self, raw: &Measurement) -> Reading {
450        let t_fine = self.t_fine(raw.temperature);
451        Reading {
452            temperature_centi_celsius: compensate_temperature(t_fine),
453            pressure_q24_8: self.compensate_pressure(raw.pressure, t_fine),
454        }
455    }
456
457    // bmp280_compensate_T_int32 up to t_fine. The products are formed in 64 bits so
458    // no 20-bit code and coefficient pair can overflow; the sum always fits 32 bits.
459    fn t_fine(&self, adc_t: u32) -> i32 {
460        let adc_t = i64::from(adc_t);
461        let dig_t1 = i64::from(self.dig_t1);
462        let var1 = (((adc_t >> 3) - (dig_t1 << 1)) * i64::from(self.dig_t2)) >> 11;
463        let near = (adc_t >> 4) - dig_t1;
464        let var2 = (((near * near) >> 12) * i64::from(self.dig_t3)) >> 14;
465        (var1 + var2) as i32
466    }
467
468    // bmp280_compensate_P_int64: pressure in Q24.8 pascals.
469    fn compensate_pressure(&self, adc_p: u32, t_fine: i32) -> u32 {
470        let mut var1 = i64::from(t_fine) - 128000;
471        let mut var2 = var1 * var1 * i64::from(self.dig_p6);
472        var2 += (var1 * i64::from(self.dig_p5)) << 17;
473        var2 += i64::from(self.dig_p4) << 35;
474        var1 =
475            ((var1 * var1 * i64::from(self.dig_p3)) >> 8) + ((var1 * i64::from(self.dig_p2)) << 12);
476        var1 = (((1i64 << 47) + var1) * i64::from(self.dig_p1)) >> 33;
477        if var1 == 0 {
478            return 0;
479        }
480        let mut p = 1048576 - i64::from(adc_p);
481        p = (((p << 31) - var2) * 3125) / var1;
482        var1 = (i64::from(self.dig_p9) * (p >> 13) * (p >> 13)) >> 25;
483        var2 = (i64::from(self.dig_p8) * p) >> 19;
484        p = ((p + var1 + var2) >> 8) + (i64::from(self.dig_p7) << 4);
485        p as u32
486    }
487}
488
489// The tail of bmp280_compensate_T_int32: hundredths of a degree Celsius.
490fn compensate_temperature(t_fine: i32) -> i32 {
491    ((i64::from(t_fine) * 5 + 128) >> 8) as i32
492}
493
494/// The raw, uncompensated 20-bit codes from the BMP280 data registers.
495#[derive(Clone, Copy, Debug, PartialEq, Eq)]
496pub struct Measurement {
497    /// The 20-bit uncompensated pressure, `up[19:0]`.
498    pub pressure: u32,
499    /// The 20-bit uncompensated temperature, `ut[19:0]`.
500    pub temperature: u32,
501}
502
503impl Measurement {
504    /// Parses the six data bytes burst-read from `0xF7..=0xFC`.
505    ///
506    /// The order on the wire is pressure then temperature, each as MSB, LSB, and an
507    /// XLSB byte whose upper nibble holds the last four bits.
508    ///
509    /// # Arguments
510    ///
511    /// * `data` - the six bytes read from the data registers.
512    ///
513    /// # Returns
514    ///
515    /// The unpacked raw measurement.
516    pub fn parse(data: &[u8; DATA_LEN]) -> Measurement {
517        let unpack = |msb: u8, lsb: u8, xlsb: u8| {
518            (u32::from(msb) << 12) | (u32::from(lsb) << 4) | (u32::from(xlsb) >> 4)
519        };
520        Measurement {
521            pressure: unpack(data[0], data[1], data[2]),
522            temperature: unpack(data[3], data[4], data[5]),
523        }
524    }
525
526    /// Builds the six data bytes a device holding these codes would return.
527    ///
528    /// # Returns
529    ///
530    /// The bytes as they sit at `0xF7..=0xFC`. Only the low 20 bits of each code are
531    /// carried.
532    pub fn to_bytes(&self) -> [u8; DATA_LEN] {
533        let pack = |code: u32| {
534            [
535                ((code >> 12) & 0xFF) as u8,
536                ((code >> 4) & 0xFF) as u8,
537                ((code & 0x0F) << 4) as u8,
538            ]
539        };
540        let p = pack(self.pressure);
541        let t = pack(self.temperature);
542        [p[0], p[1], p[2], t[0], t[1], t[2]]
543    }
544
545    /// Returns whether the pressure measurement was skipped (`osrs_p = 0`).
546    pub fn pressure_skipped(&self) -> bool {
547        self.pressure == SKIPPED_OUTPUT
548    }
549
550    /// Returns whether the temperature measurement was skipped (`osrs_t = 0`).
551    pub fn temperature_skipped(&self) -> bool {
552        self.temperature == SKIPPED_OUTPUT
553    }
554}
555
556/// A compensated BMP280 reading.
557///
558/// The fields are the exact integer outputs of the reference compensation code; the
559/// methods present them in conventional units.
560#[derive(Clone, Copy, Debug, PartialEq, Eq)]
561pub struct Reading {
562    /// Temperature in hundredths of a degree Celsius.
563    pub temperature_centi_celsius: i32,
564    /// Pressure in pascals as Q24.8 fixed point, that is units of 1/256 Pa.
565    pub pressure_q24_8: u32,
566}
567
568impl Reading {
569    /// Returns the temperature in degrees Celsius.
570    pub fn celsius(&self) -> f32 {
571        self.temperature_centi_celsius as f32 / 100.0
572    }
573
574    /// Returns the pressure in whole pascals.
575    pub fn pascals(&self) -> u32 {
576        self.pressure_q24_8 >> 8
577    }
578
579    /// Returns the pressure in hectopascals (millibars).
580    pub fn hectopascals(&self) -> f32 {
581        (f64::from(self.pressure_q24_8) / 25_600.0) as f32
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    // The temperature and pressure coefficients of the BME280 module's sample set; the
590    // two parts share the dig_T and dig_P formulas verbatim, so the set exercises the
591    // same arithmetic here.
592    fn sample_calibration() -> Calibration {
593        Calibration {
594            dig_t1: 28485,
595            dig_t2: 26735,
596            dig_t3: 50,
597            dig_p1: 37190,
598            dig_p2: -10646,
599            dig_p3: 3024,
600            dig_p4: 7758,
601            dig_p5: -120,
602            dig_p6: -7,
603            dig_p7: 9900,
604            dig_p8: -10230,
605            dig_p9: 4285,
606        }
607    }
608
609    // The worked temperature example carried by the BME280 module: dig_T1 = 27504,
610    // dig_T2 = 26435, dig_T3 = -1000 with adc_T = 519888 gives 25.08 degrees C. The
611    // temperature formula is identical between the two parts.
612    fn worked_example_calibration() -> Calibration {
613        Calibration {
614            dig_t1: 27504,
615            dig_t2: 26435,
616            dig_t3: -1000,
617            ..sample_calibration()
618        }
619    }
620
621    #[test]
622    fn register_map_matches_table_18() {
623        assert_eq!(register::CALIBRATION, 0x88);
624        assert_eq!(register::CHIP_ID, 0xD0);
625        assert_eq!(register::RESET, 0xE0);
626        assert_eq!(register::STATUS, 0xF3);
627        assert_eq!(register::CTRL_MEAS, 0xF4);
628        assert_eq!(register::CONFIG, 0xF5);
629        assert_eq!(register::DATA, 0xF7);
630        // Table 18 reset state of the id register, and section 4.3.2's reset word.
631        assert_eq!(CHIP_ID, 0x58);
632        assert_eq!(RESET_WORD, 0xB6);
633        // Table 17 spans calib00..calib23; section 3.9 burst-reads 0xF7 to 0xFC.
634        assert_eq!(CALIBRATION_LEN, 0x9F - 0x88 + 1);
635        assert_eq!(DATA_LEN, 0xFC - 0xF7 + 1);
636    }
637
638    #[test]
639    fn i2c_address_follows_the_sdo_pin() {
640        // Section 5.2: 1110110 with SDO to GND, 1110111 with SDO to VDDIO.
641        assert_eq!(I2C_ADDRESS_PRIMARY, 0b1110110);
642        assert_eq!(I2C_ADDRESS_SECONDARY, 0b1110111);
643    }
644
645    #[test]
646    fn status_flags_sit_in_bits_3_and_0() {
647        // Table 19: measuring[0] is bit 3, im_update[0] is bit 0.
648        assert!(measuring(0x08));
649        assert!(!measuring(0x01));
650        assert!(image_updating(0x01));
651        assert!(!image_updating(0x08));
652        assert!(!measuring(0x00) && !image_updating(0x00));
653    }
654
655    #[test]
656    fn oversampling_codes_and_factors_match_tables_21_and_22() {
657        let table = [
658            (0b000, Oversampling::Skipped, 0),
659            (0b001, Oversampling::X1, 1),
660            (0b010, Oversampling::X2, 2),
661            (0b011, Oversampling::X4, 4),
662            (0b100, Oversampling::X8, 8),
663            (0b101, Oversampling::X16, 16),
664        ];
665        for (code, setting, factor) in table {
666            assert_eq!(setting.code(), code);
667            assert_eq!(Oversampling::from_code(code), setting);
668            assert_eq!(setting.factor(), factor);
669        }
670        // "101, 110, 111" (Table 22) and "101, Others" (Table 21) all mean x16.
671        assert_eq!(Oversampling::from_code(0b110), Oversampling::X16);
672        assert_eq!(Oversampling::from_code(0b111), Oversampling::X16);
673    }
674
675    #[test]
676    fn mode_codes_match_table_10() {
677        assert_eq!(Mode::Sleep.code(), 0b00);
678        assert_eq!(Mode::Forced.code(), 0b01);
679        assert_eq!(Mode::Normal.code(), 0b11);
680        assert_eq!(Mode::from_code(0b00), Mode::Sleep);
681        // "01 and 10" are both forced mode.
682        assert_eq!(Mode::from_code(0b01), Mode::Forced);
683        assert_eq!(Mode::from_code(0b10), Mode::Forced);
684        assert_eq!(Mode::from_code(0b11), Mode::Normal);
685    }
686
687    #[test]
688    fn standby_codes_and_periods_match_table_11() {
689        let table = [
690            (0b000, Standby::Ms0_5, 500),
691            (0b001, Standby::Ms62_5, 62_500),
692            (0b010, Standby::Ms125, 125_000),
693            (0b011, Standby::Ms250, 250_000),
694            (0b100, Standby::Ms500, 500_000),
695            (0b101, Standby::Ms1000, 1_000_000),
696            (0b110, Standby::Ms2000, 2_000_000),
697            (0b111, Standby::Ms4000, 4_000_000),
698        ];
699        for (code, standby, micros) in table {
700            assert_eq!(standby.code(), code);
701            assert_eq!(Standby::from_code(code), standby);
702            assert_eq!(standby.microseconds(), micros);
703        }
704    }
705
706    #[test]
707    fn ctrl_meas_packs_the_fields_of_table_20() {
708        // osrs_t in bits 7:5, osrs_p in bits 4:2, mode in bits 1:0. Table 7's
709        // "indoor navigation" row: x2 temperature, x16 pressure, normal mode.
710        let ctrl = CtrlMeas {
711            temperature: Oversampling::X2,
712            pressure: Oversampling::X16,
713            mode: Mode::Normal,
714        };
715        assert_eq!(ctrl.bits(), 0b0101_0111);
716        assert_eq!(CtrlMeas::from_bits(0x57), ctrl);
717        // Table 18 reset state is 0x00.
718        assert_eq!(CtrlMeas::default().bits(), 0x00);
719        assert_eq!(CtrlMeas::from_bits(0x00), CtrlMeas::default());
720        for bits in 0..=u8::MAX {
721            let decoded = CtrlMeas::from_bits(bits);
722            assert_eq!(CtrlMeas::from_bits(decoded.bits()), decoded);
723        }
724    }
725
726    #[test]
727    fn config_packs_the_fields_of_table_23() {
728        // t_sb in bits 7:5, filter in bits 4:2, spi3w_en in bit 0.
729        let config = Config {
730            standby: Standby::Ms62_5,
731            filter: 0b100,
732            spi_3wire: true,
733        };
734        assert_eq!(config.bits(), 0b0011_0001);
735        assert_eq!(Config::from_bits(0x31), config);
736        assert_eq!(Config::default().bits(), 0x00);
737        // Bit 1 is reserved and is dropped on decode.
738        assert_eq!(Config::from_bits(0x02), Config::default());
739        for bits in 0..=u8::MAX {
740            let decoded = Config::from_bits(bits);
741            assert_eq!(Config::from_bits(decoded.bits()), decoded);
742        }
743    }
744
745    #[test]
746    fn calibration_parses_the_table_17_layout_and_round_trips() {
747        // Table 17: twelve little-endian words, dig_T1..dig_T3 then dig_P1..dig_P9,
748        // dig_T1 and dig_P1 unsigned and the rest signed.
749        let bytes: [u8; 24] = [
750            0x70, 0x6B, 0x43, 0x67, 0x18, 0xFC, 0x7D, 0x8E, 0x43, 0xD6, 0xD0, 0x0B, 0x27, 0x0B,
751            0x8C, 0x00, 0xF9, 0xFF, 0x8C, 0x3C, 0xF8, 0xC6, 0x70, 0x17,
752        ];
753        let calib = Calibration::parse(&bytes);
754        assert_eq!(
755            calib,
756            Calibration {
757                dig_t1: 27504,
758                dig_t2: 26435,
759                dig_t3: -1000,
760                dig_p1: 36477,
761                dig_p2: -10685,
762                dig_p3: 3024,
763                dig_p4: 2855,
764                dig_p5: 140,
765                dig_p6: -7,
766                dig_p7: 15500,
767                dig_p8: -14600,
768                dig_p9: 6000,
769            }
770        );
771        assert_eq!(calib.to_bytes(), bytes);
772        let sample = sample_calibration();
773        assert_eq!(Calibration::parse(&sample.to_bytes()), sample);
774    }
775
776    #[test]
777    fn measurement_parses_the_burst_read_and_round_trips() {
778        // Tables 24 and 25: up[19:12], up[11:4], up[3:0] in the upper nibble, then the
779        // same for ut. Pressure 0x655AC = 415148, temperature 0x7EED0 = 519888.
780        let data = [0x65, 0x5A, 0xC0, 0x7E, 0xED, 0x00];
781        let raw = Measurement::parse(&data);
782        assert_eq!(raw.pressure, 415_148);
783        assert_eq!(raw.temperature, 519_888);
784        assert_eq!(raw.to_bytes(), data);
785        // The low nibble of each XLSB byte is not part of the code.
786        assert_eq!(
787            Measurement::parse(&[0x65, 0x5A, 0xCF, 0x7E, 0xED, 0x0F]),
788            raw
789        );
790        assert_eq!(
791            Measurement::parse(&[0xFF; 6]).to_bytes(),
792            [0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF0]
793        );
794    }
795
796    #[test]
797    fn skipped_measurements_read_as_0x80000() {
798        // Tables 21 and 22: a skipped measurement sets its output to 0x80000, which
799        // is also the Table 18 reset state of the data registers.
800        let raw = Measurement::parse(&[0x80, 0x00, 0x00, 0x80, 0x00, 0x00]);
801        assert_eq!(raw.pressure, SKIPPED_OUTPUT);
802        assert!(raw.pressure_skipped() && raw.temperature_skipped());
803        assert!(!Measurement::parse(&[0x65, 0x5A, 0xC0, 0x7E, 0xED, 0x00]).pressure_skipped());
804    }
805
806    #[test]
807    fn temperature_matches_the_worked_example() {
808        // 25.08 degrees C for adc_T = 519888, as in the BME280 module's anchor; the
809        // shift-based integer path lands exactly on the 128422 t_fine that example
810        // quotes.
811        let calib = worked_example_calibration();
812        let t_fine = calib.t_fine(519_888);
813        assert_eq!(t_fine, 128_422);
814        assert_eq!(compensate_temperature(t_fine), 2508);
815        let reading = calib.compensate(&Measurement {
816            pressure: 415_148,
817            temperature: 519_888,
818        });
819        assert_eq!(reading.temperature_centi_celsius, 2508);
820        assert!((reading.celsius() - 25.08).abs() < 0.001);
821    }
822
823    #[test]
824    fn reference_vector_pins_the_integer_output() {
825        // The exact output of the section 3.11.3 code for the Table 17 layout bytes in
826        // calibration_parses_the_table_17_layout_and_round_trips and the burst read
827        // 65 5A C0 7E ED 00. Both temperature functions store t_fine = 128422 for this
828        // input, so the appendix 8.1 double form is a like-for-like check here: it
829        // gives 100653.26 Pa, and the Q24.8 value below is within 0.01 Pa of it.
830        let calib = Calibration {
831            dig_t1: 27504,
832            dig_t2: 26435,
833            dig_t3: -1000,
834            dig_p1: 36477,
835            dig_p2: -10685,
836            dig_p3: 3024,
837            dig_p4: 2855,
838            dig_p5: 140,
839            dig_p6: -7,
840            dig_p7: 15500,
841            dig_p8: -14600,
842            dig_p9: 6000,
843        };
844        let raw = Measurement::parse(&[0x65, 0x5A, 0xC0, 0x7E, 0xED, 0x00]);
845        let reading = calib.compensate(&raw);
846        assert_eq!(reading.temperature_centi_celsius, 2508);
847        assert_eq!(reading.pressure_q24_8, 25_767_233);
848        assert_eq!(reading.pascals(), 100_653);
849        let (_, t_fine_ref) = reference_temperature(&calib, raw.temperature);
850        assert_eq!(calib.t_fine(raw.temperature), 128_422);
851        assert_eq!(t_fine_ref, 128_422);
852        let p_ref = reference_pressure(&calib, raw.pressure, t_fine_ref);
853        assert!((f64::from(reading.pressure_q24_8) / 256.0 - p_ref).abs() < 0.01);
854    }
855
856    #[test]
857    fn integer_compensation_tracks_the_floating_point_reference() {
858        // Sweep raw codes across several coefficient sets and require the section
859        // 3.11.3 integer path to agree with the appendix 8.1 double path to within
860        // rounding. The two temperature functions round t_fine differently (the int32
861        // path floors three shifts, the double casts once), at most three counts for
862        // these coefficients, and the pressure formula moves about 0.03 Pa per count;
863        // so the pressure functions are compared on the same signed 32-bit t_fine, the
864        // carry-over the appendix defines them to share.
865        for calib in [sample_calibration(), worked_example_calibration()] {
866            for adc_t in [400_000, 450_000, 500_000, 519_888, 540_000, 600_000] {
867                let t_fine = calib.t_fine(adc_t);
868                let t_int = compensate_temperature(t_fine);
869                let (t_ref, t_fine_ref) = reference_temperature(&calib, adc_t);
870                assert!(
871                    (t_fine - t_fine_ref).abs() <= 3,
872                    "t_fine {t_fine} vs {t_fine_ref}"
873                );
874                assert!(
875                    (f64::from(t_int) / 100.0 - t_ref).abs() < 0.02,
876                    "temperature {t_int} vs {t_ref}"
877                );
878                for adc_p in [300_000, 350_000, 415_148, 450_000, 512_000] {
879                    let p_int = calib.compensate_pressure(adc_p, t_fine);
880                    let p_ref = reference_pressure(&calib, adc_p, t_fine);
881                    assert!(
882                        (f64::from(p_int) / 256.0 - p_ref).abs() < 0.05,
883                        "pressure {p_int} (Q24.8) vs {p_ref} Pa"
884                    );
885                }
886            }
887        }
888    }
889
890    #[test]
891    fn any_20_bit_code_pair_compensates_without_overflow() {
892        let calib = sample_calibration();
893        for adc_t in (0..=0xF_FFFF).step_by(0x1_0000).chain([0xF_FFFF]) {
894            for adc_p in (0..=0xF_FFFF).step_by(0x1_0000).chain([0xF_FFFF]) {
895                let _ = calib.compensate(&Measurement {
896                    pressure: adc_p,
897                    temperature: adc_t,
898                });
899            }
900        }
901    }
902
903    #[test]
904    fn zero_dig_p1_takes_the_division_guard() {
905        // Section 3.11.3 returns 0 from bmp280_compensate_P_int64 when its var1 is
906        // zero, "to avoid exception caused by division by zero", and appendix 8.1 does
907        // the same; a dig_P1 of 0 reaches that branch for any raw code.
908        let base = sample_calibration();
909        let calib = Calibration { dig_p1: 0, ..base };
910        let raw = Measurement::parse(&[0x65, 0x5A, 0xC0, 0x7E, 0xED, 0x00]);
911        let reading = calib.compensate(&raw);
912        assert_eq!(reading.pressure_q24_8, 0);
913        assert_eq!(reading.pascals(), 0);
914        let expected = base.compensate(&raw).temperature_centi_celsius;
915        assert_eq!(reading.temperature_centi_celsius, expected);
916        let t_fine = calib.t_fine(raw.temperature);
917        assert_eq!(reference_pressure(&calib, raw.pressure, t_fine), 0.0);
918    }
919
920    #[test]
921    fn reading_unit_helpers_match_the_reference_code_comments() {
922        // Section 3.11.3: "24674867" in Q24.8 is 96386.2 Pa = 963.862 hPa, and a
923        // temperature output of "5123" equals 51.23 degrees C.
924        let reading = Reading {
925            temperature_centi_celsius: 5123,
926            pressure_q24_8: 24_674_867,
927        };
928        assert!((reading.celsius() - 51.23).abs() < 0.001);
929        assert_eq!(reading.pascals(), 96_386);
930        assert!((reading.hectopascals() - 963.862).abs() < 0.001);
931    }
932
933    // bmp280_compensate_T_double from appendix 8.1, used only to validate the integer
934    // path above. It returns the Celsius output and the signed 32-bit t_fine the
935    // appendix stores for its pressure function.
936    fn reference_temperature(c: &Calibration, adc_t: u32) -> (f64, i32) {
937        let adc_t = f64::from(adc_t);
938        let var1 = (adc_t / 16384.0 - f64::from(c.dig_t1) / 1024.0) * f64::from(c.dig_t2);
939        let var2 = {
940            let v = adc_t / 131072.0 - f64::from(c.dig_t1) / 8192.0;
941            v * v * f64::from(c.dig_t3)
942        };
943        ((var1 + var2) / 5120.0, (var1 + var2) as i32)
944    }
945
946    // bmp280_compensate_P_double from appendix 8.1, reading the signed 32-bit t_fine
947    // its temperature function stored.
948    fn reference_pressure(c: &Calibration, adc_p: u32, t_fine: i32) -> f64 {
949        let mut var1 = (f64::from(t_fine) / 2.0) - 64000.0;
950        let mut var2 = var1 * var1 * f64::from(c.dig_p6) / 32768.0;
951        var2 += var1 * f64::from(c.dig_p5) * 2.0;
952        var2 = (var2 / 4.0) + (f64::from(c.dig_p4) * 65536.0);
953        var1 =
954            (f64::from(c.dig_p3) * var1 * var1 / 524288.0 + f64::from(c.dig_p2) * var1) / 524288.0;
955        var1 = (1.0 + var1 / 32768.0) * f64::from(c.dig_p1);
956        if var1 == 0.0 {
957            return 0.0;
958        }
959        let mut p = 1048576.0 - f64::from(adc_p);
960        p = (p - (var2 / 4096.0)) * 6250.0 / var1;
961        var1 = f64::from(c.dig_p9) * p * p / 2147483648.0;
962        var2 = p * f64::from(c.dig_p8) / 32768.0;
963        p + (var1 + var2 + f64::from(c.dig_p7)) / 16.0
964    }
965}