Salutation, comrades ! Today’s driver library targets an unusual device : the DHT22 temperature and humidity sensor package, also known as AM2302.
What makes it unusual is its proprietary one-wire interface, which has nothing to do with the Dallas Semiconductor 1-Wire interface. The DHT22’s interface requires unusual I/O programming and that’s where I come in.
Before we get started, let’s talk about what this sensor can do for you. I call it a sensor package because it appears to be two distinct sensors mounted together with some sort of microcontroller for sampling and interfacing.
The DHT22 can measure relative humidity from 0 to 100 % with 0.1% resolution. Note that 100% RH doesn’t mean “dunked in water”. That will kill your sensor. And maybe other things. To learn more about relative humidity, try this Wikipedia page.
It can also measure temperature from -40°C to 80°C, with 0.1°C resolution.
As with any sensor, keep in mind that resolution does not mean accuracy.
The datasheet talks about a 0.5 Hz sampling rate, meaning one humidity and temperature measurement every two seconds. However it appears to work happily at 1 Hz. Any faster and you’ll start getting duplicate samples.
In other words, the DHT22 is good for taking environmental measurements over long periods. You won’t be using it to regulate your blast furnace or grandpa’s cryonic coffin.
The DHT22 isn’t a very complex thing. It only has four pins :
- VCC and two GND pins to power it. Use the same voltage as your microcontroller I/O pins (in our case, 3.3V)
- DATA to read it.
So it’s safe to say you could easily use the sensor itself and wire it using your mad skillz with the soldering iron. But if your ESP8266 comes in the form of a WeMos D1 Mini module, you also have the option to get this sensor as a “shield”. Here’s what it looks like :
A word of caution about self-heating : depending on how you mount this shield, the nearby ESP8266 may very well heat-up the DHT22, introducing an error of several degrees in your temperature measurements. You should use this shield only for training or firmware development… or you should take measures to counter this effect. I don’t know the applications you have in mind so you’re gonna have to figure it out on your own.
As usual, you’ll find it in my ESP8266 GitHub repository. The DHT22 branch contains the driver library (which you’ll find easily) and example code in the “user_main.c” file. If your eyes just glazed over reading these two sentences, I advise you start reading this series from the beginning. You’ll get complete instructions on how to use the source code.
As usual, once you checkout the DHT22 branch, you can compile and flash directly. If flashing fails, it probably means you forgot to match your makefile’s COM port to what Windows assigned your ESP8266. Happens to me all the time.
The example code assumes you have connected an ILI9341 LCD module to your ESP8266. If you haven’t, you can read how to do that in the relevant article.
Choice of GPIO
Because I’m using a WeMos DHT22 shield, I’ve had to choose pin D4 (GPIO 2), but you can change that value in the library’s defines. You’ll find those near the top of the “dht22.h” header.
You might recall that the ESP8266 modules use GPIO 2 to drive a blue LED. Whenever you read the sensor, you should see the LED flash briefly. It’s a simple and useful indicator that everything works.
Any GPIO pin can be used with this library except GPIO16 (D0). On the WeMos D1 Mini, that means you can use pins D1 through D8. You can read more about GPIO in my world-famous article on the topic.
Multi-sensor support, or lack thereof
My library only supports reading from a single DHT22. There’s a good reason for that : it makes no sense to support several. Read me out.
In theory, you could connect one DHT22 to each of the 8 GPIO pins available on the ESP8266 that support my library. But then you need to consider the cost of connecting all these sensors to a single ESP8266.
Obviously you’re not going to place them on top of each other : you’d get the same readings from each sensor. You’ll have to wire your sensors so you can place them in different locations. For example the different rooms in your home. And the DHT22 actually supports transmission lines of up to 100 meters.
And if you do take “advantage” of that feature, I guarantee you’ll be spending more money on cable than if you just bought an ESP8266 for each one of your sensors.
Besides, the whole point of using an ESP8266 is to go wireless.
Blocking vs. non-blocking
My library offers two different functions to read the DHT22. One is a simple function that won’t return until it has completed a read. The other uses a GPIO interrupt to let your ESP8266 do other things while a read is in progress.
You might be wondering why I went to that much trouble. Well, the DHT22 is something of a slow-poke. Even with my optimized timings, a complete read can take up to 6 milliseconds. For two 16-bit samples. Crazy, I know.
You may want to integrate the ESP8266 and DHT22 in a dedicated networked sensor device. In that case, it doesn’t matter if reading the sensor ties down your processor for a while : your device won’t be doing anything else until it has a sensor reading to transmit.
But you may also want to use the DHT22 as part of a more complex project, with other tasks running in parallel. In that situation, keeping the processor stuck in one task for several milliseconds might be a Bad Idea ™.
It’s very short, so I will document it here :
The “dht22_init” function (no argument, no return value) initializes the GPIO pin for DHT22 operation. You must call this function before attempting to read the sensor or face dire consequences. Such as nothing happening.
The “dht22_read” function is the blocking variant of the driver. It may take up to 6 ms to execute and most of that time is spent in a critical section, meaning your other FreeRTOS tasks won’t execute until it returns.
The “dht22_read_ed” function is the non-blocking variant of the driver. “ed” stands for “event-driven”, which is snooty engineer talk for “interrupt-based”. This function yeilds the CPU after just 0.1 millisecond, and then the reading process is handled by an interrupt service routine (ISR).
Use the “dht22_read_ed_busy” function to determine if an event-driven read is in progress. If so, it will return 1.
Whether you use the blocking or non-blocking driver, the samples will end-up in three global variables. Those are declared as extern, you’ll need to instantiate in any of your source file where you need them :
“sample_rh” (int) is the relative humidity in tenths of percent.
“sample_t” (int) is the temperature in tenths of degree Centigrade.
“sample_valid” (int) is non-zero if these two values resulted from a successful read of the DHT22, meaning there was no timeout and the checksum was correct.
Respect interrupt-based reading
If you intend to use the ED version, it very likely means you have other things you want to do in parallel. You need to make sure that whatever else you do doesn’t interfere with sensor reading.
If your other tasks use critical sections to prevent being interrupted, make sure those critical sections are very short. Delaying the DHT22 ISR means you will introduce an error in its decoding of the DHT22 data bits. There’s some margin for error but if your critical sections last longer than a few microseconds, then it will result in read failure.
My advice : if you need to have a long critical section, just make sure not to start reading the DHT22 just before. Check that no read is in progress, then enter your critical section.
About the example code
My example code shows you some of the inner workings of the driver. The driver basically measures the length of each bit sent by the DHT22 and derives the samples’ values from that information.
If you run the demo using the blocking driver, here’s what you’ll see :
Under the mandatory “vanity printf” you’ll see 41 pairs of numbers. Each one represents the duration of a DHT22 bit, as timed by the ESP8266. The first pair, in red, is the start bit. Each pair’s first number is how long the sensor’s DATA line stayed low and the second number, obviously, is how long the DATA line stayed high. The list reads left to right, top to bottom.
Except for the start bit, which carries no information, each bit’s state is defined by how long the DATA line stays up. As you can see, it’s three times longer for a “one” than for a “zero”. My library uses the length of the start bit (which is constant) to determine a threshold with which to decode the data bits. You can see the results if you run the non-blocking demo :
The non-blocking read function yields the ESP8266’s CPU to other tasks when the DATA line is low (since that part of the signal carries no useful data) so it doesn’t measure how long that takes. Instead it keeps track of the decoded bits for your viewing pleasure.
The line of five yellow numbers represents the 40 bits of data as bytes. Underneath, in cyan, are the decoded samples provided by the driver. Finally, the last line provides the humidity and temperature measurements in human-readable format.
My driver does not rely on any timing constant, only on the relative duration of each part of the DHT22’s signal. This means it doesn’t care how fast the ESP8266 clock is. It should also be impervious to variations from sensor to sensor.
If you intend to deploy the DHT22, you should read its datasheet : as with many environmental sensors, this little guy experiences aging. The humidity sensor seems to de-rate particularly fast.
The DHT22 is a cheap sensor. Perhaps a bit too cheap. I’m not too sure I’d trust it in the long run, at least as far as humidity measurements go. Still I recommend you take a look at its driver, if only to get a practical example of GPIO interrupts and bidirectional I/O programming.
Next time, we’ll be looking at the analog input of the ESP8266. In case you didn’t know, our little friend has a 10-bit ADC, and that’s always useful !
See you then !