Arduino 入门学习笔记(二十一):DHT11 实验
开发板:正点原子ESP32S3
外设:DHT11
没有LCD屏可以用串口打印进行测试
例程源码在文章顶部可免费下载
1. DHT11 介绍
1.1 DHT11 简介
DHT11 是一款温湿度一体化的数字传感器, 实物图如下图所示:
该传感器包括一个电容式测湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时的采集本地湿度和温度。 DHT11 与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O口。传感器内部湿度和温度数据40Bit的数据一次性传给单片机,数据采用校验和方式进行校验,有效的保证数据传输的准确性。DHT11 功耗很低, 5V 电源电压下,工作平均最大电流 0.5mA。
DHT11 的技术参数如下:
- 工作电压范围: 3.3V ~ 5.5V
- 工作电流:平均 0.5mA
- 输出:单总线数字信号
- 测量范围:湿度 5 ~ 95%RH,温度-20 ~ 60℃
- 精度:湿度±5%,温度±2℃
- 分辨率:湿度 1%,温度 0.1℃
DHT11 的管脚排列如下图所示:
1.2 DHT11 时序介绍
虽然 DHT11 与 DS18B20 类似,都是单总线访问,但是 DHT11 的访问,相对 DS18B20 来说简单很多。下面我们先来看看 DHT11 的数据结构。DHT11 数字温湿度传感器采用单总线数据格式。即,单个数据引脚端口完成输入输出双向传输。其数据包由 5byte(40bit)组成。数据分小数部分和整数部分,一次完整的数据传输为 40bit,高位先处。
DHT11 的数据格式为: 8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit温度小数部分+8bit 校验和。其中校验和数据为前面四个字节相加。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如下图所示:
由以上数据就可得到湿度和温度的值,计算方法:
- 湿度 = byte4 . byte3 = 45.0(%RH)
- 温度 = byte2 . byte1 = 28.0(℃)
- 校验 = byte4 + byte3 + byte2 + byte1 = 73 (= 湿度 + 温度) (校验正确)
可以看出, DHT11 的数据格式十分简单的, DHT11 和 MCU 的一次通信最大为 34ms 左右,建议主机连续读取时间间隔不要小于 2s。
下面,我们介绍一下 DHT11 的传输时序。 DHT11 的数据发送流程如下图所示:
首先主机发送开始信号,即:拉低数据线,保持 t1(至少 18ms)时间,然后拉高数据线 t2(10-35us)时间,然后读取 DHT11 的响应,正常的话, DHT11 会拉低数据线,保持 t3(78-88us)时间,作为响应信号,然后 DHT11 拉高数据线,保持 t4(80~92us)时间后,开始输出数据。
DHT11 输出数字‘0’时序如下图所示:
DHT11 输出数字‘1’的时序如下图所示:
DHT11 输出数字‘0’和‘1’时序,一开始都是 DHT11 拉低数据线 54us,后面拉高数据线保持的时间就不一样,数字‘0’就是 23~27us,而数字‘1’就是 68~74us。
通过以上了解,我们就可以通过 ESP32-S3 来实现对 DHT11 的读取了。DHT11 的介绍就到这里,更详细的介绍,请参考 DHT11 的数据手册。
2. 硬件设计
2.1 例程功能
DHT11 每隔 100ms 左右读取一次数据,并把温度显示在 LCD 上。 LED 闪烁用于提示程序正在运行。
2.2 硬件资源
- LED 灯
LED-IO1 - USART0
U0TXD-IO43
U0RXD-IO44 - XL9555
IIC_SDA-IO41
IIC_SCL-IO42 - SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555) - DHT11
1WIRE_DQ-IO0(在 P5 端口,使用跳线帽将 IWIRE_DQ 和 IO0 相连)
2.3 原理图
DHT11 相关原理图和 DS18B20 原理图是一样的,这里就不再列出来了。
DHT11 和 DS18B20 的接口是共用一个的,不过 DHT11 有 4 条腿,需要把 U4 的 4 个接口都用上,将 DHT11 传感器插入到这个上面就可以通过 ESP32-S3 来读取温湿度值了。连接示意图如图所示:
注意: DHT11 实验也需要跳线帽进行连接 1WIRE_DQ 和 ESP32-S3 的 IO0。 1WIRE_DQ 和IO0 连接图如下图所示:
3. 软件设计
3.1 程序流程图
下面看看本实验的程序流程图:
3.2 程序解析
DHT11 驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。 DHT11 驱动源码包括两个文件: dht11.cpp 和 dht11.h。
本实验跟 DS18B20 一样, 使用 esp32 的底层引脚定义和底层函数来完成 DHT11 的驱动。
下面我们先解析 dht11.h 的程序。 对 DHT11 的数据引脚做了相关定义。
#define DHT11_DQ_PIN GPIO_NUM_0
由于数据线会存在输入输出模式的切换以及高低电平的设置,所以为 DS18B20_DQ_PIN 做了相关宏函数供单总线时序函数调用。
#define DHT11_DQ_OUT(x) gpio_set_level(DHT11_DQ_PIN, x)
#define DHT11_DQ_IN gpio_get_level(DHT11_DQ_PIN)
#define DHT11_MODE_IN gpio_set_direction(DHT11_DQ_PIN, GPIO_MODE_INPUT)
#define DHT11_MODE_OUT gpio_set_direction(DHT11_DQ_PIN, GPIO_MODE_OUTPUT)
gpio_set_direction 函数是设置 GPIO 的模式为输入模式还是输出模式; gpio_set_level 函数是设置 GPIO 的高低电平状态; gpio_get_level 函数是获取 GPIO 的电平状态。调用这些函数,需要包含“esp_system.h”头文件。
DHT11_DQ_OUT(x)宏函数的作用是设置 GPIO_NUM_0 的电平状态, x 为 0 表示低电平,x 为 1 表示高电平。
DHT11_DQ_IN 宏函数的作用是获取 GPIO_NUM_0 的电平状态,返回值为 0 表示低电平,返回值为 1 表示高电平。
DHT11_MODE_IN 宏函数的作用是设置 GPIO_NUM_0 为输入模式,该引脚在硬件上已经有上拉电阻。
DHT11_MODE_OUT 宏函数的作用是设置 GPIO_NUM_0 为输出模式。
下面我们再解析 dht11.cpp 的程序,首先先来看一下初始化函数 dht11_init,代码如下:
/**
* @brief 初始化 DHT11
* @param 无
* @retval 0:正常, 1:不存在/不正常
*/
uint8_t dht11_init(void)
{
dht11_reset();
return dht11_check();
}
在 DHT11 初始化函数中,通过调用复位函数和自检函数,可以知道 DHT11 是否正常。
下面介绍的是复位 DHT11 函数和等待 DHT11 的回应函数,它们的定义如下:
/**
* @brief 复位 DHT11
* @param 无
* @retval 无
*/
void dht11_reset(void)
{
DHT11_MODE_OUT; /* IO 模式设置为输出 */
DHT11_DQ_OUT(0); /* 拉低 DQ */
delay(20); /* 拉高至少 18ms */
DHT11_DQ_OUT(1); /* 拉高 DQ */
delayMicroseconds(30); /* 主机拉高 10~35us */
}
/**
* @brief 等待 DHT11 的回应
* @param 无
* @retval 0:正常, 1:不存在/不正常
*/
uint8_t dht11_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
DHT11_MODE_IN; /* IO 模式设置为输入 */
while (DHT11_DQ_IN && retry < 100) /* DHT11 会拉低约 83us */
{
retry++;
delayMicroseconds(1);
}
if (retry >= 100)
{
rval = 1;
}
else
{
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* DHT11 拉低后会再次拉高约 87us */
{
retry++;
delayMicroseconds(1);
}
if (retry >= 100)
{
rval = 1;
}
}
return rval;
}
以上两个函数分别代表着前面所说的复位脉冲与应答信号,大家可以对比前面的时序图进行理解。那么在上一章 DS18B20 的实验中,也对复位脉冲以及应答信号进行了详细的解释,大家也可以对比理解。
DHT11 与 DS18B20 有所不同, DHT11 是不需要写函数,只需要读函数即可,下面我们看一下读函数:
/**
* @brief 从 DHT11 读取一个位
* @param 无
* @retval 读取到的位值: 0 / 1
*/
uint8_t dht11_read_bit(void)
{
uint8_t retry = 0;
while (DHT11_DQ_IN && retry < 100) /* 等待变为低电平 */
{
retry++;
delayMicroseconds(1);
}
retry = 0;
while (!DHT11_DQ_IN && retry < 100) /* 等待变高电平 */
{
retry++;
delayMicroseconds(1);
}
delayMicroseconds(40); /* 等待 40us */
if (DHT11_DQ_IN) /* 根据引脚状态返回 bit */
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 从 DHT11 读取一个字节
* @param 无
* @retval 读到的数据
*/
uint8_t dht11_read_byte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++) /* 循环读取 8 位数据 */
{
data <<= 1; /* 高位数据先输出, 先左移一位 */
data |= dht11_read_bit(); /* 读取 1bit 数据 */
}
return data;
}
在这里 dht11_read_bit 函数从 DHT11 处读取 1 位数据,大家可以对照前面的读时序图进行分析,读数字 0 和 1 的不同,在于高电平的持续时间,所以这个作为判断的依据。 dht11_read_byte函数就是调用一位数据读取函数进行实现。
下面介绍读取温湿度函数,其定义如下:
/**
* @brief 从 DHT11 读取一次数据
* @param temp: 温度值(范围:-20~60°)
* @param humi: 湿度值(范围:5%~95%)
* @retval 0, 正常.
* 1, 失败
*/
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
dht11_reset();
if (dht11_check() == 0)
{
for (i = 0; i < 5; i++) /* 读取 40 位数据 */
{
buf[i] = dht11_read_byte();
}
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0];
*temp = buf[2];
}
}
else
{
return 1;
}
return 0;
}
读取温湿度函数也是根据时序图进行实现的,在发送复位信号以及应答信号产生后,即可以读取 5Byte 数据进行处理,校验成功即读取数据有效成功。
18_dht11.ino 代码
在 18_dht11.ino 里面编写如下代码:
#include "led.h"
#include "uart.h"
#include "xl9555.h"
#include "spilcd.h"
#include "dht11.h
uint8_t temperature; /* 温度值 */
uint8_t humidity; /* 湿度值 */
uint8_t t = 0;
/**
* @brief 当程序开始执行时,将调用 setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_init(); /* LED 初始化 */
uart_init(0, 115200); /* 串口 0 初始化 */
xl9555_init(); /* IO 扩展芯片初始化 */
lcd_init(); /* LCD 初始化 */
dht11_init(); /* DHT11 初始化 */
lcd_show_string(30, 50, 200, 16, LCD_FONT_16, "ESP32-S3", RED);
lcd_show_string(30, 70, 200, 16, LCD_FONT_16, "DHT11 TEST", RED);
lcd_show_string(30, 90, 200, 16, LCD_FONT_16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, LCD_FONT_16, "Temp: C", BLUE);
lcd_show_string(30, 130, 200, 16, LCD_FONT_16, "Humi: %", BLUE);
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
if (t % 10 == 0) /* 每 100ms 读取一次 */
{
dht11_read_data(&temperature, &humidity); /* 读取温湿度值 */
lcd_show_num(30 + 40, 110, temperature,2,LCD_FONT_16,BLUE); /* 显示温度 */
lcd_show_num(30 + 40, 130, humidity, 2, LCD_FONT_16, BLUE); /* 显示湿度 */
}
delay(100);
t++;
if (t == 20)
{
t = 0;
LED_TOGGLE(); /* LED0 闪烁 */
}
}
在 setup 函数中,调用 led_init 函数完成 LED 初始化,调用 uart_init 函数完成串口初始化,调用 xl9555_init 函数完成 XL9555 初始化,调用 lcd_init 函数完成 LCD 屏初始化,调用 dht11_init函数完成 DHT11 初始化, LCD 显示实验信息。
在 loop 函数中,间隔 100 毫秒调用 dht11_read_data 函数获取 DHT11 温湿度传感器的温度和湿度数据,然后在 LCD 进行显示。 LED 灯每隔 2000 毫秒状态翻转,实现闪烁效果。
4. 下载验证
假定 DHT11 传感器已经接上去正确的位置,将程序下载到开发板后,可以看到 LED0 不停的闪烁,提示程序已经在运行了。 LCD 显示当前的温度和湿度值的内容如下图所示: