NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » DHT22 - Humidity and Temperature Sensor

November 15, 2012
by Noter
Noter's Avatar

I added an article to the library on the DHT22. The DHT22 is similar to the DHT11 with a one wire interface that uses pulse width to distinguish between 0's and 1's. The program uses input capture interrupts (ICP1) to measure pulse widths for decoding the incoming data. Please use this thread for comments or questions.

November 15, 2012
by Ralphxyz
Ralphxyz's Avatar

Thanks Paul, I have been looking at pulse timing for while now, now I have good example.

Ralph

May 20, 2013
by Keyster
Keyster's Avatar

hey Noter,

i know this thread is quite a few months old but i was wondering if you had any problems with the DHT22? i am adding one to my newest project and am seeing very flaky operation. when i first give it power i get a response exactly like the datasheet specifies but each response after that is kind of hit or miss, mostly miss.

the first request gives me a response 100% of the time. the second request give me a response about 80% of the time. each request after that is very unlikely to get anything but a flat line.

i have totally removed it from my MCU and hooked it to an oscilloscope and get the exact same results.

i may have a bad device but that first reading being 100% accurate every time has got me flabbergasted..

bryan

May 20, 2013
by Noter
Noter's Avatar

Other than the usual problems encountered getting a program to work, the DHT22 has been running well. I see a little variation (not much, maybe 0.2) after powering on from a cold start but after a minute it is very stable.

I give an extra ms on the start signal and then give it 10 ms to send the data before going to the idle state for about 1 second and then do it all again.

Are you running the code I posted in the library or have you written your own?

May 21, 2013
by Keyster
Keyster's Avatar

i tried to download your code to take a look at it but the link is dead. i guess the nerdkits guys have not fixed that part of the site yet. it is fully my code right now.

i did get it working last night after hours of working with it. when i got home from work i started back at it. i was getting totally different results than the night before and all i had done is turn it off and then turn it back on, no wiring changes at all. so what i did was totally start from scratch. this time i installed a button to pull the data line to ground so i could make multiple fast requests while watching on the oscope. when doing this about 1 out of 10 requests caused a reaction from the device. i then proceeded to update my code to compensate for that.

i changed my code to do a request, wait 1ms for a response. no response, try again. This worked but it just did not "feel" right. so i made my program count the number of retries it had to make to get the response and it was exactly 124 retries each time. well, my brain was telling me that this is no random error caused by a loose wire or something, it had to be a timing problem.

my code was currently taking the data line low, waiting for 2 seconds and then changing the data line to input (10k pull-up resistor takes it high).

i made a slight change and made the data line go high for 2 seconds, go low for 2ms and then change to input. BOOOM baby. it works first time every time now. the weird thing is this is EXACTLY how i envisioned the code in my head when i started this wild ride. i called this last change a "slight" change because all i did was un-comment out some code that was already there. i am not sure why it was not working before, loose connection, bad jumper wire, screw loose behind the keyboard... who knows.

May 21, 2013
by Noter
Noter's Avatar

Good, glad you have it working. According to this, the sample rate for the DHT22 is .5Hz so you may try a delay of 2 seconds between samples and see better stability. The other thing I do is keep a small 25mm fan blowing air over the sensor so it reacts to change very quickly.

Since the library is down here is the current version of my DHTxx test program. It will require minor modification to go back to the libnerdkits/lcd.h but otherwise is good to go. I haven't tested with the DHT11 recently but I think the code still works for it too.

//
//  DHTxx.c
//
//  Get relative humidity and temperature from DHTxx sensor
//  and display values on LCD.
//
//  Use input capture feature of timer1 to measure pulse width
//  from sensor on one wire signal line.
//
//#define USING_DHT11
#define USING_DHT22
//
#include <avr/io.h> 
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>

#include <libNoter/pin_io_macros.h>
#define ICP1_IN_USE
#include <libNoter/lcd_Noter_4_bit.h>

// local functions
void showbits(void);

// define ports, pins
// one wire interface to DHTxx
#define DHTxx           B,0 // ICP1

// save capture pulse width in timer ticks
volatile uint16_t pulse_width[50];
volatile uint8_t pulse_count = 0;

// LCD stream file - enable printf in functions outside of main()
FILE lcd_stream;

// capture pulse width
ISR(TIMER1_CAPT_vect, ISR_BLOCK) {
    TCNT1 = 0;                                                      // reset tick counter
    TCCR1B ^= _BV(ICES1);                                   // capture next edge
    TIFR1 |= _BV(ICF1);                                     // set capture flag after edge change
    if(READ(DHTxx)==0)                                      // at end of +VCC pulse?
        pulse_width[pulse_count++] = ICR1;  // save width in timer ticks
}

uint8_t istart;
uint16_t threshold;
// --------------------------------------------------------------------------------------------------------
int main() {
    // initialize LCD display
    lcd_init(4, 20);
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE); 
    lcd_clear_and_home();
    #if defined(USING_DHT11)
        fprintf_P(&lcd_stream, PSTR("DHT11")); 
    #elif defined(USING_DHT22)
        fprintf_P(&lcd_stream, PSTR("DHT22")); 
    #endif

    // if the pulse width is > 50 us, it's a 1
    threshold = 0.00005*F_CPU;  // ticks in 50 us with 1/1 prescaler

    // set one wire interface idle
    SET(DHTxx);
    OUTPUT(DHTxx);

    // delay 1 second to allow DHTxx to stabilize
    _delay_ms(1000);

    // set up timer for one wire input capture
    TIMSK1 = _BV(ICIE1);
    TCCR1A = 0;
    TCCR1B = _BV(ICES1); // fire on faling edge of pulse
    TCCR1B |= _BV(CS10); // 1/1 prescaler

    // enable interrupts
    sei();

    while(true){
        // clear comm error line
        lcd_goto_position(3,0);
        fprintf_P(&lcd_stream, PSTR("                   "));

        // send DHTxx start signal
        pulse_count = 0;
        CLEAR(DHTxx);
        #if defined(USING_DHT11)
            _delay_ms(20);
        #elif defined(USING_DHT22)
            _delay_ms(2);
        #endif
        PULLUP_ON(DHTxx);

        // give DHTxx time to complete measurement and send data
        _delay_ms(10);

        // set one wire interface idle
        OUTPUT(DHTxx);

        // convert pulses to bits and show
        if(pulse_count>=40){
            // convert only last 40 bits received
            istart = pulse_count-40;

            // convert pulses to bits and show RH (relative humidity), TF (temperature in farenheit)
            uint16_t humidity=0;
            double d_humidity=0;
            uint16_t temperature=0;
            double d_temperature=0;
            uint8_t checksum=0;
            uint8_t test;
            int i;
            // if the pulse width is > threshold ticks, it's a 1
            for(i=istart;i<istart+16;i++){
                humidity = (humidity<<1)|(pulse_width[i]>threshold?1:0); 
            }
            for(i=istart+16;i<istart+32;i++){
                temperature = (temperature<<1)|(pulse_width[i]>threshold?1:0); 
            }
            for(i=istart+32;i<istart+40;i++){
                checksum = (checksum<<1)|(pulse_width[i]>threshold?1:0); 
            }
            test = (humidity>>8) + (humidity&0xFF) + (temperature>>8) + (temperature&0xFF);
            if(checksum != test){
                showbits();
                lcd_goto_position(1,12);
                fprintf_P(&lcd_stream, PSTR("cs error")); // checksum error
                lcd_goto_position(2,12);
                fprintf_P(&lcd_stream, PSTR("%X"), checksum); 
                lcd_goto_position(3,12);
                fprintf_P(&lcd_stream, PSTR("%X"), test); 
                while(true); // hang, reset required to get going again
            } else {
                #if defined(USING_DHT11)
                    d_humidity = (double)((humidity&0x7FFF)>>8)+((double)((humidity&0x7F)/10);
                    d_temperature = (double)((temperature&0x7FFF)>>8)+((double)((temperature&0x7F)/10);
                #elif defined(USING_DHT22)
                    d_humidity = (double)(humidity&0x7FFF)/10.0;
                    d_temperature = (double)(temperature&0x7FFF)/10.0;
                #endif
                if(temperature & 0x8000) d_temperature *= -1;
                lcd_goto_position(2,0);
                fprintf_P(&lcd_stream, PSTR("RH %0.1f"), d_humidity); 
                lcd_goto_position(2,10);
                fprintf_P(&lcd_stream, PSTR("TF %0.1f"), ((9.0/5.0*d_temperature)+32.0)); 
            }
        } else {
            // didn't get at least 40 pulses
            lcd_goto_position(3,0);
            fprintf_P(&lcd_stream, PSTR("comm error %d"), pulse_count); 
        }
    // wait and update again
    #if defined(USING_DHT11)
        _delay_ms(1000);    
    #elif defined(USING_DHT22)
        _delay_ms(2000);
    #endif
    }
}
// --------------------------------------------------------------------------------------------------------
// show the ones and zeros we received from the DHTxx
void showbits(void){
    lcd_clear_and_home();
    int i;
    for(i=istart;i<istart+8;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(1,0);
    for(i=istart+8;i<istart+16;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(2,0);
    for(i=istart+16;i<istart+24;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(3,0);
    for(i=istart+24;i<istart+32;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(0,12);
    for(i=istart+32;i<istart+40;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
}

And the pin_io_macros.h include file:

#ifndef PIN_IO_MACROS 
    #define PIN_IO_MACROS
    // pin I/O helper macros
    //
    #define INPUT2(port,pin) DDR ## port &= ~_BV(pin) 
    #define OUTPUT2(port,pin) DDR ## port |= _BV(pin) 
    #define CLEAR2(port,pin) PORT ## port &= ~_BV(pin) 
    #define SET2(port,pin) PORT ## port |= _BV(pin) 
    #define TOGGLE2(port,pin) PORT ## port ^= _BV(pin) 
    #define READ2(port,pin) ((PIN ## port & _BV(pin))?1:0)
    #define STATE2(port,pin) ((DDR ## port & _BV(pin))?1:0)
    #define PIN_NAME2(port,pin) PIN_NAME3(P ## port ## pin)
    #define PIN_NAME3(name) #name
    //
    #define INPUT(x) INPUT2(x) 
    #define OUTPUT(x) OUTPUT2(x)
    #define CLEAR(x) CLEAR2(x)
    #define SET(x) SET2(x)
    #define TOGGLE(x) TOGGLE2(x)
    #define READ(x) READ2(x)
    #define STATE(x) STATE2(x)
    #define PULLUP_ON(x) INPUT2(x); SET2(x)
    #define PULLUP_OFF(x) INPUT2(x); CLEAR2(x)
    #define PIN_NAME(x) PIN_NAME2(x)
#endif
May 21, 2013
by Keyster
Keyster's Avatar

thanks for the code Noter, you always have such clean looking code :) .

on mine i opted for a loop count instead of the interrupt. the nature of my code does not require it to do anything while "waiting" on the next signal so i just clear interrupt enable, count the next 41 "highs" (1 header and 40 data) and then re-enable the interrupts. any count under 100 is a zero and any count over 100 is a one. shift those into variables and done.

i love the fan idea. i would not have thought of that. this device is going to be mounted on the outside of my clock project so will not be able to put a fan there BUT maybe i will have a slow 80mm fan blowing air behind the clock and place the DHT22 right in front of it. or even better yet, have the 80mm fan blowing out the top of the clock and place the DHT22 in front of some holes at the bottom. the clock will be in my workshop/garage so having some air movement behind it in the summer time will not be a bad thing i am sure. i may even PWM a computer case fan off of the Atmega so i can control the speed based on the temperature. arggggg, more work!

since this is going to be working with a clock the timing will not be an issue. i will only be pulling data from it every 5 seconds or more. the 2 second delay in the DHT22 code will be going away in lieu of this "built in" delay. :)

May 21, 2013
by Noter
Noter's Avatar

More work?? You mean more fun!

I just did the input capture for fun too. Haven't needed it yet so figured this was a good excuse to give it a try.

May 21, 2013
by pcbolt
pcbolt's Avatar

Why are the some of the "pin_io_macros" redefined? Is this done to support legacy code? For example...

#define INPUT(x) INPUT2(x)

Just curious if there was something I was missing.

May 22, 2013
by Noter
Noter's Avatar

It has to do with the stringification of a macro argument in the preprocessor, two levels must be used to achieve the desired effect. Check this for more info - 3.10.6 Argument Prescan

May 22, 2013
by pcbolt
pcbolt's Avatar

Ah... thank you for that. It's not like you to add unneeded code...so I assumed you had a very good reason, like "it won't work without it" :-)

May 23, 2013
by Noter
Noter's Avatar

At some point I plan to incorporate the DHT22 into a smart thermostat that interfaces with my cell phone over bluetooth so a version without delays is needed since there will be plenty of other stuff going on in the program. Now that I've been looking over the program again, I decided to go ahead and make the changes. In case anyone is interested, here it is completely interrupt driven. Haven't tested all the error conditions like check sum mismatch but I think they still work as before.

//
//  DHTxx.c - load on nerdkit with LCD
//
//  Get relative humidity and temperature from DHTxx sensor
//  and display values on LCD.
//
//  Use input capture feature of timer1 to measure pulse width
//  from sensor on one wire signal line.
//
//  Use watchdog to control sample rate.
//
//  Use timer0 for start signal and data transfer delays.
//
//#define USING_DHT11
#define USING_DHT22
//
#include <avr/io.h> 
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <stdlib.h>
#include <stdbool.h>

#include <libNoter/pin_io_macros.h>
#define ICP1_IN_USE
#include <libNoter/lcd_Noter_4_bit.h>

// local functions
void showbits(void);

// define ports, pins
// one wire interface to DHTxx
#define DHTxx           B,0 // ICP1

// save capture pulse width in timer ticks and convert to 0's and 1's
volatile uint16_t pulse_width[50];
volatile uint8_t pulse_count = 0;
volatile uint8_t istart;
volatile uint16_t threshold;
// final values for display or other control
volatile double d_humidity=0;
volatile double d_temperature=0;
// state table for delay timer
#define STATE_NONE              0
#define STATE_START             1
#define STATE_TRANSFER      2   
volatile uint8_t state = STATE_NONE;
// startup delays
#if defined(USING_DHT11)
    #define START_MS 20
#elif defined(USING_DHT22)
    #define START_MS 2
#endif
// DHTxx transfer time allowed
#define TRANSFER_MS 10
// Misc counters, flags, etc.
volatile uint8_t ms_count=0;
volatile uint8_t TCCR0B_prescaler=0;
volatile bool results_ready=false;

// LCD stream file - enable printf in functions outside of main()
FILE lcd_stream;

// capture pulse width
ISR(TIMER1_CAPT_vect, ISR_BLOCK) {
    TCNT1 = 0;                                                      // reset tick counter
    TCCR1B ^= _BV(ICES1);                                   // capture next edge
    TIFR1 |= _BV(ICF1);                                     // set capture flag after edge change
    if(READ(DHTxx)==0)                                      // at end of +VCC pulse?
        pulse_width[pulse_count++] = ICR1;  // save width in timer ticks
}

// watchdog start DHTxx sample
ISR(WDT_vect, ISR_NOBLOCK){
    state = STATE_START;
    TCCR0B = 0;                                 // turn off timer
    TCNT0 = 0;                                  // reset timer count
    ms_count = 0;                               // reset ms count
    state = STATE_START;                // set next state
    pulse_count = 0;                        // reset pulse count
    CLEAR(DHTxx);                               // begin start signal
    TCCR0B = TCCR0B_prescaler;  // restart timer
}

// ms timer for DHTxx startup and transfer delays
ISR(TIMER0_COMPA_vect, ISR_NOBLOCK){
    ms_count++;
    if(state == STATE_START){
        if(ms_count >= START_MS)
            TCCR0B = 0;                                 // turn off timer
            ms_count = 0;                               // reset ms count
            TCNT0 = 0;                                  // reset timer count
            state = STATE_TRANSFER;         // set next state
            PULLUP_ON(DHTxx);                       // prepare to read
            TCCR0B = TCCR0B_prescaler;  // restart timer
    }
    if(state == STATE_TRANSFER){
        if(ms_count >= TRANSFER_MS){
            TCCR0B = 0;                                 // turn off timer
            ms_count = 0;                               // reset ms count
            TCNT0 = 0;                                  // reset timer count
            state = STATE_NONE;                 // reset state
            OUTPUT(DHTxx);                          // set DHTxx line idle
            if(pulse_count>=40){                // convert pulses to bits and calc results
                // convert only last 40 bits received
                istart = pulse_count-40;
                // convert pulses to bits and show RH (relative humidity), TF (temperature in farenheit)
                uint16_t humidity=0;
                uint16_t temperature=0;
                uint8_t checksum=0;
                uint8_t test;
                int i;
                // if the pulse width is > threshold ticks, it's a 1
                for(i=istart;i<istart+16;i++){
                    humidity = (humidity<<1)|(pulse_width[i]>threshold?1:0); 
                }
                for(i=istart+16;i<istart+32;i++){
                    temperature = (temperature<<1)|(pulse_width[i]>threshold?1:0); 
                }
                for(i=istart+32;i<istart+40;i++){
                    checksum = (checksum<<1)|(pulse_width[i]>threshold?1:0); 
                }
                test = (humidity>>8) + (humidity&0xFF) + (temperature>>8) + (temperature&0xFF);
                if(checksum != test){
                    showbits();
                    lcd_goto_position(1,12);
                    fprintf_P(&lcd_stream, PSTR("cs error")); // checksum error
                    lcd_goto_position(2,12);
                    fprintf_P(&lcd_stream, PSTR("%X"), checksum); 
                    lcd_goto_position(3,12);
                    fprintf_P(&lcd_stream, PSTR("%X"), test); 
                } else {
                    #if defined(USING_DHT11)
                        d_humidity = (double)((humidity&0x7FFF)>>8)+((double)((humidity&0x7F)/10);
                        d_temperature = (double)((temperature&0x7FFF)>>8)+((double)((temperature&0x7F)/10);
                    #elif defined(USING_DHT22)
                        d_humidity = (double)(humidity&0x7FFF)/10.0;
                        d_temperature = (double)(temperature&0x7FFF)/10.0;
                    #endif
                    if(temperature & 0x8000) d_temperature *= -1;
                    results_ready = true;
                }
            } else {
                // didn't get at least 40 pulses
                lcd_clear_and_home();
                lcd_goto_position(2,0);
                fprintf_P(&lcd_stream, PSTR("comm error:")); 
                lcd_goto_position(3,0);
                fprintf_P(&lcd_stream, PSTR("pulse count %d"), pulse_count); 
            }
        }
    }
}

// --------------------------------------------------------------------------------------------------------
int main() {
    // initialize LCD display
    lcd_init(4, 20);
    fdev_setup_stream(&lcd_stream, lcd_putchar, 0, _FDEV_SETUP_WRITE);

    // if the pulse width is > 50 us, it's a 1
    threshold = 0.00005*F_CPU;  // ticks in 50 us with 1/1 prescaler

    // set one wire interface idle
    SET(DHTxx);
    OUTPUT(DHTxx);

    // startup timer for one wire input capture
    TIMSK1 = _BV(ICIE1);
    TCCR1B = _BV(ICES1); // fire on faling edge of pulse
    TCCR1B |= _BV(CS10); // 1/1 prescaler

    // set up 1ms timer for start signal and DHTxx transfer
    TIMSK0 = _BV(OCIE0A);                       // output compare match A
    TCCR0A = _BV(WGM01);                        // CTC mode
    TCCR0B_prescaler = _BV(CS02);       // save prescaler 256 for dynamic start
    OCR0A = 0.001*(F_CPU/256);          // set compare value for 1ms

    // Startup watchdog to control sampling rate
    WDTCSR = _BV(WDCE) | _BV(WDE);                                                      // change enable
    #if defined(USING_DHT11)
        WDTCSR = _BV(WDIE) | _BV(WDP1) | _BV(WDP2);                         // 1 sec timeout
    #elif defined(USING_DHT22)
        WDTCSR = _BV(WDIE) | _BV(WDP0) | _BV(WDP1) | _BV(WDP2); // 2 sec timeout
    #endif

    // enable interrupts
    sei();

    while(true){
        if(results_ready){
            results_ready = false;
            // reinitialize display content
            lcd_clear_and_home();
            #if defined(USING_DHT11)
                fprintf_P(&lcd_stream, PSTR("DHT11")); 
            #elif defined(USING_DHT22)
                fprintf_P(&lcd_stream, PSTR("DHT22")); 
            #endif
            // show results
            lcd_goto_position(2,0);
            fprintf_P(&lcd_stream, PSTR("RH %0.1f  "), d_humidity); 
            lcd_goto_position(2,10);
            fprintf_P(&lcd_stream, PSTR("TF %0.1f  "), ((9.0/5.0*d_temperature)+32.0)); 
        }
    }
}
// --------------------------------------------------------------------------------------------------------
// show the ones and zeros received from the DHTxx (when checksum error)
void showbits(void){
    lcd_clear_and_home();
    int i;
    for(i=istart;i<istart+8;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(1,0);
    for(i=istart+8;i<istart+16;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(2,0);
    for(i=istart+16;i<istart+24;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(3,0);
    for(i=istart+24;i<istart+32;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
    lcd_goto_position(0,12);
    for(i=istart+32;i<istart+40;i++){
        fprintf_P(&lcd_stream, PSTR("%d"), 
            (pulse_width[i]>threshold?1:0)); 
    }
}

Post a Reply

Please log in to post a reply.

Did you know that you can connect a pushbutton to a microcontroller with only one wire? Learn more...