基于智能家居项目 实现DHT11驱动源代码
DHT11 温湿度传感器的数据读取一般分为 四个步骤,下面详细介绍每个步骤的具体内容:
步骤一:主机发送起始信号
-  
主机(如 MCU)主动向 DHT11 发送开始信号,方式为:
-  
将数据线拉低 至少 18ms(确保 DHT11 能够识别这是一个起始信号);
 -  
然后拉高数据线 20~40μs;
 
 -  
 -  
这个动作通知 DHT11 准备发送数据。
 
步骤二:DHT11 发出响应信号
-  
接收到主机的起始信号后,DHT11 做出响应:
-  
首先将数据线拉低 80μs;
 -  
然后拉高数据线 80μs;
 
 -  
 -  
表示 DHT11 已准备好传输数据。
 
步骤三:DHT11 传输40位数据
-  
DHT11 按顺序传送 40 位数据(高位先传),格式如下:
 -   
8位湿度整数 + 8位湿度小数 + 8位温度整数 + 8位温度小数 + 8位校验和 -  
每一位的传输方式:
-  
逻辑“0”:拉高约 26~28μs;
 -  
逻辑“1”:拉高约 70μs。
 
-  
起始位:先拉低数据线约 50μs;
 -  
数据位:
 
 -  
 
步骤四:主机校验数据完整性
-  
主机接收完 40 位数据后,将前四个字节相加(无进位):
 -   
校验 = 湿度整数 + 湿度小数 + 温度整数 + 温度小数 -  
与第五字节(校验和)进行比较,验证数据是否正确;
 -  
若相符,说明读取成功。
 
在DHT11.c文件中
#include "DHT11.h"#include "delay.h"//初始化DHT11_Data引脚void DHT11_Init(){///因为PB3是不可用的,所以我们先解除把不可用//打开GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_PinRemapConfig (GPIO_Remap_SWJ_JTAGDisable , ENABLE);//初始化GPIO口为开漏输出RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init (GPIOA, &GPIO_InitStruct);//打开GPIOA时钟DHT11_H; //初始化为高电平delay_ms(1000); //进行一个延时,目的越过模块不稳定期}//主机发送起始信号void Start(){DHT11_L;delay_ms(20);DHT11_H // 释放总线,等待从机响应}//响应函数,就是主机读取IO口,是否为在指定电平,判断响应完成//比如返回0,表示响应成功u8 Respond(){u8 time_out = 0;while(DHT11_R == 1)// 等待主机响应{time_out++;delay_us(1);if(time_out > 50) //说明等待时间超长,没有等到应答信号{return 1;}}time_out =0;while(DHT11_R == 0) // 开始响应{time_out++;if(time_out > 100) //等待时间>100{return 2;}}if(time_out < 50) //响应时间过短{return 3;}while(DHT11_R == 1) //从机等待主机准备return 0;}//读取DHT1140位数据void DHT11_Read(u8 *pData){u8 i,j;for(i = 0 ; i<5;i++){for(j = 0;j<8;j++){while(DHT11_R == 0);delay_us(50);pData[i] <<= 1;if(DHT11_R == 1){pData[i] |= 1;while(DHT11_R == 1);}}}}//获取温湿度数据u8 DHT11_Get(u8 *shi,u8 * temp){u8 ret;u8 datas[5];Start();ret = Respond();if(ret ==1){return 1; //相应失败}DHT11_Read(datas);if( (datas[0]+ datas[1] + datas[2] + datas[3]) != datas[4]){return 2 ;// 接收数据错误}*shi = datas[0] + datas[1]/10;if(datas[3] & 0x80) //判断温度为负数{*temp = (datas[2] + (datas[3]& 0x0F) / 10 )* -1;}else{*temp = datas[2] + datas[3]/10;}return 0;}
重点讲一下,DHT11获取函数和DHT11读取函数
一、读取 DHT11 的 40 位数据:DHT11_Read(u8 *pData)
 
一、读取 DHT11 的 40 位数据:DHT11_Read(u8 *pData)void DHT11_Read(u8 *pData){u8 i, j;for(i = 0; i < 5; i++) // 总共要读取 5 个字节(8 位 * 5 = 40 位){for(j = 0; j < 8; j++) // 每个字节 8 位{while(DHT11_R == 0); // 等待 DHT11 拉高,表示开始发送这一位的高电平部分delay_us(50); // 延时 50us,用于判断当前是 0 还是 1pData[i] <<= 1; // 左移一位,为当前位腾出位置if(DHT11_R == 1) // 如果此时仍然为高电平,则是“1”{pData[i] |= 1; // 将当前最低位置 1while(DHT11_R == 1); // 等待高电平结束}// 如果延时后是低电平,就什么都不做,当前位默认是 0}}}🔍 核心原理解释:DHT11 发送每一位数据时,先是一个固定的低电平(约 50μs),然后是一个高电平:高电平持续 26~28μs 代表 0;高电平持续 70μs 代表 1;所以主机延时 50μs 后读取数据线状态,来判断是 0 还是 1。二、获取温湿度数据:DHT11_Get(u8 *shi,u8 *temp)u8 DHT11_Get(u8 *shi, u8 *temp){u8 ret;u8 datas[5];Start(); // 主机发送起始信号ret = Respond(); // 等待 DHT11 响应if(ret == 1){return 1; // 响应失败}DHT11_Read(datas); // 读取 40 位数据到 datas[0] ~ datas[4]// 校验和验证数据完整性if ((datas[0] + datas[1] + datas[2] + datas[3]) != datas[4]){return 2; // 校验失败,数据错误}// 获取湿度,datas[0]是整数部分,datas[1]是小数部分*shi = datas[0] + datas[1] / 10;// 获取温度,datas[2]是整数部分,datas[3]是小数部分if(datas[3] & 0x80) // 判断是否为负温(DHT11 其实不支持负温度,但兼容设计){*temp = (datas[2] + (datas[3] & 0x0F) / 10) * -1;}else{*temp = datas[2] + datas[3] / 10;}return 0; // 读取成功}🔍 注意点:Start() 和Respond() 是发送起始信号和等待响应,属于步骤 1 和 2;校验和确保数据的可靠性;DHT11 实际只返回 整数值,小数部分一般为0,设计保留是为了与 DHT22 等更高精度传感器兼容;datas[3] & 0x80 判负值是为兼容 DHT12 或 DHT22 数据格式,DHT11 实际温度是非负的,所以这段代码更具有通用性。
在DHT11.h文件
#ifndef _DHT11_H_#define _DHT11_H_#include "stm32f10x.h"#define DHT11_H GPIO_SetBits (GPIOB,GPIO_Pin_3);#define DHT11_L GPIO_ResetBits (GPIOB, GPIO_Pin_3)#define DHT11_R GPIO_ReadInputDataBit (GPIOB, GPIO_Pin_3)void DHT11_Init();u8 DHT11_Get(u8 *shi,u8 * temp);#endif
