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