From bbe6a9302ce52bba5e4db96562b9abea11ef08b0 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Tue, 16 Dec 2025 22:11:04 +0000 Subject: init --- src/lib.rs | 435 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 src/lib.rs (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7b6f3e7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,435 @@ +//! 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 + } +} -- cgit