embedded_devices/devices/microchip/mcp9808/
mod.rs

1//! Microchip Technology Inc.'s MCP9808 digital temperature sensor converts temperatures between
2//! -20°C and +100°C to a digital word with ±0.25°C/±0.5°C (typical/maximum) accuracy.
3//!
4//! The MCP9808 comes with user-programmable registers that provide flexibility for temperature sensing
5//! applications. The registers allow user-selectable settings such as Shutdown or Low-Power modes and
6//! the specification of temperature Alert window limits and critical output limits.
7//!
8//! When the temperature changes beyond the specified boundary limits, the MCP9808 outputs an Alert signal.
9//! The user has the option of setting the Alert output signal polarity as an active-low or active-high
10//! comparator output for thermostat operation, or as a temperature Alert interrupt output for
11//! microprocessor-based systems. The Alert output can also be configured as a critical temperature output only.
12//!
13//! This sensor has an industry standard 400 kHz, 2-wire, SMBus/I2C compatible serial interface,
14//! allowing up to eight or sixteen sensors to be controlled with a single serial bus.
15//! These features make the MCP9808 ideal for sophisticated, multi-zone, temperature-monitoring applications.
16//!
17//! ## Usage (sync)
18//!
19//! ```rust
20//! # #[cfg(feature = "sync")] mod test {
21//! # fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_interfaces::TransportError<(), I::Error>>
22//! # where
23//! #   I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType,
24//! #   D: embedded_hal::delay::DelayNs
25//! # {
26//! use embedded_devices::devices::microchip::mcp9808::{MCP9808Sync, address::Address, registers::AmbientTemperature};
27//! use embedded_devices::sensor::OneshotSensorSync;
28//! use uom::si::thermodynamic_temperature::degree_celsius;
29//!
30//! // Create and initialize the device
31//! let mut mcp9808 = MCP9808Sync::new_i2c(delay, i2c, Address::Default);
32//! mcp9808.init().unwrap();
33//!
34//! // Read the current temperature in °C
35//! let temp = mcp9808.measure()?
36//!     .temperature.get::<degree_celsius>();
37//! println!("Current temperature: {:?}°C", temp);
38//! # Ok(())
39//! # }
40//! # }
41//! ```
42//!
43//! ## Usage (async)
44//!
45//! ```rust
46//! # #[cfg(feature = "async")] mod test {
47//! # async fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_interfaces::TransportError<(), I::Error>>
48//! # where
49//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
50//! #   D: embedded_hal_async::delay::DelayNs
51//! # {
52//! use embedded_devices::devices::microchip::mcp9808::{MCP9808Async, address::Address, registers::AmbientTemperature};
53//! use embedded_devices::sensor::OneshotSensorAsync;
54//! use uom::si::thermodynamic_temperature::degree_celsius;
55//!
56//! // Create and initialize the device
57//! let mut mcp9808 = MCP9808Async::new_i2c(delay, i2c, Address::Default);
58//! mcp9808.init().await.unwrap();
59//!
60//! // Read the current temperature in °C
61//! let temp = mcp9808.measure().await?
62//!     .temperature.get::<degree_celsius>();
63//! println!("Current temperature: {:?}°C", temp);
64//! # Ok(())
65//! # }
66//! # }
67//! ```
68
69use embedded_devices_derive::{forward_register_fns, sensor};
70use embedded_interfaces::TransportError;
71use uom::si::f64::ThermodynamicTemperature;
72
73pub mod address;
74pub mod registers;
75
76use self::registers::{AmbientTemperature, Configuration, DeviceIdRevision, ManufacturerId, Resolution, ShutdownMode};
77
78#[cfg_attr(feature = "defmt", derive(defmt::Format))]
79#[derive(Debug, thiserror::Error)]
80pub enum InitError<BusError> {
81    /// Transport error
82    #[error("transport error")]
83    Transport(#[from] TransportError<(), BusError>),
84    /// Invalid Device Id was encountered
85    #[error("invalid device id")]
86    InvalidDeviceId,
87    /// Invalid Manufacturer Id was encountered
88    #[error("invalid manufacturer id")]
89    InvalidManufacturerId,
90}
91
92/// Measurement data
93#[derive(Debug, embedded_devices_derive::Measurement)]
94pub struct Measurement {
95    /// Measured temperature
96    #[measurement(Temperature)]
97    pub temperature: ThermodynamicTemperature,
98}
99
100/// Microchip Technology Inc.'s MCP9808 digital temperature sensor converts temperatures between
101/// -20°C and +100°C to a digital word with ±0.25°C/±0.5°C (typical/maximum) accuracy.
102///
103/// For a full description and usage examples, refer to the [module documentation](self).
104#[maybe_async_cfg::maybe(
105    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface),
106    sync(feature = "sync"),
107    async(feature = "async")
108)]
109pub struct MCP9808<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> {
110    /// The delay provider
111    delay: D,
112    /// The interface to communicate with the device
113    interface: I,
114}
115
116pub trait MCP9808Register {}
117
118#[maybe_async_cfg::maybe(
119    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
120    sync(feature = "sync"),
121    async(feature = "async")
122)]
123impl<D, I> MCP9808<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
124where
125    I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
126    D: hal::delay::DelayNs,
127{
128    /// Initializes a new device with the given address on the specified bus.
129    /// This consumes the I2C bus `I`.
130    ///
131    /// Before using this device, you should call the [`Self::init`] method which
132    /// initializes the device and ensures that it is working correctly.
133    #[inline]
134    pub fn new_i2c(delay: D, interface: I, address: self::address::Address) -> Self {
135        Self {
136            delay,
137            interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
138        }
139    }
140}
141
142#[forward_register_fns]
143#[maybe_async_cfg::maybe(
144    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface),
145    sync(feature = "sync"),
146    async(feature = "async")
147)]
148impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> MCP9808<D, I> {
149    /// Initializes the sensor by verifying its device id and manufacturer id.
150    /// Not mandatory, but recommended.
151    pub async fn init(&mut self) -> Result<(), InitError<I::BusError>> {
152        let device_id = self.read_register::<DeviceIdRevision>().await?;
153        if device_id.read_device_id() != self::registers::DEVICE_ID_VALID {
154            return Err(InitError::InvalidDeviceId);
155        }
156
157        let manufacturer_id = self.read_register::<ManufacturerId>().await?;
158        if manufacturer_id.read_manufacturer_id() != self::registers::MANUFACTURER_ID_VALID {
159            return Err(InitError::InvalidManufacturerId);
160        }
161
162        Ok(())
163    }
164}
165
166#[sensor(Temperature)]
167#[maybe_async_cfg::maybe(
168    idents(
169        hal(sync = "embedded_hal", async = "embedded_hal_async"),
170        RegisterInterface,
171        OneshotSensor
172    ),
173    sync(feature = "sync"),
174    async(feature = "async")
175)]
176impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::OneshotSensor
177    for MCP9808<D, I>
178{
179    type Error = TransportError<(), I::BusError>;
180    type Measurement = Measurement;
181
182    /// Performs a one-shot measurement. This will power up the sensor, wait until a conversion is
183    /// ready and return to sleep afterwards.
184    async fn measure(&mut self) -> Result<Self::Measurement, Self::Error> {
185        // Enable conversions
186        let mut reg_conf = self.read_register::<Configuration>().await?;
187        reg_conf.write_shutdown_mode(ShutdownMode::Continuous);
188        self.write_register(reg_conf).await?;
189
190        // Wait until conversion is ready
191        let resolution = self.read_register::<Resolution>().await?;
192        let conversion_time = 1000 + resolution.read_temperature_resolution().conversion_time_us();
193        self.delay.delay_us(conversion_time).await;
194
195        // Go to sleep
196        reg_conf.write_shutdown_mode(ShutdownMode::Shutdown);
197        self.write_register(reg_conf).await?;
198
199        // Read and return the temperature
200        let temperature = self.read_register::<AmbientTemperature>().await?.read_temperature();
201        Ok(Measurement { temperature })
202    }
203}
204
205#[maybe_async_cfg::maybe(
206    idents(
207        hal(sync = "embedded_hal", async = "embedded_hal_async"),
208        RegisterInterface,
209        ContinuousSensor
210    ),
211    sync(feature = "sync"),
212    async(feature = "async")
213)]
214impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::ContinuousSensor
215    for MCP9808<D, I>
216{
217    type Error = TransportError<(), I::BusError>;
218    type Measurement = Measurement;
219
220    /// Starts continuous measurement.
221    async fn start_measuring(&mut self) -> Result<(), Self::Error> {
222        let reg_conf = self.read_register::<Configuration>().await?;
223        self.write_register(reg_conf.with_shutdown_mode(ShutdownMode::Continuous))
224            .await?;
225        Ok(())
226    }
227
228    /// Stops continuous measurement.
229    async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
230        let reg_conf = self.read_register::<Configuration>().await?;
231        self.write_register(reg_conf.with_shutdown_mode(ShutdownMode::Shutdown))
232            .await?;
233        Ok(())
234    }
235
236    /// Expected amount of time between measurements in microseconds.
237    async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
238        // Wait until conversion is ready
239        let resolution = self.read_register::<Resolution>().await?;
240        let conversion_time = resolution.read_temperature_resolution().conversion_time_us();
241        Ok(conversion_time)
242    }
243
244    /// Returns the most recent measurement. Will never return None.
245    async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
246        let temperature = self.read_register::<AmbientTemperature>().await?.read_temperature();
247        Ok(Some(Measurement { temperature }))
248    }
249
250    /// Not supported, always returns true.
251    async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
252        Ok(true)
253    }
254
255    /// Opportunistically waits one conversion interval and returns the measurement.
256    async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
257        let interval = self.measurement_interval_us().await?;
258        self.delay.delay_us(interval).await;
259        self.current_measurement()
260            .await?
261            .ok_or_else(|| TransportError::Unexpected("measurement was not ready even though we expected it to be"))
262    }
263}