Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Discuss development of drivers for external hardware and components, such as LCD screens, sensors, motor drivers, etc.
Target audience: Users and developers of drivers.
RinusW
Posts: 9
Joined: Wed Oct 07, 2015 10:51 am

Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby RinusW » Thu Feb 18, 2016 11:52 am

The DHT11/DHT22 are well known Humidity and Temperature sensors. But I couldn't find any example of using these devices with the WiPy. So I developed my own interface drivers. The communication protocol is a sort of one wire protocol, but unique to the DHT11/DHT22 devices. The site http://www.uugear.com/portfolio/dht11-humidity-temperature-sensor-module/ gives a good overview of the DHT11 and the communication protocol used. The DHT 22 uses the same protocol, but the interpretation of the result differs.

The protocol description indicates that we have to discriminate between high pulses of 28 us and 70 us. But if we use a loop to read an input pin and store its value:

Code: Select all

for i in range(100):
   dat[i] = pin()
it costs around 20 us. So there is no time to do anything else in this loop. Therefore I decided to use a method where I first sample the input stream. Only after the end of the stream, I will convert the samples to bits and assemble the message.

In order to read the message from the DHT11/DHT22 we need an I/O pin with open drain, assuming an external pull-up resistor. In the examples I use 'GP4'. De code for the sample function is:

Code: Select all

from machine import Pin, enable_irq, disable_irq
dat=Pin('GP4', Pin.OPEN_DRAIN)
dat(1)
def getval(pin) :
    ms = [1]*300
    pin(0)
    time.sleep_us(20000)
    pin(1)
    irqf = disable_irq()
    for i in range(len(ms)):
        ms[i] = pin()      ## sample input and store value
    enable_irq(irqf)
    return ms
Notice that we initiate the transfer by pulling the pin low for 20 ms. And notice that we disable interrupts while sampling the input. Otherwise we would not succeed in reading in a valid message. On the other hand the interrupt is disabled for around 6 ms.

We now have collected a list of sample values that we need to convert to bits. As indicated a 0-bit is encoded by a low followed by a short high, and a 1-bit as a low followed by a long high. This means we need to count the consecutive high samples (indicated by the value 1). But first we have to skip a start low pulse followed by a high pulse. The function that does this all is:

Code: Select all

def decode( inp):
   res= [0]*5
   bits=[]
   ix = 0
   try:
      if inp[0] == 1 : ix = inp.index(0, ix) ## skip to first 0
      ix = inp.index(1,ix) ## skip first 0's to next 1
      ix = inp.index(0,ix) ## skip first 1's to next 0
      while len(bits) < len(res)*8 : ##need 5 * 8 bits :
         ix = inp.index(1,ix) ## index of next 1
         ie = inp.index(0,ix) ## nr of 1's = ie-ix
         bits.append(ie-ix)
         ix = ie
   except:
      return( [0xff,0xff,0xff,0xff])
   ## … second part of function
Since we don't know if we collected enough samples, we use the try - except construct to catch errors in the index search we use next. We need to detect 40 bits, i.e. the length of 40 high values. We do this by searching first for the index of the first 1, and then searching for the index of the next 0. The difference is the number of 1's, and is a measure for the width of the pulse. For the time being we just store this count. We are ready when collected 40 counts for 5 bytes of 8 bits. If we run out of samples before that, an exception will occur and we return the impossible value of [0xff,0xf,0xff,0xff].

After counting the consecutive number of 1's for 40 bits we now need to determine the exact value of each bit. This is a simple operation. If the count is larger than 2, the bit value is 1, and otherwise the bit value is 0. The second part of the convert function does this:

Code: Select all

def decode( inp):
## … first part of the function
   for i in range(len(res)) :
      for v in bits[i*8 : (i+1)*8] :   #process next 8 bit
         res[i] = res[i]<<1  ##shift byte one place to left
         if v > 2 : res[i] = res[i]+1  ##and add 1 if lsb is 1
   if (res[0]+res[1]+res[2]+res[3])&0xff != res[4] :   ##parity error!
      print("Checksum Error")
      res= [0xff,0xff,0xff,0xff]
   return(res[0:4])
The conversion takes place in the double for-loop. Then a simple redundancy check follows. The sum mod 256 of the first 4 bytes should be equal to the last byte. If not then we have an error, probably due to a communication failure. Notice that we only have to return the first 4 bytes then.

Basically this is all we need to read humidity and temperature values from the DHT11 and DHT22 devices. But since the DHT22 gives a different meaning to the 4 byte result, we need to present the result differently, depending on the device. I therefore declared 2 additional functions:

Code: Select all

def DHT11(pin) :
   res = decode(getval(pin))
   print('Humidity = {},{}'.format(res[0],res[1]))
   print('Temperat = {},{}'.format(res[2],res[3]))
#   
def DHT22(pin) :
   res = decode(getval(pin))
   hum = res[0]*256+res[1]
   tem = res[2]*256 + res[3]
   if ( tem > 0x7fff ):
      tem = 0x8000 - tem
   print('Humidity = {},{}'.format(hum//10,hum%10))
   print('Temperat = {},{}'.format(tem//10,tem%10))
These functions are more or less self-explained. Notice that for the DHT22 the result should be interpreted as two 16 bit values giving humidity and temperature in a one decimal resolution, i.e. xx,x % RH and yy,y ⁰C.

Damien
Site Admin
Posts: 530
Joined: Mon Dec 09, 2013 5:02 pm

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby Damien » Sun Feb 21, 2016 12:00 am

That's really great work! Thanks for the write up.

User avatar
patvdleer
Posts: 36
Joined: Mon Jun 13, 2016 11:52 am
Location: Maastricht, NL
Contact:

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby patvdleer » Mon Jun 13, 2016 3:43 pm

how about; (haven't really tested it yet)

Code: Select all

from machine import Pin, enable_irq, disable_irq
import time


class DHTException(Exception):
    """ BaseException for DHT """


class DHTArgumentException(DHTException):
    """ ArgumentException for DHT """


class DHTValueException(DHTException):
    """ ValueException for DHT """


class DHTChecksumException(DHTException):
    """ ChecksumException for DHT """


class DHT(object):
    pin = None

    def __init__(self, pin):
        if not isinstance(pin, Pin):
            raise DHTArgumentException("%s is not an instance of machine.Pin" % pin)
        self.pin = pin

    def getval(self):
        ms = [1] * 300
        self.pin(0)
        time.sleep_us(20000)
        self.pin(1)
        irqf = disable_irq()
        for i in range(len(ms)):
            ms[i] = self.pin()
        enable_irq(irqf)
        return ms

    def decode(self, inp):
        res = [0] * 5
        bits = []
        ix = 0
        try:
            if inp[0] == 1:
                ix = inp.index(0, ix)  ## skip to first 0
            ix = inp.index(1, ix)  ## skip first 0's to next 1
            ix = inp.index(0, ix)  ## skip first 1's to next 0
            while len(bits) < len(res) * 8:  ##need 5 * 8 bits :
                ix = inp.index(1, ix)  ## index of next 1
                ie = inp.index(0, ix)  ## nr of 1's = ie-ix
                bits.append(ie - ix)
                ix = ie
        except:
            raise DHTValueException
        for i in range(len(res)):
            for v in bits[i * 8: (i + 1) * 8]:  # process next 8 bit
                res[i] <<= 1  ##shift byte one place to left
                if v > 2:
                    res[i] += 1  ##and add 1 if lsb is 1
        if (res[0] + res[1] + res[2] + res[3]) & 0xff != res[4]:  ##parity error!
            raise DHTChecksumException
        return res[0:4]

    def read_raw(self):
        return self.decode(self.getval())

    def read(self):
        res = self.read_raw()
        return (
            float("{},{}".format(res[0], res[1])),
            float("{},{}".format(res[2], res[3])),
        )

    def print(self):
        h, t = self.read()
        print('Humidity = {}'.format(h))
        print('Temperature = {}'.format(t))


class DHT11(DHT):
    pass


class DHT22(DHT):
    def read(self):
        res = self.read_raw()
        hum = res[0] * 256 + res[1]
        tem = res[2] * 256 + res[3]
        if tem > 0x7fff:
            tem = 0x8000 - tem
        return float("{},{}".format(hum // 10, hum % 10)), float("{},{}".format(tem // 10, tem % 10))

dat = Pin(4, Pin.OPEN_DRAIN)
dht11 = DHT11(dat)
dht11.print()

quantalume
Posts: 15
Joined: Thu Aug 04, 2016 3:04 pm

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby quantalume » Mon Sep 12, 2016 6:42 pm

I was using the OP's code, and it was working reliably, but it is now broken as of MicroPython 1.8.2+. I suspect this has something to do with the support for multi-threading, since the DHT code attempts to disable interrupts in order to clock the data in at a fast-enough rate. The ESP8266 has its own driver (micropython/drivers/dht), and the sensors still work with 1.8.4. Support for the DHT family of sensors seems like a pretty important feature for a device used in IoT applications, so a new solution for the WiPy is needed.

quantalume
Posts: 15
Joined: Thu Aug 04, 2016 3:04 pm

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby quantalume » Wed Sep 21, 2016 5:38 pm

This is working again now if you use this version of firmware with multi-threading disabled: https://github.com/robert-hh/Shared-Stuff. Further discussed in this thread: https://forum.micropython.org/viewtopic.php?f=11&t=2127&start=40#p13927

quantalume
Posts: 15
Joined: Thu Aug 04, 2016 3:04 pm

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby quantalume » Thu Sep 22, 2016 7:58 pm

I took the liberty of packaging RinusW's code into a single stream. Here are the contents of what I've named dht.py:

Code: Select all

from machine import enable_irq, disable_irq
import time

def getval(pin) :
    ms = [1]*300
    pin(0)
    time.sleep_us(20000)
    pin(1)
    irqf = disable_irq()
    for i in range(len(ms)):
        ms[i] = pin()      ## sample input and store value
    enable_irq(irqf)
    return ms

def decode(inp):
    res= [0]*5
    bits=[]
    ix = 0
    try:
        if inp[0] == 1 : ix = inp.index(0, ix) ## skip to first 0
        ix = inp.index(1,ix) ## skip first 0's to next 1
        ix = inp.index(0,ix) ## skip first 1's to next 0
        while len(bits) < len(res)*8 : ##need 5 * 8 bits :
            ix = inp.index(1,ix) ## index of next 1
            ie = inp.index(0,ix) ## nr of 1's = ie-ix
            bits.append(ie-ix)
            ix = ie
    except:
        return([0xff,0xff,0xff,0xff])

    for i in range(len(res)):
        for v in bits[i*8:(i+1)*8]:   #process next 8 bit
            res[i] = res[i]<<1  ##shift byte one place to left
            if v > 2:
                res[i] = res[i]+1  ##and add 1 if lsb is 1

    if (res[0]+res[1]+res[2]+res[3])&0xff != res[4] :   ##parity error!
        print("Checksum Error")
        res= [0xff,0xff,0xff,0xff]

    return(res[0:4])

def DHT11(pin):
    res = decode(getval(pin))
    temp = 10*res[0] + res[1]
    hum = 10 * res[2] + res[3]
    return temp, hum
   
def DHT22(pin) :
    res = decode(getval(pin))
    hum = res[0]*256+res[1]
    temp = res[2]*256 + res[3]
    if (temp > 0x7fff):
        temp = 0x8000 - temp
    return temp, hum   

...and here is an example of invoking the code:

Code: Select all

from machine import Pin
from dht import DHT22

dht_pin=Pin('GP4', Pin.OPEN_DRAIN)
dht_pin(1)

temp, hum = DHT22(dht_pin)
# temp = temp * 9 // 5 + 320   # uncomment for Fahrenheit
temp_str = '{}.{}'.format(temp//10,temp%10)
hum_str = '{}.{}'.format(hum//10,hum%10)

# Print or upload it
print(temp_str, hum_str)

cblueorg
Posts: 1
Joined: Wed Nov 23, 2016 4:18 pm

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby cblueorg » Wed Nov 23, 2016 4:30 pm

In DHT11 temp and humidity return values may be switched.
Furthermore there seem to be no use for res[1] and res[3] (they are always equal to 0).

User avatar
mcauser
Posts: 180
Joined: Mon Jun 15, 2015 8:03 am

Re: Interfacing the DHT11/DHT22 Humidity/Temperature sensor

Postby mcauser » Wed Dec 07, 2016 12:31 pm

DHT11 has been superseded by the DHT12, which adds an I2C interface, backwards compatible, same pinout and a tiny blue case.
I wrote a simple I2C driver for it: https://github.com/mcauser/micropython-dht12

The Aosong AM2302, more commonly known as DHT22, has also been upgraded with I2C, whilst also maintaining backwards compatibility.
The replacement is a little black AM2320, or an even smaller version AM2321, with narrowing pins (2mm?).
A simple I2C driver for it too: https://github.com/mcauser/micropython-am2320

Initial Github issue, covering almost all Aosong DHT sensors: https://github.com/micropython/micropython/issues/2290

Hygrometers comparison: http://www.kandrsmith.org/RJS/Misc/Hygr ... _many.html


Return to “Drivers for External Components”

Who is online

Users browsing this forum: No registered users and 3 guests