0

The following code reads the DHT22 temperature and humidity values, assuming the pin value as the sensor's host pin. When the temperature drops below 0 °C, this code returns inadequate values in the range from -3200 to -3300 (most often the values range around -3275). When using an popular Adafruit library, the values are also incorrect. The code was not written by me, it originates from here, but the comments in the source are in Russian.

The problem is quite common, but there was no adequate solution provided in any case. It was mentioned in the Adafruit library issues quite a few times (here, here and here (related)), and on other websites. People even reported this issue on DHT11 and DHT21 sensors.

// "DHTLif.h"

#pragma once
#include <Arduino.h>
#include <Firmata.h>


uint64_t readingTime;


void DHTt(const uint8_t pin) {
    if ((millis() - readingTime) > 2000) {
        unsigned char receivedDHTData[5];
        float temperature, humidity;

        #define DHT_PORT PORTD
        #define DHT_DDR DDRD
        #define DHT_PIN PIND
        #define DHT_BIT pin
        int count = 32;
        unsigned char i, j;

        ReLoad: //Restarting point for error handling
        //=============MCU send START
        DHT_DDR |= (1 << DHT_BIT); //exit
        DHT_PORT &= ~(1 << DHT_BIT); //the pin's level is low, set it to high and wake up the sensor
        delay(18); //18 ms delay from the docs
        DHT_PORT |= (1 << DHT_BIT); //set the pin's level to low
        DHT_DDR &= ~(1 << DHT_BIT); //the pin as exit

        //=============Initialize DHT
        delayMicroseconds(50); //delay from the docs
        if (DHT_PIN & (1 << DHT_BIT)) { //DHT must return 0
            if (count != 0) {
                goto ReLoad;
                count--;
            } else {
                //send initialization error message
                Firmata.sendString("ERR;DHTt;INIT");
                return;
            }
        }

        delayMicroseconds(80);
        if (!(DHT_PIN & (1 << DHT_BIT))) { //the bus must be set to 0 after 80 ms
            if (count != 0) {
                goto ReLoad;
                count--;
            } else {
                //send weird behaviour error message
                Firmata.sendString("ERR;DHTt;BHVR");
                return;
            }
        }

        //===============Receive 40 bits of data
        while (DHT_PIN & (1 << DHT_BIT)); //wait until the bus is set to 1
        for (j = 0; j < 5; j++) { //loop for 0-4 bytes
            receivedDHTData[j] = 0;
            for (i = 0; i < 8; i++) { //receive bits and pack them into bytes
                while (!(DHT_PIN & (1 << DHT_BIT))); //wait until the bus is set to 0
                delayMicroseconds(30); //30 mcs delay from the docs
                if (DHT_PIN & (1 << DHT_BIT)) //if the pin is 1 after the delay,
                    receivedDHTData[j] |= 1 << (7 - i); //set the i'th bit to 1
                while (DHT_PIN & (1 << DHT_BIT)); //wait until the bus is set to 0
            }
        }

        if ((unsigned char)(receivedDHTData[0] + receivedDHTData[1] +
                receivedDHTData[2] + receivedDHTData[3]) != receivedDHTData[4]) { //checksum
            Firmata.sendString("ERR;DHTt;CHSM");
            return;
        }

        temperature = (receivedDHTData[3] * 0.1) + ((receivedDHTData[2] & 0b01111111) * 25.6); //calculating temperature for DHT22
        if (receivedDHTData[2] & 0b10000000) temperature *= -1; //if the temperature is negative
        humidity = (receivedDHTData[1] * 0.1) + (receivedDHTData[0] * 25.6); //calculating humidity for DHT22

        //form the final message
        char result[32], catres[8];
        readingTime = millis();
        strcpy(result, "OK;DHTt;");

        dtostrf(temperature, 5, 2, catres);
        strcat(result, catres);
        strcat(result, ";");

        dtostrf(humidity, 5, 2, catres);
        strcat(result, catres);

        Firmata.sendString(result);
        return;
    }
}

The only fix I found looks really silly and it does not work, the values doesn't drop below zero.

//The temperature is a 16 bit signed integer, 10 times the actual value in degrees Celsius
int16_t temperatureTimesTen = (int16_t)((receivedDHTData[2] << 8) | receivedDHTData[3]);
float temperature = (float)(temperatureTimesTen) * 0.1f;

//The maximum possible temperature for an DHT22 is 80 degrees Celsius
if (temperature > 80) temperature = temperature - 3276.7f;
Starter
  • 153
  • 12
  • That seems to be a bit of a silly way of combining bytes to form a 16 bit value if you ask me... – Majenko Oct 30 '21 at 17:18
  • I've got the exactly same code in another library. I'll try to put my DHT22 into the freezer tomorrow and see what happens. – PMF Oct 30 '21 at 21:12
  • @Majenko, at least this thing manages to work while the temperature is positive. I now wait until the temperature outside drops below zero, since my DHT22 is installed outside without a possibility to move it. Maybe, changing `temperature *= -1;` to `temperature *= -1.0f;` will help? – Starter Oct 31 '21 at 04:55
  • That's unlikely to help, because temperature is already a float at that point. – PMF Oct 31 '21 at 07:54

1 Answers1

-1

Looking at the data sheet (Page 3), the DHT22 uses, unlike the DHT11 and unlike the implementation in many libraries, a standard Base-2 format for the temperature. So converting the temperature should be as easy as:

// The temperature is a 16 bit signed integer, 10 times the actual value in degrees Celsius
int16_t temperatureTimesTen = (int16_t)((receivedDHTData[2] << 8) | receivedDHTData[3]);
float temperature = (float)(temperatureTimesTen) * 0.1;

I was not able to confirm this yet (couldn't get my sensor cold enough), but your observation that the wrong values report as being in the order of -3300 point to the same conclusion.

EDIT

I need to correct myself. The documentation is misleading, therefore my understanding of it was incorrect. After I finally got the possibility to really test my DHT22 in negative temperatures (it's snowing outside now), I found the correct formula to be:

int temp = ((receivedDHTData[2] & 0x7F) << 8 | receivedDHTData[3]) * 0.1;
// if MSB = 1 we have negative temperature
temp = ((receivedDHTData[2] & 0x80) == 0 ? temp : -temp);

return temp; // in degrees celsius

EDIT 2

For this particular sensor, decoding seems to be as follows. It appears that on negative temperatures, the resolution is only 1 degree, not 0.1 degrees.

float temperature = 0;
int32_t temp = ((receivedDHTData[2]) << 8 | receivedDHTData[3]);
// if MSB = 1 we have negative temperature
if (temp & 0x8000)
{
    temp = temp | 0xFFFF0000; // sign-extend
    temperature = temp; // Convert to negative whole-degrees
}
else
{
    temperature = (float)temp / 10.0f;
}
PMF
  • 1,184
  • 3
  • 16
  • I will try to implement this solution. I will get back to you shorty after getting any results. – Starter Nov 01 '21 at 10:58
  • That didn't help, the -3275-like values are still reported. – Starter Nov 03 '21 at 12:07
  • That is weird. Can you try to print out the values of `receivedDHTData[2]` and `receivedDHTData[3]`? The byte value at index 2 should be 255 (0xFF) if my assumption is right. – PMF Nov 03 '21 at 12:22
  • indeed, it does: https://skr.sh/i/031121/YWJ4uO4E.jpg?download=1&name=Скриншот%2003-11-2021%2016:26:01.jpg – Starter Nov 03 '21 at 13:26
  • When I feed my code with exactly these values I get the correct result. Maybe this is also a compiler/board issue? I have tested on an AVR board (Arduino Nano) – PMF Nov 03 '21 at 13:37
  • my board is also an AVR-based Arduino Uno. I am using this simple sketch: https://github.com/StarterCraft/Hyrex_AsQamm/blob/6eb87320997e0b2cf4a714d13f210cb1359f640e/AsQammArduino/DHTTest/DHTTest.ino – Starter Nov 03 '21 at 14:46
  • and this is a Serial.println-converted version of the code in question: https://github.com/StarterCraft/Hyrex_AsQamm/blob/6eb87320997e0b2cf4a714d13f210cb1359f640e/AsQammArduino/Features/DHTLifo.h – Starter Nov 03 '21 at 14:47
  • Please use the code I posted above exactly as it is, replacing lines 71 and 72. Do not squeeze it into one line (two casts on the same line may confuse some compilers). – PMF Nov 03 '21 at 16:09
  • replaced the lines, please check. – Starter Nov 04 '21 at 10:17
  • nothing has changed after changing this statement to a two lines. – Starter Nov 04 '21 at 11:16
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/131137/discussion-between-pmf-and-starter). – PMF Nov 04 '21 at 11:24
  • I just saw https://stackoverflow.com/questions/69870078/visual-studio-c-has-different-comparison-results-for-uint16-and-uint32?noredirect=1#comment123516299_69870078 Maybe this is related. You should maybe try a manual sign-extend? – PMF Nov 07 '21 at 18:43
  • Could you confirm your information with freezing the sensor? – Starter Nov 14 '21 at 03:37
  • No, I didn't have the time to test this again. I seriously wonder why you get different results than I am. I will try again. – PMF Nov 14 '21 at 13:09
  • @Starter I'm sorry this took so long, but I think the above formula should now do the trick. The data was different than what I expected from the documentation. – PMF Dec 03 '21 at 06:14
  • https://skr.sh/i/051221/kWFdQYc1.jpg?download=1&name=Скриншот%2005-12-2021%2014:27:41.jpg that didn't help, though : – Starter Dec 05 '21 at 11:28
  • @Starter: That looks like what I originally expected that it should be, but my observation showed a different binary pattern for negative temperatures. Are you sure you have a DHT22? These are the sensors with the white enclosure. – PMF Dec 05 '21 at 12:47
  • Yes, I am totally sure. This is the item card from the Aliexpress seller I've bought the sensor from. I use the `DHT22 Module` variant. Also, can you please share your binary data? https://github.com/StarterCraft/Hyrex_AsQamm/blob/29eb8f825140b98287feeb3cac1f1e4cc3ada88c/AsQammArduino/Features/DHTLifo.h#L72 is the code producing my output – Starter Dec 05 '21 at 13:25
  • My data was [2] = 0x80 and [3] = 0x04 for -0.4°C. Yours looks like base-2 encoded. Can you confirm that the correct temperature is -2.5°C? – PMF Dec 05 '21 at 15:35
  • Right now, according to local weather forecast provider (https://skr.sh/i/061221/qEcPFcRN.jpg), the temperature is around -9 degrees Celsius, and this is the current binary data: https://skr.sh/i/061221/fYC1gHnH.jpg – Starter Dec 06 '21 at 10:51
  • Also please fix your syntax: there is no `var` in C++, there is `auto` – Starter Dec 06 '21 at 10:54
  • 1111 1111 1111 0111 is -9 in standard base-2 encoding. There's no DHT variant I'm aware of that uses this encoding, particularly since all variants provide the temperature in tenths of a degree. Do you have the possibility to get a data set with a positive measurement? – PMF Dec 06 '21 at 12:10
  • can you offer me a way to decode this correctly? – Starter Dec 06 '21 at 13:06
  • I am afraid I can't do this, my sensor is installed statically outside – Starter Dec 06 '21 at 13:27
  • I think the following should do: `int32_t temperature = ((receivedDHTData[2] << 8) | receivedDHTData[3]); if (temperature & 0x8000) temperature |= 0xFFFF0000;` The result is then in whole degrees. – PMF Dec 06 '21 at 13:32
  • https://skr.sh/i/071221/bF9R2Yb2.jpg?download=1&name=Скриншот%2007-12-2021%2009:31:24.jpg Positive reading – Starter Dec 07 '21 at 06:31
  • It gets more confusing each time :-( As you show, this is decoded correctly, and 100101b is 37 decimal, which can (as specified) be correctly divided by 10 to result in 3.7°. (I'm assuming it's not 37° outside) I've more and more got the impression that you've got some chinese clone there that doesn't align with the official specs. I'll add the suggested code for this finding above, but it must remain a guess. – PMF Dec 07 '21 at 08:07
  • https://skr.sh/i/141221/775SSTjY.jpg?download=1&name=Скриншот%2014-12-2021%2008:36:51.jpg oumph, that's pretty cold. I think I need to multiply it by 0.1 – Starter Dec 14 '21 at 05:37
  • Yea, apparently. Then at least it would be consistent. Still not sure why my first suggestion didn't match then. However, I'm happy that it finally works for you. – PMF Dec 14 '21 at 10:36