一、温湿度传感器简介
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点,如图1所示。
![](https://i-blog.csdnimg.cn/blog_migrate/807f8737adbee16356524a0ad4f07e0a.png)
其中每个DHT11传感器的校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数,这也就导致了每个传感器的系数不一样,因此可能测得的温湿度数据值也不一样,在实际使用过程中需要加入温湿度补偿值以便让其可以适应大多数应用场合。DHT11的引脚说明见下图2所示。
![](https://i-blog.csdnimg.cn/blog_migrate/eeabb1272fc5fee5134525b38b2cd9ed.png)
DHT11温湿度传感器采用单线制串行接口,使系统集成变得简易快捷。其拥有的超小体积、极低功耗,信号传输距离可达 60 米以上,使其成为该类应用中,在苛刻应用场合的最佳选择。
1.1 工作原理
单总线即只有一根数据线,系统中的数据交换、控制均由数据线完成。设备(微处理器)通过一个漏极开路或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线,而让其它设备使用总线;单总线通常要求外接一个约 4.7kΩ的上拉电阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结构,只有主机呼叫传感器时,传感器才会应答,因此主机访问传感器都必须严格遵循单总线序列,如果出现序列混乱,传感器将不响应主机。
DHT11温湿度传感器的功耗很低,在5V电源电压下,工作平均最大电流 0.5mA。其工作参数如下图3所示。
![](https://i-blog.csdnimg.cn/blog_migrate/3f2aaeccccd5d6553f9e0f4d6664678c.png)
DHT11 数字湿温度传感器采用单总线数据格式,即单个数据引脚端口完成输入输出双向传输。DHT11上电后,需要等待1s以越过不稳定状态。其数据包由 5Byte(40Bit)组成。数据分小数部分和整数部分,一次完整的数据传输为40bit,高位先出。DHT11的数据格式为:
8bit湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据 + 8bit校验和
其中8bit校验和的值为前四个字节相加。传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从DHT11读到的数据如图4所示。
![](https://i-blog.csdnimg.cn/blog_migrate/aa18108317f9fb215b37d614a18e1ddd.png)
由以上数据就可得到湿度和温度的值,计算方法如下:
湿度 = byte4 . byte3 = 45.0 (%RH)
温度 = byte2 . byte1 = 28.0 ( ℃)
校验 = byte4 + byte3 + byte2 + byte1 = 73 (湿度+温度) ---> (校验正确)
1.2 部分时序说明
DHT11 和 MCU 的一次通信最大为 3ms 左右,因此在使用MCU主机连续读取温湿度传感器数据的时间间隔不要小于100ms。DHT11 的数据通信流程如图5所示。
![](https://i-blog.csdnimg.cn/blog_migrate/a59d858dbfdc838708c7e261e517c778.png)
上图中每个信号的详细说明如下:t1时刻:主机(MCU)发送开始信号,即:拉低数据线,保持至少18ms;t2时刻:主机(MCU)拉高数据线20~40us,然后判断DHT11是否响应;t3时刻:DHT11拉低数据线40~50us,作为响应信号;t4时刻:DHT11 拉高数据线,保持40~50us后,开始输出数据。
DHT11 输出数据0的时序如下图6所示。
![](https://i-blog.csdnimg.cn/blog_migrate/602e0198806e77c02399a374253e57db.png)
DHT11 输出数据1的时序如下图7所示。
![](https://i-blog.csdnimg.cn/blog_migrate/4c0c5ea112d2ac4d5a3af56a23d56737.png)
二、STM32驱动源码
由于上述已经对传感器原理进行了详细描述,这里以STM32的驱动源码实现。dht11.c文件的代码如下:
#include "dht11.h"
#include "delay.h"
#include "stdbool.h"
static STRUCT_DHT11_TYPEDEF dht11;
/* 定义DQ端口 可以直接修改该宏定义更改DQ端口引脚定义 */
#define DHT11_GPIO_RCLK RCC_APB2Periph_GPIOA
#define DHT11_GPIO_PORT GPIOA
#define DHT11_GPIO_PIN GPIO_Pin_11
#define DHT11_DQ(x) GPIO_WriteBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN, (BitAction)x)
#define DHT11_DQ_STATUS GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN)
/**
* @brief 配置DQ引脚为输入/输出模式
* @param out true-输出 false-输入
* @retval 无
*/
static void dht11_set_inout(bool out)
{
RCC_APB2PeriphClockCmd(DHT11_GPIO_RCLK, ENABLE); /* 使能端口时钟 */
GPIO_InitTypeDef GPIO_InitStructure;
if(out == true) {
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽输出 */
}
else {
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
}
GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; /* 端口配置 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); /* 初始化IO口 */
}
/**
* @brief 初始化DHT11的引脚
* @param 无
* @retval 无
*/
static void dht11_gpio_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(DHT11_GPIO_RCLK, ENABLE); /* 使能端口时钟 */
GPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; /* 端口配置 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽输出 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); /* 初始化IO口 */
}
/**
* @brief 初始化DHT11的结构体
* @param 无
* @retval 无
*/
static void dht11_struct_config(void)
{
dht11.temperature = 0.f;
dht11.temperature_offset = -0.f;
dht11.humidity = 0.f;
dht11.humidity_offset = -0.f;
}
/* 功能:向DHT11发送开始信号 */
static void dht11_start(void)
{
dht11_set_inout(true); /* 配置为输出模式 */
DHT11_DQ(0); /* 拉低DQ引脚 */
delay_ms(20); /* 拉低至少18ms */
DHT11_DQ(1); /* DQ = 1 */
delay_us(30); /* 主机拉高20~40us */
}
/*
* 功能: 等待DHT11的回应
* 返回值:
* 返回1:未检测到DHT11的存在
* 返回0:存在
*/
static unsigned char dht11_waitAck(void)
{
unsigned char retry = 0;
dht11_set_inout(false); /* 配置为输入模式 */
while(!DHT11_DQ_STATUS && retry < 100) {/* DHT11会拉低80us -- 通知主机传感器正常 */
retry ++;
delay_us(1);
}
if(retry >= 100) return 1;
else retry = 0;
while(DHT11_DQ_STATUS && retry < 100) {/* DHT11会拉高80us -- 通知主机准备接收数据 */
retry ++;
delay_us(1);
}
if(retry >= 100) return 1;
return 0;
}
/*
* 功能:从DHT11读取一个位
* 返回值:
* 1 读取数据为1
* 0 读取数据为0
*/
static unsigned char dht11_readBit(void)
{
unsigned char retry = 0;
dht11_set_inout(false);
while(DHT11_DQ_STATUS && retry < 100) { /* 拉高延时准备输出 -- 等待变为低电平 */
retry ++;
delay_us(1);
}
retry = 0;
while(!DHT11_DQ_STATUS && retry < 100) { /* 先拉低50us -- 等待变为高电平 */
retry ++;
delay_us(1);
}
delay_us(30); /* 用于判断高电平持续时间,即接收数据为1或0 */
if(DHT11_DQ_STATUS) return 1;
return 0;
}
/*
* 功能:从DHT11读取一个字节
* 返回值:读到的数据
*/
static unsigned char dht11_readByte(void)
{
unsigned char i,data=0;
for(i=0;i<8;i++) {
data <<= 1;
data |= dht11_readBit();
}
return data;
}
/**
* @brief 初始化DHT11
* @param 无
* @retval 无
*/
void dht11_init(void)
{
dht11_struct_config();
dht11_gpio_config();
}
/**
* @brief DHT11数据采集函数
* @param 无
* @retval 无
* @note 函数执行的基准时间10us
*/
void dht11_measure(void *priv)
{
unsigned char buf[5];
dht11_start();
if( !dht11_waitAck() ) {
/* 读取数据 数据格式为 湿度整数 + 湿度小数 + 温度整数 + 温度小数 + 校验和(前四位之和) */
for(unsigned char i=0;i<5;i++)
buf[i] = dht11_readByte();
if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { /* 校验接收到的数据是否正确 */
dht11.humidity = buf[0] + buf[1] * 0.1f;
dht11.temperature = buf[2] + buf[3] * 0.1f;
}
}
}
/**
* @brief 获取dht11采集的温度数据
* @param 无
* @retval 温度数据 0℃-50℃
*/
float dht11_get_temperature(void)
{
float temperature = dht11.temperature + dht11.temperature_offset;
if(temperature > 50.f) { temperature = 50.f; }
if(temperature < 0.f) { temperature = 0.f; }
return (temperature);
}
/**
* @brief 获取dht11采集的湿度数据
* @param 无
* @retval 湿度数据 20%RH-90%RH
*/
float dht11_get_humidity(void)
{
float humidity = dht11.humidity + dht11.humidity_offset;
if(humidity > 90.f) { humidity = 90.f; }
if(humidity < 20.f) { humidity = 20.f; }
return (humidity);
}
/**
* @brief 设置dht11的温度补偿值
* @param offset - 补偿值
* @retval 无
*/
void dht11_set_temperature_offset(float offset)
{
dht11.temperature_offset = offset;
}
/**
* @brief 设置dht11的湿度补偿值
* @param offset - 补偿值
* @retval 无
*/
void dht11_set_humidity_offset(float offset)
{
dht11.humidity_offset = offset;
}
dht11.h文件实现如下:
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"
typedef struct {
float temperature; /* 获取的温度数据 范围:0~50℃ */
float temperature_offset; /* 温度补偿值 */
float humidity; /* 获取的湿度数据 范围:20%~90% */
float humidity_offset; /* 湿度补偿值 */
} STRUCT_DHT11_TYPEDEF;
/* ------------------------- DHT11操作函数 ------------------------- */
void dht11_init (void); /* 初始化DHT11 */
void dht11_measure (void *priv); /* 读取温湿度 */
float dht11_get_temperature (void); /* 获取dht11采集的温度数据 */
float dht11_get_humidity (void); /* 获取dht11采集的湿度数据 */
void dht11_set_temperature_offset(float offset); /* 设置dht11的温度补偿值 */
void dht11_set_humidity_offset (float offset); /* 设置dht11的湿度补偿值 */
#endif
三、驱动示例
代码一般驱动和使用流程如下:
- 1. 在dht11.c文件中修改DQ端口宏定义,使之适配自己的端口引脚;
- 2. 调用dht11_init()函数用于初始化传感器引脚;
- 3. 周期性的调用dht11_measure()函数用于测量传感器数据(注意:这里的测量/采集数据并没有返回值进行数据返回);
- 4. 在需要获取温度和湿度数据的地方调用dht11_getTemperature()和dht11_getHumidity()获取传感器的数据;
- 5. 若传感器数据与本地的温湿度数据有差异的话,可以调用dht11_set_temperature_offset()或dht11_set_humidity_offset()设置温湿度的补偿值。
其具体驱动代码示例如下:
/* 硬件层 */
#include "delay.h"
#include "usart.h"
#include "timer.h"
#include "dht11.h"
/* ---------------- 变量定义区 ---------------- */
static unsigned char dht11_timer_id = 0;
static float temp, humi;
int main(void)
{
delay_init();
usart1_init(115200);
basic_tim_init();
/* 外设初始化 */
dht11_init();
/* 定时任务创建 */
if(dht11_timer_id == 0) {
dht11_timer_id = timer_add_task(dht11_measure, NULL, 100);
}
while (1) {
temp = dht11_get_temperature();
humi = dht11_get_humidity();
printf("当前温度: %.3f 当前湿度: %.3f\n", temp, humi);
delay_ms(500);
}
}
测试并未使用串口将数据打印出来,我这里只是通过Keil自带的Debug去调试,得到的传感器示意如下图所示。
![](https://i-blog.csdnimg.cn/direct/d496a9df830f4e51ba48216e952cc416.png)