#![deny(unsafe_code)]
#![deny(missing_docs)]
#![cfg_attr(not(test), no_std)]
use embedded_hal::{
blocking::delay::{DelayMs, DelayUs},
digital::v2::{InputPin, OutputPin},
};
#[cfg(feature = "dwt")]
use cortex_m::peripheral::DWT;
const TIMEOUT_US: u16 = 1_000;
#[derive(Debug)]
pub enum Error<E> {
Timeout,
CrcMismatch,
Gpio(E),
}
pub struct Dht11<GPIO> {
gpio: GPIO,
}
#[derive(Copy, Clone, Default, Debug)]
pub struct Measurement {
pub temperature: i16,
pub humidity: u16,
}
impl<GPIO, E> Dht11<GPIO>
where
GPIO: InputPin<Error = E> + OutputPin<Error = E>,
{
pub fn new(gpio: GPIO) -> Self {
Dht11 { gpio }
}
pub fn destroy(self) -> GPIO {
self.gpio
}
pub fn perform_measurement<D>(&mut self, delay: &mut D) -> Result<Measurement, Error<E>>
where
D: DelayUs<u16> + DelayMs<u16>,
{
let mut data = [0u8; 5];
self.perform_handshake(delay)?;
for i in 0..40 {
data[i / 8] <<= 1;
if self.read_bit(delay)? {
data[i / 8] |= 1;
}
}
self.wait_for_pulse(true, delay)?;
let crc = data[0]
.wrapping_add(data[1])
.wrapping_add(data[2])
.wrapping_add(data[3]);
if crc != data[4] {
return Err(Error::CrcMismatch);
}
let mut temp = i16::from(data[2] & 0x7f) * 10 + i16::from(data[3]);
if data[2] & 0x80 != 0 {
temp = -temp;
}
Ok(Measurement {
temperature: temp,
humidity: u16::from(data[0]) * 10 + u16::from(data[1]),
})
}
fn perform_handshake<D>(&mut self, delay: &mut D) -> Result<(), Error<E>>
where
D: DelayUs<u16> + DelayMs<u16>,
{
self.set_input()?;
delay.delay_ms(1);
self.set_low()?;
delay.delay_ms(20);
self.set_input()?;
delay.delay_us(40);
self.read_bit(delay)?;
Ok(())
}
fn read_bit<D>(&mut self, delay: &mut D) -> Result<bool, Error<E>>
where
D: DelayUs<u16> + DelayMs<u16>,
{
let low = self.wait_for_pulse(true, delay)?;
let high = self.wait_for_pulse(false, delay)?;
Ok(high > low)
}
fn wait_for_pulse<D>(&mut self, level: bool, delay: &mut D) -> Result<u32, Error<E>>
where
D: DelayUs<u16> + DelayMs<u16>,
{
let mut count = 0;
#[cfg(feature = "dwt")]
let start = DWT::get_cycle_count();
while self.read_line()? != level {
count += 1;
if count > TIMEOUT_US {
return Err(Error::Timeout);
}
delay.delay_us(1);
}
#[cfg(feature = "dwt")]
return Ok(DWT::get_cycle_count().wrapping_sub(start));
#[cfg(not(feature = "dwt"))]
return Ok(u32::from(count));
}
fn set_input(&mut self) -> Result<(), Error<E>> {
self.gpio.set_high().map_err(Error::Gpio)
}
fn set_low(&mut self) -> Result<(), Error<E>> {
self.gpio.set_low().map_err(Error::Gpio)
}
fn read_line(&self) -> Result<bool, Error<E>> {
self.gpio.is_high().map_err(Error::Gpio)
}
}