
做嵌入式开发的人迟早会跟温湿度传感器打交道。DHT11几乎是入坑时最常碰到的一颗芯片便宜、电路简单、资料遍地都是但真要把它的驱动从头到尾写明白把单总线通信的时序、协议、代码全吃透还是能卡住不少人。这篇文章就来做一次完整拆解从DHT11器件原理开始讲到单总线通信协议的关键时序再给出51单片机和STM32 HAL库两套可复现的驱动代码最后把我实测中踩过的坑一并摆出来。无论你是刚学单片机的大学生还是工作中第一次接触单总线传感器的新手这篇内容都能帮你少走弯路。哪怕是已经能跑通DHT11的老手建议也看一下时序分析和中断处理那两段这几个细节决定了你的驱动是“偶尔能跑”还是“长期稳定”。1. 内容整体设计与思路拆解1.1 先搞清楚DHT11这个传感器DHT11是AOSONG奥松推出的一款复合型温湿度传感器内部集成了一个电阻式湿度感应元件和一个NTC热敏电阻温度感应元件再配合一个8位单片机做信号处理和单总线协议封装。用户拿到手的就是一个三针或四针的封装不需要自己处理模拟信号直接用数字IO口按照协议读取即可。关键参数先列出来参数数值备注工作电压3.3V ~ 5.5V3.3V和5V单片机都能直接用温度量程0 ~ 50℃精度±2℃湿度量程20% ~ 90%RH精度±5%RH分辨率1℃ / 1%RH整数输出小数位恒为0采样周期1秒两次读取间隔至少1秒通信方式单总线1根数据线 上拉电阻数据帧40bit5字节含校验字节从参数就能看出来DHT11走的是“够用就行”的路线。温湿度精度不高采样速率也慢但胜在便宜、稳定、接口简单特别适合对精度要求不高的室内环境监测场景比如机房温度监控、温室大棚、孵化器、智能家居空气盒子这类项目。我见过不少初学者一上来就想用DHT11去做气象站级别的测量这是对这颗器件定位的误判。DHT11的精度决定了它只能告诉你“大概多少度、大概多湿”而不是精确数值。真要高精度测量后面再升级DHT22或者SHT30都行但学习单总线通信协议这件事拿DHT11练手是成本最低、效果最好的路径。1.2 为什么选择单总线而不是IIC、SPI很多人在接触DHT11之前可能已经用过或者听说过IICI2C、SPI、UART这些常见通信协议。DHT11偏偏选择了单总线这背后是有原因的。单总线的核心特点是只用一根数据线完成双向通信既传时钟信息又传数据信息而且通信方向会根据主机和从机的操作不断切换。相比IIC至少需要SCLSDA两根线SPI需要SCK/MOSI/MISO/CS四根线单总线把引脚占用降到了最低。对于DHT11这种低成本、功能单一的传感器来说能省引脚就能降低封装成本和PCB布线成本最终反映到终端售价上就很有竞争力。但单总线的代价也很明显没有硬件控制器。你翻遍STM32或者51单片机的参考手册都找不到一个叫“单总线外设”的东西。这意味着所有时序都必须用GPIO加延时函数“软模拟”出来对代码的实时性要求非常高。如果把IIC比作一条有交警指挥的双车道马路那单总线就是一条没有交通灯的单车道乡村小路所有车辆靠约定俗成的规则自己协商谁走谁停。这个“约定俗成的规则”就是我们要研究的单总线协议。这里要特别说清楚一点DHT11用的这个单总线跟MaximDallas的1-Wire协议其实是两码事。1-Wire有一套完整的命令体系支持ROM地址、多设备挂载、总线搜索等等DS18B20温度传感器就是用的这种。而DHT11的单总线是简化版没有地址概念没有命令集不支持多设备挂载本质上就是主机发一个起始信号从机应答后直接吐出40个数据位。所以别把它当成标准的1-Wire设备来操作。1.3 这篇内容适合谁你能得到什么这篇博文的核心目标是让你彻底理解并掌握DHT11的驱动开发分三个层次第一层是原理DHT11内部是怎么测量温湿度的数据以什么格式输出校验规则是什么。这一层解决“它是什么”的问题。第二层是协议单总线的起始信号、响应信号、0和1的表示方式以及为什么时序要求这么严格。这一层解决“它怎么工作”的问题。第三层是代码给出51单片机和STM32 HAL库两套完整驱动每一行代码都讲清楚为什么这样写特别是微秒级延时函数的实现、GPIO方向的切换、数据位读取的判断逻辑。这一层解决“我该怎么写出稳定驱动”的问题。看完之后你不仅能把DHT11跑起来还能举一反三去理解DHT22、AM2302这些采用类似时序的传感器以后见到任何单总线器件都不会发怵。2. 单总线协议深度拆解2.1 总线结构与电气特性先看硬件连接。DHT11通常有四个引脚但实际使用的只有三个VCC接电源3.3V~5.5V、GND接地、DATA接单片机的一个GPIO口。第四个引脚是NC空脚不用接。电路上最大的要点是DATA线上必须接一个上拉电阻典型值是4.7kΩ到10kΩ。原因很简单单总线通信时无论是主机还是从机拉着总线输出低电平释放总线后就靠上拉电阻把电平拉回高电平。如果没有这个上拉电阻总线在释放期间会处于浮空状态读到的电平是不确定的。这里有一个新手经常踩的坑现在市面上很多DHT11模块板子上已经集成好了上拉电阻和滤波电容你买回来直接接上就能用。但如果你买的是裸片传感器就一定要自己加上拉电阻。我之前见过一位网友买了好几个裸片DHT11怎么调都读不出正确数据最后发现是忘了加上拉电阻DATA引脚悬空导致时序完全乱掉。另外要注意虽然STM32等单片机可以开启GPIO内部上拉来替代外部电阻但内部上拉阻值一般在30kΩ~50kΩ阻值偏大会导致总线上升沿变缓在长线传输或者电磁干扰较强的环境下容易出问题。所以可靠性要求高的场合还是建议外部加一颗4.7kΩ上拉电阻内部上拉只作为临时调试手段。2.2 通信时序全流程起始、响应、数据位DHT11的一次完整通信过程可以分为三个阶段主机发起起始信号、DHT11响应、DHT11发送40位数据。整个过程下来大约需要4毫秒左右。第一步主机起始信号。主机先把总线拉低持续时间必须大于等于18毫秒。这个时间是给DHT11内部的单片机做唤醒用的太短了传感器可能没反应过来太长了好歹也没事但浪费时间。拉低18毫秒之后主机释放总线也就是拉高然后等待20~40微秒给DHT11一个反应时间。第二步DHT11响应信号。如果传感器正常它会主动把总线拉低持续约80微秒表示“我准备好了”。然后再释放总线拉高约80微秒表示“我要开始发数据了”。主机在这个阶段需要做的就是检测总线上有没有这个低电平响应如果等了很久都没等到说明传感器没工作或者接线有问题。第三步数据位传输。这是核心阶段。DHT11发出的每一位数据都以一个50微秒的低电平开始表示“接下来这个位要开始了”这个低电平相当于同步信号。然后是高电平高电平持续的时间长短决定了这一位是0还是1。具体怎么区分呢如果高电平持续约26~28微秒这一位是逻辑0如果高电平持续约70微秒这一位是逻辑1。主机在检测到低电平结束之后一般在40微秒左右去采样总线电平如果还是高电平说明是1如果已经是低电平说明是0。整个40位数发送完之后DHT11会把总线拉低50微秒然后释放一次通信结束。我把这些关键时间参数整理成一张速查表方便对照操作时间要求方向说明起始低电平≥18ms主机→从机唤醒DHT11起始后等待20~40us主机等待从机响应响应低电平约80us从机→主机从机应答响应高电平约80us从机→主机准备发送数据位前低电平约50us从机→主机位同步信号数据位0高电平26~28us从机→主机逻辑0数据位1高电平约70us从机→主机逻辑1传输结束拉低50us后释放从机→主机通信结束2.3 40位数据格式与校验逻辑DHT11一次发送40位数据也就是5个字节顺序是固定的。发送顺序从高位开始也就是说每个字节都是MSB先发。字节序号含义示例值第1字节湿度整数部分0x2C44第2字节湿度小数部分0x000第3字节温度整数部分0x1A26第4字节温度小数部分0x000第5字节校验和0x46对于DHT11来说第2字节和第4字节小数部分在绝大多数型号上恒为0这一点跟DHT22不同DHT22是16位分辨率小数部分有真实意义。校验规则很简单把前四个字节加起来取低8位如果跟第5个字节相等说明数据传输正确。用C语言表达就是if ((buf[0] buf[1] buf[2] buf[3]) buf[4]) // 数据有效 else // 校验失败丢弃本次数据这个校验逻辑一定要写进驱动里不能省略。因为单总线是软模拟的时序很容易受到中断、延时误差、电磁干扰的影响导致某一位读反了。没有校验的话温湿度数值可能跳变得非常离谱你甚至都意识不到是数据错了。加了校验之后错误帧直接丢弃重新读取即可可靠性大幅提升。2.4 为什么说时序精度是这个协议的灵魂理解了整个通信流程之后你会发现最考验人的其实就是时间控制。起始信号要求拉低至少18毫秒这个时间比较宽裕差个几毫秒都能忍。真正的难点在数据位读取阶段0和1的区分靠的是高电平持续26~28微秒还是约70微秒而主机常用的采样策略是“低电平结束后延时40微秒再读电平”。这里面的逻辑是如果这一位是0高电平持续26~28微秒那么延时40微秒后总线已经被拉低你读到低电平判断为0。如果这一位是1高电平持续约70微秒延时40微秒后总线还是高电平你读到高电平判断为1。所以40微秒这个采样点很关键它刚好卡在0和1的高电平持续时间之间是一个分界线。如果你的延时函数误差太大比如实际延时了60微秒那么本应是1的位也可能已经变低就会被误判成0整个字节全错了。不过DHT11也有一个比较友好的特性每一位数据之前都有50微秒的低电平作为同步信号。这意味着只要你在检测到低电平结束后再开始计时采高频前一位造成的时间误差不会积累到后一位。这就是为什么单纯用空循环延时只要误差不太离谱很多人的驱动也能跑通的原因——误差被每一位的同步信号“重置”了。3. 代码实现从零手写DHT11驱动3.1 准备工作硬件连接与开发环境先说硬件连接。以STM32F103最小系统板为例把DHT11模块按如下方式接线VCC → 3.3V或者5VGND → GNDDATA → PB8GPIOB Pin 8自己随意选只要方便就行如果你买的是裸片传感器记得在DATA和VCC之间接一个4.7kΩ上拉电阻。开发环境我用的是STM32CubeIDE或者Keil MDK都行。下面代码基于STM32 HAL库实现。用嘉立创EDA或者立创EDA画原理图时DHT11部分的电路非常简单就三个器件传感器本体、4.7kΩ上拉电阻、100nF去耦电容可选建议加上靠近VCC和GND放置。DATA引脚引出一根线到MCU的GPIO即可。3.2 微秒级延时函数的正确实现前面反复强调时序这里的核心就是微秒级延时。在STM32上HAL库的HAL_Delay()是基于SysTick实现的带的是毫秒级精度直接拿来延40微秒完全不靠谱。所以必须自己实现一个精确的微秒延时。我推荐用DWTData Watchpoint and Trace单元。DWT是Cortex-M3/M4内核自带的一个调试组件其中的CYCCNT寄存器是一个32位的自由运行计数器每个时钟周期加1。用它做延时不占用定时器资源精度高还很方便。DWT初始化代码void DWT_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; }延时函数void Delay_us_DWT(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); }这里用到了SystemCoreClock它表示系统主频。如果主频是72MHz那么SystemCoreClock / 1000000 72也就是说延1微秒需要72个时钟周期。CYCCNT减去起始值的算法利用了32位无符号整数的回绕特性即使计数器溢出差值依然正确。如果你的平台不支持DWT比如某些Cortex-M0芯片那退而求其次可以用定时器实现微秒延时或者像51单片机那样用NOP空循环。但不管哪种方式都建议实测校准一下延时精度别想当然认为写几行空循环就是正确的微秒延时。3.3 起始信号发送与响应检测先把DHT11的IO配置好。这里有个关键点DHT11的数据口是双向的主机既要输出发送起始信号又要输入读取数据所以GPIO方向需要切换。我的做法是把PB8配置为开漏输出外部上拉。开漏输出的好处是当寄存器写1时引脚处于高阻态相当于释放总线电平由上拉电阻决定。这样在和DHT11通信时不需要频繁切换GPIO模式写0就是拉低写1就是释放。读取时直接读GPIO的IDR寄存器即可。GPIO初始化void DHT11_GPIO_Init(void) { GPIO_InitTypeDef gpio_init {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); gpio_init.Pin GPIO_PIN_8; gpio_init.Mode GPIO_MODE_OUTPUT_OD; // 开漏输出 gpio_init.Pull GPIO_PULLUP; // 内部上拉配合外部上拉 gpio_init.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, gpio_init); }接下来是发送起始信号void DHT11_Start(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); // 拉低 Delay_us_DWT(20000); // 保持至少18ms实际用20ms HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); // 释放总线 Delay_us_DWT(30); // 等待20~40us }拉低时间我用了20毫秒比18毫秒略长一点留出余量。注意起始信号发出后总线已经被释放接下来DHT11可能会拉低总线所以不能再用输出模式去操作GPIO直接读取输入电平即可。响应检测代码uint8_t DHT11_WaitResponse(void) { uint32_t timeout 100; // 等待DHT11拉低总线响应信号前半段 while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_SET) { if (--timeout 0) return 1; // 超时传感器未响应 Delay_us_DWT(1); } timeout 100; // 等待DHT11释放总线响应信号后半段 while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_RESET) { if (--timeout 0) return 2; // 超时总线一直为低 Delay_us_DWT(1); } return 0; // 响应正常 }这个函数的思路是检测总线上的电平变化分别等待低电平出现和高电平出现每次等待都设置超时时间防止程序卡死在while循环里。很多新手写的驱动没有超时判断一旦传感器没接好程序就永远卡在等电平的循环里连看门狗都喂不了。3.4 数据位读取的两种思路读取数据位是整个驱动里最有技巧的部分。我见过两种主流写法。第一种是“延时采样法”也是我推荐的方式先等待低电平结束也就是等待总线变高然后延时40微秒再读取电平。如果读到高电平就是1低电平就是0。uint8_t DHT11_ReadBit(void) { uint32_t timeout 100; // 等待低电平结束跳过50us同步低电平 while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_RESET) { if (--timeout 0) return 0xFF; // 超时异常 Delay_us_DWT(1); } Delay_us_DWT(40); // 在40us处采样 if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_SET) { // 是高电平说明是1等待高电平结束再返回 timeout 100; while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) GPIO_PIN_SET) { if (--timeout 0) return 0xFF; Delay_us_DWT(1); } return 1; } else { // 是低电平说明是0 return 0; } }第二种是“计时法”检测到低电平结束后开始计时高电平持续时间超过40微秒判为1否则判为0。这种方法理论上更准确但计时的实现复杂度更高在普通单总线场合没有太大必要。如果你追求极致的时序精度可以自己尝试。读一个字节就是把8个位拼接起来注意DHT11是高位先出uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for (i 0; i 8; i) { uint8_t bit DHT11_ReadBit(); data 1; if (bit 1) data | 0x01; } return data; }3.5 数据校验与温湿度换算有了读字节的函数读取完整温湿度数据就顺理成章了。我把读取流程封装成一个函数成功返回0校验失败返回非零值uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t buf[5] {0}; uint8_t i; DHT11_Start(); // 发送起始信号 DHT11_WaitResponse(); // 等待响应 for (i 0; i 5; i) { buf[i] DHT11_ReadByte(); } // 校验和判断 if ((buf[0] buf[1] buf[2] buf[3]) ! buf[4]) return 1; // 校验失败 *humidity buf[0]; // 湿度整数部分 *temperature buf[2]; // 温度整数部分 return 0; }温湿度换算在DHT11这里其实非常简单因为小数部分恒为0所以直接用整数部分就是最终数值。比如读到buf[0]44湿度就是44%RHbuf[2]26温度就是26℃。如果使用的是带小数输出的器件换算逻辑也不复杂float humidity (float)buf[0] (float)buf[1] / 10.0f; // DHT11 float temperature (float)buf[2] (float)buf[3] / 10.0f; // DHT11对于DHT22来说数据格式有所不同湿度是16位高8位低8位温度也是16位最高位是符号位换算时要除以10.0具体参考对应手册。3.6 完整读取流程封装与调用示例把前面的模块串起来一个完整的读取流程如下。注意读取间隔要控制好两次读取之间至少间隔1.5秒给DHT11留出采样时间。int main(void) { HAL_Init(); SystemClock_Config(); DWT_Init(); DHT11_GPIO_Init(); uint8_t humidity 0; uint8_t temperature 0; HAL_Delay(2000); // 上电后等2秒让DHT11稳定 while (1) { if (DHT11_ReadData(humidity, temperature) 0) { printf(Humidity: %d%%RH, Temperature: %d°C\r\n, humidity, temperature); } else { printf(DHT11 read failed\r\n); } HAL_Delay(2000); // 间隔2秒再读 } }在51单片机上思路完全一样只是因为51没有HAL库GPIO操作变成直接对引脚寄存器的赋值延时函数用空循环实现。下面给一份基于STC89C52的完整代码框架12MHz晶振下_nop_()大约耗时1微秒sbit DHT11_PIN P2^0; void Delay_us(unsigned int us) { while (us--) { _nop_(); _nop_(); _nop_(); } } void Delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 120; j); } void DHT11_Start(void) { DHT11_PIN 0; Delay_ms(20); DHT11_PIN 1; Delay_us(30); } unsigned char DHT11_ReadByte(void) { unsigned char i, data 0; for (i 0; i 8; i) { while (DHT11_PIN 0); // 等待低电平结束 Delay_us(40); if (DHT11_PIN 1) { data (data 1) | 0x01; while (DHT11_PIN 1); // 等待高电平结束 } else { data 1; } } return data; } unsigned char DHT11_ReadData(unsigned char *humidity, unsigned char *temperature) { unsigned char buf[5] {0}; unsigned char i; DHT11_Start(); // 等待响应 if (DHT11_PIN) return 1; while (!DHT11_PIN); while (DHT11_PIN); for (i 0; i 5; i) { buf[i] DHT11_ReadByte(); } if ((buf[0] buf[1] buf[2] buf[3]) ! buf[4]) return 2; *humidity buf[0]; *temperature buf[2]; return 0; }要特别注意51代码里的延时函数在不同晶振、不同型号的51芯片上差异很大。12MHz晶振的STC89C52是12T模式一个机器周期就是1微秒上面的空循环延时基本准确。但如果换成STC15、STC8系列这类1T单片机同样的空循环实际延时缩水到原来的1/12那整个驱动就读不出数据了。这类芯片建议直接用硬件定时器实现微秒延时不要依赖空循环。4. 实测中遇到的典型问题与排查技巧4.1 上电后读不到数据、全零或者校验一直失败这是反馈最多的一类问题。根据我自己的调试经验可以按下面的顺序排查先看接线。VCC、GND别接反DATA一定要接对引脚。如果是裸片确认有没有接上拉电阻。曾经有网友把DHT11第四个NC脚当成DATA脚接结果无论如何都读不出来检查原理图才发现引脚定义搞错了。再看时序。特别是起始信号拉低时间有的代码写的是Delay_ms(18)但当时的延时函数本身就不准实际可能只拉低了1毫秒DHT11根本没被唤醒。建议拉低时间留足余量用20毫秒。然后看读取时序。检查采样点40微秒的实现准不准。STM32上推荐用DWT或者定时器延时51上建议实测一下空循环延时是否接近理论值。最后看传感器本身。DHT11坏的概率也不低尤其是那种在淘宝上几毛钱一颗的散装元件引脚氧化、内部晶振停振的情况我都见过。可以换一颗新的试试。4.2 温湿度数值跳变或者明显偏差如果数据偶尔出现一个离谱的大数比如湿度95%然后又跳回45%十有八九是校验没过的帧没有被正确处理。校验失败的数据必须丢弃不要参与后续逻辑。如果读数跟实际环境差距很大比如温度始终比室温高5度先检查传感器是不是紧挨着发热元件。DHT11对热量敏感如果距离MCU或者电源芯片太近测到的温度会偏高。这是正常的PCB布局时尽量让DHT11远离热源。如果是湿度长期偏干或者偏湿可能是传感器老化或者被污染。DHT11的湿度感应元件暴露在空气中长时间在高粉尘、油烟环境下工作读数会慢慢漂移。这种情况只能换传感器。4.3 总线卡死、程序阻塞在读取函数里单总线驱动最怕的就是卡死在while循环。比如你在等待低电平结束但DHT11因为某种原因拉低了总线之后再也没释放如果没有超时判断程序就永远卡在那里。解决办法就是在每个等待电平的循环里都加上超时计数。我的代码里用的是timeout变量递减的方式等待约100微秒还没等到目标电平就返回错误码。这个100微秒的取值留足了裕量因为DHT11的响应信号和数据位的最长低电平也就80~90微秒正常情况不会超时。还有一个小技巧在读取DHT11时会关中断但关中断时间不宜过长否则会影响系统的实时性。我的经验是读完一次完整数据大概需要4毫秒出头这个时间窗口内关中断是可以接受的但如果系统里有对时间敏感的通信协议比如需要毫秒级响应的CAN收发就要小心权衡。更好的做法是把DHT11的读取放到一个专门的低优先级任务里只在读取的时候临时屏蔽中断。4.4 裸机读取正常上RTOS之后经常失败这个坑我栽过。裸机程序里读取DHT11时总是能顺利跑完时序但到了FreeRTOS环境下明明代码逻辑一样却频繁读取出错。原因很简单任务调度和中断打断了DHT11的时序。你在延时40微秒的时候如果系统触发了一次任务切换线程上下文保存恢复加上调度器开销轻松超过几十微秒。等你的任务重新获得CPU时那个数据位早就过去了自然就读错了。解决办法有两个方向一是在读取DHT11的临界区内挂起任务调度器并关闭中断保证时序不被干扰二是把DHT11读取放在一个优先级非常高的任务里同时降低其他任务切换频率。第一种方式效果最直接但注意临界区时间要短一次读取过程约4~5毫秒期间挂起调度器对大多数系统是可以接受的。STM32裸机上的临界区保护代码很简单__disable_irq(); // 关中断 uint8_t err DHT11_ReadData(humidity, temperature); __enable_irq(); // 开中断FreeRTOS下则建议taskENTER_CRITICAL(); // 挂起调度器并关中断 err DHT11_ReadData(humidity, temperature); taskEXIT_CRITICAL();4.5 一分钟速查表我把容易踩的坑汇总成一张表方便你排查问题现象可能原因解决方法读取超时、卡死无超时判断每个电平等待循环都加超时一直读到0xFFGPIO配置错误、未接上拉检查GPIO方向、加4.7kΩ上拉校验一直失败延时函数不准用DWT或定时器精确延时上电第一次读失败传感器未稳定上电后等待1~2秒再读数值不更新读取间隔太短间隔至少1.5秒偶发读错中断干扰读取期间关中断或挂起调度器温湿度偏高靠近热源PCB布局远离发热器件数据跳变巨大校验帧未丢弃校验失败必须丢弃数据5. 扩展应用与进阶思考5.1 从DHT11升级到DHT22/AM2302把DHT11的驱动跑通之后你想进一步提升测量精度很自然就会想到DHT22也叫AM2302封装形式不同。DHT22的通信时序和DHT11几乎一模一样都是单总线都是主机发起始信号、从机应答、再吐40位数据。区别在于数据格式DHT22的湿度是16位无符号整数温度是16位有符号整数最高位为符号位1表示零下实际值除以10。也就是说读取DHT22时需要把两个字节拼成一个16位数温度还要做符号扩展。数据格式对比器件湿度温度校验DHT118位整数小数恒为08位整数小数恒为0前4字节和低8位DHT2216位除以1016位最高位为符号除以10前4字节和低8位有意思的是DHT22的校验方式跟DHT11一模一样也是前四个字节相加取低8位等于第5字节。所以只要你理解了DHT11再看DHT22的手册毫无障碍。如果你的驱动代码函数设计得合理把数据解析那一段单独抽出来升级到DHT22只需要改读取字节的拼装逻辑就行。5.2 低成本环境监测系统的完整落地DHT11最常见的落地场景是低成本环境监测系统。我做过一个简单的机房温湿度监测节点硬件就是STM32 DHT11 OLED显示屏 ESP8266DHT11每分钟采集一次温湿度ESP8266通过MQTT协议上报到本地服务器。整套硬件成本不到30块钱精度虽然不如工业级传感器但监测机房的温度趋势和湿度异常足够用了。而且DHT11采样周期1秒的特性刚好匹配一分钟上报一次的需求并不会觉得卡顿。这里可以给你一个扩展思路在采集逻辑中做软件滤波。虽然DHT11每次读取的数值本身就是整数波动幅度不大但为了更平滑可以连续读取5次取平均值。注意每次读取之间要间隔1.5秒以上所以5次平均下来大约需要8秒对于分钟级上报的场景完全够用。5.3 单总线驱动的通用方法论最后聊一个方法论层面的事。你把DHT11吃透之后再看市场上各种单总线传感器会发现套路高度相似主机发起起始信号、从机应答、从机按位发送数据、主机解析校验。只要抓住“电平翻转 时间测量 数据解析”这三个核心遇到新器件就是查手册、改参数的过程。我在开发中养成的习惯是把传感器驱动封装成独立模块对外只暴露一个读取函数。应用层永远不用关心底层是怎么拉电平、怎么延时的这样一旦要换传感器只需要替换驱动文件上层逻辑几乎不用改。这个清洁架构的思路越到后面越能体会到它的价值。还有一个小建议调试单总线时序逻辑分析仪是神器一个几十块钱的24MHz逻辑分析仪就够用。把DHT11的数据线接上去抓一次通信波形0和1的区别一目了然比靠猜和试错高效得多。我几乎每次调单总线驱动的时序问题都是靠逻辑分析仪一锤定音。个人在实际项目中最大的体会是DHT11这个传感器本身谈不上先进但它把单总线通信的每一个关键环节都浓缩在了最简单的形式里。把这个搞明白你等于打通了嵌入式开发里“用软件模拟通信协议”这条重要的技能线后面Debug任何驱动问题都会顺手很多。