raspberrypi / pico-examples

BSD 3-Clause "New" or "Revised" License
2.87k stars 826 forks source link

dht11/22 code not working for pico (fix) #11

Open novaspirit opened 3 years ago

novaspirit commented 3 years ago

https://github.com/raspberrypi/pico-examples/blob/46078742c7f8dea8b5a0998c73b38ff970fb1b64/gpio/dht_sensor/dht.c#L62

found count > 16 timing for the dht11/22 to be off, for sensor to collect data this will need to be changed to count > 46

johnwalicki commented 3 years ago

I am also experiencing a problem with the dht.c code. It just reports: Humidity = 0.0%, Temperature = 0.0C (32.0F) Bad data

johnwalicki commented 3 years ago

The suggested change by @novaspirit fixes the problem reading the GPIO pin for the DHT sensor reading. Humidity = 24.7%, Temperature = 22.6C (72.7F)

aallan commented 3 years ago

Interesting. I wrote this one and it works as is for me.

Which (of the many different versions) of this sensor do you have?

johnwalicki commented 3 years ago

I wired a DHT-22 - it requires count > 46 I also tried a DHT-11 - that also requires count >46 but was less reliable. count > 16 didn't work with either.

novaspirit commented 3 years ago

the timing to retrieve the data on dht11/22 is very sensitive. i'm not sure if the timing on the pico is off? but adjusting the values seems to off set the timing. also i have changed the initial handshake on the code to 18ms and added gpio_put(DHT_PIN, 1) on line 47

gpio_set_dir(DHT_PIN, GPIO_OUT); gpio_put(DHT_PIN, 0); sleep_ms(18); gpio_put(DHT_PIN,1); sleep_us(40); this is the site i used for data http://www.rpiblog.com/2012/11/interfacing-temperature-and-humidity.html

johnwalicki commented 3 years ago

Inserting the timing changes suggested above by @novaspirit and reverting to count > 16 was not successful. I had to change it to count > 46 again

johnwalicki commented 3 years ago

@aallan - I would be happy to submit a PR if you would consider it.

My breadboard is here: DHT Pico image

alwynallan commented 3 years ago

Haven't tried it yet on Pico, but curious why the physical pull-up resistor, don't the GPIO pins offer that option gpio_pull_up()? Also could this protocol be implemented with PIO?

I've had experience with these sensors, and they self-heat if they're read too often. A rate-limit (15 sec/reading) would be worth implementing.

depinette commented 3 years ago

Same here with a Dollatek DHT-22. Without a debugger to help me understand the existing code or an oscilloscope to check if my DHT-22 was working, I choose to write a straightforward, step by step, version of the reading process. https://gist.github.com/depinette/81d0c6edcdaad67f827896ce1d13b776

dghoti commented 3 years ago

I changed the (pull up) resistor to 4.7Kohm and edited the code to as follows: timing changed on two lines at approx 51&53 changed " if (count >16) data[j / 8] |= 1; " to " if (count >46) data[j / 8] |= 1;" on line (after the above changes) 71

My DHT11 is now working perfectly reliably.

Full code below:

/**

include

include

include "pico/stdlib.h"

include "hardware/gpio.h"

ifdef PICO_DEFAULT_LED_PIN

define LED_PIN PICO_DEFAULT_LED_PIN

endif

const uint DHT_PIN = 15; const uint MAX_TIMINGS = 85;

typedef struct { float humidity; float temp_celsius; } dht_reading;

void read_from_dht(dht_reading *result);

int main() { stdio_init_all(); gpio_init(DHT_PIN);

ifdef LED_PIN

gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);

endif

while (1) {
    dht_reading reading;
    read_from_dht(&reading);
    float fahrenheit = (reading.temp_celsius * 9 / 5) + 32;
    printf("Humidity = %.1f%%, Temperature = %.1fC (%.1fF)\n",
           reading.humidity, reading.temp_celsius, fahrenheit);

    sleep_ms(2000);
}

}

void read_from_dht(dht_reading *result) { int data[5] = {0, 0, 0, 0, 0}; uint last = 1; uint j = 0;

gpio_set_dir(DHT_PIN, GPIO_OUT);
gpio_put(DHT_PIN, 0);
sleep_ms(18);
gpio_set_dir(DHT_PIN, GPIO_IN);
sleep_us(40);

ifdef LED_PIN

gpio_put(LED_PIN, 1);

endif

for (uint i = 0; i < MAX_TIMINGS; i++) {
    uint count = 0;
    while (gpio_get(DHT_PIN) == last) {
        count++;
        sleep_us(1);
        if (count == 255) break;
    }
    last = gpio_get(DHT_PIN);
    if (count == 255) break;

    if ((i >= 4) && (i % 2 == 0)) {
        data[j / 8] <<= 1;
        if (count >46) data[j / 8] |= 1;
        j++;
    }
}

ifdef LED_PIN

gpio_put(LED_PIN, 0);

endif

if ((j >= 40) && (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))) {
    result->humidity = (float) ((data[0] << 8) + data[1]) / 10;
    if (result->humidity > 100) {
        result->humidity = data[0];
    }
    result->temp_celsius = (float) (((data[2] & 0x7F) << 8) + data[3]) / 10;
    if (result->temp_celsius > 125) {
        result->temp_celsius = data[2];
    }
    if (data[2] & 0x80) {
        result->temp_celsius = -result->temp_celsius;
    }
} else {
    printf("Bad data\n");
}

}

andreclerigo commented 3 years ago

The correct line to work should be if (count >46) data[j / 8] |= 1; instead of if (count >16) data[j / 8] |= 1; Also it's worth mentionning that in order to work with DHT11 the resistance should be 4.7k Ohm so if you´re using a board with onboard resistance above 4.7k you'll need to add another resistance in parallel to pull it down.

MrYsLab commented 3 years ago

+1 on the @novaspirit solution. Works on DHT22 and DHT11 sensors for me with and without the 10k pullup.

vmilea commented 3 years ago

According to the datasheet, the DHT sensor sends a 26-28 μs pulse for bit value 0, and 70 μs for bit value 1. But real timings are a bit off. Results from logic analyzer with a DHT11 (manufactured by ASAIR) and a couple of DHT22s (no-name) I had:

datasheet DHT11 DHT22
short pulse (0 bit) 26-28 μs 24-25 μs 30-36 μs
long pulse (1 bit) 70 μs 70-72 μs 62-71 μs
low voltage (spacing) 50 μs 53-54 μs 42-44 μs

Despite the variation, any pulse >50 μs should mean bit 1.

On the Pico side: sleep_us() has some alarm related overhead, the loop is actually delayed around 1.3ms and count increases too slowly. This is why the original code had such a low threshold (count > 16). So we need a more accurate way to measure time.

In my testing, this works reliably:

for (uint i = 0; i < MAX_TIMINGS; i++) {
    uint count = 0;
    while (gpio_get(DHT_PIN) == last) {
        count++;
        busy_wait_us_32(1);
        if (count == 255) break;
    }
    last = gpio_get(DHT_PIN);
    if (count == 255) break;

    if ((i >= 4) && (i % 2 == 0)) {
        data[j / 8] <<= 1;
        if (count > 50) data[j / 8] |= 1;
        j++;
    }
}

(edited to include real timings from sensors and safer threshold)

drankinatty commented 2 years ago

I pulled my hair out on this one. Glad I found this thread. Got the DHT22 from Amazon about $9 brand was Aideepen (generic something). Using the SDK test code I got nothing bug "bad data" output. data[] values were all 255. Minimum voltage was listed as 3.3V, and on the board I was getting 3.24V. So I even tried driving the sensor with a 5V supply - no change.

Going with what seem to be the consensus changes above and "Presto!" the sensor started working -- and dead accurate on the temperature right out of the box. (no independent measure of humidity). The changes were to the initialization and the 16 -> 46 conditional change for count. In the code that is:

    gpio_set_dir(DHT_PIN, GPIO_OUT);
    gpio_put(DHT_PIN, 0);
    sleep_ms(18);
    gpio_put(DHT_PIN,1);
    sleep_us(40);
    gpio_set_dir(DHT_PIN, GPIO_IN);

and

            if (count > 46) {
                data[j / 8] |= 1;
            }

(side-note: in the code you will find the replaced line if (count > 16) data[j / 8] |= 1; unguarded and altogether on a single line. We should strive to have all loops and conditionals affirmatively guarded and not run-together on single lines. Just a suggestion, not a criticism)

With those changes, the sensor magically sprang to life and all is good.

MKesenheimer commented 1 year ago

You can also solve the issue with the wrong resistors by adding a software pull-up:

void read_from_dht(dht_reading *result) {
  ...
  gpio_set_dir(DHT_PIN, GPIO_OUT);
  gpio_put(DHT_PIN, 0);
  sleep_ms(18);
  gpio_put(DHT_PIN, 1);
  sleep_us(40);
  gpio_set_dir(DHT_PIN, GPIO_IN);
  ...
}

int main() {
  ...
  gpio_init(DHT_PIN);
  gpio_pull_up(DHT_PIN);
}
ic3man5 commented 3 weeks ago

This is still an issue. I modified the initial code because it was causing IO contention against my AM2302 sensor I got from Amazon. The AM2302 responds about 20us into letting the pin go high. Without the 40us delay I'd get "bad data" every once in a while.

void read_from_dht(dht_reading *result) {
    int data[5] = {0, 0, 0, 0, 0};
    uint last = 1;
    uint j = 0;

    gpio_set_dir(DHT_PIN, GPIO_OUT);
    gpio_put(DHT_PIN, 0);
    sleep_ms(2);
    gpio_set_dir(DHT_PIN, GPIO_IN);
    sleep_us(40);
    // ...
    if (count > 46) data[j / 8] |= 1;
    // ...
}