#![no_std] //! 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) //! //! Implements `Read` and `Write` from [`embedded_hal::blocking::i2c`](https://docs.rs/embedded-hal/latest/embedded_hal/blocking/i2c/index.html) //! //! This crate does not implement all the features of the HTU21D. It doesn't implement the master //! hold read or the precision change. It also ignores the status bits since the sensor I used to //! develop this doesn't seem to use those in the same way as the datasheet does //! //! # How to use //! Create the GY-21: //! ```no_run //! // `delay` is your HAL's delay object //! // `i2c` is a i2c bus you have previously prepared //! //! let gy_21 = Gy21::new(i2c, delay); // Using the default I2C address for the sensor (0x40) //! // otherwise //! let gy_21 = Gy21::with_address(i2c, delay, 0x40) // Using 0x39 as the I2C address //! ``` //! //! Read the temperature, humidity and dew point temperature: //! ```no_run //! if let Ok(temp) = gy_21.temperature() { //! println!("Temperature = {}", temp); //! } //! if let Ok(rhum) = gy_21.humidity() { //! println!("Relative humidity = {}%", rhum); //! } //! if let Ok(dpt) = gy_21.dew_point_temp() { //! println!("Dew point temperature = {}\n\n", dpt); //! } //! ``` use embedded_hal::blocking::{ delay::DelayMs, i2c::{Read, Write}, }; /// Represents the GY-21 sensor pub struct Gy21 { i2c: I2C, address: u8, delay: DL, } /// Error type containing I2C Read and Write errors pub enum CommError { /// I2C Write error Write(::Error), /// I2C Read error Read(::Error), } /// Converts from I2C errors to `CommErrors` without using `From` becayse that conflicts with /// `core::convert` impl CommError { pub const fn from_write_error(err: ::Error) -> Self { Self::Write(err) } pub const fn from_read_error(err: ::Error) -> Self { Self::Read(err) } } impl Gy21 where DL: DelayMs, { const DEFAULT_I2C_ADDRESS: u8 = 0x40; const READ_TEMP: u8 = 0xF3; // Trigger a temp measurement without the sensor holding the clock const READ_RHUM: u8 = 0xF5; // Trigger a rhum measurement without the sensor holding the clock const TWO_POW16: f32 = 65536.0; const LAST_2BIT: u8 = 0b0000_0011; /// Create a new GY-21 sensor from an I2C bus and a delay object with the default I2C address /// (`0x40`) /// /// The I2C object must be created with a frequency <= 400kHz, which is the maximum the sensor /// is able to keep up with as stated by the datasheet. /// /// ```no_run /// let gy_21 = Gy21::new(i2c, delay); /// ``` pub const fn new(i2c: I2C, delay: DL) -> Self { Self { i2c, address: Self::DEFAULT_I2C_ADDRESS, delay, } } /// Create a new GY-21 sensor from an I2C bus, a delay object and an I2C address /// /// ```no_run /// let gy_21 = Gy21::with_address(i2c, delay, 0x40); /// ``` pub const fn with_address(i2c: I2C, delay: DL, address: u8) -> Self { Self { i2c, address, delay, } } fn read_sensor(&mut self, word: u8) -> Result> { // A reading is requested by sending the word Self::READ_TEMP at self.address // The sensor won't reply while it's reading so the function must wait before reading I2C. // Sensor replies with 16 bits. The phrase has the following structure: // [ [m m m m m m m m] [l l l l l l s s] ] // Where: // - m is a bit in the MSB part of the reading // - l is a bit in the LSB part of the reading // - s is a status bit self.i2c .write(self.address, &[word]) .map_err(CommError::from_write_error)?; self.delay.delay_ms(50); // Wait for the sensor to read let mut buf = [0, 0]; self.i2c .read(self.address, &mut buf) .map_err(CommError::from_read_error)?; let msb = (buf[0] as u16) << 8; // Set the first 8 bits of the message as the MSB of a u16 let lsb = (buf[1] & !Self::LAST_2BIT) as u16; // Discard the status bits and cast to u16 Ok(msb | lsb) } /// Read the temperature /// /// ```no_run /// let reading = match gy_21.temperature() { /// Ok(val) => val; /// Err(_) => panic()! /// } /// ``` /// /// # Errors /// Will return an error in the following cases: /// - `Write(i2c_err)` if writing to the I2C bus fails /// - `Read(i2c_err)` if reading the I2C bus fails pub fn temperature(&mut self) -> Result> { let reading = self.read_sensor(Self::READ_TEMP)?; // Temperature is calculated with the forula provided by the datasheet // T = -46.85 + 175.72 * (signal_output / 2^16) let temperature = -46.85 + 175.72 * (reading as f32 / Self::TWO_POW16); Ok(temperature) } /// Read the relative humidity /// /// ```no_run /// let reading = match gy_21.humidity() { /// Ok(val) => val; /// Err(_) => panic()! /// } /// ``` /// /// # Errors /// Will return an error in the following cases: /// - `Write(i2c_err)` if writing to the I2C bus fails /// - `Read(i2c_err)` if reading the I2C bus fails pub fn humidity(&mut self) -> Result> { let reading = self.read_sensor(Self::READ_RHUM)?; // Relative humidity is calculated with the forula provided by the datasheet // rH=-6+125 * (signal_output / 2^16) let rhumidty = -6.0 + 125.0 * (reading as f32 / Self::TWO_POW16); Ok(rhumidty) } /// Calculate the dew point temperature based on temperature and relative humidity readings /// /// The dew point of a given body of air is the temperature to which it must be cooled to become saturated with water vapor. /// /// ```no_run /// let reading = match gy_21.dew_point_temp() { /// Ok(val) => val; /// Err(_) => panic()! /// } /// ``` /// /// # Errors /// Will return an error in the following cases: /// - `Write(i2c_err)` if writing to the I2C bus fails /// - `Read(i2c_err)` if reading the I2C bus fails pub fn dew_point_temp(&mut self) -> Result> { use libm::Libm; let temp = self.temperature()?; let rhum = self.humidity()?; // Constants and formulas as defined by the datasheet let (a, b, c) = (8.1332, 1762.39, 235.66); let part_pressure = Libm::::exp10(a - (b / (temp + c))); let dew_point_temp = -((b / (Libm::::log10(rhum * (part_pressure / 100.0)) - a)) + c); Ok(dew_point_temp) } }