bme680_driver/
lib.rs

1#![no_main]
2#![no_std]
3
4//! # BME680 Environmental Sensor Driver
5//!
6//! A type-safe, `no_std` driver for the Bosch BME680.
7//! This driver uses the typestate pattern to ensure the sensor is correctly
8//! initialized and configured before measurements are taken.
9//!
10//! ## Features
11//! - **Flexible Configuration**: Individually enable/disable Temperature, Humidity,
12//!   Pressure, or Gas measurements to save power.
13//! - **Fixed-Point Arithmetic**: No FPU required.
14//! - **Typestate Pattern**: Prevents measuring before initialization.
15//!
16//! ## Units
17//! - **Temperature**: Centigrade (C * 100) -> 2350 = 23.50 °C
18//! - **Humidity**: Milli-percent (RH % * 1000) -> 45123 = 45.123 %
19//! - **Pressure**: Pascal (Pa) -> 101325 = 1013.25 hPa
20//! - **Gas Resistance**: Ohms (Ω)
21
22mod calc;
23
24use core::marker::PhantomData;
25use embedded_hal::{self, delay::DelayNs, i2c};
26
27/// Internal register addresses for the BME680.
28pub(crate) mod regs {
29    pub const ADDR_RES_HEAT_VAL: u8 = 0x00;
30    pub const ADDR_RES_HEAT_RANGE: u8 = 0x02;
31    pub const ADDR_RANGE_SW_ERR: u8 = 0x04;
32    /// Status register containing the "new data" bit.
33    pub const ADDR_EAS_STATUS_0: u8 = 0x1D;
34    /// Start of the measurement data registers (Pressure MSB).
35    pub const ADDR_PRESS_MSB: u8 = 0x1F;
36    pub const ADDR_RES_HEAT_0: u8 = 0x5A;
37    pub const ADDR_GAS_WAIT_0: u8 = 0x64;
38    /// Ctrl Gas 1: RUN_GAS and NB_CONV settings.
39    pub const ADDR_CTRL_GAS_1: u8 = 0x71;
40    /// Ctrl Hum: Humidity oversampling.
41    pub const ADDR_CTRL_HUM: u8 = 0x72;
42    /// Ctrl Meas: Temp/Pres oversampling and Mode selection.
43    pub const ADDR_CTRL_MEAS: u8 = 0x74;
44    /// Config: IIR Filter and SPI 3-wire selection.
45    pub const ADDR_CONFIG: u8 = 0x75;
46    /// Start of the first calibration data block.
47    pub const ADDR_CALIB_0: u8 = 0x89;
48    /// Chip ID register.
49    pub const ADDR_ID: u8 = 0xD0;
50    /// Soft Reset register.
51    pub const ADDR_RESET: u8 = 0xE0;
52    /// Start of the second calibration data block.
53    pub const ADDR_CALIB_1: u8 = 0xE1;
54}
55
56/// Sizes of various data blocks in memory.
57mod reg_sizes {
58    pub const SIZE_CALIB_0: usize = 25;
59    pub const SIZE_CALIB_1: usize = 16;
60    pub const SIZE_CALIB_TOTAL: usize = SIZE_CALIB_0 + SIZE_CALIB_1;
61    pub const SIZE_RAW_DATA: usize = 13;
62}
63
64/// Bit masks for register configuration.
65mod msks {
66    /// Command to trigger a soft reset.
67    pub const MSK_RESET: u8 = 0xB6;
68    pub const MSK_RES_HEAT_RANGE: u8 = 0x30;
69    pub const MSK_NB_CONV: u8 = 0xF;
70    pub const MSK_PAR_H1_LSB: u8 = 0xF;
71    pub const MSK_GAS_RANGE: u8 = 0x0F;
72    pub const MSK_OSRS_P: u8 = 0x1C;
73    pub const MSK_OSRS_T: u8 = 0xE0;
74    pub const MSK_OSRS_H: u8 = 0x3;
75    pub const MSK_RUN_GAS: u8 = 0x10;
76    pub const MSK_MODE: u8 = 0x3;
77    pub const MSK_FILTER: u8 = 0x1C;
78}
79
80// --- Typestates ---
81
82pub struct Idle;
83/// Sensor has been created but not yet initialized with calibration data.
84pub struct Uninitialized;
85/// Sensor is initialized, configured, and ready for measurements.
86pub struct Ready;
87
88/// Error types for the BME680 driver.
89pub mod error {
90    /// Errors that can occur during communication or configuration.
91    #[derive(Debug, Clone, Copy)]
92    pub enum Bme680Error<E> {
93        /// I2C bus error.
94        I2CError(E),
95        /// Provided wait time exceeds the 4096ms hardware limit.
96        InvalidWaitTime,
97        /// Provided profile index is out of bounds (0-9).
98        InvalidProfileIndex,
99        /// Sensor measurement timed out.
100        Timeout,
101        /// Gas heating plate has not reached a stable temperature.
102        HeaterNotStable,
103        /// Gas measurement data is not yet valid.
104        GasDataNotReady,
105    }
106
107    /// Result type alias for BME680 operations.
108    pub type Result<T, E> = core::result::Result<T, Bme680Error<E>>;
109}
110
111/// Oversampling settings for Temperature, Pressure, and Humidity.
112///
113/// Higher oversampling rates increase accuracy (reduce noise) but lead to
114/// longer measurement times and higher power consumption.
115#[derive(Debug, Clone, Copy, PartialEq)]
116#[repr(u8)]
117pub enum Oversampling {
118    /// No measurement performed. Used to disable a specific sensor.
119    Skipped = 0,
120    /// 1x Oversampling.
121    X1 = 1,
122    /// 2x Oversampling.
123    X2 = 2,
124    /// 4x Oversampling.
125    X4 = 3,
126    /// 8x Oversampling.
127    X8 = 4,
128    /// 16x Oversampling.
129    X16 = 5,
130}
131
132impl Oversampling {
133    pub fn from_u8(value: u8) -> Self {
134        match value {
135            0 => Oversampling::Skipped,
136            1 => Oversampling::X1,
137            2 => Oversampling::X2,
138            3 => Oversampling::X4,
139            4 => Oversampling::X8,
140            5 => Oversampling::X16,
141            _ => panic!("Invalid Oversampling Value"),
142        }
143    }
144}
145
146/// Infinite Impulse Response (IIR) filter coefficient.
147///
148/// Used to filter short-term disturbances (noise) in pressure and temperature.
149/// Does not affect humidity or gas measurements.
150#[derive(Debug, Clone, Copy)]
151#[repr(u8)]
152pub enum IIRFilter {
153    IIR0 = 0,
154    IIR1 = 1,
155    IIR3 = 2,
156    IIR7 = 3,
157    IIR15 = 4,
158    IIR31 = 5,
159    IIR63 = 6,
160    IIR127 = 7,
161}
162
163/// Available heating profile slots (0 to 9) stored in the sensor.
164#[derive(Debug, Clone, Copy)]
165#[repr(u8)]
166pub enum GasProfileIndex {
167    Profile0 = 0,
168    Profile1 = 1,
169    Profile2 = 2,
170    Profile3 = 3,
171    Profile4 = 4,
172    Profile5 = 5,
173    Profile6 = 6,
174    Profile7 = 7,
175    Profile8 = 8,
176    Profile9 = 9,
177}
178
179/// Temperature wrapper for type-safety.
180/// Value is stored in Centigrade * 100 (e.g., 2350 = 23.50 °C).
181#[derive(Clone, Copy)]
182pub struct Celsius(pub i32);
183
184/// Duration wrapper for type-safety. Stored in milliseconds.
185#[derive(Debug, Clone, Copy)]
186pub struct Milliseconds(pub u32);
187
188/// Configuration for the gas sensor heating plate.
189#[derive(Clone, Copy)]
190pub struct GasProfile {
191    /// Slot index in the sensor memory where this profile is stored.
192    pub index: GasProfileIndex,
193    /// Target temperature in Celsius.
194    pub target_temp: Celsius,
195    /// Duration to maintain the temperature before measurement.
196    pub wait_time: Milliseconds,
197}
198
199/// Grouped oversampling settings for all three environmental sensors.
200///
201/// Use `Oversampling::Skipped` to disable specific measurements.
202pub struct OversamplingConfig {
203    /// Temperature oversampling.
204    pub temp_osrs: Oversampling,
205    /// Humidity oversampling.
206    pub hum_osrs: Oversampling,
207    /// Pressure oversampling.
208    pub pres_osrs: Oversampling,
209}
210
211impl OversamplingConfig {
212    /// Returns `true` if all measurements are set to `Skipped`.
213    ///
214    /// This is used internally to determine if a forced measurement command
215    /// needs to be sent or if the sensor should remain idle.
216    pub fn is_all_skipped(&self) -> bool {
217        self.temp_osrs == Oversampling::Skipped
218            && self.hum_osrs == Oversampling::Skipped
219            && self.pres_osrs == Oversampling::Skipped
220    }
221}
222
223/// Internal struct to track which sensors are enabled for the current measurement cycle.
224///
225/// This is derived from the register settings before a measurement starts
226/// to determine if we need to wait for the gas heater or skip calculation steps.
227struct MeasConfig {
228    osrs_config: OversamplingConfig,
229    gas_enabled: bool,
230}
231
232/// Complete sensor configuration used for setup.
233pub struct Config {
234    /// Oversampling settings for Temperature, Pressure, and Humidity.
235    pub osrs_config: OversamplingConfig,
236    /// IIR Filter settings to reduce noise in T and P measurements.
237    pub iir_filter: IIRFilter,
238    /// Gas heater configuration.
239    ///
240    /// Set to `None` to disable gas measurement entirely. This saves significant
241    /// power (~12-18mA) and time, as the heating phase is skipped.
242    pub gas_profile: Option<GasProfile>,
243    /// Current ambient temperature estimate (in °C * 100).
244    ///
245    /// This is required for the heater resistance calculation formula to ensure
246    /// the target temperature (e.g., 300°C) is reached accurately.
247    pub ambient_temp: Celsius,
248}
249
250/// Factory-fused calibration coefficients read from the sensor.
251///
252/// These parameters are unique to every individual chip and are read from
253/// non-volatile memory during initialization (`init()`). They are required
254/// to compensate the raw ADC values into physical units.
255#[derive(Debug, Default, Copy, Clone)]
256pub struct CalibData {
257    pub par_h1: u16,
258    pub par_h2: u16,
259    pub par_h3: i8,
260    pub par_h4: i8,
261    pub par_h5: i8,
262    pub par_h6: u8,
263    pub par_h7: i8,
264    pub par_g1: i8,
265    pub par_g2: i16,
266    pub par_g3: i8,
267    pub par_t1: u16,
268    pub par_t2: i16,
269    pub par_t3: i8,
270    pub par_p1: u16,
271    pub par_p2: i16,
272    pub par_p3: i8,
273    pub par_p4: i16,
274    pub par_p5: i16,
275    pub par_p6: i8,
276    pub par_p7: i8,
277    pub par_p8: i16,
278    pub par_p9: i16,
279    pub par_p10: u8,
280    pub res_heat_range: u8,
281    pub res_heat_val: u8,
282    pub range_sw_err: i8,
283}
284
285/// Raw ADC output and status bits read directly from the sensor registers.
286///
287/// This struct holds the uncompensated data read from `regs::ADDR_PRESS_MSB` onwards.
288/// It is used internally by the driver to calculate the final physical values.
289#[derive(Debug, Copy, Clone)]
290pub struct RawData {
291    pub(crate) temp_adc: u32,
292    pub(crate) hum_adc: u16,
293    pub(crate) press_adc: u32,
294    pub(crate) gas_adc: u16,
295    /// Range switching error used for gas calculation.
296    pub(crate) gas_range: u8,
297    /// Indicates if the gas measurement is valid.
298    pub(crate) gas_valid_r: bool,
299    /// Indicates if the target heater temperature was reached.
300    pub(crate) heat_stab_r: bool,
301}
302
303/// Intermediate temperature values used for compensation.
304///
305/// These values (`t_fine`) are calculated during temperature compensation and
306/// are required for the subsequent pressure and humidity compensation formulas.
307#[derive(Debug, Copy, Clone, Default)]
308pub struct CalcTempData {
309    pub(crate) temp_fine: i32,
310    pub(crate) temp_comp: i32,
311}
312
313/// Represents temperature in Centigrade (degrees Celsius * 100).
314///
315/// This wrapper ensures type safety and prevents mixing units.
316/// Use the `.split()` method to easily format this for display.
317///
318/// # Example
319/// A value of `2350` represents **23.50 °C**.
320#[derive(Debug, Copy, Clone, Default)]
321pub struct Temperature(pub i32);
322
323impl Temperature {
324    /// Splits the fixed-point value into integral (degrees) and fractional (decimals) parts.
325    ///
326    /// # Returns
327    /// A tuple `(whole, fraction)`.
328    ///
329    /// # Example
330    /// ```rust
331    /// use bme680_driver::Temperature;
332    /// let temp = Temperature(2350);
333    /// assert_eq!(temp.split(), (23, 50)); // Represents 23.50 °C
334    /// ```
335    pub fn split(&self) -> (i32, i32) {
336        (self.0 / 100, self.0 % 100)
337    }
338}
339
340/// Represents relative humidity in milli-percent (percent * 1000).
341///
342/// # Example
343/// A value of `45123` represents **45.123 %rH**.
344#[derive(Debug, Copy, Clone, Default)]
345pub struct Humidity(pub i32);
346
347impl Humidity {
348    /// Splits the fixed-point value into integral and fractional parts.
349    ///
350    /// # Returns
351    /// A tuple `(whole, fraction)`. The fraction represents 3 decimal places.
352    ///
353    /// # Example
354    /// ```rust
355    /// use bme680_driver::Humidity;
356    /// let hum = Humidity(45123);
357    /// assert_eq!(hum.split(), (45, 123)); // Represents 45.123 %
358    /// ```
359    pub fn split(&self) -> (i32, i32) {
360        (self.0 / 1000, self.0 % 1000)
361    }
362}
363
364/// Represents atmospheric pressure in Pascal (Pa).
365///
366/// # Example
367/// A value of `101325` represents **101325 Pa** (or 1013.25 hPa).
368#[derive(Debug, Copy, Clone, Default)]
369pub struct Pressure(pub u32);
370
371impl Pressure {
372    /// Converts the raw Pascal value to Hectopascal (hPa) and splits it into parts.
373    ///
374    /// Since 1 hPa = 100 Pa, this effectively splits the integer at the hundreds place.
375    ///
376    /// # Returns
377    /// A tuple `(hPa_integral, hPa_decimal)`.
378    ///
379    /// # Example
380    /// ```rust
381    /// use bme680_driver::Pressure;
382    /// let press = Pressure(101325);
383    /// assert_eq!(press.as_hpa(), (1013, 25)); // Represents 1013.25 hPa
384    /// ```
385    pub fn as_hpa(&self) -> (u32, u32) {
386        (self.0 / 100, self.0 % 100)
387    }
388}
389
390/// Represents gas resistance in Ohms (Ω).
391///
392/// A higher gas resistance typically indicates cleaner air (fewer VOCs).
393#[derive(Debug, Copy, Clone, Default)]
394pub struct Gas(pub u32);
395
396/// Compensated measurement result in physical units.
397///
398/// All fields use strong types (`Temperature`, `Humidity`, etc.) to prevent unit confusion.
399/// If a measurement was skipped/disabled, the corresponding field contains `0` (Default).
400#[derive(Debug, Copy, Clone, Default)]
401pub struct Measurement {
402    /// Temperature data.
403    pub temp: Temperature,
404    /// Humidity data.
405    pub hum: Humidity,
406    /// Atmospheric pressure data.
407    pub pres: Pressure,
408    /// Gas resistance data.
409    pub gas: Gas,
410}
411
412/// The main BME680 driver structure.
413///
414/// Use `Bme680::new(...)` to start. The `STATE` generic uses the Typestate pattern
415/// to track initialization status at compile time.
416#[derive(Debug, Copy, Clone)]
417pub struct Bme680<I2C, STATE> {
418    i2c: I2C,
419    address: u8,
420    pub(crate) calib_data: CalibData,
421    /// Tracks the calculated wait time required for the current configuration.
422    current_wait_time: Milliseconds,
423    _state: PhantomData<STATE>,
424}
425
426impl<I2C, E> Bme680<I2C, Idle>
427where
428    I2C: i2c::I2c<Error = E>,
429{
430    /// Creates a new driver instance in the `Uninitialized` state.
431    ///
432    /// This does not communicate with the sensor yet.
433    ///
434    /// # Arguments
435    /// * `i2c` - The I2C bus object.
436    /// * `address` - The I2C address of the sensor (typically `0x76` or `0x77`).
437    pub fn new(i2c: I2C, address: u8) -> Bme680<I2C, Uninitialized> {
438        Bme680 {
439            i2c,
440            address,
441            calib_data: CalibData::default(),
442            current_wait_time: Milliseconds(0),
443            _state: PhantomData,
444        }
445    }
446}
447
448impl<I2C, STATE, E> Bme680<I2C, STATE>
449where
450    I2C: i2c::I2c<Error = E>,
451{
452    /// Performs a soft-reset of the sensor.
453    ///
454    /// This resets all internal registers to their default values.
455    /// A delay of at least 2ms is required after the reset command.
456    fn reset(&mut self, delay: &mut impl DelayNs) -> error::Result<(), E> {
457        self.write_reg(&[regs::ADDR_RESET, msks::MSK_RESET])?;
458
459        delay.delay_ms(2);
460
461        Ok(())
462    }
463
464    /// Reads data from a starting register address into a provided buffer.
465    ///
466    /// This is a low-level helper function for I2C communication.
467    fn read_into(&mut self, reg_address: u8, buffer: &mut [u8]) -> error::Result<(), E> {
468        self.i2c
469            .write_read(self.address, &[reg_address], buffer)
470            .map_err(|e| error::Bme680Error::I2CError(e))
471    }
472
473    /// Reads a single byte from a specific register address.
474    fn read_reg_byte(&mut self, reg_address: u8) -> error::Result<u8, E> {
475        let mut buffer = [0];
476
477        self.i2c
478            .write_read(self.address, &[reg_address], &mut buffer)
479            .map_err(|e| error::Bme680Error::I2CError(e))?;
480
481        Ok(buffer[0])
482    }
483
484    /// Writes a byte slice (typically `[Register, Value]`) to the sensor.
485    fn write_reg(&mut self, data: &[u8]) -> error::Result<(), E> {
486        self.i2c
487            .write(self.address, data)
488            .map_err(|e| error::Bme680Error::I2CError(e))?;
489        Ok(())
490    }
491}
492
493impl<I2C, E> Bme680<I2C, Uninitialized>
494where
495    I2C: i2c::I2c<Error = E>,
496{
497    /// Initializes the sensor: performs a soft-reset and loads factory calibration data.
498    ///
499    /// This transitions the driver state from `Uninitialized` to `Ready`.
500    ///
501    /// # Errors
502    /// Returns an error if the I2C communication fails during reset or calibration reading.
503    pub fn init(mut self, delay: &mut impl DelayNs) -> error::Result<Bme680<I2C, Ready>, E> {
504        // Sensor requires time to start up before reset
505        delay.delay_ms(2);
506
507        self.reset(delay)?;
508
509        // Read the factory calibration data (requires ~25ms I2C traffic)
510        let calib_data = self.get_calib_data()?;
511
512        Ok(Bme680 {
513            i2c: self.i2c,
514            address: self.address,
515            calib_data: calib_data,
516            current_wait_time: Milliseconds(0),
517            _state: PhantomData,
518        })
519    }
520
521    /// Reads factory-fused calibration coefficients from the sensor's ROM.
522    ///
523    /// The BME680 stores calibration data in two non-contiguous memory blocks.
524    /// These bytes are required to compensate the raw ADC values into physical units.
525    fn get_calib_data(&mut self) -> error::Result<CalibData, E> {
526        let mut calib_data = CalibData::default();
527        let mut buffer = [0u8; reg_sizes::SIZE_CALIB_TOTAL];
528
529        // 1. Read first block (0x89..0xA0)
530        self.read_into(regs::ADDR_CALIB_0, &mut buffer[0..reg_sizes::SIZE_CALIB_0])?;
531        // 2. Read second block (0xE1..0xF0)
532        self.read_into(regs::ADDR_CALIB_1, &mut buffer[reg_sizes::SIZE_CALIB_0..])?;
533
534        // Mapping raw buffer bytes to compensation parameters (Bosch proprietary logic)
535        // See BME680 datasheet, Section 3.11.1
536        calib_data.par_t1 = ((buffer[33] as i32) | ((buffer[34] as i32) << 8)) as u16;
537        calib_data.par_t2 = ((buffer[1] as i32) | ((buffer[2] as i32) << 8)) as i16;
538        calib_data.par_t3 = buffer[3] as i8;
539        calib_data.par_p1 = ((buffer[5] as i32) | ((buffer[6] as i32) << 8)) as u16;
540        calib_data.par_p2 = ((buffer[7] as i32) | ((buffer[8] as i32) << 8)) as i16;
541        calib_data.par_p3 = buffer[9] as i8;
542        calib_data.par_p4 = ((buffer[11] as i32) | ((buffer[12] as i32) << 8)) as i16;
543        calib_data.par_p5 = ((buffer[14] as i32) | ((buffer[13] as i32) << 8)) as i16;
544        calib_data.par_p6 = buffer[16] as i8;
545        calib_data.par_p7 = buffer[15] as i8;
546        calib_data.par_p8 = ((buffer[19] as i32) | ((buffer[20] as i32) << 8)) as i16;
547        calib_data.par_p9 = ((buffer[21] as i32) | ((buffer[22] as i32) << 8)) as i16;
548        calib_data.par_p10 = buffer[23];
549
550        // Use mask constant for bitwise operations
551        calib_data.par_h1 =
552            (((buffer[26] & msks::MSK_PAR_H1_LSB) as i32) | ((buffer[27] as i32) << 4)) as u16;
553        calib_data.par_h2 = (((buffer[26] >> 4) as i32) | ((buffer[25] as i32) << 4)) as u16;
554        calib_data.par_h3 = buffer[28] as i8;
555        calib_data.par_h4 = buffer[29] as i8;
556        calib_data.par_h5 = buffer[30] as i8;
557        calib_data.par_h6 = buffer[31];
558        calib_data.par_h7 = buffer[32] as i8;
559        calib_data.par_g1 = buffer[37] as i8;
560        calib_data.par_g2 = ((buffer[35] as i32) | ((buffer[36] as i32) << 8)) as i16;
561        calib_data.par_g3 = buffer[38] as i8;
562
563        // Additional heater-specific calibration values
564        calib_data.res_heat_val = self.read_reg_byte(regs::ADDR_RES_HEAT_VAL)?;
565
566        // Use mask for range reading (Bits 4,5)
567        calib_data.res_heat_range =
568            (self.read_reg_byte(regs::ADDR_RES_HEAT_RANGE)? & msks::MSK_RES_HEAT_RANGE) >> 4;
569
570        calib_data.range_sw_err = (self.read_reg_byte(regs::ADDR_RANGE_SW_ERR)? as i8) >> 4;
571
572        Ok(calib_data)
573    }
574}
575
576impl<I2C, E> Bme680<I2C, Ready>
577where
578    I2C: i2c::I2c<Error = E>,
579{
580    /// Applies a full sensor configuration.
581    ///
582    /// This method sets oversampling, filters, and gas profiles.
583    /// If `config.gas_profile` is `None`, the gas sensor is disabled to save power.
584    pub fn configure_sensor(&mut self, config: &mut Config) -> error::Result<(), E> {
585        self.config_oversampling(&config.osrs_config)?;
586        self.config_iir_filter(config.iir_filter)?;
587
588        if let Some(x) = config.gas_profile {
589            self.enable_gas_measurement()?;
590            self.select_gas_profile(&x)?;
591            self.set_gas_heater_profile(x, config.ambient_temp)?;
592        } else {
593            self.disable_gas_measurement()?;
594        }
595
596        Ok(())
597    }
598
599    /// Configures heating duration and target temperature for a gas profile.
600    ///
601    /// This calculates the necessary register value based on the current ambient temperature.
602    pub fn set_gas_heater_profile(
603        &mut self,
604        config: GasProfile,
605        amb_temp: Celsius,
606    ) -> error::Result<(), E> {
607        self.config_heater_on_time(config.wait_time, config.index)?;
608        self.config_target_resistance(config.target_temp, amb_temp, config.index)?;
609
610        Ok(())
611    }
612
613    /// Triggers a measurement in 'Forced Mode', waits for completion, and returns compensated data.
614    ///
615    ///
616    ///
617    /// # Power Saving
618    /// If all measurements are set to `Skipped` and gas is disabled, this function
619    /// returns immediately with default values, consuming minimal power.
620    pub fn read_new_data(&mut self, delay: &mut impl DelayNs) -> error::Result<Measurement, E> {
621        // Read config back from sensor to ensure we don't wait unnecessarily
622        let meas_config = self.get_meas_config()?;
623
624        // Optimization: Don't trigger a measurement if nothing is enabled.
625        if !meas_config.gas_enabled && meas_config.osrs_config.is_all_skipped() {
626            return Ok(Measurement::default());
627        }
628
629        // 1. Wake up sensor and start measurement cycle
630        self.activate_forced_mode()?;
631
632        // 2. Wait for heating phase (if gas is enabled)
633        // The sensor measures T, P, H first, then heats up for gas measurement.
634        if meas_config.gas_enabled {
635            delay.delay_ms(self.current_wait_time.0);
636        }
637
638        // 3. Poll for "New Data" bit and read ADC values
639        let raw_data = self.get_raw_data(delay)?;
640
641        let mut temp = CalcTempData::default();
642        let mut hum = 0;
643        let mut pres = 0;
644        let mut gas = 0;
645
646        // 4. Apply mathematical compensation to raw values (if not skipped)
647        if meas_config.osrs_config.temp_osrs != Oversampling::Skipped {
648            temp = self.calc_temp(raw_data.temp_adc);
649
650            // Humidity and Pressure compensation depends on "fine temperature"
651            if meas_config.osrs_config.hum_osrs != Oversampling::Skipped {
652                hum = self.calc_hum(temp.temp_comp, raw_data.hum_adc);
653            }
654
655            if meas_config.osrs_config.pres_osrs != Oversampling::Skipped {
656                pres = self.calc_pres(temp.temp_fine, raw_data.press_adc);
657            }
658        }
659
660        // 5. Check gas validity bits
661        if meas_config.gas_enabled && !raw_data.gas_valid_r {
662            return Err(error::Bme680Error::GasDataNotReady);
663        } else if meas_config.gas_enabled && !raw_data.heat_stab_r {
664            return Err(error::Bme680Error::HeaterNotStable);
665        }
666
667        if meas_config.gas_enabled {
668            gas = self.calc_gas(raw_data.gas_adc, raw_data.gas_range);
669        }
670
671        Ok(Measurement {
672            temp: Temperature(temp.temp_comp),
673            hum: Humidity(hum),
674            pres: Pressure(pres),
675            gas: Gas(gas),
676        })
677    }
678
679    /// Reads the Chip ID from the sensor (expected value: 0x61).
680    pub fn read_chip_id(&mut self) -> error::Result<u8, E> {
681        Ok(self.read_reg_byte(regs::ADDR_ID)?)
682    }
683
684    /// Selects one of the 10 available gas heater profiles.
685    pub fn select_gas_profile(&mut self, profile: &GasProfile) -> error::Result<(), E> {
686        self.current_wait_time = profile.wait_time;
687        let register = self.read_reg_byte(regs::ADDR_CTRL_GAS_1)?;
688
689        // Clear NB_CONV bits using mask, then set new profile index
690        self.write_reg(&[
691            regs::ADDR_CTRL_GAS_1,
692            (register & !msks::MSK_NB_CONV) | (profile.index as u8),
693        ])?;
694        Ok(())
695    }
696
697    /// Polls the sensor until new data is available and reads all ADC values.
698    ///
699    /// This method includes a timeout mechanism (max. 50ms).
700    fn get_raw_data(&mut self, delay: &mut impl DelayNs) -> error::Result<RawData, E> {
701        let mut new_data = false;
702        let mut timeout_us = 50000; // 50ms Timeout
703
704        while !new_data {
705            if timeout_us <= 0 {
706                return Err(error::Bme680Error::Timeout);
707            }
708            // Check bit 7 (MSB) in EAS_STATUS_0 register
709            // Note: 0x80 is implied for Bit 7, or we could add MSK_NEW_DATA later
710            new_data = (self.read_reg_byte(regs::ADDR_EAS_STATUS_0)? >> 7) != 0;
711
712            delay.delay_us(500);
713            timeout_us -= 500;
714        }
715
716        let mut buffer = [0u8; reg_sizes::SIZE_RAW_DATA];
717
718        // Burst read starting from ADDR_PRESS_MSB Register
719        self.read_into(regs::ADDR_PRESS_MSB, &mut buffer)?;
720
721        // Reconstruct 20-bit and 16-bit ADC values from register bytes
722        let press_adc =
723            ((buffer[2] as u32) >> 4) | ((buffer[1] as u32) << 4) | ((buffer[0] as u32) << 12);
724        let temp_adc =
725            ((buffer[5] as u32) >> 4) | ((buffer[4] as u32) << 4) | ((buffer[3] as u32) << 12);
726        let hum_adc = ((buffer[7] as u32) | ((buffer[6] as u32) << 8)) as u16;
727        let gas_adc = (((buffer[12] as u32) >> 6) | ((buffer[11] as u32) << 2)) as u16;
728
729        // Use mask for gas range
730        let gas_range = buffer[12] & msks::MSK_GAS_RANGE;
731
732        // Status bits for gas measurement
733        let gas_valid_r = ((buffer[12] >> 5) & 0x1) != 0;
734        let heat_stab_r = ((buffer[12] >> 4) & 0x1) != 0;
735
736        Ok(RawData {
737            temp_adc,
738            hum_adc,
739            press_adc,
740            gas_adc,
741            gas_range,
742            gas_valid_r,
743            heat_stab_r,
744        })
745    }
746
747    /// Sets oversampling rates for Humidity, Temperature, and Pressure.
748    ///
749    /// Writes to registers CTRL_HUM and CTRL_MEAS.
750    fn config_oversampling(&mut self, osrs_config: &OversamplingConfig) -> error::Result<(), E> {
751        // Humidity configuration (Register 0x72)
752        // We must read first to preserve other bits if they existed (though CTRL_HUM usually only has SPI bit)
753        let ctrl_hum = self.read_reg_byte(regs::ADDR_CTRL_HUM)?;
754        let new_hum = (ctrl_hum & !msks::MSK_OSRS_H) | osrs_config.hum_osrs as u8;
755        self.write_reg(&[regs::ADDR_CTRL_HUM, new_hum])?;
756
757        // Temperature & Pressure configuration (Register 0x74)
758        let ctrl_meas = self.read_reg_byte(regs::ADDR_CTRL_MEAS)?;
759
760        // Prepare new bits
761        let temp_pres_combined =
762            ((osrs_config.temp_osrs as u8) << 5) | ((osrs_config.pres_osrs as u8) << 2);
763
764        // Clear old bits (Using !Mask) and set new ones
765        let new_meas = (ctrl_meas & !(msks::MSK_OSRS_T | msks::MSK_OSRS_P)) | temp_pres_combined;
766
767        self.write_reg(&[regs::ADDR_CTRL_MEAS, new_meas])?;
768
769        Ok(())
770    }
771
772    /// Configures the IIR filter coefficient.
773    fn config_iir_filter(&mut self, iir_filter: IIRFilter) -> error::Result<(), E> {
774        let register = self.read_reg_byte(regs::ADDR_CONFIG)?;
775        // Clear filter bits and set new value
776        let new_reg_val = (register & !msks::MSK_FILTER) | ((iir_filter as u8) << 2);
777        self.write_reg(&[regs::ADDR_CONFIG, new_reg_val])?;
778        Ok(())
779    }
780
781    /// Enables the gas sensing functionality in the sensor (CTRL_GAS_1).
782    fn enable_gas_measurement(&mut self) -> error::Result<(), E> {
783        let register = self.read_reg_byte(regs::ADDR_CTRL_GAS_1)?;
784        // Set RUN_GAS bit (Bit 4)
785        self.write_reg(&[
786            regs::ADDR_CTRL_GAS_1,
787            (register & !msks::MSK_RUN_GAS) | msks::MSK_RUN_GAS,
788        ])?;
789        Ok(())
790    }
791
792    /// Disables the gas sensing functionality in the sensor.
793    fn disable_gas_measurement(&mut self) -> error::Result<(), E> {
794        let register = self.read_reg_byte(regs::ADDR_CTRL_GAS_1)?;
795        // Clear RUN_GAS bit (Bit 4)
796        self.write_reg(&[regs::ADDR_CTRL_GAS_1, register & !msks::MSK_RUN_GAS])?;
797        Ok(())
798    }
799
800    /// Activates 'Forced Mode' to trigger a single measurement cycle.
801    ///
802    /// The sensor returns to Sleep mode automatically after the measurement.
803    fn activate_forced_mode(&mut self) -> error::Result<(), E> {
804        let register = self.read_reg_byte(regs::ADDR_CTRL_MEAS)?;
805        // Clear Mode bits and set to 01 (Forced)
806        self.write_reg(&[regs::ADDR_CTRL_MEAS, (register & !msks::MSK_MODE) | 0b01])?;
807        Ok(())
808    }
809
810    /// Reads the current configuration back from the sensor.
811    ///
812    /// Used internally to verify which sensors are enabled before waiting/reading.
813    fn get_meas_config(&mut self) -> error::Result<MeasConfig, E> {
814        let mut buffer = [0u8; 4];
815
816        // Burst read starting from CTRL_GAS_1 register
817        self.read_into(regs::ADDR_CTRL_GAS_1, &mut buffer)?;
818
819        // Extract values using masks (AND logic)
820        // Bit 4 of byte 0
821        let gas_enabled = (buffer[0] & msks::MSK_RUN_GAS) != 0;
822
823        // Bits 0-2 of byte 1
824        let osrs_h = buffer[1] & msks::MSK_OSRS_H;
825
826        // Bits 2-4 of byte 3 (CTRL_MEAS is buffer[3] relative to CTRL_GAS_1)
827        let osrs_p = (buffer[3] & msks::MSK_OSRS_P) >> 2;
828
829        // Bits 5-7 of byte 3
830        let osrs_t = (buffer[3] & msks::MSK_OSRS_T) >> 5;
831
832        let osrs_config = OversamplingConfig {
833            temp_osrs: Oversampling::from_u8(osrs_t),
834            hum_osrs: Oversampling::from_u8(osrs_h),
835            pres_osrs: Oversampling::from_u8(osrs_p),
836        };
837
838        Ok(MeasConfig {
839            osrs_config,
840            gas_enabled,
841        })
842    }
843}