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, µs) 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}