embedded_devices/devices/texas_instruments/tmp117/
mod.rs

1//! The TMP117 is a high-precision digital temperature sensor. It is designed to meet ASTM E1112
2//! and ISO 80601 requirements for electronic patient thermometers. The TMP117 provides a 16-bit
3//! temperature result with a resolution of 0.0078 °C and an accuracy of up to ±0.1 °C across the
4//! temperature range of –20 °C to 50 °C with no calibration. The TMP117 has in interface that is
5//! I2C- and SMBus™-compatible, programmable alert functionality, and the device can support up to four
6//! devices on a single bus. Integrated EEPROM is included for device programming with an additional
7//! 48-bits memory available for general use.
8//!
9//! The low power consumption of the TMP117 minimizes the impact of self-heating on measurement accuracy.
10//! The TMP117 operates from 1.7 V to 5.5 V and typically consumes 3.5 μA.
11//!
12//! For non-medical applications, the TMP117 can serve as a single chip digital alternative to a Platinum RTD.
13//! The TMP117 has an accuracy comparable to a Class AA RTD, while only using a fraction of the power of the
14//! power typically needed for a PT100 RTD. The TMP117 simplifies the design effort by removing many of the
15//! complexities of RTDs such as precision references, matched traces, complicated algorithms, and calibration.
16//!
17//! The TMP117 units are 100% tested on a production setup that is NIST traceable and verified with
18//! equipment that is calibrated to ISO/IEC 17025 accredited standards.
19//!
20//! ## Usage (sync)
21//!
22//! ```
23//! # #[cfg(feature = "sync")] mod test {
24//! # fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_interfaces::TransportError<(), I::Error>>
25//! # where
26//! #   I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType,
27//! #   D: embedded_hal::delay::DelayNs
28//! # {
29//! use embedded_devices::devices::texas_instruments::tmp117::{TMP117Sync, address::Address, registers::Temperature};
30//! use embedded_devices::sensor::OneshotSensorSync;
31//! use uom::si::thermodynamic_temperature::degree_celsius;
32//!
33//! // Create and initialize the device. Default conversion mode is continuous.
34//! let mut tmp117 = TMP117Sync::new_i2c(delay, i2c, Address::Gnd);
35//! tmp117.init().unwrap();
36//!
37//! // Perform a one-shot measurement now and return to sleep afterwards.
38//! let temp = tmp117.measure()?
39//!     .temperature.get::<degree_celsius>();
40//! println!("Oneshot temperature: {}°C", temp);
41//! # Ok(())
42//! # }
43//! # }
44//! ```
45//!
46//! ## Usage (async)
47//!
48//! ```
49//! # #[cfg(feature = "async")] mod test {
50//! # async fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_interfaces::TransportError<(), I::Error>>
51//! # where
52//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
53//! #   D: embedded_hal_async::delay::DelayNs
54//! # {
55//! use embedded_devices::devices::texas_instruments::tmp117::{TMP117Async, address::Address, registers::Temperature};
56//! use embedded_devices::sensor::OneshotSensorAsync;
57//! use uom::si::thermodynamic_temperature::degree_celsius;
58//!
59//! // Create and initialize the device. Default conversion mode is continuous.
60//! let mut tmp117 = TMP117Async::new_i2c(delay, i2c, Address::Gnd);
61//! tmp117.init().await.unwrap();
62//!
63//! // Perform a one-shot measurement now and return to sleep afterwards.
64//! let temp = tmp117.measure().await?
65//!     .temperature.get::<degree_celsius>();
66//! println!("Oneshot temperature: {:?}°C", temp);
67//! # Ok(())
68//! # }
69//! # }
70//! ```
71
72use embedded_devices_derive::{forward_register_fns, sensor};
73use embedded_interfaces::{TransportError, registers::WritableRegister};
74use uom::si::f64::ThermodynamicTemperature;
75
76pub mod address;
77pub mod registers;
78
79use self::registers::{AveragingMode, Configuration, ConversionMode, Temperature};
80
81#[derive(Debug, thiserror::Error)]
82pub enum InitError<BusError> {
83    /// Transport error
84    #[error("transport error")]
85    Transport(#[from] TransportError<(), BusError>),
86    /// Invalid Device Id was encountered
87    #[error("invalid device id {0:#04x}")]
88    InvalidDeviceId(u16),
89}
90
91#[derive(Debug, thiserror::Error)]
92pub enum EepromError<BusError> {
93    /// Transport error
94    #[error("transport error")]
95    Transport(#[from] TransportError<(), BusError>),
96    /// EEPROM is still busy after 13ms
97    #[error("eeprom still busy")]
98    EepromStillBusy,
99}
100
101/// Measurement data
102#[derive(Debug, embedded_devices_derive::Measurement)]
103pub struct Measurement {
104    /// Measured temperature
105    #[measurement(Temperature)]
106    pub temperature: ThermodynamicTemperature,
107}
108
109/// The TMP117 is a high-precision digital temperature sensor. It provides a 16-bit
110/// temperature result with a resolution of 0.0078 °C and an accuracy of up to ±0.1 °C across the
111/// temperature range of –20 °C to 50 °C with no calibration.
112///
113/// For a full description and usage examples, refer to the [module documentation](self).
114#[maybe_async_cfg::maybe(
115    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface),
116    sync(feature = "sync"),
117    async(feature = "async")
118)]
119pub struct TMP117<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> {
120    /// The delay provider
121    delay: D,
122    /// The interface to communicate with the device
123    interface: I,
124}
125
126pub trait TMP117Register {}
127
128#[maybe_async_cfg::maybe(
129    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
130    sync(feature = "sync"),
131    async(feature = "async")
132)]
133impl<D, I> TMP117<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
134where
135    I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
136    D: hal::delay::DelayNs,
137{
138    /// Initializes a new device with the given address on the specified bus.
139    /// This consumes the I2C bus `I`.
140    ///
141    /// Before using this device, you should call the [`Self::init`] method which
142    /// initializes the device and ensures that it is working correctly.
143    #[inline]
144    pub fn new_i2c(delay: D, interface: I, address: self::address::Address) -> Self {
145        Self {
146            delay,
147            interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
148        }
149    }
150}
151
152#[forward_register_fns]
153#[sensor(Temperature)]
154#[maybe_async_cfg::maybe(
155    idents(
156        hal(sync = "embedded_hal", async = "embedded_hal_async"),
157        RegisterInterface,
158        ResettableDevice
159    ),
160    sync(feature = "sync"),
161    async(feature = "async")
162)]
163impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> TMP117<D, I> {
164    /// Initialize the sensor by waiting for the boot-up period and verifying its device id.
165    /// Calling this function is not mandatory, but recommended to ensure proper operation.
166    pub async fn init(&mut self) -> Result<(), InitError<I::BusError>> {
167        use self::registers::DeviceIdRevision;
168        use crate::device::ResettableDevice;
169
170        // Soft-reset device
171        self.reset().await?;
172
173        // Verify device id
174        let device_id = self.read_register::<DeviceIdRevision>().await?.read_device_id();
175        if device_id != self::registers::DEVICE_ID_VALID {
176            return Err(InitError::InvalidDeviceId(device_id));
177        }
178
179        Ok(())
180    }
181
182    /// Write a register value into EEPROM. Usually this will persist the register
183    /// value as the new power-on default. Not all registers / register-bits support this.
184    pub async fn write_eeprom<R>(&mut self) -> Result<(), EepromError<I::BusError>>
185    where
186        R: WritableRegister + TMP117Register,
187    {
188        use self::registers::{EepromLockMode, EepromUnlock};
189
190        // Unlock EEPROM
191        self.write_register(EepromUnlock::default().with_lock_mode(EepromLockMode::Unlocked))
192            .await?;
193
194        // Wait 7ms for EEPROM write to complete
195        self.delay.delay_ms(7).await;
196
197        // Wait up to 5ms for eeprom busy flag to be reset
198        const TRIES: u8 = 5;
199        for _ in 0..TRIES {
200            if !self.read_register::<EepromUnlock>().await?.read_busy() {
201                // EEPROM write complete, lock eeprom again
202                self.write_register(EepromUnlock::default().with_lock_mode(EepromLockMode::Locked))
203                    .await?;
204
205                return Ok(());
206            }
207
208            // Wait another 1ms for EEPROM write to complete
209            self.delay.delay_ms(1).await;
210        }
211
212        Err(EepromError::EepromStillBusy)
213    }
214}
215
216#[maybe_async_cfg::maybe(
217    idents(
218        hal(sync = "embedded_hal", async = "embedded_hal_async"),
219        RegisterInterface,
220        ResettableDevice
221    ),
222    sync(feature = "sync"),
223    async(feature = "async")
224)]
225impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::device::ResettableDevice
226    for TMP117<D, I>
227{
228    type Error = TransportError<(), I::BusError>;
229
230    /// Performs a soft-reset of the device. The datasheet specifies a time to reset of 2ms which
231    /// is automatically awaited before allowing further communication.
232    async fn reset(&mut self) -> Result<(), Self::Error> {
233        self.write_register(Configuration::default().with_soft_reset(true))
234            .await?;
235        self.delay.delay_ms(2).await;
236        Ok(())
237    }
238}
239
240#[maybe_async_cfg::maybe(
241    idents(
242        hal(sync = "embedded_hal", async = "embedded_hal_async"),
243        RegisterInterface,
244        OneshotSensor
245    ),
246    sync(feature = "sync"),
247    async(feature = "async")
248)]
249impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::OneshotSensor
250    for TMP117<D, I>
251{
252    type Error = TransportError<(), I::BusError>;
253    type Measurement = Measurement;
254
255    /// Performs a one-shot measurement. This will set the conversion mode to
256    /// [`self::registers::ConversionMode::Oneshot´] causing the device to perform a
257    /// single conversion a return to sleep afterwards.
258    async fn measure(&mut self) -> Result<Self::Measurement, Self::Error> {
259        // Read current averaging mode to determine required measurement delay
260        let reg_conf = self.read_register::<Configuration>().await?;
261
262        // Initiate measurement
263        self.write_register(reg_conf.with_conversion_mode(ConversionMode::Oneshot))
264            .await?;
265
266        // Active conversion time is only linearly influenced by the averaging factor.
267        // A single-conversion takes 15.5ms.
268        let active_conversion_time = reg_conf.read_averaging_mode().factor() as u32 * 15_500;
269        self.delay.delay_us(active_conversion_time).await;
270
271        // Read and return the temperature
272        let temperature = self.read_register::<Temperature>().await?.read_temperature();
273        Ok(Measurement { temperature })
274    }
275}
276
277#[maybe_async_cfg::maybe(
278    idents(
279        hal(sync = "embedded_hal", async = "embedded_hal_async"),
280        RegisterInterface,
281        ContinuousSensor
282    ),
283    sync(feature = "sync"),
284    async(feature = "async")
285)]
286impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::ContinuousSensor
287    for TMP117<D, I>
288{
289    type Error = TransportError<(), I::BusError>;
290    type Measurement = Measurement;
291
292    /// Starts continuous measurement.
293    async fn start_measuring(&mut self) -> Result<(), Self::Error> {
294        let reg_conf = self.read_register::<Configuration>().await?;
295        self.write_register(reg_conf.with_conversion_mode(ConversionMode::Continuous))
296            .await?;
297        Ok(())
298    }
299
300    /// Stops continuous measurement.
301    async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
302        let reg_conf = self.read_register::<Configuration>().await?;
303        self.write_register(reg_conf.with_conversion_mode(ConversionMode::Shutdown))
304            .await?;
305        Ok(())
306    }
307
308    /// Expected amount of time between measurements in microseconds.
309    async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
310        let reg_conf = self.read_register::<Configuration>().await?;
311        let min_conversion_time = match reg_conf.read_averaging_mode() {
312            AveragingMode::X_1 => 0,
313            AveragingMode::X_8 => 125_000,
314            AveragingMode::X_32 => 500_000,
315            AveragingMode::X_64 => 1_000_000,
316        };
317        let conversion_time_us = reg_conf
318            .read_conversion_cycle_time()
319            .interval_us()
320            .max(min_conversion_time);
321        Ok(conversion_time_us)
322    }
323
324    /// Returns the most recent measurement. Will never return None.
325    async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
326        let temperature = self.read_register::<Temperature>().await?.read_temperature();
327        Ok(Some(Measurement { temperature }))
328    }
329
330    /// Not supported, always returns true.
331    async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
332        Ok(true)
333    }
334
335    /// Opportunistically waits one conversion interval and returns the measurement.
336    async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
337        let interval = self.measurement_interval_us().await?;
338        self.delay.delay_us(interval).await;
339        self.current_measurement()
340            .await?
341            .ok_or_else(|| TransportError::Unexpected("measurement was not ready even though we expected it to be"))
342    }
343}