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//! ## Units
11//! - **Temperature**: Centigrade (C * 100)
12//! - **Humidity**: Milli-percent (RH % * 1000)
13//! - **Pressure**: Pascal (Pa)
14//! - **Gas Resistance**: Ohms (Ω)
15
16mod calc;
17
18use core::marker::PhantomData;
19use embedded_hal::{self, delay::DelayNs, i2c};
20
21/// Memory addresses and sizes for calibration data registers.
22mod calib_mem {
23    pub const ADDR: [u8; 2] = [0x89, 0xE1];
24    pub const SIZES: [usize; 2] = [25, 16];
25    pub const TOTAL_SIZE: usize = 25 + 16;
26}
27
28/// Memory address and size for the measurement data registers.
29mod raw_data_mem {
30    pub const ADDR: u8 = 0x1F;
31    pub const SIZE: usize = 12;
32}
33
34// --- Typestates ---
35
36pub struct Idle;
37/// Sensor has been created but not yet initialized with calibration data.
38pub struct Uninitialized;
39/// Sensor is initialized, configured, and ready for measurements.
40pub struct Ready;
41
42/// Error types for the BME680 driver.
43pub mod error {
44    /// Errors that can occur during communication or configuration.
45    #[derive(Debug, Clone, Copy)]
46    pub enum Bme680Error<E> {
47        /// I2C bus error.
48        I2CError(E),
49        /// Provided wait time exceeds the 4096ms hardware limit.
50        InvalidWaitTime,
51        /// Provided profile index is out of bounds (0-9).
52        InvalidProfileIndex,
53        /// Sensor measurement timed out.
54        Timeout,
55        /// Gas heating plate has not reached a stable temperature.
56        HeaterNotStable,
57        /// Gas measurement data is not yet valid.
58        GasDataNotReady,
59    }
60
61    /// Result type alias for BME680 operations.
62    pub type Result<T, E> = core::result::Result<T, Bme680Error<E>>;
63}
64
65/// Oversampling settings for Temperature, Pressure, and Humidity.
66///
67/// Higher oversampling rates increase accuracy but lead to longer measurement times.
68#[derive(Debug, Clone, Copy)]
69#[repr(u8)]
70pub enum Oversampling {
71    /// No measurement performed.
72    Skipped = 0,
73    /// 1x Oversampling.
74    X1 = 1,
75    /// 2x Oversampling.
76    X2 = 2,
77    /// 4x Oversampling.
78    X4 = 3,
79    /// 8x Oversampling.
80    X8 = 4,
81    /// 16x Oversampling.
82    X16 = 5,
83}
84
85/// Infinite Impulse Response (IIR) filter coefficient.
86///
87/// Used to filter short-term disturbances in pressure and temperature.
88#[derive(Debug, Clone, Copy)]
89#[repr(u8)]
90pub enum IIRFilter {
91    IIR0 = 0,
92    IIR1 = 1,
93    IIR3 = 2,
94    IIR7 = 3,
95    IIR15 = 4,
96    IIR31 = 5,
97    IIR63 = 6,
98    IIR127 = 7,
99}
100
101/// Available heating profile slots (0 to 9).
102#[derive(Debug, Clone, Copy)]
103#[repr(u8)]
104pub enum GasProfileIndex {
105    Profile0 = 0,
106    Profile1 = 1,
107    Profile2 = 2,
108    Profile3 = 3,
109    Profile4 = 4,
110    Profile5 = 5,
111    Profile6 = 6,
112    Profile7 = 7,
113    Profile8 = 8,
114    Profile9 = 9,
115}
116
117/// Temperature wrapper for type-safety.
118/// Value is stored in Centigrade (e.g., 2350 = 23.50 °C).
119#[derive(Clone, Copy)]
120pub struct Celsius(pub i32);
121
122/// Duration wrapper for type-safety. Stored in milliseconds.
123#[derive(Debug, Clone, Copy)]
124pub struct Milliseconds(pub u32);
125
126/// Configuration for the gas sensor heating plate.
127#[derive(Clone, Copy)]
128pub struct GasProfile {
129    /// Slot index in the sensor memory.
130    pub index: GasProfileIndex,
131    /// Target temperature in Celsius.
132    pub target_temp: Celsius,
133    /// Duration to maintain the temperature before measurement.
134    pub wait_time: Milliseconds,
135}
136
137/// Grouped oversampling settings for all three environmental sensors.
138pub struct OversamplingConfig {
139    pub temp_osrs: Oversampling,
140    pub hum_osrs: Oversampling,
141    pub pres_osrs: Oversampling,
142}
143
144/// Complete sensor configuration used for setup.
145pub struct Config {
146    pub osrs_config: OversamplingConfig,
147    pub iir_filter: IIRFilter,
148    pub gas_profile: GasProfile,
149    /// Baseline temperature for heater resistance calculation.
150    pub ambient_temp: Celsius,
151}
152
153/// Factory-fused calibration coefficients read from the sensor.
154/// These are unique to every individual chip.
155#[derive(Debug, Default, Copy, Clone)]
156pub struct CalibData {
157    pub par_h1: u16,
158    pub par_h2: u16,
159    pub par_h3: i8,
160    pub par_h4: i8,
161    pub par_h5: i8,
162    pub par_h6: u8,
163    pub par_h7: i8,
164    pub par_g1: i8,
165    pub par_g2: i16,
166    pub par_g3: i8,
167    pub par_t1: u16,
168    pub par_t2: i16,
169    pub par_t3: i8,
170    pub par_p1: u16,
171    pub par_p2: i16,
172    pub par_p3: i8,
173    pub par_p4: i16,
174    pub par_p5: i16,
175    pub par_p6: i8,
176    pub par_p7: i8,
177    pub par_p8: i16,
178    pub par_p9: i16,
179    pub par_p10: u8,
180    pub res_heat_range: u8,
181    pub res_heat_val: u8,
182    pub range_sw_err: i8,
183}
184
185/// Raw ADC output and status bits read directly from the sensor.
186#[derive(Debug, Copy, Clone)]
187pub struct RawData {
188    pub(crate) temp_adc: u32,
189    pub(crate) hum_adc: u16,
190    pub(crate) press_adc: u32,
191    pub(crate) gas_adc: u16,
192    pub(crate) gas_range: u8,
193    pub(crate) gas_valid_r: bool,
194    pub(crate) heat_stab_r: bool,
195}
196
197/// Intermediate temperature values used for compensation.
198#[derive(Debug, Copy, Clone)]
199pub struct CalcTempData {
200    pub(crate) temp_fine: i32,
201    pub(crate) temp_comp: i32,
202}
203
204/// Represents temperature in Centigrade (degrees Celsius * 100).
205///
206/// Use the `.split()` method to easily format this for display.
207///
208/// # Example
209/// A value of `2350` represents **23.50 °C**.
210#[derive(Debug, Copy, Clone)]
211pub struct Temperature(pub i32);
212
213impl Temperature {
214    /// Splits the fixed-point value into integral (degrees) and fractional (decimals) parts.
215    ///
216    /// # Returns
217    /// A tuple `(whole, fraction)`.
218    ///
219    /// # Example
220    /// ```rust
221    /// let temp = Temperature(2350);
222    /// assert_eq!(temp.split(), (23, 50)); // Represents 23.50 °C
223    /// ```
224    pub fn split(&self) -> (i32, i32) {
225        (self.0 / 100, self.0 % 100)
226    }
227}
228
229/// Represents relative humidity in milli-percent (percent * 1000).
230///
231/// # Example
232/// A value of `45123` represents **45.123 %rH**.
233#[derive(Debug, Copy, Clone)]
234pub struct Humidity(pub i32);
235
236impl Humidity {
237    /// Splits the fixed-point value into integral and fractional parts.
238    ///
239    /// # Returns
240    /// A tuple `(whole, fraction)`. The fraction represents 3 decimal places.
241    ///
242    /// # Example
243    /// ```rust
244    /// let hum = Humidity(45123);
245    /// assert_eq!(hum.split(), (45, 123)); // Represents 45.123 %
246    /// ```
247    pub fn split(&self) -> (i32, i32) {
248        (self.0 / 1000, self.0 % 1000)
249    }
250}
251
252/// Represents atmospheric pressure in Pascal (Pa).
253///
254/// # Example
255/// A value of `101325` represents **101325 Pa** (or 1013.25 hPa).
256#[derive(Debug, Copy, Clone)]
257pub struct Pressure(pub u32);
258
259impl Pressure {
260    /// Converts the raw Pascal value to Hectopascal (hPa) and splits it into parts.
261    ///
262    /// Since 1 hPa = 100 Pa, this effectively splits the integer at the hundreds place.
263    ///
264    /// # Returns
265    /// A tuple `(hPa_integral, hPa_decimal)`.
266    ///
267    /// # Example
268    /// ```rust
269    /// let press = Pressure(101325);
270    /// assert_eq!(press.as_hpa(), (1013, 25)); // Represents 1013.25 hPa
271    /// ```
272    pub fn as_hpa(&self) -> (u32, u32) {
273        (self.0 / 100, self.0 % 100)
274    }
275}
276
277/// Represents gas resistance in Ohms (Ω).
278#[derive(Debug, Copy, Clone)]
279pub struct Gas(pub u32);
280
281/// Compensated measurement result in physical units.
282///
283/// All fields use strong types (`Temperature`, `Humidity`, etc.) to prevent unit confusion.
284#[derive(Debug, Copy, Clone)]
285pub struct Measurement {
286    /// Temperature data.
287    pub temp: Temperature,
288    /// Humidity data.
289    pub hum: Humidity,
290    /// Atmospheric pressure data.
291    pub pres: Pressure,
292    /// Gas resistance data.
293    pub gas: Gas,
294}
295
296/// The main BME680 driver structure.
297///
298/// Use `Bme680::new(...)` to start. The `STATE` generic tracks initialization.
299#[derive(Debug, Copy, Clone)]
300pub struct Bme680<I2C, STATE> {
301    i2c: I2C,
302    address: u8,
303    pub(crate) calib_data: CalibData,
304    current_wait_time: Milliseconds,
305    _state: PhantomData<STATE>,
306}
307
308impl<I2C, E> Bme680<I2C, Idle>
309where
310    I2C: i2c::I2c<Error = E>,
311{
312    /// Creates a new driver instance in the `Uninitialized` state.
313    pub fn new(i2c: I2C, address: u8) -> Bme680<I2C, Uninitialized> {
314        Bme680 {
315            i2c,
316            address,
317            calib_data: CalibData::default(),
318            current_wait_time: Milliseconds(0),
319            _state: PhantomData,
320        }
321    }
322}
323impl<I2C, STATE, E> Bme680<I2C, STATE>
324where
325    I2C: i2c::I2c<Error = E>,
326{
327    /// Performs a soft-reset of the sensor.
328    ///
329    /// This resets all internal registers to their default values.
330    /// A delay of at least 2ms is required after the reset command.
331    fn reset(&mut self, delay: &mut impl DelayNs) -> error::Result<(), E> {
332        self.i2c
333            .write(self.address, &[0xE0, 0xB6])
334            .map_err(|e| error::Bme680Error::I2CError(e))?;
335
336        delay.delay_ms(2);
337
338        Ok(())
339    }
340
341    /// Reads data from a starting register address into a provided buffer.
342    fn read_into(&mut self, reg_address: u8, buffer: &mut [u8]) -> error::Result<(), E> {
343        self.i2c
344            .write_read(self.address, &[reg_address], buffer)
345            .map_err(|e| error::Bme680Error::I2CError(e))
346    }
347
348    /// Reads a single byte from a specific register address.
349    fn read_reg_byte(&mut self, reg_address: u8) -> error::Result<u8, E> {
350        let mut buffer = [0];
351
352        self.i2c
353            .write_read(self.address, &[reg_address], &mut buffer)
354            .map_err(|e| error::Bme680Error::I2CError(e))?;
355
356        Ok(buffer[0])
357    }
358
359    /// Writes a byte slice (typically [Register, Value]) to the sensor.
360    fn write_reg(&mut self, data: &[u8]) -> error::Result<(), E> {
361        self.i2c
362            .write(self.address, data)
363            .map_err(|e| error::Bme680Error::I2CError(e))?;
364        Ok(())
365    }
366}
367
368impl<I2C, E> Bme680<I2C, Uninitialized>
369where
370    I2C: i2c::I2c<Error = E>,
371{
372    /// Initializes the sensor: performs a soft-reset and loads factory calibration data.
373    ///
374    /// This transitions the driver state from `Uninitialized` to `Ready`.
375    pub fn init(mut self, delay: &mut impl DelayNs) -> error::Result<Bme680<I2C, Ready>, E> {
376        delay.delay_ms(2);
377
378        self.reset(delay)?;
379
380        let calib_data = self.get_calib_data()?;
381
382        Ok(Bme680 {
383            i2c: self.i2c,
384            address: self.address,
385            calib_data: calib_data,
386            current_wait_time: Milliseconds(0),
387            _state: PhantomData,
388        })
389    }
390
391    /// Reads factory-fused calibration coefficients from the sensor's ROM.
392    fn get_calib_data(&mut self) -> error::Result<CalibData, E> {
393        let mut calib_data = CalibData::default();
394        let mut buffer = [0u8; calib_mem::TOTAL_SIZE];
395
396        // Calibration data is stored in two non-contiguous memory blocks
397        self.read_into(calib_mem::ADDR[0], &mut buffer[0..calib_mem::SIZES[0]])?;
398        self.read_into(calib_mem::ADDR[1], &mut buffer[calib_mem::SIZES[0]..])?;
399
400        // Mapping raw buffer bytes to compensation parameters (Bosch proprietary logic)
401        calib_data.par_t1 = ((buffer[33] as i32) | ((buffer[34] as i32) << 8)) as u16;
402        calib_data.par_t2 = ((buffer[1] as i32) | ((buffer[2] as i32) << 8)) as i16;
403        calib_data.par_t3 = buffer[3] as i8;
404        calib_data.par_p1 = ((buffer[5] as i32) | ((buffer[6] as i32) << 8)) as u16;
405        calib_data.par_p2 = ((buffer[7] as i32) | ((buffer[8] as i32) << 8)) as i16;
406        calib_data.par_p3 = buffer[9] as i8;
407        calib_data.par_p4 = ((buffer[11] as i32) | ((buffer[12] as i32) << 8)) as i16;
408        calib_data.par_p5 = ((buffer[14] as i32) | ((buffer[13] as i32) << 8)) as i16;
409        calib_data.par_p6 = buffer[16] as i8;
410        calib_data.par_p7 = buffer[15] as i8;
411        calib_data.par_p8 = ((buffer[19] as i32) | ((buffer[20] as i32) << 8)) as i16;
412        calib_data.par_p9 = ((buffer[21] as i32) | ((buffer[22] as i32) << 8)) as i16;
413        calib_data.par_p10 = buffer[23];
414        calib_data.par_h1 = (((buffer[26] & 0x0F) as i32) | ((buffer[27] as i32) << 4)) as u16;
415        calib_data.par_h2 = (((buffer[26] >> 4) as i32) | ((buffer[25] as i32) << 4)) as u16;
416        calib_data.par_h3 = buffer[28] as i8;
417        calib_data.par_h4 = buffer[29] as i8;
418        calib_data.par_h5 = buffer[30] as i8;
419        calib_data.par_h6 = buffer[31];
420        calib_data.par_h7 = buffer[32] as i8;
421        calib_data.par_g1 = buffer[37] as i8;
422        calib_data.par_g2 = ((buffer[35] as i32) | ((buffer[36] as i32) << 8)) as i16;
423        calib_data.par_g3 = buffer[38] as i8;
424
425        // Additional heater-specific calibration values
426        calib_data.res_heat_val = self.read_reg_byte(0x00)?;
427        calib_data.res_heat_range = (self.read_reg_byte(0x02)? >> 4) & 0x03;
428        calib_data.range_sw_err = (self.read_reg_byte(0x04)? as i8) >> 4;
429
430        Ok(calib_data)
431    }
432}
433
434impl<I2C, E> Bme680<I2C, Ready>
435where
436    I2C: i2c::I2c<Error = E>,
437{
438    /// Applies a full sensor configuration.
439    pub fn configure_sensor(&mut self, config: &mut Config) -> error::Result<(), E> {
440        self.config_oversampling(&config.osrs_config)?;
441        self.config_iir_filter(config.iir_filter)?;
442        self.enable_gas_measurement()?;
443        self.select_gas_profile(&config.gas_profile)?;
444        self.set_gas_heater_profile(config.gas_profile, config.ambient_temp)?;
445
446        Ok(())
447    }
448
449    /// Configures heating duration and target temperature for a gas profile.
450    pub fn set_gas_heater_profile(
451        &mut self,
452        config: GasProfile,
453        amb_temp: Celsius,
454    ) -> error::Result<(), E> {
455        self.config_heater_on_time(config.wait_time, config.index)?;
456        self.config_target_resistance(config.target_temp, amb_temp, config.index)?;
457
458        Ok(())
459    }
460
461    /// Triggers a measurement in 'Forced Mode', waits for completion, and returns compensated data.
462    pub fn read_new_data(&mut self, delay: &mut impl DelayNs) -> error::Result<Measurement, E> {
463        self.activate_forced_mode()?;
464
465        // Wait for the specified heater time before attempting to read raw data
466        delay.delay_ms(self.current_wait_time.0);
467
468        let raw_data = self.get_raw_data(delay)?;
469
470        // Apply mathematical compensation to raw values
471        let temp = self.calc_temp(raw_data.temp_adc);
472        let hum = self.calc_hum(temp.temp_comp, raw_data.hum_adc);
473        let pres = self.calc_pres(temp.temp_fine, raw_data.press_adc);
474
475        if !raw_data.gas_valid_r {
476            return Err(error::Bme680Error::GasDataNotReady);
477        } else if !raw_data.heat_stab_r {
478            return Err(error::Bme680Error::HeaterNotStable);
479        }
480
481        let gas = self.calc_gas(raw_data.gas_adc, raw_data.gas_range);
482
483        Ok(Measurement {
484            temp: Temperature(temp.temp_comp),
485            hum: Humidity(hum),
486            pres: Pressure(pres),
487            gas: Gas(gas),
488        })
489    }
490
491    /// Reads the Chip ID from the sensor (expected value: 0x61).
492    pub fn read_chip_id(&mut self) -> error::Result<u8, E> {
493        Ok(self.read_reg_byte(0xD0)?)
494    }
495
496    /// Polls the sensor until new data is available and reads all ADC values.
497    fn get_raw_data(&mut self, delay: &mut impl DelayNs) -> error::Result<RawData, E> {
498        let mut new_data = false;
499        let mut timeout_us = 5000; // 5ms Timeout
500
501        while !new_data {
502            if timeout_us <= 0 {
503                return Err(error::Bme680Error::Timeout);
504            }
505            // Check bit 7 in register 0x1D (new_data_0)
506            new_data = (self.read_reg_byte(0x1D)? >> 7) != 0;
507
508            delay.delay_us(500);
509            timeout_us -= 500;
510        }
511
512        let mut buffer = [0u8; raw_data_mem::SIZE + 1];
513
514        self.i2c
515            .write_read(self.address, &[raw_data_mem::ADDR], &mut buffer)
516            .map_err(|e| error::Bme680Error::I2CError(e))?;
517
518        // Reconstruct 20-bit and 16-bit ADC values from register bytes
519        let press_adc =
520            ((buffer[2] as u32) >> 4) | ((buffer[1] as u32) << 4) | ((buffer[0] as u32) << 12);
521        let temp_adc =
522            ((buffer[5] as u32) >> 4) | ((buffer[4] as u32) << 4) | ((buffer[3] as u32) << 12);
523        let hum_adc = ((buffer[7] as u32) | ((buffer[6] as u32) << 8)) as u16;
524        let gas_adc = (((buffer[12] as u32) >> 6) | ((buffer[11] as u32) << 2)) as u16;
525        let gas_range = buffer[12] & 0x0F;
526
527        let gas_valid_r = ((buffer[12] >> 5) & 0x1) != 0;
528        let heat_stab_r = ((buffer[12] >> 4) & 0x1) != 0;
529
530        Ok(RawData {
531            temp_adc,
532            hum_adc,
533            press_adc,
534            gas_adc,
535            gas_range,
536            gas_valid_r,
537            heat_stab_r,
538        })
539    }
540
541    /// Sets oversampling rates for Humidity, Temperature, and Pressure.
542    fn config_oversampling(&mut self, osrs_config: &OversamplingConfig) -> error::Result<(), E> {
543        // Humidity configuration (Register 0x72)
544        let register = self.read_reg_byte(0x72)?;
545        let mut new_reg_val = (register & 0xF8) | osrs_config.hum_osrs as u8;
546        self.write_reg(&[0x72, new_reg_val])?;
547
548        // Temperature & Pressure configuration (Register 0x74)
549        let register = self.read_reg_byte(0x74)?;
550        let temp_pres_combined =
551            ((osrs_config.temp_osrs as u8) << 0x5) | ((osrs_config.pres_osrs as u8) << 0x2);
552        new_reg_val = (register & 0x03) | temp_pres_combined;
553        self.write_reg(&[0x74, new_reg_val])?;
554
555        Ok(())
556    }
557
558    /// Configures the IIR filter coefficient.
559    fn config_iir_filter(&mut self, iir_filter: IIRFilter) -> error::Result<(), E> {
560        let register = self.read_reg_byte(0x75)?;
561        let new_reg_val = (register & 0xE3) | ((iir_filter as u8) << 0x2);
562        self.write_reg(&[0x75, new_reg_val])?;
563        Ok(())
564    }
565
566    /// Enables the gas sensing functionality in the sensor.
567    fn enable_gas_measurement(&mut self) -> error::Result<(), E> {
568        let register = self.read_reg_byte(0x71)?;
569        self.write_reg(&[0x71, (register & 0xEF) | (0b1 << 0x4)])?;
570        Ok(())
571    }
572
573    /// Selects one of the 10 available gas heater profiles.
574    pub fn select_gas_profile(&mut self, profile: &GasProfile) -> error::Result<(), E> {
575        self.current_wait_time = profile.wait_time;
576        let register = self.read_reg_byte(0x71)?;
577        self.write_reg(&[0x71, (register & 0xF0) | (profile.index as u8)])?;
578        Ok(())
579    }
580
581    /// Activates 'Forced Mode' to trigger a single measurement cycle.
582    fn activate_forced_mode(&mut self) -> error::Result<(), E> {
583        let register = self.read_reg_byte(0x74)?;
584        self.write_reg(&[0x74, (register & 0xFC) | 0b01])?;
585        Ok(())
586    }
587}