ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

DHT11实战:STM32F1 HAL库驱动、嘉立创EDA原理图与升级选型

DHT11实战:STM32F1 HAL库驱动、嘉立创EDA原理图与升级选型 1. 为什么DHT11至今还在被大量使用如果你最近在做一个环境监测的小项目比如温室大棚监控、机房温湿度记录、或者家里养了爬宠需要盯着温湿度搜索一圈之后你大概率会买几颗DHT11回来。这颗传感器从上市到现在已经超过十五年参数放在今天看并不亮眼湿度精度±5%RH温度精度±2℃采样间隔不能低于1秒单总线协议对时序要求还挺苛刻。但它的销量始终没怎么跌过原因很实在——便宜、够用、资料多、随便一个单片机都能驱动。我手头几个项目里DHT11出现的场景通常是这样的需求只需要知道“大概湿度够不够”“温度有没有超过阈值”对精度要求不高成本又压得很死。这时候用一颗几块钱的DHT11比上一颗高精度I2C传感器省下来的钱可能不多但省下来的调试时间和外围电路设计时间是真金白银。尤其是新手入门阶段用DHT11练手单总线时序、练手GPIO方向切换、练手微秒级延时是一个非常好的教学载体。不过问题也恰恰出在这里。很多人把DHT11当成一个“插上就能用”的模块结果在STM32F1上折腾半天读不出数据或者在嘉立创EDA里画原理图时随手一画板子打回来发现数据乱跳。这篇内容就是想把DHT11从原理到驱动、从画图到排查的整条链路讲清楚然后再聊聊当项目升级时新一代数字温湿度传感器该怎么选、怎么迁移。适合读这篇内容的人正在用STM32F1或者类似MCU驱动DHT11的嵌入式初学者准备自己画板子把DHT11集成进去的硬件爱好者以及那些DHT11用着还行、但隐约觉得该升级又不知道往哪升的人。注意本文所有代码基于STM32F1系列和HAL库编写其他平台思路一致时序参数需要根据主频重新计算。2. DHT11到底是怎么工作的2.1 内部结构拆解一颗传感器里装了什么DHT11的外观是一个四针或者三针的塑料壳子里面其实封装了两套独立的感应结构。测湿部分用的是一种高分子感湿电阻湿度变化时它的阻值会跟着变测温部分用的是一颗NTC热敏电阻温度升高阻值下降。这两路模拟信号不会直接输出而是先送到内部一颗8位单片机做ADC采样和校准校准系数出厂时已经烧录在OTP内存里用户改不了。这个结构决定了几个关键特性。第一DHT11输出的是数字信号不需要外部再加ADC省了一路模拟电路。第二因为内部有MCU做处理所以它需要一定的响应时间数据手册里写的采样周期不低于1秒就是这个原因。第三校准系数固定意味着它的精度天花板在出厂那一刻就定死了后期没法通过软件补偿大幅提升。我拆过一颗坏掉的DHT11里面就是一块很小的PCB上面贴了感湿膜和热敏电阻再加一颗COB封装的控制芯片。结构简单到让人怀疑它凭什么卖这个价但反过来想正因为简单它的可靠性在正确使用的前提下其实还不错。2.2 单总线协议一根线怎么完成双向通信DHT11最让人又爱又恨的就是它的通信方式。它只有一根数据线主机和从机轮流控制这根线的高低电平来传递信息。没有时钟线没有片选线全靠严格的时序约定。一次完整的通信流程是这样的主机先把数据线拉低至少18毫秒然后释放再拉低20到40微秒接着释放。这组动作是告诉DHT11“我要开始读数据了”。DHT11收到之后会先拉低80微秒作为应答再拉高80微秒表示“我准备好了”。接下来它连续输出40位数据每一位都是先拉低50微秒作为起始然后用高电平的持续时间来区分是0还是1。高电平持续26到28微秒表示0持续70微秒表示1。40位数据分成五组第一组是湿度的整数部分第二组是湿度的小数部分DHT11固定为0第三组是温度的整数部分第四组是温度的小数部分同样固定为0第五组是校验和等于前四组相加取低八位。这套协议看起来不复杂但实操时会发现两个坑。第一个坑是微秒级延时的精度HAL库自带的HAL_Delay只能到毫秒用不了。第二个坑是GPIO方向切换的速度在读数据阶段需要快速在输出和输入模式之间切换切换太慢就会错过位起始信号。提示DHT11的高电平持续时间是用来区分0和1的唯一依据所以读取时的采样点必须落在正确的时间窗口内。一般建议在位起始后的30到40微秒之间采样。2.3 为什么时序容错这么差很多人抱怨DHT11难驱动根本原因是它的时序窗口太窄。以区分0和1为例0的高电平是26到28微秒1的高电平是70微秒中间有超过40微秒的间隔。理论上容错空间不小但问题出在起始信号的检测上。如果主机在拉低18毫秒之后释放DHT11需要在20到40微秒内响应这个时间窗口对中断干扰极其敏感。我试过在跑着RTOS的工程里直接调用DHT11读取函数失败率超过一半。原因是任务切换和中断会打断微秒级延时导致时序偏移。后来改成读之前关中断读完再开成功率立刻上到95%以上。所以如果你在F1上读DHT11经常失败先检查是不是有其他中断在捣乱。3. 原理图设计嘉立创EDA上画DHT11电路的那些细节3.1 引脚连接不是随便接上就行DHT11通常有四个引脚VCC、DATA、NC、GND。三针版本把NC去掉了。VCC接3.3V还是5V数据手册写的是3.3V到5.5V但实测下来3.3V供电时信号幅度也是3.3V和STM32F1的IO电平匹配得很好。如果接5VDATA线的高电平就是5V虽然F1的很多引脚标称容忍5V但为了保险建议统一用3.3V供电。DATA引脚需要接一个上拉电阻典型值4.7k到10k。这个电阻的作用是在总线释放时把电平拉高因为没有它的话总线就是浮空状态读到的数据全是乱的。我见过有人在嘉立创EDA里忘了加上拉电阻板子打回来之后数据偶尔能读出来但极不稳定查了半天才找到原因。NC引脚悬空就行不用管。GND老老实实接地别想着省一根线。3.2 去耦电容和走线布局的实战经验DHT11的供电引脚旁边建议放一颗0.1微法的去耦电容位置越靠近传感器越好。这个电容的作用是滤掉电源上的高频噪声因为DHT11内部的模拟电路对电源质量比较敏感。我试过在电机驱动的板子上不加去耦电容DHT11读出来的湿度值会在电机启动瞬间跳变十几个百分点加上电容之后就稳了。走线方面DATA线尽量短不要和电机驱动线、继电器控制线平行走。如果板子上有大电流开关器件DHT11最好放在远离它们的位置。嘉立创EDA里做DRC检查的时候注意DATA线的网络别和其他信号线靠得太近间距至少保持0.3毫米以上。还有一个细节如果DHT11是外接的通过排线连到板子上排线长度不要超过20厘米。太长了分布电容会变大上升沿变缓读出来的位宽度会失真。我试过用50厘米的杜邦线连DHT11成功率直接掉到30%以下换成15厘米的排线就恢复正常了。3.3 一个可直接参考的原理图方案如果你在嘉立创EDA里画图可以参考这个连接方式DHT11的VCC接3.3V同时并联一颗0.1微法电容到地DATA引脚接一个4.7k上拉电阻到3.3V然后连到STM32F1的某个GPIO比如PA0GND接地。如果板子上有多个DHT11每个都要独立上拉不能共用一根DATA线。注意有些人为了省IO口想把多个DHT11的DATA线接到同一个引脚上通过不同地址区分。DHT11不支持地址寻址这样做只会导致总线冲突读出来的数据全是错的。4. STM32F1 HAL库驱动DHT11的完整实现4.1 微秒级延时的三种实现方式HAL库默认的HAL_Delay只能做到毫秒级驱动DHT11必须自己实现微秒延时。我试过三种方式各有优劣。第一种是用系统滴答定时器Systick做计数。把Systick的 reload 值设成1微秒对应的计数值然后轮询计数标志。这种方式精度最高但会干扰HAL库本身的毫秒计时需要小心处理。第二种是用一个通用定时器比如TIM2配置成1微秒计数一次读CNT寄存器做延时。这种方式不干扰Systick精度也不错是我最推荐的做法。配置的时候注意预分频值要算对F1主频72MHz的话预分频设成71计数器时钟就是1MHz每个计数就是1微秒。第三种是用空循环做粗略延时根据主频和循环次数估算。这种方式最省事但精度最差受编译器优化影响很大不推荐用在正式项目里。我一般用TIM2做微秒延时代码大概长这样void delay_us(uint16_t us) { __HAL_TIM_SET_COUNTER(htim2, 0); while (__HAL_TIM_GET_COUNTER(htim2) us); }调用之前确保TIM2已经初始化并且启动否则计数器不动会死循环。4.2 GPIO方向切换的正确姿势DHT11的DATA线在通信过程中需要多次切换方向。主机发送起始信号时是输出模式释放总线后要切成输入模式来读取DHT11的应答和数据。切换速度直接影响读取成功率。HAL库提供的HAL_GPIO_Init函数可以重新配置引脚方向但它的执行时间比较长大概几十微秒用在位读取的间隙里会错过时序。我的做法是直接操作寄存器修改CRL或CRH寄存器的对应位速度快很多。具体来说把PA0配置成输出模式时CRL的对应位设成0x3通用推挽输出50MHz配置成输入模式时设成0x4浮空输入或者0x8上拉输入。因为外部已经有上拉电阻了用浮空输入就行。#define DHT11_SET_OUT() do { GPIOA-CRL ~(0xF 0); GPIOA-CRL | (0x3 0); } while(0) #define DHT11_SET_IN() do { GPIOA-CRL ~(0xF 0); GPIOA-CRL | (0x4 0); } while(0) #define DHT11_OUT_H() (GPIOA-BSRR GPIO_PIN_0) #define DHT11_OUT_L() (GPIOA-BRR GPIO_PIN_0) #define DHT11_IN() (GPIOA-IDR GPIO_PIN_0)这套宏定义直接操作寄存器切换时间在纳秒级完全不影响时序。如果你的DHT11接在其他引脚上把GPIOA和对应的位改掉就行。4.3 完整读取函数的逐步拆解读取DHT11的完整流程可以分为五个阶段起始信号、等待应答、读取40位数据、校验、返回结果。我把它写成一个函数返回0表示成功返回1表示失败。起始阶段主机拉低至少18毫秒然后拉高20到40微秒接着切成输入模式等待DHT11响应。DHT11_SET_OUT(); DHT11_OUT_L(); HAL_Delay(20); // 拉低20ms满足最小18ms要求 DHT11_OUT_H(); delay_us(30); // 拉高30us DHT11_SET_IN(); // 切换为输入等待DHT11应答等待应答阶段DHT11会先拉低80微秒再拉高80微秒。我们需要检测这两个电平变化如果在超时时间内没等到就返回失败。uint32_t timeout 0; while (DHT11_IN() timeout 100) { timeout; delay_us(1); } if (timeout 100) return 1; timeout 0; while (!DHT11_IN() timeout 100) { timeout; delay_us(1); } if (timeout 100) return 1; timeout 0; while (DHT11_IN() timeout 100) { timeout; delay_us(1); } if (timeout 100) return 1;读取数据阶段循环40次每次等待位起始的低电平结束然后延时30到40微秒采样高电平如果还是高电平就说明这一位是1否则是0。for (int i 0; i 40; i) { timeout 0; while (!DHT11_IN() timeout 100) { timeout; delay_us(1); } delay_us(35); if (DHT11_IN()) { data[i / 8] | (1 (7 - (i % 8))); } timeout 0; while (DHT11_IN() timeout 100) { timeout; delay_us(1); } }校验阶段把前四个字节加起来取低八位和第五个字节比较相等则数据有效。uint8_t sum data[0] data[1] data[2] data[3]; if (sum ! data[4]) return 2;最终返回湿度和温度值DHT11的小数字节固定为0所以直接用整数部分就行。4.4 调用时需要注意的坑第一个坑是连续读取的间隔。两次读取之间至少要间隔1秒否则DHT11内部还没完成上一次转换读出来的数据可能是旧的或者错误的。我试过500毫秒读一次前几次正常后面就开始出错了改成1.5秒之后很稳定。第二个坑是读取期间关中断。前面说过中断会打断微秒延时导致时序偏移。我的做法是在读取函数开头调用__disable_irq()读完再__enable_irq()。这样做的副作用是系统会丢失这段时间的中断所以读取频率不能太高。如果项目里有对实时性要求很高的中断可以考虑只关掉优先级低于某个阈值的中断。第三个坑是上电后的稳定时间。DHT11刚上电的一秒内读出来的数据是不准确的建议上电后延时1到2秒再开始第一次读取。5. DHT11的局限和常见问题排查5.1 那些参数表上看不到的短板DHT11的数据手册写得很清楚湿度精度±5%RH温度精度±2℃量程湿度20%到90%RH温度0到50℃。这些数字看起来还能接受但实际用起来会发现几个手册没写的问题。第一是长期漂移。DHT11的感湿膜在潮湿环境里用久了会老化半年到一年之后读数会慢慢偏高。我有一批放在温室里的DHT11用了八个月之后和标准表对比湿度读数普遍高了7到10个百分点。这个漂移是不可逆的只能换新的。第二是响应速度慢。从低湿环境移到高湿环境DHT11需要几分钟才能稳定到新的读数。如果项目需要快速跟踪湿度变化DHT11完全不够用。第三是低温失效。虽然手册写量程从0℃开始但接近0℃时读数会明显不准零下之后干脆不工作。北方户外项目千万别用DHT11。5.2 读取失败排查速查表现象可能原因排查方法完全读不到数据超时返回接线错误或传感器损坏检查VCC和GND是否接反换一颗传感器试试偶尔能读偶尔失败上拉电阻缺失或阻值不合适确认DATA线有4.7k到10k上拉示波器看波形上升沿数据全是0或全是1时序偏差太大检查微秒延时是否准确示波器测位宽湿度值明显偏高传感器老化或受潮和新传感器对比确认是否已经漂移温度值正常湿度值乱跳电源噪声干扰加0.1微法去耦电容远离电机驱动线读取成功率低于50%中断干扰或延时不准读取期间关中断用定时器做微秒延时5.3 几个我从踩坑里总结的技巧技巧一读之前先发一个起始信号再丢弃。有时候DHT11处于不确定状态先读一次不管结果等1秒后再读第二次成功率会提高。这个操作相当于给传感器一个复位。技巧二如果DATA线比较长可以在靠近单片机的一端再加一个100欧姆的串联电阻抑制反射。虽然DHT11速率很低反射影响不大但在一些干扰强的环境里这个电阻能明显改善波形。技巧三不要在DHT11的供电线上接其他大功率器件。我试过把DHT11和一颗无线模块共用一路3.3V无线模块发射时DHT11读数就会跳分开供电之后问题消失。6. 新一代数字温湿度传感器的技术演进6.1 从DHT11到DHT22同一路线的升级DHT22也叫AM2302是DHT11的同门升级版。封装差不多协议兼容但参数全面提升湿度精度±2%RH温度精度±0.5℃量程扩展到0到100%RH和-40到80℃。价格大概是DHT11的三到四倍。如果你的项目本来用DHT11想升级但不想改代码DHT22是最省事的选择。时序基本一致只需要把读取间隔从1秒改成2秒因为DHT22的转换时间更长。我试过同一套驱动代码直接读DHT22改一下延时参数就能用数据明显更稳。但DHT22也没有解决根本问题依然是单总线依然对时序敏感依然有长期漂移。它只是把DHT11的短板补了一部分没有换赛道。6.2 SHT3x和SHT4xI2C接口带来的质变SHT3x系列是Sensirion出的数字温湿度传感器I2C接口湿度精度±2%RH温度精度±0.2℃量程覆盖0到100%RH和-40到125℃。SHT4x是更新的一代精度和长期稳定性进一步提升。I2C接口的好处是显而易见的。第一时序由硬件外设保证不需要微秒级延时也不怕中断干扰。第二可以挂多个传感器在同一组I2C总线上通过不同地址区分省IO口。第三通信速率高读取一次只要几毫秒采样率可以做到很高。我用SHT30做过一个冷链运输的温湿度记录仪每秒读一次连续跑了三个月数据漂移在0.1℃以内。同样的场景用DHT11根本做不到。缺点是价格。SHT3x大概是DHT11的十倍以上SHT4x更贵。而且I2C需要上拉电阻和更规范的PCB布局对新手来说门槛稍高。6.3 选型对比什么场景该用什么传感器接口湿度精度温度精度采样率价格区间适用场景DHT11单总线±5%RH±2℃1Hz极低教学、低成本监测DHT22单总线±2%RH±0.5℃0.5Hz低一般环境监测SHT30I2C±2%RH±0.2℃10Hz以上中工业、冷链、高精度SHT40I2C±1.8%RH±0.2℃10Hz以上中高高端监测、长期部署选型的核心逻辑是先看精度要求再看采样率要求最后看预算。如果精度要求±5%RH以内、采样间隔大于1秒DHT11够用。如果要求±2%RH、需要快速响应直接上I2C传感器不要在单总线上继续折腾。7. 从DHT11迁移到新一代传感器的实操路径7.1 硬件改板的注意事项从DHT11换到I2C传感器硬件上要做几处改动。第一DHT11的4.7k上拉电阻去掉换成I2C的4.7k上拉电阻接在SCL和SDA上。第二供电电压确认SHT3x支持2.4V到5.5V和DHT11一样可以用3.3V。第三如果板子上已经画了DHT11的封装换传感器意味着重新画板所以建议在新项目里直接预留两种传感器的兼容封装。嘉立创EDA里有现成的SHT3x封装库直接调用就行。注意SHT3x的引脚间距和DHT11不一样布局的时候留够空间。7.2 驱动层抽象让代码兼容两种传感器如果项目之前写了DHT11的驱动想平滑迁移可以在驱动层加一层抽象。定义一个统一的接口结构体typedef struct { uint8_t (*init)(void); uint8_t (*read)(float *temp, float *humi); } sensor_driver_t;DHT11和SHT3x各自实现这两个函数上层业务代码只调用接口不关心底层是哪种传感器。这样迁移的时候只需要换一个驱动文件业务逻辑完全不用动。我在一个环境监测项目里就是这么做的最早用DHT11后来换成SHT30上层的数据记录和报警逻辑一行没改只替换了驱动实现。7.3 数据校准和验证方法换传感器之后建议做一次对比验证。把旧传感器和新传感器放在同一个环境里每隔一分钟记录一次连续记录几个小时然后对比两组数据。如果差异在旧传感器的精度范围内说明新传感器工作正常。如果有条件找一个标准温湿度计做参考。我用过一款校准过的温湿度记录仪做基准对比下来SHT30的读数偏差在0.3℃和1.5%RH以内DHT11偏差到了1.8℃和6%RH。这个差距在要求不高的场景里无所谓但在精密监测里就是能不能用的区别。最后分享一个小经验不管用哪种传感器远离发热源和气流死角。我见过有人把DHT11装在单片机旁边读出来的温度永远比环境高3到5℃因为单片机和稳压芯片在发热。传感器要装在板子边缘或者用排线引出来离热源越远越好。
返回列表