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}