1pub const I2C_ADDRESS_PRIMARY: u8 = 0x76;
16pub const I2C_ADDRESS_SECONDARY: u8 = 0x77;
18pub const CHIP_ID: u8 = 0x60;
20
21pub mod register {
23 pub const CHIP_ID: u8 = 0xD0;
25 pub const RESET: u8 = 0xE0;
27 pub const CALIB_TEMP_PRESS: u8 = 0x88;
29 pub const CALIB_HUMIDITY: u8 = 0xE1;
31 pub const DATA: u8 = 0xF7;
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct Calibration {
42 dig_t1: u16,
43 dig_t2: i16,
44 dig_t3: i16,
45 dig_p1: u16,
46 dig_p2: i16,
47 dig_p3: i16,
48 dig_p4: i16,
49 dig_p5: i16,
50 dig_p6: i16,
51 dig_p7: i16,
52 dig_p8: i16,
53 dig_p9: i16,
54 dig_h1: u8,
55 dig_h2: i16,
56 dig_h3: u8,
57 dig_h4: i16,
58 dig_h5: i16,
59 dig_h6: i8,
60}
61
62impl Calibration {
63 pub fn from_registers(temp_press: &[u8; 26], humidity: &[u8; 7]) -> Calibration {
77 let tp = temp_press;
78 let h = humidity;
79 Calibration {
80 dig_t1: u16::from_le_bytes([tp[0], tp[1]]),
81 dig_t2: i16::from_le_bytes([tp[2], tp[3]]),
82 dig_t3: i16::from_le_bytes([tp[4], tp[5]]),
83 dig_p1: u16::from_le_bytes([tp[6], tp[7]]),
84 dig_p2: i16::from_le_bytes([tp[8], tp[9]]),
85 dig_p3: i16::from_le_bytes([tp[10], tp[11]]),
86 dig_p4: i16::from_le_bytes([tp[12], tp[13]]),
87 dig_p5: i16::from_le_bytes([tp[14], tp[15]]),
88 dig_p6: i16::from_le_bytes([tp[16], tp[17]]),
89 dig_p7: i16::from_le_bytes([tp[18], tp[19]]),
90 dig_p8: i16::from_le_bytes([tp[20], tp[21]]),
91 dig_p9: i16::from_le_bytes([tp[22], tp[23]]),
92 dig_h1: tp[25],
93 dig_h2: i16::from_le_bytes([h[0], h[1]]),
94 dig_h3: h[2],
95 dig_h4: ((h[3] as i8 as i16) * 16) | (h[4] & 0x0F) as i16,
96 dig_h5: ((h[5] as i8 as i16) * 16) | (h[4] >> 4) as i16,
97 dig_h6: h[6] as i8,
98 }
99 }
100
101 pub fn compensate(&self, raw: &RawMeasurement) -> Measurement {
114 let t_fine = self.t_fine(raw.temperature);
115 Measurement {
116 temperature_centi_celsius: compensate_temperature(t_fine),
117 pressure_centi_pascals: self.compensate_pressure(raw.pressure, t_fine),
118 humidity_q22_10: self.compensate_humidity(raw.humidity, t_fine),
119 }
120 }
121
122 fn t_fine(&self, adc_t: i32) -> i32 {
124 let var1 = ((adc_t / 8) - (self.dig_t1 as i32 * 2)) * self.dig_t2 as i32 / 2048;
125 let near = (adc_t / 16) - self.dig_t1 as i32;
126 let var2 = (((near * near) / 4096) * self.dig_t3 as i32) / 16384;
127 var1 + var2
128 }
129
130 fn compensate_pressure(&self, adc_p: i32, t_fine: i32) -> u32 {
132 let mut var1 = t_fine as i64 - 128000;
133 let mut var2 = var1 * var1 * self.dig_p6 as i64;
134 var2 += (var1 * self.dig_p5 as i64) * 131072;
135 var2 += self.dig_p4 as i64 * 34359738368;
136 var1 = (var1 * var1 * self.dig_p3 as i64) / 256 + (var1 * self.dig_p2 as i64 * 4096);
137 var1 = (140737488355328 + var1) * self.dig_p1 as i64 / 8589934592;
138 if var1 == 0 {
139 return 3_000_000;
140 }
141 let mut var4 = 1048576 - adc_p as i64;
142 var4 = (((var4 * 2147483648) - var2) * 3125) / var1;
143 var1 = (self.dig_p9 as i64 * (var4 / 8192) * (var4 / 8192)) / 33554432;
144 var2 = (self.dig_p8 as i64 * var4) / 524288;
145 var4 = ((var4 + var1 + var2) / 256) + (self.dig_p7 as i64 * 16);
146 let pressure = ((var4 / 2) * 100) / 128;
147 pressure.clamp(3_000_000, 11_000_000) as u32
148 }
149
150 fn compensate_humidity(&self, adc_h: i32, t_fine: i32) -> u32 {
152 let var1 = t_fine - 76800;
153 let var2 = adc_h * 16384;
154 let var3 = self.dig_h4 as i32 * 1048576;
155 let var4 = self.dig_h5 as i32 * var1;
156 let var5 = (((var2 - var3) - var4) + 16384) / 32768;
157 let var2 = (var1 * self.dig_h6 as i32) / 1024;
158 let var3 = (var1 * self.dig_h3 as i32) / 2048;
159 let var4 = ((var2 * (var3 + 32768)) / 1024) + 2097152;
160 let var2 = ((var4 * self.dig_h2 as i32) + 8192) / 16384;
161 let var3 = var5 * var2;
162 let var4 = ((var3 / 32768) * (var3 / 32768)) / 128;
163 let var5 = (var3 - ((var4 * self.dig_h1 as i32) / 16)).clamp(0, 419430400);
164 ((var5 / 4096) as u32).min(102400)
165 }
166}
167
168fn compensate_temperature(t_fine: i32) -> i32 {
170 ((t_fine * 5 + 128) / 256).clamp(-4000, 8500)
171}
172
173#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub struct RawMeasurement {
176 pub temperature: i32,
178 pub pressure: i32,
180 pub humidity: i32,
182}
183
184impl RawMeasurement {
185 pub fn from_registers(data: &[u8; 8]) -> RawMeasurement {
198 let pressure =
199 (i32::from(data[0]) << 12) | (i32::from(data[1]) << 4) | (i32::from(data[2]) >> 4);
200 let temperature =
201 (i32::from(data[3]) << 12) | (i32::from(data[4]) << 4) | (i32::from(data[5]) >> 4);
202 let humidity = (i32::from(data[6]) << 8) | i32::from(data[7]);
203 RawMeasurement {
204 temperature,
205 pressure,
206 humidity,
207 }
208 }
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub struct Measurement {
217 pub temperature_centi_celsius: i32,
219 pub pressure_centi_pascals: u32,
221 pub humidity_q22_10: u32,
223}
224
225impl Measurement {
226 pub fn celsius(&self) -> f32 {
228 self.temperature_centi_celsius as f32 / 100.0
229 }
230
231 pub fn pascals(&self) -> u32 {
233 self.pressure_centi_pascals / 100
234 }
235
236 pub fn hectopascals(&self) -> f32 {
238 self.pressure_centi_pascals as f32 / 10_000.0
239 }
240
241 pub fn relative_humidity_percent(&self) -> f32 {
243 self.humidity_q22_10 as f32 / 1024.0
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 fn sample_calibration() -> Calibration {
254 Calibration {
255 dig_t1: 28485,
256 dig_t2: 26735,
257 dig_t3: 50,
258 dig_p1: 37190,
259 dig_p2: -10646,
260 dig_p3: 3024,
261 dig_p4: 7758,
262 dig_p5: -120,
263 dig_p6: -7,
264 dig_p7: 9900,
265 dig_p8: -10230,
266 dig_p9: 4285,
267 dig_h1: 75,
268 dig_h2: 354,
269 dig_h3: 0,
270 dig_h4: 339,
271 dig_h5: 50,
272 dig_h6: 30,
273 }
274 }
275
276 #[test]
277 fn temperature_matches_the_datasheet_worked_example() {
278 let calib = Calibration {
284 dig_t1: 27504,
285 dig_t2: 26435,
286 dig_t3: -1000,
287 ..sample_calibration()
288 };
289 let t_fine = calib.t_fine(519888);
290 assert!((t_fine - 128422).abs() <= 1, "t_fine {t_fine}");
291 assert_eq!(compensate_temperature(t_fine), 2508);
292 }
293
294 #[test]
295 fn integer_compensation_tracks_the_floating_point_reference() {
296 let calib = sample_calibration();
297 for &adc_t in &[400_000, 500_000, 519_888, 540_000] {
300 let t_fine = calib.t_fine(adc_t);
301 let t_int = compensate_temperature(t_fine);
302 let t_ref = reference_temperature(&calib, adc_t);
303 assert!(
304 ((t_int as f64) / 100.0 - t_ref).abs() < 0.02,
305 "temperature {t_int} vs {t_ref}"
306 );
307
308 for &adc_p in &[300_000, 415_148, 512_000] {
309 let p_int = calib.compensate_pressure(adc_p, t_fine);
310 let p_ref = reference_pressure(&calib, adc_p, adc_t);
311 assert!(
312 ((p_int as f64) / 100.0 - p_ref).abs() < 2.0,
313 "pressure {p_int} (centi-Pa) vs {p_ref} Pa"
314 );
315 }
316
317 for &adc_h in &[20_000, 30_000, 45_000] {
318 let h_int = calib.compensate_humidity(adc_h, t_fine);
319 let h_ref = reference_humidity(&calib, adc_h, adc_t);
320 assert!(
321 ((h_int as f64) / 1024.0 - h_ref).abs() < 0.05,
322 "humidity {h_int} (Q22.10) vs {h_ref} %"
323 );
324 }
325 }
326 }
327
328 #[test]
329 fn raw_measurement_unpacks_the_data_registers() {
330 let data = [0x53, 0xD0, 0xE0, 0x81, 0xD9, 0x00, 0x6E, 0x62];
332 let raw = RawMeasurement::from_registers(&data);
333 assert_eq!(raw.pressure, 0x5_3D0E);
334 assert_eq!(raw.temperature, 0x8_1D90);
335 assert_eq!(raw.humidity, 0x6E62);
336 }
337
338 #[test]
339 fn measurement_unit_helpers_convert_correctly() {
340 let measurement = Measurement {
341 temperature_centi_celsius: 2508,
342 pressure_centi_pascals: 10_065_300,
343 humidity_q22_10: 47_104,
344 };
345 assert!((measurement.celsius() - 25.08).abs() < 0.001);
346 assert_eq!(measurement.pascals(), 100_653);
347 assert!((measurement.hectopascals() - 1006.53).abs() < 0.01);
348 assert!((measurement.relative_humidity_percent() - 46.0).abs() < 0.001);
349 }
350
351 fn reference_temperature(c: &Calibration, adc_t: i32) -> f64 {
354 let var1 = (adc_t as f64 / 16384.0 - c.dig_t1 as f64 / 1024.0) * c.dig_t2 as f64;
355 let var2 = {
356 let v = adc_t as f64 / 131072.0 - c.dig_t1 as f64 / 8192.0;
357 v * v * c.dig_t3 as f64
358 };
359 ((var1 + var2) / 5120.0).clamp(-40.0, 85.0)
360 }
361
362 fn reference_t_fine(c: &Calibration, adc_t: i32) -> f64 {
363 let var1 = (adc_t as f64 / 16384.0 - c.dig_t1 as f64 / 1024.0) * c.dig_t2 as f64;
364 let var2 = {
365 let v = adc_t as f64 / 131072.0 - c.dig_t1 as f64 / 8192.0;
366 v * v * c.dig_t3 as f64
367 };
368 var1 + var2
369 }
370
371 fn reference_pressure(c: &Calibration, adc_p: i32, adc_t: i32) -> f64 {
372 let t_fine = reference_t_fine(c, adc_t);
373 let mut var1 = (t_fine / 2.0) - 64000.0;
374 let mut var2 = var1 * var1 * c.dig_p6 as f64 / 32768.0;
375 var2 += var1 * c.dig_p5 as f64 * 2.0;
376 var2 = (var2 / 4.0) + (c.dig_p4 as f64 * 65536.0);
377 let var3 = c.dig_p3 as f64 * var1 * var1 / 524288.0;
378 var1 = (var3 + c.dig_p2 as f64 * var1) / 524288.0;
379 var1 = (1.0 + var1 / 32768.0) * c.dig_p1 as f64;
380 if var1 <= 0.0 {
381 return 30000.0;
382 }
383 let mut pressure = 1048576.0 - adc_p as f64;
384 pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1;
385 var1 = c.dig_p9 as f64 * pressure * pressure / 2147483648.0;
386 var2 = pressure * c.dig_p8 as f64 / 32768.0;
387 pressure += (var1 + var2 + c.dig_p7 as f64) / 16.0;
388 pressure.clamp(30000.0, 110000.0)
389 }
390
391 fn reference_humidity(c: &Calibration, adc_h: i32, adc_t: i32) -> f64 {
392 let t_fine = reference_t_fine(c, adc_t);
393 let var1 = t_fine - 76800.0;
394 let var2 = c.dig_h4 as f64 * 64.0 + (c.dig_h5 as f64 / 16384.0) * var1;
395 let var3 = adc_h as f64 - var2;
396 let var4 = c.dig_h2 as f64 / 65536.0;
397 let var5 = 1.0 + (c.dig_h3 as f64 / 67108864.0) * var1;
398 let var6 = 1.0 + (c.dig_h6 as f64 / 67108864.0) * var1 * var5;
399 let var6 = var3 * var4 * (var5 * var6);
400 (var6 * (1.0 - c.dig_h1 as f64 * var6 / 524288.0)).clamp(0.0, 100.0)
401 }
402}