手把手教你玩转DHT11(原理+驱动)
大家生活中一定经常使用温湿度数据,比如:天气预报、智能家居、智慧大屏等等。这些数据可以通过温湿度传感器进行获取。在嵌入式开发中,温湿度传感器是一种十分常用的传感器。本文将为大家介绍温湿度传感器 DHT11,内容包含模块介绍、工作原理、驱动方法,并提供编程实战示例。
1. 源码下载及前置阅读
本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!
本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):
https://www.lxlinux.net/e/stm32/dht11-tutorial.html
如果不知道如何搭建 STM32 编程环境,不知道如何烧录 STM32 代码,可以阅读这篇文章:
https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html
如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:
https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html
文中所使用的芯片是 STM32F103C8T6 ,配套了一个工程模板,如果你需要自己搭建一个工程模板,可以参考下文:
https://www.lxlinux.net/e/stm32/create-stm32-hal-project-template.html
2. DHT11介绍
DHT11(数字温湿度传感器)为 3 或 4 针单排引脚封装,连接方便。具有品质卓越、超快响应、抗干扰能力强、性价比极高、超小的体积、极低的功耗的优点,使其成为在测温、测湿应用,在苛刻应用场合的一个非常不错的选择。
DHT11 内置一个电阻式感湿元件和一个 NTC 测温元件,并与一个单片机相连接(DHT11 内部)。每个 DHT11 都在极为精确的湿度校验室中进行校准,校准系数以程序的形式存在传感器中,传感器内部在检测信号的处理过程中要调用这些校准系数。DHT11 采用简易快捷的单线制串行接口,方便系统集成。
2.1 DHT11型号介绍
DHT11 有 3 脚和 4 脚两款,在使用上没有差别,接线都一样,主要接三根,四脚的款式有一脚悬空。四脚款接杜邦线会有点不稳,只能插面包板上,建议直接买三脚的。
<img src="https://lxlinux.superbed.verylink.top/item/655b12b2c458853aef4b8a5b.jpg" style="zoom: 67%;" />
同样可以测量温湿度的还有 DHT20、DHT22 等,都是大同小异。
DHT11 虽然可以同时测量温湿度,但是测量范围是打不过专业测温传感器的,比如 ds18b20 测量的温度范围就有 -55°C ~ 125°C,而 DHT11 只有 0~50℃。
2.2 DHT11工作参数及引脚介绍
DHT11 工作参数:
- 湿度测量范围:20~90%RH
- 湿度测量精度:±5%RH
- 温度测量范围:0~50℃
- 温度测量精度:±2℃
- 工作电压:DC 3.3V/5V
接线如下,别把正负极接反啦,接反会烧坏掉的。
DHT11 | STM32 |
---|---|
VCC | 3.3/5V |
DATA | 任意一个GPIO口 |
GND | GND |
DHT11 采用单总线协议,也就是使用一根 DATA 线进行数据的收发。DHT11 的 DATA 线一次通讯时间 4ms 左右,数据分整数部分、小数部分和校验位,具体为: 8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。
3. DHT11工作原理
3.1 正常工作验证
上电后,「电源指示灯/POWER」红灯亮,表示上电成功,正常工作。
3.2 DHT11工作时序
3.2.1 整体工作时序
DHT11 整体工作时序为:主机发送开始信号、DHT11 响应输出、主机接收 40bit 数据(湿度数据+温度数据+校验值),结束信号(可选)。具体过程如下:
-
总线空闲状态为高电平,主机拉低总线等待 DHT11 响应, 主机把总线拉低必须大于 18ms,保证 DHT11 能检测到起始信号;
-
主机发送开始信号结束后,拉高总线电平并延时等待 20-40us 后,读取 DHT11 的响应信号;
-
DHT11 接收到主机的开始信号后,等待微处理器开始信号结束,发送 80us 低电平响应信号;
-
DHT11 发送 80us 高电平准备发送数据;
-
DHT11 发送 40bit 数据(湿度数据+温度数据+校验值)。
过程 | 主机 | DHT11 |
---|---|---|
1 | 拉低>18ms | |
2 | 拉高20~40us | |
3 | 响应 80us 低电平 | |
4 | 拉高 80us | |
5 | 发送 40bit 数据(湿度数据+温度数据+校验值) |
3.2.2 起始及响应信号
总流程讲完介绍一下细分流程:
首先主机拉低总线至少 18ms,然后再拉高总线,延时 20~40us,此时起始信号(有时也叫复位信号)发送完毕。
DHT11 检测到复位信号后,触发一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了。DHT11 之后拉高总线 80us,然后开始传输数据。如果检测到响应信号为高电平,则 DHT11 初始化失败,请检查线路是否连接正常。
3.2.3 读时序
DHT11 开始传输数据。每 1bit 数据都以 50us 低电平开始,告诉主机开始传输一位数据了。DHT11 以高电平的长短定义数据位是 0 还是 1:当 50us 低电平过后拉高总线,高电平持续 26~28us 表示 0,高电平持续 70us 表示数据 1。
当最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,表示数据传输完毕,随后总线由上拉电阻拉高进入空闲状态。
位数据0表示方式:
以 50us 低电平开始,高电平持续 26~28us 表示 0。
位数据1表示方式:
以 50us 低电平开始,高电平持续 70us 表示 1。
3.3 DHT11数据格式
DHT11 的 DATA 传输一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。
数据格式为:8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位,一共 5 字节(40bit)数据。
正常情况下,前四个字节的和刚好与校验位相等,通过这种机制可以保证数据传输的准确性。
4. 编程实战
4.1 硬件接线
本教程使用的硬件如下:
-
单片机:STM32F103C8T6
-
温湿度传感器:DHT11
-
串口:USB 转 TTL
-
烧录器:ST-LINK V2
DHT11 | STM32 | USB 转 TTL |
---|---|---|
VCC | 3.3/5V | |
DAT | A8 | |
GND | G | |
A10 | TX | |
A9 | RX | |
G | GND |
我们使用 A8 作为 DHT11 的数据引脚,串口 1 进行 log 输出。
接线如下图:
4.2 加载DHT11模块
我们在模板工程里的 BSP 目录下创建一个 dht11 目录,然后创建 dht11.c 及 dht11.h 两个空文件。
打开工程,跟着我的贪吃蛇点点点:)
4.3 微秒级延时实现
DHT11 对时序要求严格,需要微妙级延时,我们常用的 HAL_Delay()
是毫秒级时延。实现微秒级延时的方法有很多,但是我们可以直接用模板工程中 delay 文件的 delay_us
。
void delay_us(uint32_t nus) { uint32_t temp; SysTick->LOAD = nus * g_fac_us; /* 时间加载 */ SysTick->VAL = 0x00; /* 清空计数器 */ SysTick->CTRL |= 1 << 0 ; /* 开始倒数 */ do { temp = SysTick->CTRL; } while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */ SysTick->CTRL &= ~(1 << 0) ; /* 关闭SYSTICK */ SysTick->VAL = 0X00; /* 清空计数器 */ }
4.4 DATA引脚配置
DHT11 采用单总线协议与单片机通信,有时作为输入有时作为输出,所以我们需要在 DATA 引脚上配置输入和输出。
void DHT_GPIO_INPUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.Pin=DHT11_PIN; GPIO_InitStructure.Mode=GPIO_MODE_INPUT; GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_IO,&GPIO_InitStructure); } void DHT_GPIO_OUTPUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.Pin=DHT11_PIN; GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_IO,&GPIO_InitStructure); }
4.5 起始及响应信号实现
按照前面介绍的 DHT11 工作时序:
**主机发送起始信号:**先将总线设为输出模式,总线空闲状态为高电平,拉低总线至少 18ms,然后再拉高总线,延时 20~40us,此时复位信号发送完毕。
**DHT11 发送响应信号:**总线设为输入模式,使用 while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN))
检测总线从高电平跳变到低电平,等待 DHT11 拉低总线 。DHT11 响应信号持续 80us,使用 while(!HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN))
检测总线低电平跳变到高电平。之后 DHT11 拉高总线 80us ,使用 while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN))
检测总线从高电平跳变到低电平,然后 DHT11 开始传输数据。
起始信号及响应信号代码如下:
void DHT11_Start() { DHT_GPIO_OUTPUT(); HAL_GPIO_WritePin(DHT11_IO, DHT11_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(DHT11_IO, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); //拉低总线至少 18ms HAL_GPIO_WritePin(DHT11_IO, DHT11_PIN, GPIO_PIN_SET); DHT_GPIO_INPUT(); while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)); //上一步将总线设为高电平,等待DHT11响应低电平 while(!HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)); //上一步DHT11响应低电平,等待DHT11拉高总线 while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)); //上一步DHT11拉高了总线,等待DHT11拉低总线,开始传送数据 }
4.6 读取1字节数据
将 DHT11 发来的二进制数据存储到 ReadData 变量中,读取一位后,左移一位,循环8次,最终得到 1 byte 数据。
那么如何判断我们读到的数据是 0 还是 1 呢?
通过 3.2.3 的分析可以知道,0 和 1 的时序只是高电平持续时间不同,所以我们只需要在 DHT11 拉低电平之后延时 40~60 微秒(代码中使用 50 微秒),再读取电平状态就可以了,如果是高电平则为 1,低电平则为 0 。
uint8_t DHT_Read_Byte(void) //从DHT11读取一位(8字节)信号 { uint8_t i; uint8_t ReadData = 0; //ReadData用于存放8bit数据,即8个单次读取的1bit数据的组合 uint8_t temp; //临时存放信号电平(0或1) for(i=0;i<8;i++){ while(!HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)); Delay_us(50); if(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN) == 1){ temp = 1; while(HAL_GPIO_ReadPin(DHT11_IO, DHT11_PIN)); }else{ temp = 0; } ReadData = ReadData << 1; ReadData |= temp; } return ReadData; }
4.7 一次数据读取及显示
根据 3.2 的时序,我们就可以使用代码实现 DHT11 一次读取数据过程。
注意:DHT11 读取数据间隔至少为 2 秒,否则读取到的数据可能不稳定,所以在最后可以延时 2 秒。
void DHT_Read() { uint8_t i; DHT11_Start(); DHT_GPIO_INPUT(); for(i= 0;i < 5;i++){ Data[i] = DHT_Read_Byte(); } if((Data[0]+Data[1]+Data[2]+Data[3])==Data[4]) { printf("湿度: %d.%dRH ,", Data[0], Data[1]); printf("温度: %d.%d℃\r\n", Data[2], Data[3]); }else{ printf("ERROR DATA\r\n"); } HAL_Delay(2000); }
dht11.h文件内容如下:
#ifndef __DHT11_H__ #define __DHT11_H__ #include "stdio.h" #include "stm32f1xx.h" #define DHT11_IO GPIOA #define DHT11_PIN GPIO_PIN_8 void DHT_Read(void); #endif
4.8 最终效果
5. 小结
通过本文的学习与实践,相信大家已经了解并掌握 DHT11 的特性和使用,能够更好地应用于嵌入式开发。无论是构建智能家居系统还是开发物联网设备,DHT11 都可以成为您的得力助手,让我们一起玩转 DHT11,love and peace!
另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。
刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!
有收获?希望老铁们来个三连击,给更多的人看到这篇文章
推荐阅读:
欢迎关注我的博客:良许嵌入式教程网,满满都是干货!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
再有人问你数据库连接池 Druid 的原理,这篇文章甩给他!
在 Spring Boot 项目中,数据库连接池已经成为标配,然而,我曾经遇到过不少连接池异常导致业务错误的事故。很多经验丰富的工程师也可能不小心在这方面出现问题。 在这篇文章中,我们将探讨数据库连接池,深入解析其实现机制,以便更好地理解和规避潜在的风险。 1 为什么需要连接池 假如没有连接池,我们操作数据库的流程如下: 应用程序使用数据库驱动建立和数据库的 TCP 连接 ; 用户进行身份验证 ; 身份验证通过,应用进行读写数据库操作 ; 操作结束后,关闭 TCP 连接 。 创建数据库连接是一个比较昂贵的操作,若同时有几百人甚至几千人在线,频繁地进行连接操作将占用更多的系统资源,但数据库支持的连接数是有限的,创建大量的连接可能会导致数据库僵死。 当我们有了连接池,应用程序启动时就预先建立多个数据库连接对象,然后将连接对象保存到连接池中。当客户请求到来时,从池中取出一个连接对象为客户服务。当请求完成时,客户程序调用关闭方法,将连接对象放回池中。 相比之下,连接池的优点显而易见: 1、资源重用: 因为数据库连接可以重用,避免了频繁创建,释放连接引起的大量性能开销,同时也增加了系统运行环境的...
- 下一篇
再见了Future,图解JDK21虚拟线程的结构化并发
Java为我们提供了许多启动线程和管理线程的方法。在本文中,我们将介绍一些在Java中进行并发编程的选项。我们将介绍结构化并发的概念,然后讨论Java 21中一组预览类——它使将任务拆分为子任务、收集结果并对其进行操作变得非常容易,而且不会不小心留下任何挂起的任务。 1 基础方法 通过Lambda表达式启动平台线程的这种创建线程的方法最简单,适用于简单情况。 // Lambda表达式启动平台线程的一种方法。 Thread.ofPlatform().start(() -> { // 在这里执行在独立线程上运行的操作 }); 问题 创建平台线程是昂贵的 若应用程序用户量很大,平台线程数量可能增长到超出JVM支持的限制 显然,大多数应用程序服务器不鼓励这种行为。因此,继续下一种方法——Java Futures。 2 Java Future类 JDK 5引入,开发者需要改变思考方式。不再考虑启动新线程,而考虑将“任务”提交到线程池以供执行。JDK 5还引入ExecutorService,任务将提交到该服务。ExecutorService是一个定义了提交任务并返回Java Future的机...
相关文章
文章评论
共有0条评论来说两句吧...