DHT11 是一款温湿度复合传感器,常用于单片机系统中进行环境温湿度的测量。以下是对 DHT11 温湿度传感器的详细讲解:
一、传感器概述
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。
二、主要特点
- 体积小、功耗低:适合集成到各种小型电子设备中。
- 数字信号输出:无需复杂的模拟信号处理,直接输出数字信号,方便与单片机进行通信。
- 响应速度快:能够快速准确地测量环境温湿度变化。
- 精度较高:温度测量范围为 0℃ - 50℃,精度为 ±2℃;湿度测量范围为 20% - 90% RH,精度为 ±5% RH。
三、工作原理
- 传感器通过单片机的一个 I/O 口进行通信。单片机向 DHT11 发送启动信号后,DHT11 开始采集温湿度数据。
- 采集完成后,DHT11 将温湿度数据转换为数字信号,并通过数据线逐位发送给单片机。
- 单片机接收到数据后,进行校验和处理,以确保数据的准确性。
四、通信协议
- 单片机与 DHT11 之间采用单总线数据格式进行通信。通信过程包括初始化、发送启动信号、接收数据等步骤。
- 初始化时,单片机将数据线拉低一段时间,然后释放数据线,等待 DHT11 的响应。
- 发送启动信号时,单片机将数据线拉低至少 18ms,然后释放数据线,等待 DHT11 的响应。
- DHT11 接收到启动信号后,会发送一个 80μs 的低电平响应信号,接着发送 80μs 的高电平响应信号,表示准备好发送数据。
- DHT11 发送的数据包括 40 位,分别为 8 位湿度整数数据、8 位湿度小数数据、8 位温度整数数据、8 位温度小数数据和 8 位校验和。数据以低位在前的方式逐位发送。
- 单片机接收到数据后,进行校验和计算。如果校验和正确,则表示数据接收成功;否则,需要重新进行数据采集。
五、使用方法
- 硬件连接:将 DHT11 的 VCC 引脚连接到单片机的电源引脚,GND 引脚连接到地,DATA 引脚连接到单片机的一个 I/O 口。
- 软件编程:在单片机程序中,需要实现 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 毫秒,发送启动信号给 DHT11dht = 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 还是 0DH11_Star(); // 调用启动 DHT11 的函数for (i = 0; i < 5; i++) {// 卡 g 点:等待 DHT11 将引脚拉低,表示开始发送一组数据while (!dht);// 有效数据都是高电平,持续时间不一样,延时 50us 后根据引脚电平判断数据是 0 还是 1for (j = 0; j < 8; j++) {while (!dht); // 等待引脚拉低,表示开始读取一位数据Delay40us(); // 延时 40 微秒,根据引脚在这个时间后的电平判断数据位if (dht == 1) {flag = 1; // 如果引脚为高电平,标志位设为 1,表示读取到的数据位是 1while (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)延时 15msDelay15ms();//(2)写指令 38H(不检测忙碌信号) Write_Cmd_Func(0x38);//(3)延时 5msDelay5ms();//(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();// 初始化 LCD1602LCD1602_Init();// 再次延时 1000 毫秒Delay1000ms();Delay1000ms();// 设置 ledOne 为 0,可能用于控制某个 LEDledOne = 0;while (1) {// 延时 1000 毫秒Delay1000ms();// 读取 DHT11 传感器的数据Read_Data();// 如果温度(datas[2])大于等于 24if (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);}
}
成功实现: