pamoja_sensors/tmp117.rs
1//! Texas Instruments TMP117 high-accuracy digital temperature sensor.
2//!
3//! The TMP117 returns a 16-bit two's-complement temperature at 7.8125 m°C per count,
4//! accurate to ±0.1 °C without calibration, and carries its own alert limits, a
5//! user offset, and a small EEPROM. This module decodes the temperature register,
6//! builds the same-format values the limit and offset registers take, and assembles
7//! or parses the configuration register field by field, following the datasheet's
8//! register map, its 16-bit temperature data table, and its conversion cycle table.
9//!
10//! Temperatures are returned in integer nano- and micro-degrees so the conversion
11//! stays in integer arithmetic; the `f32` form is exact too, since one count is
12//! 1/128 °C.
13
14/// The TMP117 register addresses, written to the pointer register.
15pub mod register {
16 /// Temperature result register, read-only, two's complement at 7.8125 m°C.
17 pub const TEMP_RESULT: u8 = 0x00;
18 /// Configuration register: mode, cycle, averaging, alert setup, and flags.
19 pub const CONFIGURATION: u8 = 0x01;
20 /// High limit register, same format as the temperature result.
21 pub const THIGH_LIMIT: u8 = 0x02;
22 /// Low limit register, same format as the temperature result.
23 pub const TLOW_LIMIT: u8 = 0x03;
24 /// EEPROM unlock register.
25 pub const EEPROM_UL: u8 = 0x04;
26 /// EEPROM1 scratch register, factory-programmed with part of the unique ID.
27 pub const EEPROM1: u8 = 0x05;
28 /// EEPROM2 scratch register.
29 pub const EEPROM2: u8 = 0x06;
30 /// Temperature offset register, added to the result after linearisation.
31 pub const TEMP_OFFSET: u8 = 0x07;
32 /// EEPROM3 scratch register.
33 pub const EEPROM3: u8 = 0x08;
34 /// Device ID register: revision in bits 15:12, device ID in bits 11:0.
35 pub const DEVICE_ID: u8 = 0x0F;
36}
37
38/// The four 7-bit bus addresses, selected by where the ADD0 pin is tied.
39pub mod address {
40 /// ADD0 tied to ground.
41 pub const ADD0_GND: u8 = 0x48;
42 /// ADD0 tied to V+.
43 pub const ADD0_VPLUS: u8 = 0x49;
44 /// ADD0 tied to SDA.
45 pub const ADD0_SDA: u8 = 0x4A;
46 /// ADD0 tied to SCL.
47 pub const ADD0_SCL: u8 = 0x4B;
48}
49
50/// The device ID field a TMP117 reports (bits 11:0 of the Device_ID register).
51pub const DEVICE_ID: u16 = 0x0117;
52
53/// The factory value of the configuration register (0x0220): continuous conversion,
54/// a 1 s cycle, 8 averaged conversions, alert mode, ALERT active low.
55pub const CONFIG_RESET: u16 = 0x0220;
56
57/// The factory value of the high limit register (0x6000, 192 °C).
58pub const HIGH_LIMIT_RESET: u16 = 0x6000;
59
60/// The factory value of the low limit register (0x8000, -256 °C).
61pub const LOW_LIMIT_RESET: u16 = 0x8000;
62
63/// The temperature register value after a reset (0x8000, -256 °C), held until the
64/// first conversion completes.
65pub const TEMP_RESULT_RESET: u16 = 0x8000;
66
67/// The command byte that, sent after the general-call address `0x00`, resets every
68/// register to its power-up value.
69pub const GENERAL_CALL_RESET: u8 = 0x06;
70
71/// Writing this to the EEPROM unlock register (bit 15, EUN) makes subsequent writes
72/// to the programmable registers persist in EEPROM.
73pub const EEPROM_UNLOCK: u16 = 0x8000;
74
75/// One temperature count in nanodegrees Celsius: 7.8125 m°C.
76const LSB_NANO_CELSIUS: i64 = 7_812_500;
77
78/// Decodes a temperature, limit, or offset register to nanodegrees Celsius.
79///
80/// One count is 7.8125 m°C, so this conversion is exact for every code.
81///
82/// # Arguments
83///
84/// * `raw` - the signed 16-bit register value.
85///
86/// # Returns
87///
88/// The temperature in nanodegrees Celsius.
89pub fn nano_celsius(raw: i16) -> i64 {
90 raw as i64 * LSB_NANO_CELSIUS
91}
92
93/// Decodes a temperature, limit, or offset register to microdegrees Celsius.
94///
95/// Odd codes end in half a microdegree, which is dropped toward zero: `0x0001` is
96/// `7812` and `0xFFFF` is `-7812`. Use [`nano_celsius`] where that half matters.
97///
98/// # Arguments
99///
100/// * `raw` - the signed 16-bit register value.
101///
102/// # Returns
103///
104/// The temperature in microdegrees Celsius, truncated toward zero.
105pub fn micro_celsius(raw: i16) -> i32 {
106 (raw as i64 * 78_125 / 10) as i32
107}
108
109/// Decodes a temperature, limit, or offset register to degrees Celsius.
110///
111/// One count is exactly 1/128 °C, so the result is exact in `f32`.
112///
113/// # Arguments
114///
115/// * `raw` - the signed 16-bit register value.
116///
117/// # Returns
118///
119/// The temperature in degrees Celsius.
120pub fn celsius(raw: i16) -> f32 {
121 raw as f32 / 128.0
122}
123
124/// Builds the register value nearest a temperature in microdegrees Celsius.
125///
126/// The inverse of [`micro_celsius`], for the limit and offset registers and for
127/// testing a node against what a sensor sends without one attached. Rounds to the
128/// nearest count, halves away from zero, and saturates at the ±256 °C register
129/// range.
130///
131/// # Arguments
132///
133/// * `micro` - the temperature in microdegrees Celsius.
134///
135/// # Returns
136///
137/// The signed 16-bit register value.
138pub fn raw_from_micro_celsius(micro: i32) -> i16 {
139 let scaled = micro as i64 * 4;
140 let half = if micro < 0 { -15_625 } else { 15_625 };
141 saturate((scaled + half) / 31_250)
142}
143
144/// Builds the register value nearest a temperature in degrees Celsius.
145///
146/// The inverse of [`celsius`]. Rounds to the nearest count, halves away from zero,
147/// and saturates at the ±256 °C register range.
148///
149/// # Arguments
150///
151/// * `degrees` - the temperature in degrees Celsius.
152///
153/// # Returns
154///
155/// The signed 16-bit register value.
156pub fn raw_from_celsius(degrees: f32) -> i16 {
157 let counts = degrees * 128.0;
158 if counts.is_nan() {
159 return 0;
160 }
161 let rounded = if counts < 0.0 {
162 -((-counts + 0.5) as i64)
163 } else {
164 (counts + 0.5) as i64
165 };
166 saturate(rounded)
167}
168
169fn saturate(counts: i64) -> i16 {
170 counts.clamp(i16::MIN as i64, i16::MAX as i64) as i16
171}
172
173/// Splits a temperature-format register into the two bytes the bus carries.
174///
175/// The TMP117 sends and receives register bytes most significant byte first.
176///
177/// # Arguments
178///
179/// * `raw` - the signed 16-bit register value.
180///
181/// # Returns
182///
183/// The most significant byte then the least significant byte.
184pub fn temperature_bytes(raw: i16) -> [u8; 2] {
185 raw.to_be_bytes()
186}
187
188/// Joins the two bytes read from a temperature-format register.
189///
190/// # Arguments
191///
192/// * `bytes` - the most significant byte then the least significant byte.
193///
194/// # Returns
195///
196/// The signed 16-bit register value.
197pub fn temperature_from_bytes(bytes: [u8; 2]) -> i16 {
198 i16::from_be_bytes(bytes)
199}
200
201/// Returns the device ID field of a Device_ID register read (bits 11:0).
202///
203/// # Arguments
204///
205/// * `raw` - the Device_ID register.
206///
207/// # Returns
208///
209/// The 12-bit device ID, [`DEVICE_ID`] for a TMP117.
210pub fn device_id(raw: u16) -> u16 {
211 raw & 0x0FFF
212}
213
214/// Returns the revision field of a Device_ID register read (bits 15:12).
215///
216/// # Arguments
217///
218/// * `raw` - the Device_ID register.
219///
220/// # Returns
221///
222/// The 4-bit silicon revision number.
223pub fn revision(raw: u16) -> u8 {
224 (raw >> 12) as u8
225}
226
227/// Returns whether the configuration register's HIGH_Alert flag (bit 15) is set.
228///
229/// In alert mode the flag means the last result was above the high limit and reading
230/// the configuration register clears it; in therm mode it stays set until a result
231/// falls below the low limit.
232///
233/// # Arguments
234///
235/// * `config` - the configuration register.
236///
237/// # Returns
238///
239/// `true` if the high alert flag is set.
240pub fn high_alert(config: u16) -> bool {
241 config & (1 << 15) != 0
242}
243
244/// Returns whether the configuration register's LOW_Alert flag (bit 14) is set.
245///
246/// The flag means the last result was below the low limit; it always reads `0` in
247/// therm mode.
248///
249/// # Arguments
250///
251/// * `config` - the configuration register.
252///
253/// # Returns
254///
255/// `true` if the low alert flag is set.
256pub fn low_alert(config: u16) -> bool {
257 config & (1 << 14) != 0
258}
259
260/// Returns whether the configuration register's Data_Ready flag (bit 13) is set.
261///
262/// The flag is set when a conversion completes and cleared by reading either the
263/// temperature register or the configuration register.
264///
265/// # Arguments
266///
267/// * `config` - the configuration register.
268///
269/// # Returns
270///
271/// `true` if a new temperature result is waiting.
272pub fn data_ready(config: u16) -> bool {
273 config & (1 << 13) != 0
274}
275
276/// Returns whether the configuration register's EEPROM_Busy flag (bit 12) is set.
277///
278/// # Arguments
279///
280/// * `config` - the configuration register.
281///
282/// # Returns
283///
284/// `true` while the EEPROM is programming or loading at power-up.
285pub fn eeprom_busy(config: u16) -> bool {
286 config & (1 << 12) != 0
287}
288
289/// Returns whether the EEPROM unlock register's EEPROM_Busy flag (bit 14) is set.
290///
291/// This mirrors bit 12 of the configuration register.
292///
293/// # Arguments
294///
295/// * `unlock` - the EEPROM_UL register.
296///
297/// # Returns
298///
299/// `true` while the EEPROM is programming or loading at power-up.
300pub fn eeprom_unlock_busy(unlock: u16) -> bool {
301 unlock & (1 << 14) != 0
302}
303
304/// The conversion mode (configuration bits 11:10, MOD).
305#[derive(Clone, Copy, Debug, PartialEq, Eq)]
306pub enum ConversionMode {
307 /// Convert continuously at the configured cycle (the default, code `00`).
308 Continuous,
309 /// Abort any conversion and power down (code `01`).
310 Shutdown,
311 /// Convert once, then power down (code `11`).
312 OneShot,
313}
314
315impl ConversionMode {
316 /// Returns the 2-bit field code for this mode.
317 pub fn code(self) -> u8 {
318 match self {
319 ConversionMode::Continuous => 0b00,
320 ConversionMode::Shutdown => 0b01,
321 ConversionMode::OneShot => 0b11,
322 }
323 }
324
325 /// Builds a mode from a 2-bit field code.
326 ///
327 /// Code `10` is documented as continuous conversion that reads back as `00`, so
328 /// it maps to [`ConversionMode::Continuous`].
329 pub fn from_code(code: u8) -> ConversionMode {
330 match code & 0b11 {
331 0b01 => ConversionMode::Shutdown,
332 0b11 => ConversionMode::OneShot,
333 _ => ConversionMode::Continuous,
334 }
335 }
336}
337
338/// The number of conversions averaged into each result (configuration bits 6:5, AVG).
339#[derive(Clone, Copy, Debug, PartialEq, Eq)]
340pub enum Averaging {
341 /// No averaging: each result is one 15.5 ms conversion.
342 None,
343 /// 8 averaged conversions (the default).
344 X8,
345 /// 32 averaged conversions.
346 X32,
347 /// 64 averaged conversions.
348 X64,
349}
350
351impl Averaging {
352 /// Returns the 2-bit field code for this averaging setting.
353 pub fn code(self) -> u8 {
354 match self {
355 Averaging::None => 0b00,
356 Averaging::X8 => 0b01,
357 Averaging::X32 => 0b10,
358 Averaging::X64 => 0b11,
359 }
360 }
361
362 /// Builds an averaging setting from a 2-bit field code.
363 pub fn from_code(code: u8) -> Averaging {
364 match code & 0b11 {
365 0b00 => Averaging::None,
366 0b01 => Averaging::X8,
367 0b10 => Averaging::X32,
368 _ => Averaging::X64,
369 }
370 }
371
372 /// Returns how many conversions are averaged into a result.
373 pub fn conversions(self) -> u8 {
374 match self {
375 Averaging::None => 1,
376 Averaging::X8 => 8,
377 Averaging::X32 => 32,
378 Averaging::X64 => 64,
379 }
380 }
381
382 /// Returns the active conversion time in microseconds, which is also the shortest
383 /// continuous cycle this averaging allows and the length of a one-shot conversion.
384 pub fn conversion_micros(self) -> u32 {
385 match self {
386 Averaging::None => 15_500,
387 Averaging::X8 => 125_000,
388 Averaging::X32 => 500_000,
389 Averaging::X64 => 1_000_000,
390 }
391 }
392}
393
394/// The nominal conversion cycle in continuous mode (configuration bits 9:7, CONV).
395///
396/// The names give the cycle with no averaging; with averaging the cycle is never
397/// shorter than the conversions take, see [`cycle_micros`](ConversionCycle::cycle_micros).
398#[derive(Clone, Copy, Debug, PartialEq, Eq)]
399pub enum ConversionCycle {
400 /// 15.5 ms (code `000`).
401 Ms15_5,
402 /// 125 ms (code `001`).
403 Ms125,
404 /// 250 ms (code `010`).
405 Ms250,
406 /// 500 ms (code `011`).
407 Ms500,
408 /// 1 s (code `100`, the default).
409 S1,
410 /// 4 s (code `101`).
411 S4,
412 /// 8 s (code `110`).
413 S8,
414 /// 16 s (code `111`).
415 S16,
416}
417
418impl ConversionCycle {
419 /// Returns the 3-bit field code for this cycle setting.
420 pub fn code(self) -> u8 {
421 match self {
422 ConversionCycle::Ms15_5 => 0b000,
423 ConversionCycle::Ms125 => 0b001,
424 ConversionCycle::Ms250 => 0b010,
425 ConversionCycle::Ms500 => 0b011,
426 ConversionCycle::S1 => 0b100,
427 ConversionCycle::S4 => 0b101,
428 ConversionCycle::S8 => 0b110,
429 ConversionCycle::S16 => 0b111,
430 }
431 }
432
433 /// Builds a cycle setting from a 3-bit field code.
434 pub fn from_code(code: u8) -> ConversionCycle {
435 match code & 0b111 {
436 0b000 => ConversionCycle::Ms15_5,
437 0b001 => ConversionCycle::Ms125,
438 0b010 => ConversionCycle::Ms250,
439 0b011 => ConversionCycle::Ms500,
440 0b100 => ConversionCycle::S1,
441 0b101 => ConversionCycle::S4,
442 0b110 => ConversionCycle::S8,
443 _ => ConversionCycle::S16,
444 }
445 }
446
447 /// Returns the nominal cycle in microseconds, with no averaging.
448 pub fn nominal_micros(self) -> u32 {
449 match self {
450 ConversionCycle::Ms15_5 => 15_500,
451 ConversionCycle::Ms125 => 125_000,
452 ConversionCycle::Ms250 => 250_000,
453 ConversionCycle::Ms500 => 500_000,
454 ConversionCycle::S1 => 1_000_000,
455 ConversionCycle::S4 => 4_000_000,
456 ConversionCycle::S8 => 8_000_000,
457 ConversionCycle::S16 => 16_000_000,
458 }
459 }
460
461 /// Returns the actual continuous-mode cycle in microseconds for an averaging
462 /// setting, per the datasheet's conversion cycle table.
463 ///
464 /// When the averaged conversions take longer than the nominal cycle there is no
465 /// standby time and the cycle stretches to the conversion time.
466 ///
467 /// # Arguments
468 ///
469 /// * `averaging` - the averaging setting in force.
470 ///
471 /// # Returns
472 ///
473 /// The time between result updates, in microseconds.
474 pub fn cycle_micros(self, averaging: Averaging) -> u32 {
475 self.nominal_micros().max(averaging.conversion_micros())
476 }
477}
478
479/// How the limits are applied (configuration bit 4, T/nA).
480#[derive(Clone, Copy, Debug, PartialEq, Eq)]
481pub enum AlertMode {
482 /// Alert mode (the default): a result above the high limit sets HIGH_Alert, one
483 /// below the low limit sets LOW_Alert, and reading the configuration clears them.
484 Alert,
485 /// Therm mode: HIGH_Alert sets above the high limit and clears only once a result
486 /// falls below the low limit, so the two limits act as a hysteresis band.
487 Therm,
488}
489
490/// The ALERT pin's active level (configuration bit 3, POL).
491#[derive(Clone, Copy, Debug, PartialEq, Eq)]
492pub enum AlertPolarity {
493 /// Active low (the default).
494 ActiveLow,
495 /// Active high.
496 ActiveHigh,
497}
498
499/// What the ALERT pin reflects (configuration bit 2, DR/Alert).
500#[derive(Clone, Copy, Debug, PartialEq, Eq)]
501pub enum AlertPin {
502 /// The alert flags (the default).
503 AlertFlags,
504 /// The data ready flag.
505 DataReady,
506}
507
508/// A decoded TMP117 configuration register.
509///
510/// Build one, set the fields, and turn it into the 16-bit register value with
511/// [`bits`](Configuration::bits); or parse a register read with
512/// [`from_bits`](Configuration::from_bits). [`Configuration::default`] is the factory
513/// state, `0x0220`. The four flags are read-only on the device and are ignored when
514/// written; `soft_reset` triggers a 2 ms reset when written and always reads back
515/// clear.
516///
517/// # Examples
518///
519/// ```
520/// use pamoja_sensors::tmp117::{Averaging, Configuration, ConversionCycle};
521///
522/// // Average 32 conversions every 4 s, leaving everything else at the factory default.
523/// let config = Configuration {
524/// cycle: ConversionCycle::S4,
525/// averaging: Averaging::X32,
526/// ..Configuration::default()
527/// };
528/// // The high byte then the low byte are written to the configuration register.
529/// let [hi, lo] = config.bits().to_be_bytes();
530/// assert_eq!(Configuration::from_bits(u16::from_be_bytes([hi, lo])), config);
531/// assert_eq!(config.cycle.cycle_micros(config.averaging), 4_000_000);
532/// ```
533#[derive(Clone, Copy, Debug, PartialEq, Eq)]
534pub struct Configuration {
535 /// HIGH_Alert flag (bit 15), read-only.
536 pub high_alert: bool,
537 /// LOW_Alert flag (bit 14), read-only.
538 pub low_alert: bool,
539 /// Data_Ready flag (bit 13), read-only.
540 pub data_ready: bool,
541 /// EEPROM_Busy flag (bit 12), read-only.
542 pub eeprom_busy: bool,
543 /// The conversion mode.
544 pub mode: ConversionMode,
545 /// The continuous-mode conversion cycle.
546 pub cycle: ConversionCycle,
547 /// The number of conversions averaged per result.
548 pub averaging: Averaging,
549 /// Whether the limits act as alerts or as a therm hysteresis band.
550 pub alert_mode: AlertMode,
551 /// The ALERT pin's active level.
552 pub alert_polarity: AlertPolarity,
553 /// What the ALERT pin reflects.
554 pub alert_pin: AlertPin,
555 /// Soft_Reset (bit 1): set to trigger a software reset when written.
556 pub soft_reset: bool,
557}
558
559impl Default for Configuration {
560 fn default() -> Self {
561 Configuration {
562 high_alert: false,
563 low_alert: false,
564 data_ready: false,
565 eeprom_busy: false,
566 mode: ConversionMode::Continuous,
567 cycle: ConversionCycle::S1,
568 averaging: Averaging::X8,
569 alert_mode: AlertMode::Alert,
570 alert_polarity: AlertPolarity::ActiveLow,
571 alert_pin: AlertPin::AlertFlags,
572 soft_reset: false,
573 }
574 }
575}
576
577impl Configuration {
578 /// Assembles the 16-bit configuration register value.
579 ///
580 /// # Returns
581 ///
582 /// The register value to write, most significant byte first on the bus.
583 pub fn bits(self) -> u16 {
584 let mut bits = 0u16;
585 bits |= u16::from(self.high_alert) << 15;
586 bits |= u16::from(self.low_alert) << 14;
587 bits |= u16::from(self.data_ready) << 13;
588 bits |= u16::from(self.eeprom_busy) << 12;
589 bits |= u16::from(self.mode.code()) << 10;
590 bits |= u16::from(self.cycle.code()) << 7;
591 bits |= u16::from(self.averaging.code()) << 5;
592 bits |= u16::from(matches!(self.alert_mode, AlertMode::Therm)) << 4;
593 bits |= u16::from(matches!(self.alert_polarity, AlertPolarity::ActiveHigh)) << 3;
594 bits |= u16::from(matches!(self.alert_pin, AlertPin::DataReady)) << 2;
595 bits |= u16::from(self.soft_reset) << 1;
596 bits
597 }
598
599 /// Parses a 16-bit configuration register value.
600 ///
601 /// # Arguments
602 ///
603 /// * `bits` - the register value, as read from the device.
604 ///
605 /// # Returns
606 ///
607 /// The decoded configuration.
608 pub fn from_bits(bits: u16) -> Configuration {
609 Configuration {
610 high_alert: high_alert(bits),
611 low_alert: low_alert(bits),
612 data_ready: data_ready(bits),
613 eeprom_busy: eeprom_busy(bits),
614 mode: ConversionMode::from_code((bits >> 10) as u8),
615 cycle: ConversionCycle::from_code((bits >> 7) as u8),
616 averaging: Averaging::from_code((bits >> 5) as u8),
617 alert_mode: if bits & (1 << 4) != 0 {
618 AlertMode::Therm
619 } else {
620 AlertMode::Alert
621 },
622 alert_polarity: if bits & (1 << 3) != 0 {
623 AlertPolarity::ActiveHigh
624 } else {
625 AlertPolarity::ActiveLow
626 },
627 alert_pin: if bits & (1 << 2) != 0 {
628 AlertPin::DataReady
629 } else {
630 AlertPin::AlertFlags
631 },
632 soft_reset: bits & (1 << 1) != 0,
633 }
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640
641 // Table 7-1, 16-Bit Temperature Data Format: every row, as (hex, °C).
642 const TABLE_7_1: [(u16, f64); 11] = [
643 (0x8000, -256.0),
644 (0xF380, -25.0),
645 (0xFFF0, -0.125),
646 (0xFFFF, -0.0078125),
647 (0x0000, 0.0),
648 (0x0001, 0.0078125),
649 (0x0010, 0.125),
650 (0x0080, 1.0),
651 (0x0C80, 25.0),
652 (0x3200, 100.0),
653 (0x7FFF, 255.9921875),
654 ];
655
656 #[test]
657 fn temperature_table_rows_decode_to_the_datasheet_values() {
658 // Table 7-1 prints the last row as 255.9921; the exact value at 7.8125 m°C
659 // per count is 255.9921875 °C.
660 for &(hex, degrees) in &TABLE_7_1 {
661 let raw = hex as i16;
662 assert_eq!(
663 nano_celsius(raw),
664 (degrees * 1e9).round() as i64,
665 "{hex:04X}"
666 );
667 assert_eq!(celsius(raw), degrees as f32, "{hex:04X}");
668 }
669 assert_eq!(micro_celsius(0x0C80), 25_000_000);
670 assert_eq!(micro_celsius(0x3200), 100_000_000);
671 assert_eq!(micro_celsius(0xF380_u16 as i16), -25_000_000);
672 assert_eq!(micro_celsius(0xFFF0_u16 as i16), -125_000);
673 assert_eq!(micro_celsius(0x8000_u16 as i16), -256_000_000);
674 assert_eq!(micro_celsius(0x7FFF), 255_992_187);
675 }
676
677 #[test]
678 fn one_count_is_the_7_8125_millidegree_lsb() {
679 // Section 6.5, temperature resolution (LSB) 7.8125 m°C; Table 7-1 rows 0001
680 // and FFFF.
681 assert_eq!(nano_celsius(1), 7_812_500);
682 assert_eq!(nano_celsius(-1), -7_812_500);
683 assert_eq!(micro_celsius(1), 7_812);
684 assert_eq!(micro_celsius(-1), -7_812);
685 assert_eq!(celsius(1), 0.0078125);
686 assert_eq!(celsius(-1), -0.0078125);
687 }
688
689 #[test]
690 fn register_resets_decode_to_the_datasheet_temperatures() {
691 // Table 7-3: Temp_Result 8000h (-256 °C until the first conversion),
692 // THigh_Limit 6000h, TLow_Limit 8000h. 6000h is 24576 counts, 192 °C.
693 assert_eq!(celsius(TEMP_RESULT_RESET as i16), -256.0);
694 assert_eq!(micro_celsius(HIGH_LIMIT_RESET as i16), 192_000_000);
695 assert_eq!(micro_celsius(LOW_LIMIT_RESET as i16), -256_000_000);
696 }
697
698 #[test]
699 fn builders_reproduce_the_table_rows_and_saturate_at_the_register_range() {
700 for &(hex, degrees) in &TABLE_7_1 {
701 assert_eq!(raw_from_celsius(degrees as f32), hex as i16, "{hex:04X}");
702 }
703 assert_eq!(raw_from_micro_celsius(25_000_000), 0x0C80);
704 assert_eq!(raw_from_micro_celsius(-25_000_000), 0xF380_u16 as i16);
705 assert_eq!(raw_from_micro_celsius(7_812), 1);
706 assert_eq!(raw_from_micro_celsius(-7_812), -1);
707 assert_eq!(raw_from_micro_celsius(3_906), 0);
708 assert_eq!(raw_from_micro_celsius(3_907), 1);
709 assert_eq!(raw_from_micro_celsius(-3_906), 0);
710 assert_eq!(raw_from_micro_celsius(-3_907), -1);
711 // Section 7.6.4: the range of the register is ±256 °C.
712 assert_eq!(raw_from_micro_celsius(256_000_000), i16::MAX);
713 assert_eq!(raw_from_micro_celsius(i32::MAX), i16::MAX);
714 assert_eq!(raw_from_micro_celsius(-256_000_000), i16::MIN);
715 assert_eq!(raw_from_micro_celsius(i32::MIN), i16::MIN);
716 assert_eq!(raw_from_celsius(300.0), i16::MAX);
717 assert_eq!(raw_from_celsius(-300.0), i16::MIN);
718 assert_eq!(raw_from_celsius(f32::INFINITY), i16::MAX);
719 assert_eq!(raw_from_celsius(f32::NEG_INFINITY), i16::MIN);
720 assert_eq!(raw_from_celsius(f32::NAN), 0);
721 }
722
723 #[test]
724 fn every_code_round_trips_and_the_integer_paths_track_the_floating_point_lsb() {
725 // Section 7.3.3: two's complement, 16 bits, 7.8125 m°C resolution.
726 for code in i16::MIN..=i16::MAX {
727 let reference = code as f64 * 0.0078125;
728 assert_eq!(nano_celsius(code) as f64, reference * 1e9, "{code}");
729 assert_eq!(celsius(code) as f64, reference, "{code}");
730 assert_eq!(
731 micro_celsius(code) as f64,
732 (reference * 1e6).trunc(),
733 "{code}"
734 );
735 assert_eq!(raw_from_micro_celsius(micro_celsius(code)), code, "{code}");
736 assert_eq!(raw_from_celsius(celsius(code)), code, "{code}");
737 assert_eq!(
738 temperature_from_bytes(temperature_bytes(code)),
739 code,
740 "{code}"
741 );
742 }
743 }
744
745 #[test]
746 fn register_bytes_travel_most_significant_first() {
747 // Section 7.5.3: register bytes are sent with the most significant byte first.
748 // Table 7-1: 25 °C is 0C80h.
749 assert_eq!(temperature_bytes(0x0C80), [0x0C, 0x80]);
750 assert_eq!(temperature_from_bytes([0x0C, 0x80]), 0x0C80);
751 assert_eq!(celsius(temperature_from_bytes([0xF3, 0x80])), -25.0);
752 }
753
754 #[test]
755 fn bus_addresses_follow_the_add0_pin() {
756 // Table 7-2: 1001000x ground, 1001001x V+, 1001010x SDA, 1001011x SCL.
757 assert_eq!(address::ADD0_GND, 0b1001000);
758 assert_eq!(address::ADD0_VPLUS, 0b1001001);
759 assert_eq!(address::ADD0_SDA, 0b1001010);
760 assert_eq!(address::ADD0_SCL, 0b1001011);
761 }
762
763 #[test]
764 fn register_addresses_match_the_register_map() {
765 // Table 7-3.
766 assert_eq!(register::TEMP_RESULT, 0x00);
767 assert_eq!(register::CONFIGURATION, 0x01);
768 assert_eq!(register::THIGH_LIMIT, 0x02);
769 assert_eq!(register::TLOW_LIMIT, 0x03);
770 assert_eq!(register::EEPROM_UL, 0x04);
771 assert_eq!(register::EEPROM1, 0x05);
772 assert_eq!(register::EEPROM2, 0x06);
773 assert_eq!(register::TEMP_OFFSET, 0x07);
774 assert_eq!(register::EEPROM3, 0x08);
775 assert_eq!(register::DEVICE_ID, 0x0F);
776 }
777
778 #[test]
779 fn device_id_register_splits_into_revision_and_id() {
780 // Table 7-3 and Table 7-15: reset 0117h, Rev[3:0] in 15:12, DID[11:0] = 117h.
781 assert_eq!(device_id(0x0117), DEVICE_ID);
782 assert_eq!(revision(0x0117), 0);
783 assert_eq!(device_id(0x1117), DEVICE_ID);
784 assert_eq!(revision(0x1117), 1);
785 assert_ne!(device_id(0x0116), DEVICE_ID);
786 }
787
788 #[test]
789 fn default_configuration_is_the_factory_reset_value() {
790 // Table 7-3 and Table 7-6: 0220h, MOD 00, CONV 100, AVG 01, T/nA 0, POL 0,
791 // DR/Alert 0.
792 assert_eq!(Configuration::default().bits(), CONFIG_RESET);
793 assert_eq!(
794 Configuration::from_bits(CONFIG_RESET),
795 Configuration::default()
796 );
797 let config = Configuration::default();
798 assert_eq!(config.mode, ConversionMode::Continuous);
799 assert_eq!(config.cycle, ConversionCycle::S1);
800 assert_eq!(config.averaging, Averaging::X8);
801 assert_eq!(config.alert_mode, AlertMode::Alert);
802 assert_eq!(config.alert_polarity, AlertPolarity::ActiveLow);
803 assert_eq!(config.alert_pin, AlertPin::AlertFlags);
804 }
805
806 #[test]
807 fn configuration_fields_sit_at_the_datasheet_bit_positions() {
808 // Figure 7-14 and Table 7-6.
809 let config = Configuration {
810 mode: ConversionMode::OneShot,
811 cycle: ConversionCycle::S16,
812 averaging: Averaging::X64,
813 alert_mode: AlertMode::Therm,
814 alert_polarity: AlertPolarity::ActiveHigh,
815 alert_pin: AlertPin::DataReady,
816 soft_reset: true,
817 ..Configuration::default()
818 };
819 assert_eq!(config.bits(), 0b0000_1111_1111_1110);
820 assert_eq!(Configuration::from_bits(config.bits()), config);
821 let shutdown = Configuration {
822 mode: ConversionMode::Shutdown,
823 cycle: ConversionCycle::Ms15_5,
824 averaging: Averaging::None,
825 ..Configuration::default()
826 };
827 assert_eq!(shutdown.bits(), 0b0000_0100_0000_0000);
828 }
829
830 #[test]
831 fn mode_code_10_reads_back_as_continuous() {
832 // Table 7-6, MOD[1:0]: 10 is continuous conversion, same as 00.
833 assert_eq!(ConversionMode::from_code(0b10), ConversionMode::Continuous);
834 assert_eq!(ConversionMode::from_code(0b00), ConversionMode::Continuous);
835 assert_eq!(ConversionMode::from_code(0b01), ConversionMode::Shutdown);
836 assert_eq!(ConversionMode::from_code(0b11), ConversionMode::OneShot);
837 assert_eq!(
838 Configuration::from_bits(0b10 << 10).mode,
839 ConversionMode::Continuous
840 );
841 }
842
843 #[test]
844 fn flag_readers_pick_the_status_bits() {
845 // Table 7-6: HIGH_Alert bit 15, LOW_Alert bit 14, Data_Ready bit 13,
846 // EEPROM_Busy bit 12.
847 assert!(high_alert(0x8000));
848 assert!(low_alert(0x4000));
849 assert!(data_ready(0x2000));
850 assert!(eeprom_busy(0x1000));
851 assert!(!high_alert(0x7FFF));
852 assert!(!low_alert(0xBFFF));
853 assert!(!data_ready(0xDFFF));
854 assert!(!eeprom_busy(0xEFFF));
855 let read = Configuration::from_bits(0xA220);
856 assert!(read.high_alert && read.data_ready && !read.low_alert);
857 // Table 7-10: EUN bit 15, EEPROM_Busy bit 14 of the unlock register.
858 assert_eq!(EEPROM_UNLOCK, 1 << 15);
859 assert!(eeprom_unlock_busy(0x4000));
860 assert!(!eeprom_unlock_busy(EEPROM_UNLOCK));
861 }
862
863 #[test]
864 fn averaging_counts_match_the_datasheet() {
865 // Table 7-6, AVG[1:0]: 00 none, 01 8, 10 32, 11 64.
866 assert_eq!(Averaging::None.conversions(), 1);
867 assert_eq!(Averaging::X8.conversions(), 8);
868 assert_eq!(Averaging::X32.conversions(), 32);
869 assert_eq!(Averaging::X64.conversions(), 64);
870 for code in 0..4 {
871 assert_eq!(Averaging::from_code(code).code(), code);
872 }
873 }
874
875 #[test]
876 fn conversion_cycle_matches_every_cell_of_table_7_7() {
877 // Table 7-7, Conversion Cycle Time in CC Mode, rows CONV 000..111 and columns
878 // AVG 00, 01, 10, 11, in microseconds.
879 const TABLE_7_7: [[u32; 4]; 8] = [
880 [15_500, 125_000, 500_000, 1_000_000],
881 [125_000, 125_000, 500_000, 1_000_000],
882 [250_000, 250_000, 500_000, 1_000_000],
883 [500_000, 500_000, 500_000, 1_000_000],
884 [1_000_000, 1_000_000, 1_000_000, 1_000_000],
885 [4_000_000, 4_000_000, 4_000_000, 4_000_000],
886 [8_000_000, 8_000_000, 8_000_000, 8_000_000],
887 [16_000_000, 16_000_000, 16_000_000, 16_000_000],
888 ];
889 for (conv, row) in TABLE_7_7.iter().enumerate() {
890 let cycle = ConversionCycle::from_code(conv as u8);
891 assert_eq!(cycle.code(), conv as u8);
892 assert_eq!(cycle.nominal_micros(), row[0]);
893 for (avg, µs) in row.iter().enumerate() {
894 let averaging = Averaging::from_code(avg as u8);
895 assert_eq!(
896 cycle.cycle_micros(averaging),
897 micros,
898 "CONV {conv:03b} AVG {avg:02b}"
899 );
900 }
901 }
902 // Section 6.5: one-shot conversion time 15.5 ms typical.
903 assert_eq!(Averaging::None.conversion_micros(), 15_500);
904 }
905
906 #[test]
907 fn general_call_reset_is_the_datasheet_command_byte() {
908 // Section 7.5.3.1.6: a second byte of 0000 0110 after the general-call address.
909 assert_eq!(GENERAL_CALL_RESET, 0b0000_0110);
910 }
911}