文章目录
- 模块介绍:DHT11温湿度传感器
-
- [1. DHT11介绍](#1. DHT11介绍)
-
- [1.1 DHT11型号介绍](#1.1 DHT11型号介绍)
- [1.2 DHT11工作参数及引脚介绍](#1.2 DHT11工作参数及引脚介绍)
- [2. DHT11工作原理](#2. DHT11工作原理)
-
- [2.1 正常工作验证](#2.1 正常工作验证)
- [2.2 DHT11工作时序【与I2C类似】](#2.2 DHT11工作时序【与I2C类似】)
-
- [2.2.1 DHT11整体工作时序](#2.2.1 DHT11整体工作时序)
- [2.2.2 DHT11起始【复位】及响应信号](#2.2.2 DHT11起始【复位】及响应信号)
- [2.2.3 读时序](#2.2.3 读时序)
- [2.3 DHT11数据格式](#2.3 DHT11数据格式)
- [3. DHT11编程实战](#3. DHT11编程实战)
-
- [1. 搭建框架](#1. 搭建框架)
- [2. 填写输入输出口函数内容](#2. 填写输入输出口函数内容)
- [3. 起始信号](#3. 起始信号)
- [4. 读取一个字节和所有数据的函数](#4. 读取一个字节和所有数据的函数)
- [5. 主函数](#5. 主函数)
- [6. 代码展示](#6. 代码展示)
- [7. 实验结果](#7. 实验结果)
模块介绍:DHT11温湿度传感器
生活中经常使用温湿度数据,比如:天气预报、智能家居、智能大屏等等。这些数据可以通过温湿度传感器进行获取。在嵌入式开发中,温湿度传感器是一种十分常用的传感器。
1. DHT11介绍
DHT11(数字温湿度传感器)为3或4针单排引脚封装,连接方便。具有品质卓越、超快响应、抗干扰能力强、性价比高、超小体积、极低功耗的优点,使其称为在测温、测湿应用,在苛刻应用场合的不错选择。
DHT11内置一个电阻式感湿元件 和一个NTC测温元件 ,并与一个单片机相连接(DHT11内部)。每个DHT11都在极为精确的湿度校验室中进行校准,校准系数以程序的形式存在传感器中,传感器内部在检测信号的处理过程中要调用这些校准系数。DHT11采用简易快捷的单线制串行接收,方便系统集成。
1.1 DHT11型号介绍
DHT11有3脚和4脚两款,在使用上没有差别,接线都一样,主要接三根,四脚的款式有一脚悬空。四脚款接杜邦线会有点不稳,只能插面包板上,建议直接买三脚的。

同样可以测量温湿度的还有DHT20、DHT22等,都是大同小异。
DHT11虽然可以同时测量温湿度,但是测量范围没有专业测温传感器范围大。比如DS18B20专业测量温度的,温度范围是-55℃~-125℃,而DHT11只有0 ~ 50 ℃。
1.2 DHT11工作参数及引脚介绍
DHT11工作参数:
- 湿度测量范围:20 ~ 90%RH
- 湿度测量精度:±5%RH
- 温度测量范围:0 ~ 50℃
- 温度测量精度:±2℃
- 工作电压: DC 3.3V/5V
接线如下:正负极如果接反,会烧坏传感器
| DHT11 | STM32 |
|---|---|
| VCC | 3.3V/5V |
| DATA | 任意一个GPIO口 |
| GND | GND |
DHT11采用单总线协议,也就是使用一根DATA线进行数据的收发。
DHT11的DATA线一次通讯时间4ms左右,数据分整数部分、小数部分和校验位,具体为:8bit湿度整数数据 + 8bit温度小数数据 + 8bit校验位。
2. DHT11工作原理
2.1 正常工作验证
上电后,【电源指示灯/POWER】红灯亮,表示上电成功,正常工作。

2.2 DHT11工作时序【与I2C类似】
2.2.1 DHT11整体工作时序
DHT11整体工作时序为:主机发送开始信号、DHT11响应输出、主机输出40bit数据(湿度数据+温度数据+校验值),结束信号(可选)。
具体过程如下:
- 总线空闲状态为高电平,主机拉低总线等待DHT11响应,主机把总线拉低必须大于18ms,保证DHT11能检测到起始信号;
- 主机发送开始信号结束后,拉高总线电平并延时等待20 ~ 40us后,读取DHT11的响应信号;
- DHT11接收到主机的开始信号后,等待微处理器开始信号结束,发送80us低电平响应信号;
- DHT11发送80us高电平准备发送数据;
- DHT11发送40bit数据(湿度数据+温度数据+校验值)

2.2.2 DHT11起始【复位】及响应信号
首先主机拉低总线至少18ms,然后再拉高总线,延时20 ~ 40us,此时起始信号(有时也叫复位信号)发送完毕。
DHT11检测到复位信号,触发一次采样,并拉低总线80us表示响应信号,告诉主机数据已经准备好了。DHT11之后拉高总线80us,然后开始传输数据。如果检测到响应信号为高电平,则DHT11初始化失败,请检查线路是否连接正常。


2.2.3 读时序
DHT11开始传输数据。
每1 bit数据都以50us低电平开始,告诉主机开始传输一位数据了。
DHT11以高电平的长短定义数据位是0还是1:当50us低电平过后拉高总线,高电平持续26 ~ 28us表示0,高电平持续70us表示数据1。
当最后1 bit数据传送完毕后,DHT11拉低总线50us,表示数据传输完毕,随后总线由上拉电阻拉高进入空闲状态。
位数据0表示方式:
以50us低电平开始,高电平持续26 ~ 28 us表示0 。

位数据1表示方式:
以50us低电平开始,高电平程序70us表示1 。50us时是低电平,图中画的不太准。

2.3 DHT11数据格式
DHT11的DATA传输一次完整的数据为40bit,按照高位在前,低位在后的顺序传输。
数据格式为:8bit湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据 + 8bit校验位,一共5字节(40bit)数据。
校验位 = 前面几个bit数据加起来,放到校验位。
正常情况下,前四个字节的和刚好与校验位相等,通过这种机制可以保证数据传输的准确性。
判断如果前面几个bit数据加起来等于校验位,说明数据传输正确。
3. DHT11编程实战
1. 搭建框架
在串口打印功能实验基础上做,在BSP文件夹下建立dht11文件夹,以及.c和.h文件;
将dht11文件加载进来,以及头文件加载。
DHT11无初始化函数,因为DHT11只有一个IO口,既要作为输入口,也要作为输出口
dht11.c
c
// 配置输入口
void dht11_gpio_input(void)
{
}
// 配置输出口
void dht11_gpio_output(void)
{
}
// 时序:起始信号
void dht11_start(void)
{
}
// 读数据, 读一个字节
uint8_t dht11_read_byte(void)
{
}
// 触发一次转换,读取所有数据
void dht11_read(uint8_t *result)
{
}
dht11.h
c
#define DHT11_PORT GPIOA
#define DHT11_PIN GPIO_PIN_5
#define DHT11_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define DHT11_DQ_OUT(x) do { x ? \
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); \
}while(0)
#define DHT11_DQ_IN HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)
2. 填写输入输出口函数内容
c
// 配置输入口
void dht11_gpio_input(void)
{
// 打开时钟
DHT11_CLK_ENABLE();
引脚FHT11_PIN
模式是输入
无上拉或下拉
高速
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
// 配置输出口
void dht11_gpio_output(void)
{
// 打开时钟
DHT11_CLK_ENABLE();
引脚DHT11_PIN
模式是推挽输出
无上拉或下拉
高速
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
3. 起始信号
c
void dht11_start(void)
{
dht11_gpio_output(); //引脚为输出模式
DHT11_DQ_OUT(1/0); //表示高低电平
先是高电平
拉低
delay_ms(20); // 拉低至少18ms
拉高,但不用管20 ~ 40 us
// 主机拉高,等待从机,主动权交给从机
dht11_gpio_input(); // 输入模式
// 等待dht11总线拉低电平
while(DHT11_DQ_IN);
// 等待DHT11拉高电平
while(!DHT11_DQ_IN);
// 等待dht11总线拉低电平
while(DHT11_DQ_IN);
}
4. 读取一个字节和所有数据的函数
逻辑上:
数据0:从高电平拉低50us,再拉高26 ~ 28 us
数据1:从高电平拉低50us,再拉高70 us
等待上升沿之后,再等待50us判断是数据0还是1?50us是高电平,表示数据1;如果是低电平,表示数据0
c
char dht11_data[5] = {0};
uint8_t dht11_read_byte(void)
{
uint8_t temp = 0;
uint8_t i = 0;
// 定义一个数据,将8位保存起来
uint8_t read_data = 0;
for(i = 0; i < 8; i++)
{
// 一个字节是8位,读8次
while(!DHT11_DQ_IN);
delay_us(50);
if(DHT11_DQ_IN == 1)
{
temp = 1;
while(DHT11_DQ_IN);
}
else
temp = 0;
// 先移位,再把读到的数据插进去
read_data = read_data << 1;
read_data |= temp;
}
return read_data;
}
void dht11_read(uint8_t *result)
{
uint8_t i = 0;
// 1.起始响应信号
dht11_start();
// 2.读数据,输入模式
// 3. 循环读入5个数据,40bit数据
for(i = 0; i < 5; i++)
dht11_data[i] = dht11_read_byte();
// 校验读出数据是否准确
if(dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3] == dht11_data[4])
{
// 将4个字节拷贝到result
memcpy(result, dht11_data, 4);
printf("湿度:%d.%dRH,", dht11_data[0], dht11_data[1]);
printf("温度:%d.%d℃,", dht11_data[2], dht11_data[3]);
}
delay_ms(2000);
}
5. 主函数
main.c
c
uint8_t dht11_result[4];
while(1)
{
// 为了避免数据冲突,数据要清空
memset(dht11_result, 0, 4);
dht11_read(dht11_result);
}
6. 代码展示
dht11.c
c
#include "dht11.h"
#include "delay.h"
#include "string.h"
#include "stdio.h"
char dht11_data[5] = {0};
void dht11_gpio_input(void)
{
GPIO_InitTypeDef gpio_initstruct;
// 打开时钟
DHT11_CLK_ENABLE();
// 调用GPIO初始化函数
gpio_initstruct.Pin = DHT11_PIN;
gpio_initstruct.Mode = GPIO_MODE_INPUT;
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
void dht11_gpio_output(void)
{
GPIO_InitTypeDef gpio_initstruct;
// 打开时钟
DHT11_CLK_ENABLE();
// 调用GPIO初始化函数
gpio_initstruct.Pin = DHT11_PIN;
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio_initstruct);
}
// 起始及响应信号
void dht11_start(void)
{
dht11_gpio_output();
DHT11_DQ_OUT(1);
DHT11_DQ_OUT(0);
delay_ms(20);
DHT11_DQ_OUT(1);
// 响应信号:等待拉高/拉低
dht11_gpio_input(); // 输入模式
while(DHT11_DQ_IN); // 等待DHT11拉低电平
while(!DHT11_DQ_IN); // 等待DHT11拉高电平
while(DHT11_DQ_IN); // 等待DHT11总线拉低电平
}
// 读时序[读取每个字节]
uint8_t dht11_read_byte(void)
{
uint8_t temp = 0;
uint8_t i = 0;
// 定义一个数据,将8位保存起来
uint8_t read_data = 0;
// 读时序+读数据
for(i = 0; i < 8; i++)
{
while(!DHT11_DQ_IN); // 期待变成上升沿
delay_us(50);
if(DHT11_DQ_IN == 1) // 如果是高电平,=1
{
temp = 1; // 数据1
while(DHT11_DQ_IN); // 50us后,等待下降沿,拉低电平
}
else
temp = 0;
// 先移位,再把读到的数据插进去
read_data = read_data << 1; // 左移1位,右边空出1位
read_data |= temp;
}
return read_data;
}
// 读取每个数据,要有返回值接收
void dht11_read(uint8_t *result)
{
uint8_t i = 0;
// 1. 起始响应信号
dht11_start();
// 2. 读数据,输入模式
dht11_gpio_input();
// 3. 循环读入5个数据,40bit数据
for(i = 0; i < 5; i++)
dht11_data[i] = dht11_read_byte();
// 校验读出数据是否准确
if(dht11_data[0] + dht11_data[1] + dht11_data[2] + dht11_data[3] == dht11_data[4])
{
// 将4个字节拷贝到result
memcpy(result, dht11_data, 4);
printf("湿度:%d.%dRH,",dht11_data[0], dht11_data[1]);
printf("温度:%d.%d℃,",dht11_data[2], dht11_data[3]);
}
delay_ms(2000);
}
dht11.h
c
#ifndef __DHT11_H__
#define __DHT11_H__
#include "sys.h"
#define DHT11_PORT GPIOA
#define DHT11_PIN GPIO_PIN_5
#define DHT11_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
// 输出口:IO口拉高拉低
#define DHT11_DQ_OUT(x) do{ x ? \
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); \
} while(0)
// 输入口
#define DHT11_DQ_IN HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)
void dht11_read(uint8_t *result);
#endif
main.c
c
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "dht11.h"
#include "string.h"
uint8_t i = 0;
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init();
uart1_init(115200);
printf("hello world! \r\n");
uint8_t dht11_result[4];
while(1)
{
// 为了避免数据冲突,数据要清空
memset(dht11_result, 0, 4);
dht11_read(dht11_result);
}
}
7. 实验结果
如果用手捂住DHT11传感器,温湿度会变化。
