michaelbeaumont / dht-sensor

Rust embedded-hal based driver for the DHT11/DHT22 sensor

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

bluepill example

pdgilbert opened this issue · comments

I have been trying to get your stm32f042 example working on a bluepill using stm32f1xx_hal from git. I had to modify the clock and pin settings a bit because of hal and MCU differences. I've tried with dht-sensor release (0.1.1) and with the github version. My code (below) compiles but running stalls after "Waiting on the sensor..." I hope you can make suggestions and answer some questions:

  • Is the clock setting only for the delay or does it affect the data rate?
  • I've tried with pins pa1 and pa0 with no luck on either. What mode is the open drain data pin supposed to be in? (I'm assuming TIM or TSC but I'm not sure which.)
  • Possibly the problem is with my modification to cortex_m::interrupt::free(). In your code I'm not sure if the the enclosure is executed only when the pin is first set, or every time it is read. In the the first case I think my setting of ``into_open_drain_output()``` is ok with newer hal versions, but in the second case I've probably messed up.
  • I'm running the DHT11 on 3v, but I also tried on 5v. Should it matter?

If it's not already obvious, I am an newbie so really appreciate explanations. Thanks.

#![no_std]
#![no_main]

use crate::hal::{delay, prelude::*, stm32};
//use cortex_m;
use cortex_m_rt::entry;
use cortex_m_semihosting::hprintln;
use panic_halt as _;
//use stm32f0xx_hal as hal;
use stm32f1xx_hal as hal;

use dht_sensor::*;

#[entry]
fn main() -> ! {
    let  p = stm32::Peripherals::take().unwrap();
    let cp = stm32::CorePeripherals::take().unwrap();
    //let mut rcc = p.RCC.configure().sysclk(8.mhz()).freeze(&mut p.FLASH);
    let mut rcc = p.RCC.constrain();
    let clocks = rcc.cfgr.adcclk(2.mhz()).freeze(&mut p.FLASH.constrain().acr);
   
    // This is used by `dht-sensor` to wait for signals
    //let mut delay = delay::Delay::new(cp.SYST, &rcc);
    let mut delay = delay::Delay::new(cp.SYST, clocks);   
    
    // this is long so a watch can be used to check it.
    //hprintln!("10 second wait test...").unwrap();
    //delay.delay_ms(10000_u16);
    //hprintln!("done Waiting.").unwrap();

    // This could be any `gpio` port
    //let gpio::gpioa::Parts { pa1, .. } = p.GPIOA.split(&mut rcc);
    let mut gpioa = p.GPIOA.split(&mut rcc.apb2);

    // The DHT11 datasheet suggests 1 second
    delay.delay_ms(1000_u16);

    // An `Output<OpenDrain>` is both `InputPin` and `OutputPin`
    //let mut pa1 = cortex_m::interrupt::free(|cs| pa1.into_open_drain_output(cs));
    let mut pa1    = gpioa.pa1.into_open_drain_output(&mut gpioa.crl); 

    hprintln!("Waiting on the sensor...").unwrap();

    match dht11::Reading::read(&mut delay, &mut pa1) {
        Ok(dht11::Reading {
            temperature,
            relative_humidity,
        }) => hprintln!("{}C, {}% RH", temperature, relative_humidity).unwrap(),
        Err(e) => hprintln!("Error {:?}", e).unwrap(),
    }
    hprintln!("Looping forever now, thanks!").unwrap();

    loop {}
}

Hi! Glad to try and help. Keep in mind I'm not much more than an embedded newbie myself... 🙂

  1. The data rate of the sensor is fixed and doesn't depend on the clock, like you said it's only important for the implementation of Delay.
  2. You should be able to use any GPIO pin, I don't think they're in either TSC or TIM mode by default (again not 100% but I don't think those modes are relevant for input/output)
  3. No, you're right, the enclosure is executed in the same statement, so once and the pin mode is immediately set. Like you mentioned HALs differ in how and when they use cortex_m::interrupt::free.
  4. I think either is fine? (3.3V-5.5V)

Check out and try to decipher the datasheet.
It's hard to say what might be wrong (unfortunately I don't have a blue pill board).
If I were debugging it I would:

  • Of course double check that the sensor is wired correctly (pull up resistor)
  • Convince myself that open drain mode really works for both input and output, by testing both separately with a very simple case.

I did test this pretty extensively with both the DHT11 and DHT22 and didn't ever notice any flakiness (e.g. due to timing). However, my understanding of failure modes of embedded software like this is relatively limited...

Hi @michaelbeaumont,
I have had the same issue. There are two possible infinite loops in the code that will never return if the sensor does not respond.

while pin.is_low()? {}
while pin.is_high()? {}

After some investigation it seems that those infinite loops can be triggered in two different cases (tested on the bluepill):

  1. Not initializing the pin to high. The likely explanation here is that on the bluepill by default the pin is low, which confuses the sensor into thinking we want a measurement. Then when the actual read is called the polling has been done too quickly and we get stuck in one of the two loops. Or a similar problem.
  2. If the sensor is pooled too quickly after a measurement. When I forgot a delay in my main loop, I ended up stuck in one of the while loops in the read method.

It's probably worth having some form of timeout in those loops to avoid getting stuck if the sensor is not ready or is malfunctioning. What do you think?

I did some quick tests and changed the two while loops from read to this:

while_with_timeout(delay, || pin.is_low(), 10)?;
while_with_timeout(delay, || pin.is_high(), 10)?;

Where while_with_timeout is defined as:

fn while_with_timeout<E, D, F>(delay: &mut D, func: F, timeout_ms: u16) -> Result<(), DhtError<E>> 
where
    D: Delay,
    F: Fn() -> Result<bool, E>
{
    let mut count = 0;

    while func()? {
        delay.delay_us(10_u8);
        count += 1;
        if count >= timeout_ms*100 {
            return Err(DhtError::Timeout);
        }
    }
    Ok(())
}

This approach seems to solve the problems I had. I don't get stuck if the pin is in a weird state before the read or if I read to quickly.

The timings used for the delay and timeouts are arbitrary, I will check on the oscilloscope for more precise timings with the DHT22. I saw a couple of other loops that should be changed too if we want to be 100% robust.

I will make a pull request after cleaning up the code.

@azerupi Awesome, thanks! Sorry, I hadn't gotten around to testing with the bluepill yet. The Timeout is a great way to handle it. Feel free to add the two "common mistakes" you ran into to the docs as well!