0x05 从DHT11温度湿度传感器模块读取数据

【目标】

  • 看懂简单的时序图
  • 理解简单的读取设备数据的逻辑

【所需素材】

  • 4位7段数码管 x 1
  • 270Ω 左右电阻 x 9
  • 6x6x5毫米轻触开关 x 1
  • DHT11温度湿度传感器模块 x 1
图1 DHT11湿度温度传感器模块

图2 DHT11模块原理图

PS:我用的DHT11是一个封装好的模块,如图1,

包含一个DHT11传感器,一个LED电源指示灯,DATA PIN已经接4.7k上拉电阻。

如果自己买单独的传感器,自己接线的话请参考下图:

图3 DHT11湿度温度传感器原理图

VCC和GND直接接一个104瓷片电容,信号会稳定不少。

【基本原理】

DHT11-datasheet_cn

DHT11传感器共4个引脚,VCC、GND、data、NC,

NC悬空,模块直接引出其他三个脚,一个用于数据传输,其他两个是电源。

从 DHT11获得一次测量数据,大概可以分为三步。

  1. 树莓派发送开始信号到DHT11
  2. DHT11响应开始信号
  3. DHT11返回数据

时序图表示为:

图4 DHT11时序图

图例中深色部分的主机信号,在这里是指由树莓派控制,

浅色部分是由DHT11控制。

途中起伏的形状代表的就是pin的高低电平,在VCC线上代表高电平,GND线上代表低电平。

通过这种交互式的操作,完成整个通信过程。

 

这个交互过程的每个阶段对高低电平有时长的要求。见下面图5和图6:

图5 DHT11初始化时序时长定义
图6 数据传输时长定义

 

那么我们把图4、5、6解读出来就变成如下的操作:

  1. 树莓派将DATA PIN的电平置低(持续>18ms);
  2. 树莓派将DATA PIN的电平置高
  3. 由于接下来的操作将由DHT11完成,所以需要将pinMode改为INPUT来获取DHT11的响应信号
  4. 等待DHT11响应(20-40us);
  5. DHT11将DATA PIN的电平置低(持续80us);
  6. DHT11将DATA PIN的电平置高(持续80us);
  7. DHT11将DATA PIN的电平置低(持续50us);
    (DHT11通过串口的形式向外发送数据,每一bit的数据都是以一个50us左右的低电平开始。)
  8. DHT11将DATA PIN的电平置高(数据位,后详);
  9. 循环5、6。
  10. 结束

DHT11测量一次将返回40bit的数据,共5个字节。

数据格式为:

0字节:湿度整数数据

1字节:湿度小数数据

2字节:温度整数数据

3字节:温度小数数据

4字节:校验和

由于DHT11的测量精度只精确到个位数,所以湿度和温度的小数位始终返回0。

4字节校验和= 0字节+1字节+2字节+3字节

校验和用以判断接收到的数据的正确性。

 

DHT11传输40bit数据,是通过交替切换DATA PIN的高低电平来区分不bit的间隔,通过控制高电平的持续时长来表示1和0。

DHT11每一bit数据都以持续50us的低电平开始,然后拉高至高电平,高电平持续22-24us表示0,持续70us表示1。

然后再拉低到低电平,持续50us。如此循环,直至最后一位数据传输完成。

【硬件连接】

图7 DHT11传感器硬件连接

这个图是独立传感器模块的链接方式。

已经封装好的带PCB板的模块,都会有上拉电阻的电路,根据模块自己调整下接线即可。

【程序实现】

我们为DHT11单独写一个.h和.c文件,方便程序移植

dht11.h
// 定义DHT11的数据PIN
#define DHT11_DATA_PIN 15

// 为了方便函数返回数据,定义一个struct,比用指针安全
struct dht11_measuring_data {
        int humidity_int;
        int humidity_decimal;
        int temperature_int;
        int temperature_decimal;
        int checksum;
};

void dht11_init();
struct dht11_measuring_data readDHT11();
dht11.c
#include <wiringPi.h>
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include "dht11.h"

void dht11_init()
{
        pinMode(DHT11_DATA_PIN, OUTPUT);
        // delay(1000); // DHT11上电后会有1秒左右的不稳定时间,
                        // 由于我们用树莓派并不会上电后马上执行测量取数,所以可以不用这个延时。
}

struct dht11_measuring_data readDHT11()
{
        int i,j;
        int data[5],bits[40];  // data用于记录测量到的数值,bits记录每一bit高电平持续的时长
        struct timeval t_val;
        struct timeval t_val_end;
        struct timeval t_result;
        int dht11error = 0;

        struct dht11_measuring_data dht11_data;

        dht11_data.humidity_int = data[0];
        dht11_data.humidity_decimal = data[1];
        dht11_data.temperature_int = data[2];
        dht11_data.temperature_decimal = data[3];
        dht11_data.checksum = data[4];

        for(j=0;j<3;j++)
        {  /*
            * 重试,实际用下来DHT11返回数据校验失败的几率还是比较高的。
            * 而且感觉不是信号问题,因为返回的40bit数据中,经常是只有一位数据错误,导致校验失败
            */
                pinMode(DHT11_DATA_PIN, OUTPUT);  // 设置数据PIN为输出模式
                dht11error = 0;
                delay(20);
                digitalWrite (DHT11_DATA_PIN, LOW);  // 先将数据PIN拉低,告诉DHT11准备开始测量
                delay(20);  // 这个低电平,至少保持18ms
                digitalWrite (DHT11_DATA_PIN, HIGH); // 拉高数据PIN后等待DHT11响应

                pinMode(DHT11_DATA_PIN, INPUT); // 数据PIN切换为输入模式,等待DHT11响应信号

                gettimeofday(&t_val, NULL); // 记录当前时间
                while(digitalRead(DHT11_DATA_PIN) == HIGH) {    // DHT11响应,返回一个20~40 us的高电平
                        gettimeofday(&t_val_end, NULL);
                        timersub(&t_val_end, &t_val, &t_result);
                        if (t_result.tv_usec > 1000)  // 如果此低电平持续时间过长,则返回错误
                        {
                                printf("(1)Connection to DHT11 failed.\n");
                                dht11error = -1;
                                break;
                        }
                }
                if(dht11error) {
                        continue;
                }

                gettimeofday(&t_val, NULL);
                while (digitalRead(DHT11_DATA_PIN) == LOW) {    // DHT11返回数据开始信号,由一个80 us的低电平开始
                        gettimeofday(&t_val_end, NULL);
                        struct timeval t_result;
                        timersub(&t_val_end, &t_val, &t_result);
                        if (t_result.tv_usec > 1000)
                        {
                                printf("(2)DHT11 response Error\n");
                                dht11error = -2;
                                break;
                        }
                }
                if(dht11error)
                        continue;


                gettimeofday(&t_val, NULL);
                while (digitalRead(DHT11_DATA_PIN) == HIGH) {  // DHT11返回数据开始信号,一个80 us的高电平结束
                        gettimeofday(&t_val_end, NULL);
                        struct timeval t_result;
                        timersub(&t_val_end, &t_val, &t_result);
                        if (t_result.tv_usec > 1000)
                        {
                                printf("(3)DHT11 response Error\n");
                                dht11error = -3;
                                break;
                        }
                }
                if(dht11error)
                        continue;

                for (i = 0; i < 40; i++)
                {
                 /* DHT11开始返回40bit的数据
                  * 每一bit都是由一个50us的低电平作为开始
                  */
                        while (digitalRead(DHT11_DATA_PIN) == LOW) {
                        // 其实此处可以不用检测低电平持续的时长,由于上拉电阻的存在,数据pin不会持续保持在低电平
                                gettimeofday(&t_val_end, NULL);
                                timersub(&t_val_end, &t_val, &t_result);
                                if (t_result.tv_usec > 200)
                                {
                                        dht11error = -4;
                                        break;
                                }
                        }

                        gettimeofday(&t_val, NULL);
                        while (digitalRead(DHT11_DATA_PIN) == HIGH) {  // 测量此处高电平持续时长
                                gettimeofday(&t_val_end, NULL);
                                timersub(&t_val_end, &t_val, &t_result);
                                if (t_result.tv_usec > 500)
                                {
                                        dht11error = -4;
                                        break;
                                }
                        }
                        bits[i] = t_result.tv_usec; // 记录当前bit的高电平时长
                }
                for(i=0;i<5;i++)
                {
                        data[i] = 0;
                }

                printf("----------------------------------------------------------------\n");  // 在终端输出一些DEBUG信息
                printf("数据样本高电平时长(us):\n");
                for (i = 0; i < 40; i++) {  // 将40个时长数据整理为5字节的可用数据
                        data[i/8] <<= 1;
                        printf(" %2d:%2d |",i,bits[i]);
                        if((i+1)%8 == 0)
                                printf("\n");
                        if (bits[i] > 50)
                        {
                        /* 22~24us代表0,70us代表1,所以我们用50us这个阈值来做判断
                         * C语言测量此时长的误差基本在3us以下,
                         * 如果用Python的话,获取时间再做计算消耗的时间是ms级的,所以不适用此方法。
                         * 我看过网上有人写的的Python的代码,一般是用计数法,就是while里面计数,根据计数估算时长。
                         */
                                data[i/8] |= 1;
                        }
                }
                printf("\n");
                printf("d0 : %X\n",data[0]);
                printf("d1 : %X\n",data[1]);
                printf("d2 : %X\n",data[2]);
                printf("d3 : %X\n",data[3]);
                printf("d4 : %X\n",data[4]);

                if(dht11error)
                        continue;

                if ( (data[0] + data[1] + data[2] + data[3]) == data[4] ) {  // 校验数据,如果数据校验成功,函数直接返回数据
                        dht11_data.humidity_int = data[0];
                        dht11_data.humidity_decimal = data[1];
                        dht11_data.temperature_int = data[2];
                        dht11_data.temperature_decimal = data[3];
                        dht11_data.checksum = data[4];

                        return dht11_data;
                }
                printf("Data error,retrying...\n");
        }
        printf("Fetch data failed from DHT11!\n");
        dht11_data.checksum = dht11error;
        return dht11_data;
}
main.c
#include <wiringPi.h>
#include <stdbool.h>
#include <time.h>
#include <stdio.h>
#include "dht11.h"

#define SEG_A 27
#define SEG_B 29
#define SEG_C 24
#define SEG_D 22
#define SEG_E 21
#define SEG_F 28
#define SEG_G 25
#define SEG_DP 23
#define DIG1 2
#define DIG2 3
#define DIG3 4
#define DIG4 5
#define BTN 6


int matrix[10] = {
    0b11111100, // 0x7E, // 0
    0b01100000, // 0x30, // 1
    0b11011010, // 0x6D, // 2
    0b11110010, // 0x79, // 3
    0b01100110, // 0x33, // 4
    0b10110110, // 0x5B, // 5
    0b10111110, // 0x5F, // 6
    0b11100100, // 0x70, // 7
    0b11111110, // 0x7F, // 8
    0b11110110 // 0x7B // 9
};

int seg[8] = {
    SEG_A,
    SEG_B,
    SEG_C,
    SEG_D,
    SEG_E,
    SEG_F,
    SEG_G,
    SEG_DP
};

int dig[4] = {
        DIG1,
        DIG2,
        DIG3,
        DIG4
};

/* 每分钟取一次DHT11的数据,
 * 我们检测时间的分钟数只要变化,就进行取数
 */
int lastmin = 60;
struct dht11_measuring_data dht11_data;

void showDig(short digtoshow, short num, bool showDP)
{
    short i, tmp;
        tmp = matrix[num];
        if(showDP)
            tmp |= 1;
        for (i=0;i<4;i++)
        {
                digitalWrite(dig[i],HIGH);
        }
    for (i=0;i<8;i++)
    {
        if((tmp&0x80) == 0x80)
        {
            digitalWrite(seg[i],HIGH);
        } else {
            digitalWrite(seg[i],LOW);
        }
                tmp<<=1;
    }
    digitalWrite(dig[digtoshow],LOW);
}

void showPage(short page)
{
    time_t timep;
    struct tm *p;
    time(&timep);
    p = localtime(&timep);

    while (lastmin != p->tm_min)
    {
                dht11_data = readDHT11();
                lastmin = p->tm_min;
        }

        switch(page) {
                case 1:
                        showDig(0,p->tm_hour / 10, FALSE);
                        delay(2);
                        showDig(1,p->tm_hour % 10, ((p->tm_sec % 2) == 0));
                        delay(2);
                        showDig(2,p->tm_min / 10, FALSE);
                        delay(2);
                        showDig(3,p->tm_min % 10, FALSE);
                        delay(2);
                        break;

                case 2:
                        showDig(0,(p->tm_mon + 1) / 10, FALSE);
                        delay(2);
                        showDig(1,(p->tm_mon + 1) % 10, TRUE);
                        delay(2);
                        showDig(2,p->tm_mday / 10, FALSE);
                        delay(2);
                        showDig(3,p->tm_mday % 10, FALSE);
                        delay(2);
                        break;

                case 3:
                        showDig(0,dht11_data.humidity_int / 10, FALSE);
                        delay(2);
                        showDig(1,dht11_data.humidity_int % 10, TRUE);
                        delay(2);
                        showDig(2,dht11_data.humidity_decimal / 10, FALSE);
                        delay(2);
                        showDig(3,dht11_data.humidity_decimal % 10, FALSE);
                        delay(2);
                        break;

                case 4:
                        showDig(0,dht11_data.temperature_int / 10, FALSE);
                        delay(2);
                        showDig(1,dht11_data.temperature_int % 10, TRUE);
                        delay(2);
                        showDig(2,dht11_data.temperature_decimal / 10, FALSE);
                        delay(2);
                        showDig(3,dht11_data.temperature_decimal % 10, FALSE);
                        delay(2);
                        break;
        }
}

int main(void)
{
    short i,page=1;

    if(wiringPiSetup() != 0)
        return 1;

        pinMode(BTN,INPUT);
        pullUpDnControl(BTN, PUD_UP);

        dht11_init();

    for(i=0;i<8;i++)
    {
        pinMode(seg[i],OUTPUT);
    }

        for(i=0;i<4;i++)
        {
                pinMode(dig[i],OUTPUT);
        }

        while(1)
        {
                if(!digitalRead(BTN))
                {
                        page++;
                        if(page > 4)
                        {
                                page = 1;
                        }
                        for (i=0;i<4;i++)
                        {
                                digitalWrite(dig[i],HIGH);
                        }
                        delay(200);
                } else {
                        showPage(page);
                }
        }

    return 0;
}

【实现效果】

发表评论

电子邮件地址不会被公开。 必填项已用*标注