PIC16F18855/75 with DHT11

Temperature and Humidity sensor controlled by a PIC and displayed to an LCD for debugging. In the future the LCD may be replace, e.g., with a Bluetooth module and read by another system.

The Wiring

We base our example here on our previous post and add the DHT11 connected to RB0 pin. Please refer to PIC16F18855/75 with MCP23017 for information how to setup a PIN. We want RB0 to be setup as an digital output pin without IOC.

The DHT11 has 4 pins, but only 3 are used. From left to right: VCC, DATA, NC, GND. The DATA pin is pulled up with a 5kOhm resistor. The DHT11 module and the PIC are just grounding the signal when they need to communicate.

The DHT11 uses a proprietary time-based protocol over 1 wire. Meaning that there is no clock and the period and duty cycle must be very precise.

The Code

We will reuse the i2c module (modules/i2c.h and modules/i2c.c) and the lcd module (modules/lcd.h and modules/lcd.c) from our previous post and add a new dht11 module (modules/dht11.h and modules/dht11.c).

/* 
 * File:   dht11.h
 * Author: Jan Kubovy <jan@kubovy.eu>
 */
#include <stdint.h>

#ifndef DHT11_H
#define	DHT11_H

#ifdef	__cplusplus
extern "C" {
#endif

#ifndef DHT11_PIN
#define DHT11_PIN PORTBbits.RB0
#define DHT11_LAT LATB0
#define DHT11_TRIS TRISB0
#endif

#define DHT11_ERR_VALUE ((uint8_t) 255)
#define DHT11_OK ((uint8_t) 0)
#define DHT11_ERR_NO_RESPONSE ((uint8_t) 1)
#define DHT11_ERR_CHKSUM ((uint8_t) 2)

typedef union {
    struct  {
        uint8_t temp_decimal;
        uint8_t temp_integral;
        uint8_t rh_decimal;
        uint8_t rh_integral;
        uint8_t chksum;
    };
} DHT11_Response;

typedef union {
    struct  {
        uint8_t temp;
        uint8_t rh;
        uint8_t status;
        uint8_t retries;
    };
} DHT11_Result;

/**
 * Requests a measurement from the DHT11 module.
 * 
 * @return The result of the measurement.
 */
DHT11_Result DHT11_measure(void);

#ifdef	__cplusplus
}
#endif

#endif	/* DHT11_H */

The dht11.c implementation:

/* 
 * File:   dht11.c
 * Author: Jan Kubovy <jan@kubovy.eu>
 */

#include <stdbool.h>
#include <stdint.h>
#include "../mcc_generated_files/mcc.h"
#include "dht11.h"

DHT11_Response DHT11_read(void){
    bool raw[40];
    DHT11_Response result;
    result.chksum = 0;

    uint8_t byte = 0, count, i;
    unsigned int counter = 0;

    DHT11_TRIS = 0; // Configure PIN as output
    DHT11_LAT = 0;  // Sends 0 to the sensor
    __delay_ms(18); // Wait min 18ms
    DHT11_LAT = 1;   // Sends 1 to the sensor
    DHT11_TRIS = 1; // Configure PIN as input
    
    while(DHT11_PIN) { // Wait for falling edge on PIN
        counter++;
        __delay_us(1);
        if(counter == 40) {
            __delay_ms(1000);
            return result;
        }
    }

    while(!DHT11_PIN) { // Wait for rising edge on PIN
        counter++;
        __delay_us(1);
        if(counter==100) {
            __delay_ms(1000);
            return result;
        }
    }

    while(DHT11_PIN) { // Wait for falling edge PIN
        counter++;
        __delay_us(1);
        if(counter==180) {
            __delay_ms(1000);
            return result;
        }
    }

    for(i = 0; i < 40; i++) {
        while(!DHT11_PIN); // Wait for rising edge on PIN
        __delay_us(24);
        raw[i] = DHT11_PIN;
        count = 0;
        while(DHT11_PIN) { // Wait for falling edge PIN
            __delay_us(1);
            if (count++ == 50) break;
        }
    }

    for(i = 0; i < 40; i++) {
        if(raw[i]) {
            byte |= (1 << (7 - (i % 8)));  // Set bit (7-b) 
        } else {
            byte &= ~(1 << (7 - (i % 8))); // Clear bit (7-b)
        }
        if (i % 8 == 7) switch(i / 8) {
            case 0:
                result.rh_decimal = byte;
                break;
            case 1:
                result.rh_integral = byte;
                break;
            case 2:
                result.temp_decimal = byte;
                break;
            case 3:
                result.temp_integral = byte;
                break;
            case 4:
                result.chksum = byte;
                break;
        }
    }

    return result;
}

DHT11_Result DHT11_measure(void) {
    DHT11_Result result;
    DHT11_Response data;
    result.retries = 0;
    bool isValid;

    DHT11_read(); // The sensor returns last value - ensure measurement here
    __delay_ms(50);
    do {
        if (result.retries > 0) __delay_ms(50);
        data = DHT11_read();
        isValid = (data.rh_decimal + data.rh_integral + data.temp_decimal + data.temp_integral) == data.chksum;
    } while(!isValid && result.retries++ < 5);

    if(data.chksum != 0) {
        if(isValid) {
            result.temp = data.temp_decimal;
            result.rh = data.rh_decimal;
            result.status = DHT11_OK;
        } else {
            result.status = DHT11_ERR_CHKSUM;
        }
    } else {
            result.status = DHT11_ERR_NO_RESPONSE;
    }
    return result;
}

The main.c:

/* 
 * File:   main.c
 * Author: Jan Kubovy <jan@kubovy.eu>
 */

#define LCD_ADDRESS 0x27 // change this according to ur setup
#define LCD_COLS 20
#define LCD_ROWS 4

#include <stdio.h>
#include "mcc_generated_files/mcc.h"
#include "modules/dht11.h"
#include "modules/i2c.h"
#include "modules/lcd.h"

void testDHT() {
    DHT11_Result dht11 = DHT11_measure();
    char message[LCD_COLS + 1] = "DHT11 (X retries)";
    message[7] = 48 + dht11.retries;
    LCD_send_string(message, 1);
    LCD_send_string("--------------------", 2);
    

    if (dht11.status == DHT11_OK) {
        char message1[LCD_COLS + 1] = "Temp:      ?C\0";
        message1[10] = 48 + ((dht11.temp / 10) % 10);
        message1[11] = 48 + (dht11.temp % 10);
        LCD_send_string(message1, 3);

        char message2[LCD_COLS + 1] = "Humidity:  ?%\0";
        message2[10] = 48 + ((dht11.rh / 10) % 10);
        message2[11] = 48 + (dht11.rh % 10);
        LCD_send_string(message2, 4);
    } else switch(dht11.status) {
        case DHT11_ERR_CHKSUM:
            LCD_send_string("|c|Checksum mismatch!", 3);
            break;
        case DHT11_ERR_NO_RESPONSE:
            LCD_send_string("|c|No response!", 3);
            break;
        default:
            LCD_send_string("|c|Unknown error!", 3);
            break;
    }
}

void main(void) {
    SYSTEM_Initialize(); // initialize the device

    __delay_ms(500);

    LCD_init(LCD_ADDRESS, LCD_COLS, LCD_ROWS);
    LCD_backlight(true);

    while (1) {
        while(PORTBbits.RB4); // wait for RB4 falling edge (button press)
        while(!PORTBbits.RB4); // wait for RB4 raising edge (button release)
        testDHT();
    }
}

 

Conclusion

Time-based one wire protocol are saving wires on one hand but complicate things on the software level. The need of precision puts special requirements on the microcontroller’s internal oscillator and on the program itself, e.g., interrupts may disturb the measurement.

We tried to address these issues by retrying couple times in case of fault data transfer before giving up.

Happy tuning.

Leave a Reply

Your email address will not be published. Required fields are marked *