DTH11温湿度传感器

时间:2024-09-29 22:42:05

DHT11 是一款温湿度复合传感器,常用于单片机系统中进行环境温湿度的测量。以下是对 DHT11 温湿度传感器的详细讲解:

一、传感器概述

DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。

二、主要特点

  1. 体积小、功耗低:适合集成到各种小型电子设备中。
  2. 数字信号输出:无需复杂的模拟信号处理,直接输出数字信号,方便与单片机进行通信。
  3. 响应速度快:能够快速准确地测量环境温湿度变化。
  4. 精度较高:温度测量范围为 0℃ - 50℃,精度为 ±2℃;湿度测量范围为 20% - 90% RH,精度为 ±5% RH。

三、工作原理

  1. 传感器通过单片机的一个 I/O 口进行通信。单片机向 DHT11 发送启动信号后,DHT11 开始采集温湿度数据。
  2. 采集完成后,DHT11 将温湿度数据转换为数字信号,并通过数据线逐位发送给单片机。
  3. 单片机接收到数据后,进行校验和处理,以确保数据的准确性。

四、通信协议

  1. 单片机与 DHT11 之间采用单总线数据格式进行通信。通信过程包括初始化、发送启动信号、接收数据等步骤。
  2. 初始化时,单片机将数据线拉低一段时间,然后释放数据线,等待 DHT11 的响应。
  3. 发送启动信号时,单片机将数据线拉低至少 18ms,然后释放数据线,等待 DHT11 的响应。
  4. DHT11 接收到启动信号后,会发送一个 80μs 的低电平响应信号,接着发送 80μs 的高电平响应信号,表示准备好发送数据。
  5. DHT11 发送的数据包括 40 位,分别为 8 位湿度整数数据、8 位湿度小数数据、8 位温度整数数据、8 位温度小数数据和 8 位校验和。数据以低位在前的方式逐位发送。
  6. 单片机接收到数据后,进行校验和计算。如果校验和正确,则表示数据接收成功;否则,需要重新进行数据采集。

五、使用方法

  1. 硬件连接:将 DHT11 的 VCC 引脚连接到单片机的电源引脚,GND 引脚连接到地,DATA 引脚连接到单片机的一个 I/O 口。
  2. 软件编程:在单片机程序中,需要实现 DHT11 的初始化、启动信号发送、数据接收和校验等功能。可以使用定时器或延时函数来满足通信协议的时序要求。

操作时序:

在这里插入图片描述

复位信号和响应信号:

被调函数(DTH11):

#include "reg52.h"
#include "delay.h"

sbit dht = P3^6; // 定义连接 DHT11 传感器的引脚,这里连接到 P3.6
char datas[5]; // 用于存储从 DHT11 读取的数据

/*
DHT11 的时序逻辑分析:
a : dht = 1  设置引脚为高电平。
b £ºdht = 0  将引脚拉低。
ÑÓʱ30ms  延时 30 毫秒。
c£º dht = 1  将引脚拉高。
在 60us 后读 d 点,如果 d 点是低电平(被模块拉低),说明模块存在。
*/

void DH11_Star() {
    dht = 1; // 设置引脚为高电平
    dht = 0; // 将引脚拉低
    Delay30ms(); // 延时 30 毫秒,发送启动信号给 DHT11
    dht = 1; // 将引脚拉高

    // 卡 d 点:等待 DHT11 拉低引脚作为响应开始信号
    while (dht);

    // 卡 e 点:等待 DHT11 释放引脚(变为高电平),表示准备发送数据
    while (!dht);

    // 卡 f 点:等待 DHT11 再次拉高引脚,确认准备发送数据
    while (dht);
}

void Read_Data() {
    int i; // 用于循环计数,表示读取的数据组数(这里共 5 组数据)
    int j; // 用于循环计数,表示每组数据中的每一位
    char temp; // 临时变量,用于存储每一位数据在移位过程中的值
    char flag; // 标志位,用于表示读取到的数据位是 1 还是 0
    DH11_Star(); // 调用启动 DHT11 的函数

    for (i = 0; i < 5; i++) {
        // 卡 g 点:等待 DHT11 将引脚拉低,表示开始发送一组数据
        while (!dht);

        // 有效数据都是高电平,持续时间不一样,延时 50us 后根据引脚电平判断数据是 0 还是 1
        for (j = 0; j < 8; j++) {
            while (!dht); // 等待引脚拉低,表示开始读取一位数据
            Delay40us(); // 延时 40 微秒,根据引脚在这个时间后的电平判断数据位

            if (dht == 1) {
                flag = 1; // 如果引脚为高电平,标志位设为 1,表示读取到的数据位是 1
                while (dht); // 等待引脚再次拉低,准备读取下一位数据
            } else {
                flag = 0; // 如果引脚为低电平,标志位设为 0,表示读取到的数据位是 0
            }

            temp = temp << 1; // 将 temp 左移一位,为存储下一位数据做准备
            temp |= flag; // 将标志位的值存入 temp,完成一位数据的读取
        }

        datas[i] = temp; // 将读取到的一组数据存储到 datas 数组中
    }
}

delay函数:
 

#include <intrins.h>
void Delay15ms()//@11.0592MHz
{
	unsigned char i, j;

	i = 27;
	j = 226;
	do
	{
		while (--j);
	} while (--i);
}


void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}

void Delay30ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay40us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 15;
	while (--i);
}


void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

LCD1602:
 

#include "reg52.h"
#include <intrins.h>
#include "delay.h"

#define databuffer P0 // 定义 8 位数据总线,连接到 P0 端口

sbit RS = P1^0; // 寄存器选择引脚,连接到 P1.0
sbit RW = P1^1; // 读写选择引脚,连接到 P1.1
sbit EN = P1^4; // 使能引脚,连接到 P1.4

/*
RS(Register Select):
    - P1.0
    - 当 RS = 0 时,选择指令寄存器;当 RS = 1 时,选择数据寄存器。
RW(Read/Write):
    - P1.1
    - 当 RW = 0 时,进行写操作;当 RW = 1 时,进行读操作。
E(Enable):
    - P1.4
    - 下降沿触发数据传输。
 */

void check_busy() {
    char temp = 0x80; // 初始化为 1000 0000,用于读取忙碌标志位
    databuffer = 0x80;
    // 当单片机给 1602 发送数据时,通过检查忙碌标志位来避免死循环。

    // 高电平表示忙碌,在读取时序中,此时模块不能接收命令。如果为低电平表示不忙碌。
    while (temp & 0x80) {
        // 进入读时序
        RS = 0; // 设置为指令寄存器
        RW = 1; // 设置为读操作

        EN = 0;
        _nop_(); // 空操作,可能用于延时等待稳定

        EN = 1;
        _nop_();
        _nop_();
        temp = databuffer; // 读取数据总线上的值

        EN = 0;
        _nop_();
    }
}

// 写指令函数
void Write_Cmd_Func(char cmd) {
    check_busy(); // 检查忙碌状态
    // 根据时序图配置引脚
    RS = 0; // 选择指令寄存器
    RW = 0; // 选择写操作

    EN = 0;
    _nop_(); // 延时等待稳定
    databuffer = cmd; // 将指令写入数据总线
    _nop_(); // 延时等待稳定

    EN = 1;
    _nop_();
    _nop_();
    EN = 0;
    _nop_();
}

// 写数据函数
void Write_data_Func(char datashow) {
    check_busy(); // 检查忙碌状态
    RS = 1; // 选择数据寄存器
    RW = 0; // 选择写操作

    EN = 0;
    _nop_(); // 延时等待稳定
    databuffer = datashow; // 将数据写入数据总线
    _nop_(); // 延时等待稳定

    EN = 1;
    _nop_();
    _nop_();
    EN = 0;
    _nop_();
}


void LCD1602_Init() {
    //(1)延时 15ms
    Delay15ms();

    //(2)写指令 38H(不检测忙碌信号) 
    Write_Cmd_Func(0x38);

    //(3)延时 5ms
    Delay5ms();

    //(4)以后每次写指令、读/写数据操作均需要检测忙碌信号

    //(5)写指令 38H:显示模式设置
    Write_Cmd_Func(0x38);

    //(6)写指令 08H:显示关闭
    Write_Cmd_Func(0x08);

    //(7)写指令 01H:显示清屏
    Write_Cmd_Func(0x01);

    //(8)写指令 06H:显示光标移动设置
    Write_Cmd_Func(0x06);

    //(9)写指令 0CH:显示开及光标设置
    Write_Cmd_Func(0x0c);
}
void LCD1602_showline(char row, char col, char *string) {
    // 根据行号设置显示地址
    switch (row) {
        case 1:
            // 设置第一行的显示地址
            Write_Cmd_Func(0x80 + col);
            // 循环写入字符串中的每个字符
            while (*string) {
                // 将当前字符写入数据寄存器以显示在 LCD 上
                Write_data_Func(*string);
                // 指针指向下一个字符
                string++;
            }
            break;
        case 2:
            // 设置第二行的显示地址
            Write_Cmd_Func(0x80 + 0x40 + col);
            while (*string) {
                Write_data_Func(*string);
                string++;
            }
            break;
    }
}

主函数:

#include "reg52.h"
#include <intrins.h>
#include "delay.h"
#include "uart.h"
#include "lcd1602.h"
#include "dht11.h"
#include "config.h"

// 定义用于存储温度和湿度显示字符串的字符数组
char temp[8];
char huma[8];
// 声明外部变量 datas,可能用于存储从 DHT11 传感器读取的数据
extern char datas[5];

// 函数用于构建温度和湿度的显示字符串
void build_datas() {
    // 将湿度的标识字符 'H' 存入 huma 数组的第一个位置
    huma[0] = 'H';
    // 将 datas 数组中第一个元素(湿度整数部分的十位数字)转换为 ASCII 码存入 huma 数组的第二个位置
    huma[1] = datas[0]/10 + 0x30;
    // 将 datas 数组中第一个元素(湿度整数部分的个位数字)转换为 ASCII 码存入 huma 数组的第三个位置
    huma[2] = datas[0]%10 + 0x30;
    // 存入小数点
    huma[3] = '.';
    // 将 datas 数组中第二个元素(湿度小数部分的十位数字)转换为 ASCII 码存入 huma 数组的第四个位置
    huma[4] = datas[1]/10 + 0x30;
    // 将 datas 数组中第二个元素(湿度小数部分的个位数字)转换为 ASCII 码存入 huma 数组的第五个位置
    huma[5] = datas[1]%10 + 0x30;
    // 存入百分号
    huma[6] = '%';
    // 字符串结束标志
    huma[7] = '\0';

    // 将温度的标识字符 'T' 存入 temp 数组的第一个位置
    temp[0] = 'T';
    // 将 datas 数组中第三个元素(温度整数部分的十位数字)转换为 ASCII 码存入 temp 数组的第二个位置
    temp[1] = datas[2]/10 + 0x30;
    // 将 datas 数组中第三个元素(温度整数部分的个位数字)转换为 ASCII 码存入 temp 数组的第三个位置
    temp[2] = datas[2]%10 + 0x30;
    // 存入小数点
    temp[3] = '.';
    // 将 datas 数组中第四个元素(温度小数部分的十位数字)转换为 ASCII 码存入 temp 数组的第四个位置
    temp[4] = datas[3]/10 + 0x30;
    // 将 datas 数组中第四个元素(温度小数部分的个位数字)转换为 ASCII 码存入 temp 数组的第五个位置
    temp[5] = datas[3]%10 + 0x30;
    // 存入摄氏度符号
    temp[6] = 'C';
    // 字符串结束标志
    temp[7] = '\0';
}

int main() {
    // 延时 1000 毫秒
    Delay1000ms();
    // 初始化串口
    UartInit();
    // 初始化 LCD1602
    LCD1602_Init();
    // 再次延时 1000 毫秒
    Delay1000ms();
    Delay1000ms();
    // 设置 ledOne 为 0,可能用于控制某个 LED
    ledOne = 0;

    while (1) {
        // 延时 1000 毫秒
        Delay1000ms();
        // 读取 DHT11 传感器的数据
        Read_Data();

        // 如果温度(datas[2])大于等于 24
        if (datas[2] >= 24) {
            // 设置风扇状态为关闭(fengshan 可能是一个变量用于控制风扇)
            fengshan = 0;
        } else {
            // 否则设置风扇状态为开启
            fengshan = 1;
        }

        // 构建温度和湿度的显示字符串
        build_datas();
        // 发送湿度字符串到串口
        sent_string(huma);
        // 发送回车换行符到串口
        sent_string("\r\n");
        // 发送温度字符串到串口
        sent_string(temp);
        // 发送回车换行符到串口
        sent_string("\r\n");
        // 在 LCD1602 的第一行第二列开始显示湿度字符串
        LCD1602_showline(1, 2, huma);
        // 在 LCD1602 的第二行第二列开始显示温度字符串
        LCD1602_showline(2, 2, temp);
    }
}

成功实现: