#![doc = include_str!("../README.md")]
#![deny(unsafe_code)]
#![cfg_attr(not(test), no_std)]
use embedded_hal::{
delay::DelayNs,
digital::{InputPin, OutputPin},
};
use core::marker::PhantomData;
#[cfg(feature = "use-dwt")]
use fugit::HertzU32;
#[cfg(feature = "use-dwt")]
use cortex_m::peripheral::DWT;
use crate::error::DHT11Error;
#[allow(unused)]
const TIMEOUT_US: u32 = 200;
#[cfg(not(feature = "use-dwt"))]
#[allow(unused)]
const TIMEOUT_CYCLES: u32 = 2_000_000;
pub mod error {
pub type Result<T, E> = core::result::Result<T, DHT11Error<E>>;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
pub enum DHT11Error<E> {
NoResponse,
ChecksumMismatch,
PinError(E),
Timeout,
}
}
pub struct Idle;
pub struct Uninitialized;
pub struct Initialized;
pub struct SensorValue {
pub integral: u8,
pub decimal: u8,
}
pub type Temperature = SensorValue;
pub type Humidity = SensorValue;
pub struct Measurement {
pub humidity: Humidity,
pub temperature: Temperature,
pub checksum: u8,
}
impl Measurement {
pub fn temp_as_f32(&self) -> f32 {
self.temperature.integral as f32 + (self.temperature.decimal as f32 / 10.0)
}
pub fn temp_as_fixed(&self) -> i16 {
(self.temperature.integral as i16 * 10) + (self.temperature.decimal as i16)
}
pub fn hum_as_f32(&self) -> f32 {
self.humidity.integral as f32 + (self.humidity.decimal as f32 / 10.0)
}
pub fn hum_as_fixed(&self) -> i16 {
(self.humidity.integral as i16 * 10) + (self.humidity.decimal as i16)
}
}
pub struct DHT11<PIN, STATE> {
pin: PIN,
_state: PhantomData<STATE>,
clock_info: u32,
}
impl<PIN, E, STATE> DHT11<PIN, STATE>
where PIN: InputPin<Error = E> + OutputPin<Error = E>
{
fn is_bit_high(&self, _low_duration: u32, high_duration: u32) -> bool {
#[cfg(feature = "use-dwt")]
{
let cycles_per_us = self.clock_info / 1_000_000;
high_duration > (40 * cycles_per_us)
}
#[cfg(not(feature = "use-dwt"))]
{
high_duration > _low_duration
}
}
fn wait_for_state(&mut self, target_high: bool, _delay: &mut impl DelayNs) -> error::Result<u32, E> {
#[cfg(feature = "use-dwt")]
{
let cycles_per_us = self.clock_info / 1_000_000;
let start = DWT::cycle_count();
while self.pin.is_low().map_err(DHT11Error::PinError)? == target_high {
if DWT::cycle_count().wrapping_sub(start) > (TIMEOUT_US * cycles_per_us) {
return Err(DHT11Error::Timeout);
}
}
Ok(DWT::cycle_count().wrapping_sub(start))
}
#[cfg(not(feature = "use-dwt"))]
{
let mut count: u32 = 0;
while self.pin.is_low().map_err(DHT11Error::PinError)? == target_high {
count += 1;
if count > TIMEOUT_CYCLES { return Err(DHT11Error::Timeout); }
}
Ok(count)
}
}
fn send_start_signal(&mut self, delay: &mut impl DelayNs) -> error::Result<(), E> {
self.pin.set_high().map_err(DHT11Error::PinError)?;
delay.delay_ms(1);
self.pin.set_low().map_err(DHT11Error::PinError)?;
delay.delay_ms(20);
self.pin.set_high().map_err(DHT11Error::PinError)?;
self.wait_for_state(false, delay)?;
Ok(())
}
}
impl<PIN, E> DHT11<PIN, Idle>
where PIN: InputPin<Error = E> + OutputPin<Error = E>
{
#[cfg(feature = "use-dwt")]
pub fn new(pin: PIN, clock_info: HertzU32) -> DHT11<PIN, Uninitialized> {
DHT11 {
pin,
_state: PhantomData,
clock_info: clock_info.to_Hz(),
}
}
#[cfg(not(feature = "use-dwt"))]
pub fn new(pin: PIN) -> DHT11<PIN, Uninitialized> {
DHT11 {
pin,
_state: PhantomData,
clock_info: 0,
}
}
#[cfg(feature = "use-dwt")]
pub fn new_and_initialized(pin: PIN, delay: &mut impl DelayNs, clock_info: HertzU32) -> error::Result<DHT11<PIN, Initialized>, E> {
let uninit = Self::new(pin, clock_info);
let init = uninit.initialize(delay)?;
delay.delay_ms(2_000);
Ok(init)
}
#[cfg(not(feature = "use-dwt"))]
pub fn new_and_initialized(pin: PIN, delay: &mut impl DelayNs) -> error::Result<DHT11<PIN, Initialized>, E> {
let uninit = Self::new(pin);
let init = uninit.initialize(delay)?;
delay.delay_ms(2_000);
Ok(init)
}
}
impl<PIN, E> DHT11<PIN, Uninitialized>
where PIN: InputPin<Error = E> + OutputPin<Error = E>
{
pub fn initialize(mut self, delay: &mut impl DelayNs) -> error::Result<DHT11<PIN, Initialized>, E> {
self.send_start_signal(delay)?;
Ok(DHT11 {
pin: self.pin,
_state: PhantomData,
clock_info: self.clock_info,
})
}
}
impl<PIN, E> DHT11<PIN, Initialized>
where PIN: InputPin<Error = E> + OutputPin<Error = E>
{
pub fn read_temp_and_hum(&mut self, delay: &mut impl DelayNs) -> error::Result<Measurement, E> {
self.send_start_signal(delay)?;
self.wait_for_state(true, delay)?; self.wait_for_state(false, delay)?;
self.wait_for_state(true, delay)?;
self.wait_for_state(false, delay)?;
let mut data = [0u8; 5];
for i in 0..40 {
let low_duration = self.wait_for_state(true, delay)?;
let high_duration = self.wait_for_state(false, delay)?;
if self.is_bit_high(low_duration, high_duration) {
data[i / 8] |= 1 << (7 - (i % 8));
}
}
let checksum_calculated = data[0]
.wrapping_add(data[1])
.wrapping_add(data[2])
.wrapping_add(data[3]);
if checksum_calculated != data[4] { return Err(DHT11Error::ChecksumMismatch); }
Ok(Measurement {
humidity: Humidity {
integral: data[0],
decimal: data[1],
},
temperature: Temperature {
integral: data[2],
decimal: data[3],
},
checksum: checksum_calculated,
})
}
}