ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32驱动DHT11深度教程:单总线时序、上拉电阻与HAL库/标准库实战

STM32驱动DHT11深度教程:单总线时序、上拉电阻与HAL库/标准库实战 DHT11这颗传感器可以说是嵌入式入门路上绕不开的一个老朋友了。网上关于它的教程多得数不清但大部分都停留在“照着抄代码能出数”的层面时序为什么这么写、上拉电阻到底怎么选、为什么有时候读出来永远是0xFF、换了HAL库怎么就不转了——这些真正让人卡壳的问题反而很少有人讲透。这篇东西就打算以STM32为平台把DHT11从硬件原理、画图注意事项到标准库和HAL库的驱动写法、再到常见故障排查完整过一遍。不搞虚的全是实际调试时会遇到的东西。1. DHT11到底是个什么器件——先把规格书里容易踩的坑讲清楚1.1 单总线协议一个引脚怎么又供电又传数据DHT11用的是单总线协议英文叫Single-Wire Bus。这个名字听起来挺玄乎说白了就是主机和从机之间只靠一根数据线通信这根线既要承载命令又要承载数据而且它是开漏结构必须靠外部上拉电阻才能正常工作。你可以把它想成一条窄路平时大家都在路边等着高电平谁要说话就把路占住拉低说完再放开让车子继续走。为什么DHT11选这种看似别扭的通信方式因为它便宜、引脚少、封装小。传感器只有3个引脚实际是4个其中一个悬空VCC、GND、DATA不需要专门的时钟线省下来的引脚和走线对PCB面积和单片机资源都是实打实的节约。代价就是时序非常敏感微秒级别的偏差都可能导致通信失败这也是很多新手第一次用DHT11时会反复“翻车”的根本原因。注意DHT11的DATA引脚绝对不能直接推挽输出硬怼。它的本质是开漏结构你在代码里配置GPIO时要么配置成开漏输出带上拉要么在外部电路上加一个上拉电阻否则通信结果会非常不稳定后面会细说。1.2 数据格式40位数据到底怎么拆解DHT11一次完整的数据帧是40位也就是5个字节。这5个字节的分配是这样的第1字节湿度整数部分第2字节湿度小数部分DHT11的湿度分辨率是1%RH所以这一位实际输出0第3字节温度整数部分第4字节温度小数部分同样恒为0第5字节校验和等于前4个字节相加的低8位校验的逻辑非常简单如果前4个字节之和等于第5个字节就认为这帧数据有效。比如读到湿度是45%RH、温度是26°C那么前4个字节就是0x2D、0x00、0x1A、0x00校验字节应该是0x47。这里有个新手容易误解的地方DHT11的“小数部分”其实是为了协议兼容性预留的不是真的能测出零点几度的变化。它的测量精度是±2°C、±5%RH分辨率也只有1所以就算读出来小数位有数值也没有实际意义。拿它做精密测量是不现实的但做环境监测、温控逻辑判断、毕业设计展示完全够用。1.3 时序到底长什么样DHT11的通信过程可以拆成几个阶段主机发起起始信号 → DHT11响应 → DHT11连续输出40位数据 → 总线释放。先说起始信号主机先把总线拉低保持至少18ms实际建议大于20ms然后再释放总线释放后主机要立刻把引脚从输出模式切换成输入模式准备接收DHT11的响应。为什么需要这么久因为DHT11内部是8位单片机它在收到起始信号后要先完成一次温湿度采集这需要时间18ms是从规格书上来的经验值拉太短DHT11可能还没准备好。DHT11收到起始信号后会先把总线拉低80us再把总线拉高80us这就是它的响应信号。之后就开始逐位输出40位数据了。每一位数据的格式是先是50us的低电平然后是高电平高电平的持续时间决定了这一位是0还是1。高电平持续26us~28us算逻辑0持续70us表示逻辑1。所以你读DHT11的关键不是去测量电平状态而是去测量高电平的宽度。整个传输过程可以用下面这张表来概括阶段总线状态时长典型值备注主机起始信号拉低18ms建议实际做20ms以上主机释放释放并切输入20~40us切换不及时易丢响应DHT11响应低拉低80us紧随主机释放后DHT11响应高拉高80us为数据位做准备每位数据低电平50us 高电平变化低50us固定高26~28us为0高70us为1传输结束释放总线50us由外部上拉维持高电平这段时序用逻辑分析仪看非常清晰比用示波器还直观后面调试部分我会说为什么建议备一个几十块钱的逻辑分析仪。2. 硬件连接与原理图设计——嘉立创画图时最容易忽略的细节2.1 最小硬件连接除了DATA线还需要什么DHT11的硬件连接相当简单VCC接3.3V或5V都可以但要注意如果接5VSTM32的GPIO不一定能承受5V建议还是3.3V供电或者用稳压方案GND接地DATA接STM32的一个普通GPIO。有一点必须强调DATA引脚和VCC之间要接一个上拉电阻。DHT11的数据手册里写的是5kΩ左右但实际工程中4.7kΩ是最常见的取值。如果你用的是嘉立创EDA去画原理图直接放一个封装为0603或0805的4.7kΩ电阻就行。注意不要用STM32内部的上拉来代替这颗外部电阻。内部上拉的阻值大约在30kΩ~50kΩ之间驱动能力太弱在长线传输或寄生电容较大的情况下会让信号边沿变得很钝导致时序测量不准。我在实际测试中发现用内部上拉在面包板上能跑通但一旦换成杜邦线延长到20cm以上误码率就明显上升。2.2 上拉电阻怎么选4.7k不是万能的很多人画原理图的时候直接抄参考设计放一个4.7kΩ就完事了这没什么问题但如果你还想优化可以按照自己的实际应用来选。如果DATA线很短比如直接贴在STM32最小系统板上可以选10kΩ功耗更低信号边沿也够快。如果线比较长或者DHT11离单片机比较远就选4.7kΩ甚至2.2kΩ这样上拉能力更强能更快把总线拉回高电平。我的经验是面包板杜邦线场景用4.7kΩ最稳PCB上近距离连接用10kΩ也没问题超过30cm的引线建议用2.2kΩ但要注意这会让低电平时的灌电流变大GPIO本身能承受只是心里有个数就好。2.3 供电与PCB布局的几个注意点DHT11虽然便宜但对电源质量还是有点敏感的。尤其是当你用USB供电或者模块电源时纹波大的话会让读到的温湿度数据跳变看起来像是传感器坏了其实是供电问题。在嘉立创画原理图时建议在DHT11的VCC和GND之间加一个100nF去耦电容位置要尽量靠近DHT11的引脚。如果系统里还有电机、继电器这类大负载最好让DHT11的供电和负载供电分开走线或者至少加一个磁珠隔离。PCB布局上DHT11的开孔封装就是那种带网格的通孔设计是为了让空气流通所以画PCB的时候不要把上面覆盖大面积铺铜或丝印让它裸露在空气里才能准确感知环境温湿度。很多同学打样回来后发现测出来的温度比实际偏高好几度排查半天发现是DHT11紧挨着STM32芯片或者LDO稳压器被这几颗发热大户给“烤热”了。尽量让DHT11远离发热器件实在躲不开就在结构上做隔热处理或者把DHT11接到一小段延长线上伸出PCB。3. STM32驱动编写标准库与HAL库两条路线全写一遍3.1 先搞定微秒级延时delay_us的三种实现写DHT11驱动最核心的依赖就是微秒级延时。DHT11时序里动辄几十微秒的窗口如果延时函数精度差整个通信就会崩。三种常用实现第一种裸循环延时。用简单的for循环配合空指令通过示波器或者逻辑分析仪校准次数。优点是零依赖缺点是换个编译优化等级时序就变了甚至晶振频率不同也要重调。void delay_us_us(uint32_t us) { uint32_t i; for (i 0; i us * 8; i) { __NOP(); } }第二种SysTick延时。利用STM32内核自带的SysTick定时器配置成1us一次中断或者查询计数精度高、不占用额外定时器。static uint32_t us_ticks; void delay_us_init(void) { SysTick-CTRL 0; SysTick-LOAD 72 - 1; // 72MHz主频1us中断一次 SysTick-VAL 0; SysTick-CTRL 0x03; // 使能SysTick使用内核时钟 } void delay_us_systick(uint32_t us) { us_ticks us; while (us_ticks); } void SysTick_Handler(void) { if (us_ticks) us_ticks--; }第三种定时器延时。用通用定时器比如TIM2、TIM3配置成微秒级计数查询CNT寄存器实现延时。这种适合HAL库场景因为HAL的HAL_Delay只能做到毫秒级微秒级还是要自己写。// TIM2以1MHz计数TIM2-CNT的值就是当前微秒数 void delay_us_tim2(uint32_t us) { TIM2-CNT 0; while (TIM2-CNT us); }三种方案我都用过最推荐的是SysTick方案因为它不占用额外的通用定时器而且精度足够。但要注意如果工程里RTOS或者别的模块也在用SysTick那就要换定时器方案不要硬抢。3.2 标准库版驱动GPIO翻转实现单总线时序标准库的写法核心就是控制GPIO的ODR和IDR寄存器来做输出和输入切换。先定义引脚操作宏#define DHT11_GPIO_PORT GPIOB #define DHT11_GPIO_PIN GPIO_Pin_8 #define DHT11_OUT_LOW() GPIO_ResetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN) #define DHT11_OUT_HIGH() GPIO_SetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN) #define DHT11_IN_READ() GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN) #define DHT11_IO_OUT() do { \ GPIO_InitTypeDef GPIO_InitStructure; \ 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); \ } while(0) #define DHT11_IO_IN() do { \ GPIO_InitTypeDef GPIO_InitStructure; \ GPIO_InitStructure.GPIO_Pin DHT11_GPIO_PIN; \ GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; \ GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStructure); \ } while(0)上面这段有个关键点把引脚从输出切到输入时模式用浮空输入。因为外部已经有4.7kΩ上拉电阻把总线拉高不需要内部上拉用浮空输入可以减少内部上拉带来的信号边沿影响。注意如果你用的是开漏输出模式可以不用切换模式直接通过输出寄存器控制高低电平读的时候读IDR就行。但这种方式对新手来说容易混淆还是推荐用“推挽输出切换输入模式”的方式逻辑更清晰。接着是核心的读位函数。每一位的读取流程是先等低电平结束然后开始计时高电平的宽度。uint8_t DHT11_ReadBit(void) { uint8_t retry 0; // 等待低电平结束50us的低电平 while (!DHT11_IN_READ()) { if (retry 200) return 0xFF; // 超时保护约200us delay_us_systick(1); } // 高电平开始延时判断 delay_us_systick(40); // 40us后判断电平 if (DHT11_IN_READ()) return 1; // 高电平仍然有效说明是1 else return 0; // 高电平已结束说明是0 }为什么40us之后判断因为逻辑0的高电平持续26~28us逻辑1的高电平持续70us。在40us这个时间点逻辑0已经结束高电平变成低电平了而逻辑1还保持在高电平。用这个中间值做判断容错最好。我看到有些人用30us、50us不是不行但40us是实际测试下来最稳妥的。读取整帧数据的函数uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] {0, 0, 0, 0, 0}; uint8_t i, j; // 1. 主机拉低起始信号 DHT11_IO_OUT(); DHT11_OUT_LOW(); delay_us_systick(20000); // 拉低20ms // 2. 释放总线切换输入模式 DHT11_OUT_HIGH(); DHT11_IO_IN(); // 3. 等待DHT11响应信号低电平80us // 释放总线后DHT11会在20~40us后拉低 delay_us_systick(40); // 4. 确认响应信号 if (DHT11_IN_READ()) return 1; // 应该检测到低电平如果没有说明传感器没响应 // 5. 等待响应低电平结束 while (!DHT11_IN_READ()); // 6. 等待响应高电平结束 while (DHT11_IN_READ()); // 7. 读取40位数据 for (j 0; j 5; j) { for (i 0; i 8; i) { data[j] 1; data[j] | DHT11_ReadBit(); } } // 8. 校验 if ((uint8_t)(data[0] data[1] data[2] data[3]) ! data[4]) return 2; // 校验失败 *humidity data[0]; *temperature data[2]; return 0; // 成功 }这套代码写完后用标准库跑逻辑上没毛病但有几个地方值得特别注意步骤1里拉低20ms以后释放总线不是立刻就切输入。要先输出高电平让总线被上拉电阻拉起然后再切输入模式。如果先切输入再拉高引脚就处于浮空状态总线电平不确定。很多人的代码顺序反了结果就是读不到响应。步骤3里的40us延时是为了跳过主机释放总线到DHT11响应之间的空白期。这个时间其实不是固定的我实测在30us到50us之间都见过。步骤5和6的while循环一定要加超时保护否则DHT11如果损坏或者没接好程序会死等在这里整个系统卡死。这个毛病在延时函数delay卡死的排查里非常典型后面专门说。3.3 HAL库版驱动基于定时器的稳定实现HAL库和标准库在GPIO控制层面核心逻辑是一样的区别主要在初始化代码和延时函数。HAL库的HAL_Delay只有毫秒级所以微秒延时需要用定时器来补齐。先用CubeMX配置一个基本定时器比如TIM6配置为1MHz计数频率。如果你用72MHz主频预分频PSC设为72-1自动重载ARR设大一点比如0xFFFF这样TIM6-CNT的值就是1us的计数器。// 初始化代码CubeMX生成后补充如下 void DHT11_DelayUs(uint32_t us) { __HAL_TIM_SET_COUNTER(htim6, 0); while (__HAL_TIM_GET_COUNTER(htim6) us); }然后是HAL库版本的引脚操作封装。HAL库的GPIO读函数是HAL_GPIO_ReadPin写函数是HAL_GPIO_WritePin模式切换需要重新初始化GPIO。#define DHT11_GPIO_PORT GPIOB #define DHT11_GPIO_PIN GPIO_PIN_8 void DHT11_Pin_Mode_Output(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_GPIO_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStruct); } void DHT11_Pin_Mode_Input(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_GPIO_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(DHT11_GPIO_PORT, GPIO_InitStruct); } void DHT11_Output_Low(void) { HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET); } void DHT11_Output_High(void) { HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); } uint8_t DHT11_Input_Read(void) { return HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN); }注意HAL库的GPIO_MODE_INPUT加上GPIO_NOPULL就是浮空输入。如果你在CubeMX里把这个引脚初始化成了上拉模式通信过程中时序会受影响。最好的做法是在CubeMX里不要初始化这个引脚的复用功能保持默认模拟输入状态然后在代码里动态切换。HAL库版本的数据读取函数和标准库完全一样只是把底层宏替换成HAL函数。如果你之前用的是正点原子或者野火的HAL库模板思路完全可以直接套。3.4 数据校验与温湿度换算数据校验的逻辑很简单但是一定要养成习惯。我问过很多初学者他们的代码里根本没有校验这一步直接拿前四个字节当有效数据用。现场调试偶尔能出正确数据但一旦出现干扰或者传感器老化就会出现离谱的跳变比如湿度显示255%RH、温度显示-45°C。校验代码就三行if ((uint8_t)(data[0] data[1] data[2] data[3]) ! data[4]) { return 2; // 校验失败丢弃本次数据 }温湿度换算是DHT11最容易的一点因为它不需要任何计算。湿度整数部分直接就是百分比单位%RH温度整数部分直接就是摄氏度°C。注意温度有可能出现负数表示但DHT11本身在零下的精度也很差本质上不适合户外低温测量室内场景基本不会遇到负数问题。如果你想把数据格式化成字符串用于串口打印或者OLED显示建议用sprintfchar displayBuf[32]; sprintf(displayBuf, T:%d.%d C H:%d.%d %%, data[2], data[3], data[0], data[1]);但要注意DHT11的小数位恒为0所以打印出来永远是.0这是正常现象不要以为是传感器坏了。4. 时序不对/数据全0xFF——高频问题排查实录4.1 延时函数卡死排查“读DHT11程序跑着跑着卡死了”“delay函数进了出不来”这应该是DHT11开发中排名第一的故障现象。常见的根因有三种第一种等待响应信号的while循环没有超时保护。DHT11如果没接好、供电不稳或者损坏它就不会拉低总线发出响应信号。如果你写的是while(!DHT11_IN_READ());没加超时程序就会永远死在这个循环里。SysTick中断还在跑但主循环已经丧失响应表现出来就是整个系统“卡死”。排查思路用调试器在线仿真暂停程序看看PC指针停在哪一行。如果停在while等待响应那几行基本就实锤了。解决办法很容易给每个while循环加一个计数变量超过阈值就跳出返回错误码。uint16_t timeout 0; while (!DHT11_IN_READ()) { if (timeout 5000) return 0xFF; // 约5ms超时 delay_us_systick(1); }第二种SysTick已经被其他模块占用。有些工程模板把SysTick配置成了10ms时基HAL_Delay依赖它你又想拿SysTick做1us延时结果两个功能冲突。SysTick的LOAD寄存器被改来改去中断回调也互相干扰延时函数自然就乱了。排查思路检查启动文件里SystemInit之后、main函数之前的SysTick配置代码看看有没有被改成别的时基。解决办法如果工程里大量依赖HAL_Delay建议不要再动SysTick改用通用定时器做微秒延时。TIM2、TIM3、TIM4、TIM5这些都可以。第三种中断优先级问题导致延时被打断。如果系统里开了串口中断、定时器中断且中断处理函数耗时较长那么你的微秒延时会被频繁打断计时不准确读DHT11的时序窗口就被拉宽或者压缩读出来的数据大概率不对。排查思路在DHT11读取期间关闭优先级较高的中断或者把DHT11的时序读取放在最低优先级中断上下文里执行又或者用临界区保护整段读取过程。4.2 引脚配置错误导致数据恒为0xFF数据恒为0xFF意思是每个位读出来都是1。这通常意味着总线一直保持高电平DHT11根本没把总线拉低过。先查硬件连接DHT11的DATA引脚有没有接到STM32对应的GPIO上有没有虚焊面包板上的杜邦线是不是松了这些基础问题听着弱智但实际发生概率极高。再查GPIO配置你用来读时序的引脚在初始化的时候有没有被复用成其他功能比如PA9和PA10默认是USART1的TX和RX如果你把DHT11接到PA9上这在一些开发板的默认工程里就会有问题。我用标准库的时候遇到一个隐蔽的情况引脚的GPIO_Speed没配置默认速度是2MHz结果GPIO翻转不够快读出来的数据全是乱的。换成GPIO_Speed_50MHz之后问题消失。HAL库也一样GPIO_SPEED_FREQ_HIGH是必须的。4.3 上拉电阻缺失或阻值不当如果你用的是现成的DHT11模块上面一般已经焊好了上拉电阻直接用没问题。如果用的是裸的DHT11传感器件插面包板用的时候忘记加上拉电阻这就像没有马路却想让车跑一样总线电平完全浮空读回来的数据随机跳变。还有一种情况你在开发板上外接了很长的杜邦线线上寄生电容变大上拉电阻又太大导致信号上升沿变得很缓。这种情况下示波器看波形你会发现高电平不是陡峭的方波而是慢慢爬坡的曲线。在电平到达判定阈值之前你如果已经采样了读出来的就是0。所以别人用4.7k能跑通的代码在你这里因为线长了30cm就失败用2.2k电阻换上基本上立竿见影。4.4 调试环境问题ST-LINK和串口对时序的干扰这种情况也比较常见程序里加了串口打印一旦打印温湿度数据读取就失败不打印就正常。原因就是串口打印占用时间太长UART发送一个字节在9600波特率下要1ms左右而数据帧有几十个字节如果在DHT11读取过程中插入打印时序早就错乱了。解决办法是把串口打印放在DHT11数据读取完成之后不要边读边打印。或者用DMA方式发送串口数据把打印操作从阻塞变成后台执行。ST-LINK在线调试时如果设置了断点或者单步执行DHT11的时序会被调试器暂停通信失败是正常的。这不算bug你把断点设在主循环的打印语句处不设在DHT11读取函数内部就不会干扰时序。5. 进阶玩法与优化建议——把DHT11项目做得更像样5.1 结合OLED实时显示DHT11读出的温湿度如果不做显示就只能用串口看调试信息不能算一个完整的作品。很多人把DHT11和0.96寸OLED搭配在一起做一个迷你温湿度计这个组合非常适合入门。驱动OLED建议用I2C接口的SSD1306方案只需要SCL和SDA两根线配上U8g2或者SSD1306的软件库显示中文和数字都很方便。注意I2C的速率不要太高默认100kHz就行否则OLED初始化可能失败。DHT11的刷新率不需要太高我的经验是2秒读一次就够了。DHT11本身的响应速度就那么快你1毫秒读一次也读不出更新的数据反而浪费CPU。可以在主循环里加一个500ms或者1s的软件标志位到了时间才发一次读取命令。5.2 结合串口上报到上位机用STM32的USART把温湿度数据发到电脑串口助手是许多毕业设计的标准形态。串口发送建议用DMA空闲中断CPU占用低不会干扰DHT11时序。自己定义一套简单的帧格式即可比如帧头长度湿度温度校验和。串口助手上可以写一个小工具来解析也可以直接用现成的匿名上位机之类的软件把数据映射到曲线图上实现温湿度曲线绘制。注意串口打印的波特率和数据读取之间没有任何直接关系但发送时如果开启中断并且中断优先级和SysTick相冲突影响就大了。建议把USART中断优先级调低或者等待DHT11读取完成后再触发发送。5.3 系统的低功耗优化思路如果你打算用电池供电做一个无线温湿度节点DHT11的读取方式和功耗就值得好好设计。DHT11在空闲状态下耗电很小但每次读取过程需要约20ms的起始信号这个期间传感器内部在做测量电流会比空闲时高不少。所以尽量减少读取次数比如5分钟读一次其他时间让MCU进入休眠模式用RTC唤醒。STM32的休眠模式需要先把DHT11的GPIO引脚配置成模拟输入避免悬空引脚造成漏电。唤醒后重新配置GPIO再发起读取。这部分优化在小项目里看似微不足道但放到批量产品里电池寿命能差好几倍。5.4 结合阿里云/微信小程序上报最近有不少人做“智能家居”相关的东西比如把DHT11的数据通过ESP8266或者STM32Wi-Fi模块上传到云平台再在手机上查看。STM32作为MCU通过串口和ESP8266通信ESP8266负责Wi-Fi连接和MQTT协议上报。底层数据流是DHT11 → STM32读取并校验 → 串口AT指令透传或者MQTT封装 → ESP8266 → 云平台。这种方案比直接用ESP8266接DHT11“正规”一些因为MCU负责时序敏感的单总线通信Wi-Fi模块只负责网络传输互不干扰。实战中我见过不少人试图用ESP8266直接驱动DHT11结果网络一忙起来时序就丢最后还是老老实实加了一颗STM32。5.5 数据滤波与防抖处理如果你拿DHT11做闭环控制比如控制加湿器或者风扇原始数据肯定不能直接用。DHT11的读数虽然不像DHT22那样不规律但还是会有一点抖动尤其在湿度上相邻两次读取可能有1%RH到2%RH的跳变。简单的做法是取滑动平均值保留最近10次读数输出平均值。更激进一点还可以加“限幅滤波”如果本次读数比上次突然变化超过5%RH就当作干扰丢掉保持上次值。这在电控场景中很有用因为真实环境的温湿度变化是很缓慢的突变基本都是传感器问题或者干扰。6. 写在最后的一些个人体会DHT11这颗传感器网上评价两极分化有人说它精度差、响应慢、时序难搞是“电子垃圾”也有人说它是入门单总线协议的最佳教学素材便宜又皮实。我在实际项目中确实不会用它做精确测量但作为低成本环境监测的粗粒度感知器件它在大批量产品里依然有自己的一席之地。如果你正在用STM32驱动DHT11我的建议是先把逻辑分析仪和示波器备好别靠猜。时序问题用仪器看一次波形就全明白了用眼睛瞪代码瞪半天可能也找不出问题。驱动代码别光抄自己敲一遍把每一个延时为什么要这么久、每一个while循环为什么必须加超时都弄清楚。这样你以后遇到DHT22、DS18B20、甚至其他单总线传感器都能举一反三。最后还有一个小技巧DHT11读取失败的次数多了以后不要急着去改时序参数先检查传感器供电电压是不是掉到3.3V以下了。很多“玄学故障”到头来都是电源问题。以后你在别的项目里遇到莫名其妙的传感器数据异常也可以先怀疑电源再怀疑代码这样排查效率会高很多。
返回列表