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}