Skip to main content

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, &micros) 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}