aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 7b6f3e71a2f6b7b38819592634c55c3564441e7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
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<DeviceAddr> for u8 {
    fn from(addr: DeviceAddr) -> Self {
        match addr {
            DeviceAddr::Primary => 0x76,
            DeviceAddr::Secondary => 0x77,
        }
    }
}

impl From<DeviceAddr> 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, D> {
    i2c: I2C,
    delay: D,
    device_addr: u8,
    calib: Bme690Calib,
}

/// Async BME690 driver
pub struct AsyncBme690<I2C, D> {
    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<I2C, D> Bme690<I2C, D>
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<u8>) -> Result<Self, I2C::Error> {
        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<I2C, D> AsyncBme690<I2C, D>
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<u8>) -> Result<Self, I2C::Error> {
        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
    }
}