Friday, 19 December 2014

DHT22 Temperature/RH Sensor on the RaspberryPi

The DHT22 is a low cost, single wire sensor which measures temperature and relative humidity.

I started out just playing around with this thing, just wiring it to a Pi to see it working.

But I got sucked in much deeper into thinking about how it worked and assessing its limitations.

Output Data Stream

The DHT22 produces a serial stream of data. In other words the sensor has just one "wire" over which it sends data and receives commands.

Pin 1 = +V, 2 = data, 3 =no connect, 4 = 0V

It does this by simply raising and lowering the voltage level, and varying the amount of time that the wire is either "high" or "low". In my circuit "high" is approximately 3.3 Volts, while low is approximately 0 Volts.

The data output consists of a series of low-to-high-to-low sequences where each sequence represents either a logical 0 or a 1. 

A logical 1 waveform looks like this:-

While logical 0 is like this:-

There should be 40 of these zeros and ones in a complete data set, and this data stream may start out looking something like this:-
00000010 00110101 0000...
The first 2 bytes contain the rh (relative humidity) value. In this example:-
02hex & 34hex = 234hex = 564
As data values are 10 x actual readings, 564 is an rh of 56.4%.

Likewise, the third and forth bytes represent 10 x the temperature reading.

The Checksum

In order to validate the integrity of the sensors transmitted data, the unit generates a simple checksum, which occupies that last byte of the 40 bit data stream.

This sensor calculates the checksum by adding the 4 data bytes together, and using only the lower 8 bits of the result as the 1 byte checksum.

Here is an example where the rh is 60.2% and temperature is 23.1'C:-
60.2%: 602 = 025Ahex, so byte 1 = 02hex and byte 2 = 5Ahex
23.1: 231 = E7hex, so byte 3 = 00hex and byte 4 = E7hex
02h + 5Ah + 00h + E7h = 143h
So the high byte is disgarded, and the checksum becomes 43hex.

When our system [software] reads this data it simply repeats the process and checks the result against the checksum. If there was a data transmission or reception error, our calculated checksum will not match the checksum transmitted from the sensor, so we can ignore this bad data.

There are of course a couple of exceptions. If the data error is in the 1st or 3rd bytes, this error will not be detected by the checksum, because we only use the lower 8 bits of the result. Also, it is possible that 2 or more errors may cancel one another out (e.g. a -1 error in rh and a -1 error in received checksum).

However, in the first case, an error in the 1st or 3rd bytes will make a very large difference to either the rh or temperature. For example, 01hex in the first byte is 256. This would be a change of 25.6% rh (or 25.6'C for the 3rd byte).

In the second case? Well, someone with good maths abilities could probably tell me the likelyhood of canceling errors.

Connecting sensor to Pi

The DHT22 is a four terminal package, but only pins 1, 2 & 4 are used.

I simply plugged it into a breadboard and wired as follows:-

Initially I fitted a 10k Ohm resistor to pins 1 & 2. During testing I replaced this with a 4.7k and also added a 0.1uF capacitor across the supply pins (1 & 4) although these changes did not appear to make any significant improvement in performance.

Testing with wiringPi

Once again I'm relying on Gordon's wiringPi software, and here are a few points regarding the notes that follow.

Once wiringPi is downloaded (use the latest snapshot) it should be unzipped and located on the RaspberryPi in a suitable folder (e.g. /home/pi/wiringPi-version).

The Pi uses pcManFM filemanager. If you navigate to a folder then press F4, a terminal window will open at that point in the file system. So where I say: run sudo ./rht03 in examples, I mean navigate to /home/pi/wiringPi-version/examples, press F4, and type: sudo ./rht03 in terminal.

So lets start by building wiringPi:-
Run ./build from your wiringPi-version folder. The terminal should show something like this:-

wiringPi Build script

WiringPi Library
[Compile] wiringPi.c
[Compile] wiringSerial.c
[Compile] wiringShift.c
[Compile] piHiPri.c

All Done

Now open rht03.c in the examples folder (e.g. edit in LeafPad).
Change pin number to suit your wiring using wiringPi pin numbers, in my case:-
#define    RHT03_PIN    15

Run make rht03 from the examples folder.

Now run the demo program by typing: sudo ./rht03 from the examples folder.

You may start to see rh and temperature readings displayed in your terminal. However, you may also notice your Pi soon becomes paralyzed as the cpu load sits at 100%.

Put the little critter out of its misery by hitting <ctrl><c>

I opened rht03.c again at added a 5 second sleep:

#include <stdio.h>
#include <unistd.h>

#include <wiringPi.h>
#include <maxdetect.h>

#define    RHT03_PIN    15
  for (;;)
    delay (100) ;

    if (!readRHT03 (RHT03_PIN, &newTemp, &newRh))

After saving, making and running rht03 again, I found the cpu load to be reasonably low, and the screen spat out readings from time to time. Sometimes the readings appeared 5 seconds apart, and sometimes there were much longer gaps.

I had another look at rht03.c and noticed that it doesn't output results unless they have changed since last time, hence the apparently erratic display rate.

This demo program calls readRHT03 which is in ../devLib/maxdetect.c. This file contains most of the routines used to talk to the DHT22 sensor.

Very basically: readRHT03 calls maxDetectRead which "wakes up" the sensor by holding the data line low for 10ms and then pulsing it high for 40us. It then calls maxDetectLowHighWait and waits for the data line to switch from a high to a low state (this is the signal from the sensor indicating that data transmission is about to follow).

The maxDetectRead routine then calls maxDetectClockByte which checks if the data line is a 1 or a 0 (after a 30us delay) and uses this to build up the data, bit-by-bit.

I decided to sprinkle this code with printf so I could see more closely what was going on.

#include <stdio.h>

int maxDetectRead (const int pin, unsigned char buffer [4])
  int i ;
  unsigned int checksum ;
  unsigned char localBuf [5] ;

// Wake up the RHT03 by pulling the data line low, then high
//    Low for 10mS, high for 40uS.

  pinMode      (pin, OUTPUT) ;
  digitalWrite (pin, 0) ; delay             (10) ;
  digitalWrite (pin, 1) ; delayMicroseconds (40) ;
  pinMode      (pin, INPUT) ;

// Now wait for sensor to pull pin low

  maxDetectLowHighWait (pin) ;

// and read in 5 bytes (40 bits)

  for (i = 0 ; i < 5 ; ++i)
    localBuf [i] = maxDetectClockByte (pin) ;

  checksum = 0 ;
  for (i = 0 ; i < 4 ; ++i)
    buffer [i] = localBuf [i] ;
    printf("localbuf: %d: %#x\n", i, localBuf [i]);
    checksum += localBuf [i] ;
  checksum &= 0xFF ;
  printf("checksum: %#x...Buff: %#x....", checksum, localBuf[4]);
  return checksum == localBuf [4] ;

 * readRHT03:
 *    Read the Temperature & Humidity from an RHT03 sensor

int readRHT03 (const int pin, int *temp, int *rh)
  static unsigned int nextTime   = 0 ;
  static          int lastTemp   = 0 ;
  static          int lastRh     = 0 ;
  static          int lastResult = TRUE ;

  unsigned char buffer [4] ;

// Don't read more than once a second

  if (millis () < nextTime)
    *temp = lastTemp ;
    *rh   = lastRh ;
    return lastResult ;
  lastResult = maxDetectRead (pin, buffer) ;
  printf("lastResult: %d\n", lastResult);
  if (lastResult)
    *temp      = lastTemp   = (buffer [2] * 256 + buffer [3]) ;
    *rh        = lastRh     = (buffer [0] * 256 + buffer [1]) ;
    nextTime   = millis () + 2000 ;
    return TRUE ;
    return FALSE ;

After running ./build in wiringPi-version folder and restarting sudo ./rht03 I get an output in terminal like this.

You can see a checksum error for the data block with lastResult: 0

I'm probably seeing approx 15% of results with checksum errors. I don't know if this is typical for this sensor.

Testing with Gambas

In order to use Gambas to write a test program, I hacked wiringPi (similar to the method I used here) to create a single library that supports this temperature sensor.

The wiringPi routine was then declared in Gambas like this:-

Public Extern readRHT03(iPin As Integer, ptrTemp As Pointer, ptrRH As Pointer) As Integer

...and I used a Gambas routine a bit like this:-

Public Sub ReadTemp()
Dim iReply As Integer
Dim intTemp As Integer
Dim intRH As Integer
  iReply = readRHT03(15, VarPtr(intTemp), VarPtr(intRH))
  If iReply > 0 Then
    Inc lngReadings
    If blnStarted Then
      If intTemp > intLastTemp + 50 Or intTemp < intLastTemp - 50 Then
        Inc lngErrorCount
      If intRH > intLastHumid + 50 Or intRH < intLastHumid - 50 Then
        Inc lngErrorCount
      blnStarted = True

    intLastTemp = intTemp
    intLastHumid = intRH
    Label1.text = CStr(intTemp/10) & "'C"
    Label2.text = CStr(intRH/10) & "%"

Note that intTemp and intRH are 10x the actual value, so have to be divided by 10 for display.

This test program took readings from the DHT22 at 10 second intervals and also recorded the total number of readings and the number of errors. I did not record checksum errors, I only recorded readings which were either more than 5'C or 5% different from the previous reading. So this error count may include data error in the high order data byte which do not affect the calculated checksum.

In approximately 3500 readings I recorded 5 errors.


Since the DHT22 is designed to measure temperature and relative humidity in an environment like a house, it is not too much of a problem to drop readings when a checksum error is detected, or when a reading is clearly very different from previous and subsequent readings.

So with that in mind I should be able to find a use for this thing.

See also: DHT22: is it damp under my house?

1 comment:

  1. Thanks man, this is only guide that got my AM2302 working without any problems, cheers!