//! BME690 Environmental Sensor Driver //! //! This is an AI-generated port of the official Bosch BME690 C driver to Rust. //! Original C driver: https://github.com/boschsensortec/BME690_SensorAPI //! //! The BME690 is a digital environmental sensor that measures: //! - Temperature (°C) //! - Pressure (Pa) //! - Humidity (%) //! //! This driver provides both blocking and async implementations using embedded-hal traits. #![no_std] /// I2C device address options #[derive(Debug, Clone, Copy)] pub enum DeviceAddr { /// Primary address 0x76 (SDO connected to GND) Primary, /// Secondary address 0x77 (SDO connected to VDD) Secondary, } impl From for u8 { fn from(addr: DeviceAddr) -> Self { match addr { DeviceAddr::Primary => 0x76, DeviceAddr::Secondary => 0x77, } } } impl From for u16 { fn from(addr: DeviceAddr) -> Self { match addr { DeviceAddr::Primary => 0x76, DeviceAddr::Secondary => 0x77, } } } /// Temperature calibration coefficients struct TempCalib { par_t1: u16, par_t2: u16, par_t3: i8, } /// Pressure calibration coefficients struct PressCalib { par_p1: i16, par_p2: u16, par_p3: i8, par_p4: i8, par_p5: i16, par_p6: i16, par_p7: i8, par_p8: i8, par_p9: i16, par_p10: i8, par_p11: i8, } /// Humidity calibration coefficients struct HumCalib { par_h1: i16, par_h2: i8, par_h3: u8, par_h4: i8, par_h5: i16, par_h6: u8, } /// Sensor measurement data #[derive(Debug, Clone, Copy)] pub struct Measurement { /// Temperature in degrees Celsius pub temperature: f32, /// Pressure in Pascals pub pressure: f32, /// Relative humidity in percent (0-100%) pub humidity: f32, } type FieldData = [u8; 17]; // BME69X_LEN_FIELD from C driver type CoeffData1 = [u8; 23]; // Coefficients from register 0x8a type CoeffData2 = [u8; 14]; // Coefficients from register 0xe1 type CoeffData3 = [u8; 5]; // Coefficients from register 0x00 type CoeffDataAll = [u8; 42]; // Combined coefficient data /// Combined calibration data for all sensors struct Bme690Calib { temp: TempCalib, press: PressCalib, hum: HumCalib, } /// Blocking BME690 driver pub struct Bme690 { i2c: I2C, delay: D, device_addr: u8, calib: Bme690Calib, } /// Async BME690 driver pub struct AsyncBme690 { i2c: I2C, delay: D, device_addr: u8, calib: Bme690Calib, } /// Extract calibration coefficients from raw register data fn extract_calibration(coeff: &CoeffDataAll) -> Bme690Calib { // Extract temperature calibration (indices from bme69x_defs.h) let par_t1 = u16::from_be_bytes([coeff[32], coeff[31]]); // IDX_DO_C_MSB=32, LSB=31 let par_t2 = u16::from_be_bytes([coeff[1], coeff[0]]); // IDX_DTK1_C_MSB=1, LSB=0 let par_t3 = coeff[2] as i8; // IDX_DTK2_C=2 // Extract pressure calibration let par_p1 = i16::from_be_bytes([coeff[11], coeff[10]]); // IDX_O_C_MSB=11, LSB=10 let par_p2 = u16::from_be_bytes([coeff[13], coeff[12]]); // IDX_TK10_C_MSB=13, LSB=12 let par_p3 = coeff[14] as i8; // IDX_TK20_C=14 let par_p4 = coeff[15] as i8; // IDX_TK30_C=15 let par_p5 = i16::from_be_bytes([coeff[5], coeff[4]]); // IDX_S_C_MSB=5, LSB=4 let par_p6 = i16::from_be_bytes([coeff[7], coeff[6]]); // IDX_TK1S_C_MSB=7, LSB=6 let par_p7 = coeff[8] as i8; // IDX_TK2S_C=8 let par_p8 = coeff[9] as i8; // IDX_TK3S_C=9 let par_p9 = i16::from_be_bytes([coeff[19], coeff[18]]); // IDX_NLS_C_MSB=19, LSB=18 let par_p10 = coeff[20] as i8; // IDX_TKNLS_C=20 let par_p11 = coeff[21] as i8; // IDX_NLS3_C=21 // Extract humidity calibration (with sign conversion from C driver) let mut par_h5 = ((coeff[23] as i16) << 4) | ((coeff[24] as i16) >> 4); // IDX_S_H_MSB=23, LSB=24 if par_h5 > 2047 { par_h5 -= 4096; } let mut par_h1 = ((coeff[25] as i16) << 4) | ((coeff[24] as i16) & 0x0F); // IDX_O_H_MSB=25, LSB=24 if par_h1 > 2047 { par_h1 -= 4096; } let par_h2 = coeff[26] as i8; // IDX_TK10H_C=26 let par_h3 = coeff[28]; // IDX_par_h3=28 let par_h4 = coeff[27] as i8; // IDX_par_h4=27 let par_h6 = coeff[29]; // IDX_HLIN2_C=29 Bme690Calib { temp: TempCalib { par_t1, par_t2, par_t3 }, press: PressCalib { par_p1, par_p2, par_p3, par_p4, par_p5, par_p6, par_p7, par_p8, par_p9, par_p10, par_p11, }, hum: HumCalib { par_h1, par_h2, par_h3, par_h4, par_h5, par_h6 }, } } /// Parse raw field data into calibrated measurements fn parse_field_data(field_data: &FieldData, calib: &Bme690Calib) -> Measurement { // Extract pressure (bytes 2-4, registers 0x1F-0x21) - matches C driver format let pres_adc = ((field_data[2] as u32) << 16) | ((field_data[3] as u32) << 8) | (field_data[4] as u32); // Extract temperature (bytes 5-7, registers 0x22-0x24) - matches C driver format let temp_adc = ((field_data[5] as u32) << 16) | ((field_data[6] as u32) << 8) | (field_data[7] as u32); // Extract humidity (bytes 8-9, registers 0x25-0x26) - matches C driver format let hum_adc = ((field_data[8] as u16) << 8) | (field_data[9] as u16); // Calculate compensated values let temperature = calc_temperature(temp_adc, &calib.temp); let pressure = calc_pressure(pres_adc, temperature, &calib.press); let humidity = calc_humidity(hum_adc, temperature, &calib.hum); Measurement { temperature, pressure, humidity, } } /// Calculate calibrated temperature from raw ADC value /// Based on Bosch BME690 SDK floating-point implementation fn calc_temperature(temp_adc: u32, calib: &TempCalib) -> f32 { let do1 = (calib.par_t1 as i32) << 8; let dtk1 = (calib.par_t2 as f64) / (1u64 << 30) as f64; let dtk2 = (calib.par_t3 as f64) / (1u64 << 48) as f64; let cf = temp_adc as i32 - do1; let temp1 = cf as f64 * dtk1; let temp2 = (cf as f64) * (cf as f64) * dtk2; (temp1 + temp2) as f32 } /// Calculate calibrated pressure from raw ADC value and temperature /// Based on Bosch BME690 SDK floating-point implementation fn calc_pressure(pres_adc: u32, temp: f32, calib: &PressCalib) -> f32 { let o = (calib.par_p1 as u32) * (1u32 << 3); let tk10 = (calib.par_p2 as f64) / (1u64 << 6) as f64; let tk20 = (calib.par_p3 as f64) / (1u64 << 8) as f64; let tk30 = (calib.par_p4 as f64) / (1u64 << 15) as f64; let s = ((calib.par_p5 as f64) - (1u64 << 14) as f64) / (1u64 << 20) as f64; let tk1s = ((calib.par_p6 as f64) - (1u64 << 14) as f64) / (1u64 << 29) as f64; let tk2s = (calib.par_p7 as f64) / (1u64 << 32) as f64; let tk3s = (calib.par_p8 as f64) / (1u64 << 37) as f64; let nls = (calib.par_p9 as f64) / (1u64 << 48) as f64; let tknls = (calib.par_p10 as f64) / (1u64 << 48) as f64; // nls3 = par_p11 / 2^65, split into (2^35 * 2^30) to avoid overflow let nls3 = (calib.par_p11 as f64) / ((1u64 << 35) as f64 * (1u64 << 30) as f64); let temp = temp as f64; let pres_adc = pres_adc as f64; let tmp1 = o as f64 + (tk10 * temp) + (tk20 * temp * temp) + (tk30 * temp * temp * temp); let tmp2 = pres_adc * (s + (tk1s * temp) + (tk2s * temp * temp) + (tk3s * temp * temp * temp)); let tmp3 = pres_adc * pres_adc * (nls + (tknls * temp)); let tmp4 = pres_adc * pres_adc * pres_adc * nls3; (tmp1 + tmp2 + tmp3 + tmp4) as f32 } /// Calculate calibrated humidity from raw ADC value and temperature /// Based on Bosch BME690 SDK floating-point implementation fn calc_humidity(hum_adc: u16, temp: f32, calib: &HumCalib) -> f32 { let temp_comp = (temp as f64 * 5120.0) - 76800.0; let oh = (calib.par_h1 as f64) * (1u64 << 6) as f64; let sh = (calib.par_h5 as f64) / (1u64 << 16) as f64; let tk10h = (calib.par_h2 as f64) / (1u64 << 14) as f64; let tk1sh = (calib.par_h4 as f64) / (1u64 << 26) as f64; let tk2sh = (calib.par_h3 as f64) / (1u64 << 26) as f64; let hlin2 = (calib.par_h6 as f64) / (1u64 << 19) as f64; let hoff = (hum_adc as f64) - (oh + tk10h * temp_comp); let hsens = hoff * sh * (1.0 + (tk1sh * temp_comp) + (tk1sh * tk2sh * temp_comp * temp_comp)); let hum_float_val = hsens * (1.0 - hlin2 * hsens); // Clamp to 0-100 range hum_float_val.max(0.0).min(100.0) as f32 } // Blocking implementation impl Bme690 where I2C: embedded_hal::i2c::I2c, D: embedded_hal::delay::DelayNs, { /// Create a new BME690 sensor instance /// /// # Arguments /// * `i2c` - I2C peripheral /// * `delay` - Delay provider /// * `device_addr` - I2C address (Primary = 0x76, Secondary = 0x77) pub fn new(mut i2c: I2C, delay: D, device_addr: impl Into) -> Result { let device_addr = device_addr.into(); // Read calibration data - BME690 uses registers 0x8a, 0xe1, 0x00 let mut coeff1 = CoeffData1::default(); let mut coeff2 = CoeffData2::default(); let mut coeff3 = CoeffData3::default(); i2c.write_read(device_addr, &[0x8a], &mut coeff1)?; i2c.write_read(device_addr, &[0xe1], &mut coeff2)?; i2c.write_read(device_addr, &[0x00], &mut coeff3)?; // Combine into single array like the C driver does let mut coeff: CoeffDataAll = [0; 42]; coeff[0..23].copy_from_slice(&coeff1); coeff[23..37].copy_from_slice(&coeff2); coeff[37..42].copy_from_slice(&coeff3); let calib = extract_calibration(&coeff); // Configure sensor i2c.write(device_addr, &[0x72, 0x01])?; // CTRL_HUM - 1x oversampling i2c.write(device_addr, &[0x74, 0x24])?; // CTRL_MEAS - sleep mode initially Ok(Self { i2c, delay, device_addr, calib, }) } fn trigger_measurement(&mut self) -> Result<(), I2C::Error> { // Trigger forced mode measurement self.i2c.write(self.device_addr, &[0x74, 0x25]) } fn wait_for_measurement(&mut self) -> Result<(), I2C::Error> { loop { let mut status = [0u8; 1]; self.i2c.write_read(self.device_addr, &[0x1D], &mut status)?; if status[0] & (1 << 7) != 0 { break; // New data available } // Wait 10ms before polling again (BME690 typical measurement time) self.delay.delay_ms(10); } Ok(()) } /// Perform a measurement and return temperature, pressure, and humidity pub fn measure(&mut self) -> Measurement { // Trigger measurement self.trigger_measurement().unwrap(); self.wait_for_measurement().unwrap(); // Read all field data at once let mut field_data = FieldData::default(); self.i2c .write_read(self.device_addr, &[0x1D], &mut field_data) .unwrap(); parse_field_data(&field_data, &self.calib) } /// Measure temperature only pub fn measure_temperature(&mut self) -> f32 { self.measure().temperature } /// Measure pressure only pub fn measure_pressure(&mut self) -> f32 { self.measure().pressure } /// Measure humidity only pub fn measure_humidity(&mut self) -> f32 { self.measure().humidity } } // Async implementation impl AsyncBme690 where I2C: embedded_hal_async::i2c::I2c, D: embedded_hal_async::delay::DelayNs, { /// Create a new async BME690 sensor instance /// /// # Arguments /// * `i2c` - Async I2C peripheral /// * `delay` - Async delay provider /// * `device_addr` - I2C address (Primary = 0x76, Secondary = 0x77) pub async fn new(mut i2c: I2C, delay: D, device_addr: impl Into) -> Result { let device_addr = device_addr.into(); // Read calibration data - BME690 uses registers 0x8a, 0xe1, 0x00 let mut coeff1 = CoeffData1::default(); let mut coeff2 = CoeffData2::default(); let mut coeff3 = CoeffData3::default(); i2c.write_read(device_addr, &[0x8a], &mut coeff1).await?; i2c.write_read(device_addr, &[0xe1], &mut coeff2).await?; i2c.write_read(device_addr, &[0x00], &mut coeff3).await?; // Combine into single array like the C driver does let mut coeff: CoeffDataAll = [0; 42]; coeff[0..23].copy_from_slice(&coeff1); coeff[23..37].copy_from_slice(&coeff2); coeff[37..42].copy_from_slice(&coeff3); let calib = extract_calibration(&coeff); // Configure sensor i2c.write(device_addr, &[0x72, 0x01]).await?; // CTRL_HUM - 1x oversampling i2c.write(device_addr, &[0x74, 0x24]).await?; // CTRL_MEAS - sleep mode initially Ok(Self { i2c, delay, device_addr, calib, }) } async fn trigger_measurement(&mut self) -> Result<(), I2C::Error> { // Trigger forced mode measurement self.i2c.write(self.device_addr, &[0x74, 0x25]).await } async fn wait_for_measurement(&mut self) -> Result<(), I2C::Error> { loop { let mut status = [0u8; 1]; self.i2c.write_read(self.device_addr, &[0x1D], &mut status).await?; if status[0] & (1 << 7) != 0 { break; // New data available } // Wait 10ms before polling again (BME690 typical measurement time) self.delay.delay_ms(10).await; } Ok(()) } /// Perform an async measurement and return temperature, pressure, and humidity pub async fn measure(&mut self) -> Measurement { // Trigger measurement self.trigger_measurement().await.unwrap(); self.wait_for_measurement().await.unwrap(); // Read all field data at once let mut field_data = FieldData::default(); self.i2c .write_read(self.device_addr, &[0x1D], &mut field_data) .await .unwrap(); parse_field_data(&field_data, &self.calib) } /// Measure temperature only (async) pub async fn measure_temperature(&mut self) -> f32 { self.measure().await.temperature } /// Measure pressure only (async) pub async fn measure_pressure(&mut self) -> f32 { self.measure().await.pressure } /// Measure humidity only (async) pub async fn measure_humidity(&mut self) -> f32 { self.measure().await.humidity } }