gy_21/
lib.rs

1#![no_std]
2
3//! Crate to interface with the [HTU21D](https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FHPC199_6%7FA6%7Fpdf%7FEnglish%7FENG_DS_HPC199_6_A6.pdf%7FCAT-HSC0004) temperature and relaive humidity sensor (commonly referred to as GY-21)
4//!
5//! Implements `Read` and `Write` from [`embedded_hal::blocking::i2c`](https://docs.rs/embedded-hal/latest/embedded_hal/blocking/i2c/index.html)
6//!
7//! This crate does not implement all the features of the HTU21D. It doesn't implement the master
8//! hold read or the precision change. It also ignores the status bits since the sensor I used to
9//! develop this doesn't seem to use those in the same way as the datasheet does
10//!
11//! # How to use
12//! Create the GY-21:
13//! ```no_run
14//! // `delay` is your HAL's delay object
15//! // `i2c` is a i2c bus you have previously prepared
16//!
17//! let gy_21 = Gy21::new(i2c, delay); // Using the default I2C address for the sensor (0x40)
18//! // otherwise
19//! let gy_21 = Gy21::with_address(i2c, delay, 0x40) // Using 0x39 as the I2C address
20//! ```
21//!
22//! Read the temperature, humidity and dew point temperature:
23//! ```no_run
24//! if let Ok(temp) = gy_21.temperature() {
25//!     println!("Temperature = {}", temp);
26//! }
27//! if let Ok(rhum) = gy_21.humidity() {
28//!     println!("Relative humidity = {}%", rhum);
29//! }
30//! if let Ok(dpt) = gy_21.dew_point_temp() {
31//!     println!("Dew point temperature = {}\n\n", dpt);
32//! }
33//! ```
34
35use embedded_hal::blocking::{
36    delay::DelayMs,
37    i2c::{Read, Write},
38};
39
40/// Represents the GY-21 sensor
41pub struct Gy21<I2C, DL> {
42    i2c: I2C,
43    address: u8,
44    delay: DL,
45}
46
47/// Error type containing I2C Read and Write errors
48pub enum CommError<I2C: Write + Read> {
49    /// I2C Write error
50    Write(<I2C as Write>::Error),
51
52    /// I2C Read error
53    Read(<I2C as Read>::Error),
54}
55
56/// Converts from I2C errors to `CommErrors` without using `From` becayse that conflicts with
57/// `core::convert`
58impl<I2C: Write + Read> CommError<I2C> {
59    pub const fn from_write_error(err: <I2C as Write>::Error) -> Self {
60        Self::Write(err)
61    }
62
63    pub const fn from_read_error(err: <I2C as Read>::Error) -> Self {
64        Self::Read(err)
65    }
66}
67
68impl<I2C: Write + Read, DL> Gy21<I2C, DL>
69where
70    DL: DelayMs<u8>,
71{
72    const DEFAULT_I2C_ADDRESS: u8 = 0x40;
73    const READ_TEMP: u8 = 0xF3; // Trigger a temp measurement without the sensor holding the clock
74    const READ_RHUM: u8 = 0xF5; // Trigger a rhum measurement without the sensor holding the clock
75
76    const TWO_POW16: f32 = 65536.0;
77    const LAST_2BIT: u8 = 0b0000_0011;
78
79    /// Create a new GY-21 sensor from an I2C bus and a delay object with the default I2C address
80    /// (`0x40`)
81    ///
82    /// The I2C object must be created with a frequency <= 400kHz, which is the maximum the sensor
83    /// is able to keep up with as stated by the datasheet.
84    ///
85    /// ```no_run
86    /// let gy_21 = Gy21::new(i2c, delay);
87    /// ```
88    pub const fn new(i2c: I2C, delay: DL) -> Self {
89        Self {
90            i2c,
91            address: Self::DEFAULT_I2C_ADDRESS,
92            delay,
93        }
94    }
95
96    /// Create a new GY-21 sensor from an I2C bus, a delay object and an I2C address
97    ///
98    /// ```no_run
99    /// let gy_21 = Gy21::with_address(i2c, delay, 0x40);
100    /// ```
101    pub const fn with_address(i2c: I2C, delay: DL, address: u8) -> Self {
102        Self {
103            i2c,
104            address,
105            delay,
106        }
107    }
108
109    fn read_sensor(&mut self, word: u8) -> Result<u16, CommError<I2C>> {
110        // A reading is requested by sending the word Self::READ_TEMP at self.address
111        // The sensor won't reply while it's reading so the function must wait before reading I2C.
112        // Sensor replies with 16 bits. The phrase has the following structure:
113        // [ [m m m m m m m m] [l l l l l l s s] ]
114        // Where:
115        //  - m is a bit in the MSB part of the reading
116        //  - l is a bit in the LSB part of the reading
117        //  - s is a status bit
118
119        self.i2c
120            .write(self.address, &[word])
121            .map_err(CommError::from_write_error)?;
122
123        self.delay.delay_ms(50); // Wait for the sensor to read
124
125        let mut buf = [0, 0];
126        self.i2c
127            .read(self.address, &mut buf)
128            .map_err(CommError::from_read_error)?;
129
130        let msb = (buf[0] as u16) << 8; // Set the first 8 bits of the message as the MSB of a u16
131        let lsb = (buf[1] & !Self::LAST_2BIT) as u16; // Discard the status bits and cast to u16
132        Ok(msb | lsb)
133    }
134
135    /// Read the temperature
136    ///
137    /// ```no_run
138    /// let reading = match gy_21.temperature() {
139    ///     Ok(val) => val;
140    ///     Err(_) => panic()!
141    /// }
142    /// ```
143    ///
144    /// # Errors
145    /// Will return an error in the following cases:
146    ///  - `Write(i2c_err)` if writing to the I2C bus fails
147    ///  - `Read(i2c_err)` if reading the I2C bus fails
148    pub fn temperature(&mut self) -> Result<f32, CommError<I2C>> {
149        let reading = self.read_sensor(Self::READ_TEMP)?;
150        // Temperature is calculated with the forula provided by the datasheet
151        // T = -46.85 + 175.72 * (signal_output / 2^16)
152        let temperature = -46.85 + 175.72 * (reading as f32 / Self::TWO_POW16);
153        Ok(temperature)
154    }
155
156    /// Read the relative humidity
157    ///
158    /// ```no_run
159    /// let reading = match gy_21.humidity() {
160    ///     Ok(val) => val;
161    ///     Err(_) => panic()!
162    /// }
163    /// ```
164    ///
165    /// # Errors
166    /// Will return an error in the following cases:
167    ///  - `Write(i2c_err)` if writing to the I2C bus fails
168    ///  - `Read(i2c_err)` if reading the I2C bus fails
169    pub fn humidity(&mut self) -> Result<f32, CommError<I2C>> {
170        let reading = self.read_sensor(Self::READ_RHUM)?;
171        // Relative humidity is calculated with the forula provided by the datasheet
172        // rH=-6+125 * (signal_output / 2^16)
173        let rhumidty = -6.0 + 125.0 * (reading as f32 / Self::TWO_POW16);
174        Ok(rhumidty)
175    }
176
177    /// Calculate the dew point temperature based on temperature and relative humidity readings
178    ///
179    /// The dew point of a given body of air is the temperature to which it must be cooled to become saturated with water vapor.
180    ///
181    /// ```no_run
182    /// let reading = match gy_21.dew_point_temp() {
183    ///     Ok(val) => val;
184    ///     Err(_) => panic()!
185    /// }
186    /// ```
187    ///
188    /// # Errors
189    /// Will return an error in the following cases:
190    ///  - `Write(i2c_err)` if writing to the I2C bus fails
191    ///  - `Read(i2c_err)` if reading the I2C bus fails
192    pub fn dew_point_temp(&mut self) -> Result<f32, CommError<I2C>> {
193        use libm::Libm;
194        let temp = self.temperature()?;
195        let rhum = self.humidity()?;
196
197        // Constants and formulas as defined by the datasheet
198        let (a, b, c) = (8.1332, 1762.39, 235.66);
199        let part_pressure = Libm::<f32>::exp10(a - (b / (temp + c)));
200        let dew_point_temp = -((b / (Libm::<f32>::log10(rhum * (part_pressure / 100.0)) - a)) + c);
201        Ok(dew_point_temp)
202    }
203}