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}