aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs435
1 files changed, 435 insertions, 0 deletions
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 @@
1//! BME690 Environmental Sensor Driver
2//!
3//! This is an AI-generated port of the official Bosch BME690 C driver to Rust.
4//! Original C driver: https://github.com/boschsensortec/BME690_SensorAPI
5//!
6//! The BME690 is a digital environmental sensor that measures:
7//! - Temperature (°C)
8//! - Pressure (Pa)
9//! - Humidity (%)
10//!
11//! This driver provides both blocking and async implementations using embedded-hal traits.
12
13#![no_std]
14
15/// I2C device address options
16#[derive(Debug, Clone, Copy)]
17pub enum DeviceAddr {
18 /// Primary address 0x76 (SDO connected to GND)
19 Primary,
20 /// Secondary address 0x77 (SDO connected to VDD)
21 Secondary,
22}
23
24impl From<DeviceAddr> for u8 {
25 fn from(addr: DeviceAddr) -> Self {
26 match addr {
27 DeviceAddr::Primary => 0x76,
28 DeviceAddr::Secondary => 0x77,
29 }
30 }
31}
32
33impl From<DeviceAddr> for u16 {
34 fn from(addr: DeviceAddr) -> Self {
35 match addr {
36 DeviceAddr::Primary => 0x76,
37 DeviceAddr::Secondary => 0x77,
38 }
39 }
40}
41
42/// Temperature calibration coefficients
43struct TempCalib {
44 par_t1: u16,
45 par_t2: u16,
46 par_t3: i8,
47}
48
49/// Pressure calibration coefficients
50struct PressCalib {
51 par_p1: i16,
52 par_p2: u16,
53 par_p3: i8,
54 par_p4: i8,
55 par_p5: i16,
56 par_p6: i16,
57 par_p7: i8,
58 par_p8: i8,
59 par_p9: i16,
60 par_p10: i8,
61 par_p11: i8,
62}
63
64/// Humidity calibration coefficients
65struct HumCalib {
66 par_h1: i16,
67 par_h2: i8,
68 par_h3: u8,
69 par_h4: i8,
70 par_h5: i16,
71 par_h6: u8,
72}
73
74/// Sensor measurement data
75#[derive(Debug, Clone, Copy)]
76pub struct Measurement {
77 /// Temperature in degrees Celsius
78 pub temperature: f32,
79 /// Pressure in Pascals
80 pub pressure: f32,
81 /// Relative humidity in percent (0-100%)
82 pub humidity: f32,
83}
84
85type FieldData = [u8; 17]; // BME69X_LEN_FIELD from C driver
86type CoeffData1 = [u8; 23]; // Coefficients from register 0x8a
87type CoeffData2 = [u8; 14]; // Coefficients from register 0xe1
88type CoeffData3 = [u8; 5]; // Coefficients from register 0x00
89type CoeffDataAll = [u8; 42]; // Combined coefficient data
90
91/// Combined calibration data for all sensors
92struct Bme690Calib {
93 temp: TempCalib,
94 press: PressCalib,
95 hum: HumCalib,
96}
97
98/// Blocking BME690 driver
99pub struct Bme690<I2C, D> {
100 i2c: I2C,
101 delay: D,
102 device_addr: u8,
103 calib: Bme690Calib,
104}
105
106/// Async BME690 driver
107pub struct AsyncBme690<I2C, D> {
108 i2c: I2C,
109 delay: D,
110 device_addr: u8,
111 calib: Bme690Calib,
112}
113
114/// Extract calibration coefficients from raw register data
115fn extract_calibration(coeff: &CoeffDataAll) -> Bme690Calib {
116 // Extract temperature calibration (indices from bme69x_defs.h)
117 let par_t1 = u16::from_be_bytes([coeff[32], coeff[31]]); // IDX_DO_C_MSB=32, LSB=31
118 let par_t2 = u16::from_be_bytes([coeff[1], coeff[0]]); // IDX_DTK1_C_MSB=1, LSB=0
119 let par_t3 = coeff[2] as i8; // IDX_DTK2_C=2
120
121 // Extract pressure calibration
122 let par_p1 = i16::from_be_bytes([coeff[11], coeff[10]]); // IDX_O_C_MSB=11, LSB=10
123 let par_p2 = u16::from_be_bytes([coeff[13], coeff[12]]); // IDX_TK10_C_MSB=13, LSB=12
124 let par_p3 = coeff[14] as i8; // IDX_TK20_C=14
125 let par_p4 = coeff[15] as i8; // IDX_TK30_C=15
126 let par_p5 = i16::from_be_bytes([coeff[5], coeff[4]]); // IDX_S_C_MSB=5, LSB=4
127 let par_p6 = i16::from_be_bytes([coeff[7], coeff[6]]); // IDX_TK1S_C_MSB=7, LSB=6
128 let par_p7 = coeff[8] as i8; // IDX_TK2S_C=8
129 let par_p8 = coeff[9] as i8; // IDX_TK3S_C=9
130 let par_p9 = i16::from_be_bytes([coeff[19], coeff[18]]); // IDX_NLS_C_MSB=19, LSB=18
131 let par_p10 = coeff[20] as i8; // IDX_TKNLS_C=20
132 let par_p11 = coeff[21] as i8; // IDX_NLS3_C=21
133
134 // Extract humidity calibration (with sign conversion from C driver)
135 let mut par_h5 = ((coeff[23] as i16) << 4) | ((coeff[24] as i16) >> 4); // IDX_S_H_MSB=23, LSB=24
136 if par_h5 > 2047 {
137 par_h5 -= 4096;
138 }
139
140 let mut par_h1 = ((coeff[25] as i16) << 4) | ((coeff[24] as i16) & 0x0F); // IDX_O_H_MSB=25, LSB=24
141 if par_h1 > 2047 {
142 par_h1 -= 4096;
143 }
144
145 let par_h2 = coeff[26] as i8; // IDX_TK10H_C=26
146 let par_h3 = coeff[28]; // IDX_par_h3=28
147 let par_h4 = coeff[27] as i8; // IDX_par_h4=27
148 let par_h6 = coeff[29]; // IDX_HLIN2_C=29
149
150 Bme690Calib {
151 temp: TempCalib { par_t1, par_t2, par_t3 },
152 press: PressCalib {
153 par_p1, par_p2, par_p3, par_p4, par_p5,
154 par_p6, par_p7, par_p8, par_p9, par_p10, par_p11,
155 },
156 hum: HumCalib { par_h1, par_h2, par_h3, par_h4, par_h5, par_h6 },
157 }
158}
159
160/// Parse raw field data into calibrated measurements
161fn parse_field_data(field_data: &FieldData, calib: &Bme690Calib) -> Measurement {
162 // Extract pressure (bytes 2-4, registers 0x1F-0x21) - matches C driver format
163 let pres_adc = ((field_data[2] as u32) << 16)
164 | ((field_data[3] as u32) << 8)
165 | (field_data[4] as u32);
166
167 // Extract temperature (bytes 5-7, registers 0x22-0x24) - matches C driver format
168 let temp_adc = ((field_data[5] as u32) << 16)
169 | ((field_data[6] as u32) << 8)
170 | (field_data[7] as u32);
171
172 // Extract humidity (bytes 8-9, registers 0x25-0x26) - matches C driver format
173 let hum_adc = ((field_data[8] as u16) << 8) | (field_data[9] as u16);
174
175 // Calculate compensated values
176 let temperature = calc_temperature(temp_adc, &calib.temp);
177 let pressure = calc_pressure(pres_adc, temperature, &calib.press);
178 let humidity = calc_humidity(hum_adc, temperature, &calib.hum);
179
180 Measurement {
181 temperature,
182 pressure,
183 humidity,
184 }
185}
186
187/// Calculate calibrated temperature from raw ADC value
188/// Based on Bosch BME690 SDK floating-point implementation
189fn calc_temperature(temp_adc: u32, calib: &TempCalib) -> f32 {
190 let do1 = (calib.par_t1 as i32) << 8;
191 let dtk1 = (calib.par_t2 as f64) / (1u64 << 30) as f64;
192 let dtk2 = (calib.par_t3 as f64) / (1u64 << 48) as f64;
193
194 let cf = temp_adc as i32 - do1;
195 let temp1 = cf as f64 * dtk1;
196 let temp2 = (cf as f64) * (cf as f64) * dtk2;
197
198 (temp1 + temp2) as f32
199}
200
201/// Calculate calibrated pressure from raw ADC value and temperature
202/// Based on Bosch BME690 SDK floating-point implementation
203fn calc_pressure(pres_adc: u32, temp: f32, calib: &PressCalib) -> f32 {
204 let o = (calib.par_p1 as u32) * (1u32 << 3);
205 let tk10 = (calib.par_p2 as f64) / (1u64 << 6) as f64;
206 let tk20 = (calib.par_p3 as f64) / (1u64 << 8) as f64;
207 let tk30 = (calib.par_p4 as f64) / (1u64 << 15) as f64;
208
209 let s = ((calib.par_p5 as f64) - (1u64 << 14) as f64) / (1u64 << 20) as f64;
210 let tk1s = ((calib.par_p6 as f64) - (1u64 << 14) as f64) / (1u64 << 29) as f64;
211 let tk2s = (calib.par_p7 as f64) / (1u64 << 32) as f64;
212 let tk3s = (calib.par_p8 as f64) / (1u64 << 37) as f64;
213
214 let nls = (calib.par_p9 as f64) / (1u64 << 48) as f64;
215 let tknls = (calib.par_p10 as f64) / (1u64 << 48) as f64;
216 // nls3 = par_p11 / 2^65, split into (2^35 * 2^30) to avoid overflow
217 let nls3 = (calib.par_p11 as f64) / ((1u64 << 35) as f64 * (1u64 << 30) as f64);
218
219 let temp = temp as f64;
220 let pres_adc = pres_adc as f64;
221
222 let tmp1 = o as f64 + (tk10 * temp) + (tk20 * temp * temp) + (tk30 * temp * temp * temp);
223 let tmp2 = pres_adc * (s + (tk1s * temp) + (tk2s * temp * temp) + (tk3s * temp * temp * temp));
224 let tmp3 = pres_adc * pres_adc * (nls + (tknls * temp));
225 let tmp4 = pres_adc * pres_adc * pres_adc * nls3;
226
227 (tmp1 + tmp2 + tmp3 + tmp4) as f32
228}
229
230/// Calculate calibrated humidity from raw ADC value and temperature
231/// Based on Bosch BME690 SDK floating-point implementation
232fn calc_humidity(hum_adc: u16, temp: f32, calib: &HumCalib) -> f32 {
233 let temp_comp = (temp as f64 * 5120.0) - 76800.0;
234
235 let oh = (calib.par_h1 as f64) * (1u64 << 6) as f64;
236 let sh = (calib.par_h5 as f64) / (1u64 << 16) as f64;
237 let tk10h = (calib.par_h2 as f64) / (1u64 << 14) as f64;
238 let tk1sh = (calib.par_h4 as f64) / (1u64 << 26) as f64;
239 let tk2sh = (calib.par_h3 as f64) / (1u64 << 26) as f64;
240 let hlin2 = (calib.par_h6 as f64) / (1u64 << 19) as f64;
241
242 let hoff = (hum_adc as f64) - (oh + tk10h * temp_comp);
243 let hsens = hoff * sh * (1.0 + (tk1sh * temp_comp) + (tk1sh * tk2sh * temp_comp * temp_comp));
244 let hum_float_val = hsens * (1.0 - hlin2 * hsens);
245
246 // Clamp to 0-100 range
247 hum_float_val.max(0.0).min(100.0) as f32
248}
249
250// Blocking implementation
251impl<I2C, D> Bme690<I2C, D>
252where
253 I2C: embedded_hal::i2c::I2c,
254 D: embedded_hal::delay::DelayNs,
255{
256 /// Create a new BME690 sensor instance
257 ///
258 /// # Arguments
259 /// * `i2c` - I2C peripheral
260 /// * `delay` - Delay provider
261 /// * `device_addr` - I2C address (Primary = 0x76, Secondary = 0x77)
262 pub fn new(mut i2c: I2C, delay: D, device_addr: impl Into<u8>) -> Result<Self, I2C::Error> {
263 let device_addr = device_addr.into();
264
265 // Read calibration data - BME690 uses registers 0x8a, 0xe1, 0x00
266 let mut coeff1 = CoeffData1::default();
267 let mut coeff2 = CoeffData2::default();
268 let mut coeff3 = CoeffData3::default();
269
270 i2c.write_read(device_addr, &[0x8a], &mut coeff1)?;
271 i2c.write_read(device_addr, &[0xe1], &mut coeff2)?;
272 i2c.write_read(device_addr, &[0x00], &mut coeff3)?;
273
274 // Combine into single array like the C driver does
275 let mut coeff: CoeffDataAll = [0; 42];
276 coeff[0..23].copy_from_slice(&coeff1);
277 coeff[23..37].copy_from_slice(&coeff2);
278 coeff[37..42].copy_from_slice(&coeff3);
279
280 let calib = extract_calibration(&coeff);
281
282 // Configure sensor
283 i2c.write(device_addr, &[0x72, 0x01])?; // CTRL_HUM - 1x oversampling
284 i2c.write(device_addr, &[0x74, 0x24])?; // CTRL_MEAS - sleep mode initially
285
286 Ok(Self {
287 i2c,
288 delay,
289 device_addr,
290 calib,
291 })
292 }
293
294 fn trigger_measurement(&mut self) -> Result<(), I2C::Error> {
295 // Trigger forced mode measurement
296 self.i2c.write(self.device_addr, &[0x74, 0x25])
297 }
298
299 fn wait_for_measurement(&mut self) -> Result<(), I2C::Error> {
300 loop {
301 let mut status = [0u8; 1];
302 self.i2c.write_read(self.device_addr, &[0x1D], &mut status)?;
303 if status[0] & (1 << 7) != 0 {
304 break; // New data available
305 }
306 // Wait 10ms before polling again (BME690 typical measurement time)
307 self.delay.delay_ms(10);
308 }
309 Ok(())
310 }
311
312 /// Perform a measurement and return temperature, pressure, and humidity
313 pub fn measure(&mut self) -> Measurement {
314 // Trigger measurement
315 self.trigger_measurement().unwrap();
316 self.wait_for_measurement().unwrap();
317
318 // Read all field data at once
319 let mut field_data = FieldData::default();
320 self.i2c
321 .write_read(self.device_addr, &[0x1D], &mut field_data)
322 .unwrap();
323
324 parse_field_data(&field_data, &self.calib)
325 }
326
327 /// Measure temperature only
328 pub fn measure_temperature(&mut self) -> f32 {
329 self.measure().temperature
330 }
331
332 /// Measure pressure only
333 pub fn measure_pressure(&mut self) -> f32 {
334 self.measure().pressure
335 }
336
337 /// Measure humidity only
338 pub fn measure_humidity(&mut self) -> f32 {
339 self.measure().humidity
340 }
341}
342
343// Async implementation
344impl<I2C, D> AsyncBme690<I2C, D>
345where
346 I2C: embedded_hal_async::i2c::I2c,
347 D: embedded_hal_async::delay::DelayNs,
348{
349 /// Create a new async BME690 sensor instance
350 ///
351 /// # Arguments
352 /// * `i2c` - Async I2C peripheral
353 /// * `delay` - Async delay provider
354 /// * `device_addr` - I2C address (Primary = 0x76, Secondary = 0x77)
355 pub async fn new(mut i2c: I2C, delay: D, device_addr: impl Into<u8>) -> Result<Self, I2C::Error> {
356 let device_addr = device_addr.into();
357
358 // Read calibration data - BME690 uses registers 0x8a, 0xe1, 0x00
359 let mut coeff1 = CoeffData1::default();
360 let mut coeff2 = CoeffData2::default();
361 let mut coeff3 = CoeffData3::default();
362
363 i2c.write_read(device_addr, &[0x8a], &mut coeff1).await?;
364 i2c.write_read(device_addr, &[0xe1], &mut coeff2).await?;
365 i2c.write_read(device_addr, &[0x00], &mut coeff3).await?;
366
367 // Combine into single array like the C driver does
368 let mut coeff: CoeffDataAll = [0; 42];
369 coeff[0..23].copy_from_slice(&coeff1);
370 coeff[23..37].copy_from_slice(&coeff2);
371 coeff[37..42].copy_from_slice(&coeff3);
372
373 let calib = extract_calibration(&coeff);
374
375 // Configure sensor
376 i2c.write(device_addr, &[0x72, 0x01]).await?; // CTRL_HUM - 1x oversampling
377 i2c.write(device_addr, &[0x74, 0x24]).await?; // CTRL_MEAS - sleep mode initially
378
379 Ok(Self {
380 i2c,
381 delay,
382 device_addr,
383 calib,
384 })
385 }
386
387 async fn trigger_measurement(&mut self) -> Result<(), I2C::Error> {
388 // Trigger forced mode measurement
389 self.i2c.write(self.device_addr, &[0x74, 0x25]).await
390 }
391
392 async fn wait_for_measurement(&mut self) -> Result<(), I2C::Error> {
393 loop {
394 let mut status = [0u8; 1];
395 self.i2c.write_read(self.device_addr, &[0x1D], &mut status).await?;
396 if status[0] & (1 << 7) != 0 {
397 break; // New data available
398 }
399 // Wait 10ms before polling again (BME690 typical measurement time)
400 self.delay.delay_ms(10).await;
401 }
402 Ok(())
403 }
404
405 /// Perform an async measurement and return temperature, pressure, and humidity
406 pub async fn measure(&mut self) -> Measurement {
407 // Trigger measurement
408 self.trigger_measurement().await.unwrap();
409 self.wait_for_measurement().await.unwrap();
410
411 // Read all field data at once
412 let mut field_data = FieldData::default();
413 self.i2c
414 .write_read(self.device_addr, &[0x1D], &mut field_data)
415 .await
416 .unwrap();
417
418 parse_field_data(&field_data, &self.calib)
419 }
420
421 /// Measure temperature only (async)
422 pub async fn measure_temperature(&mut self) -> f32 {
423 self.measure().await.temperature
424 }
425
426 /// Measure pressure only (async)
427 pub async fn measure_pressure(&mut self) -> f32 {
428 self.measure().await.pressure
429 }
430
431 /// Measure humidity only (async)
432 pub async fn measure_humidity(&mut self) -> f32 {
433 self.measure().await.humidity
434 }
435}