搞嵌入式这几年,我前前后后用过不少温湿度传感器,但 DHT11 一直是我心里比较特殊的一个。倒不是说它精度有多高、性能有多强,而是在它身上能把“单总线通信”这件事讲得特别透。单总线通信听起来很底层,其实理解起来不复杂,就是把时钟线和数据线合并成一条线,靠严格的时序来区分“0”和“1”。很多做物联网产品、智能家居、环境监测的朋友,第一个接触的器件往往就是 DHT11 温湿度传感器,再加上 STM32、51 单片机或者 ESP32 这类主控,就能快速做出一个能上报温湿度的节点。
这篇内容我会把 DHT11 的原理、单总线协议、代码实现从头到尾拆开揉碎,重点放在“为什么时序这么重要”和“代码里每个延时都在干什么”这两件事上。无论你是刚入门的学生,还是已经在做项目的工程师,我都建议把目光从“能跑起来”移到“能看懂时序”上。因为单总线这玩意儿,只要时序对了,后面所有传感器都一通百通。
1. 项目概述与整体设计思路拆解
1.1 DHT11 到底是什么,单总线又是什么
DHT11 是一款数字温湿度传感器,内部集成了一个电阻式湿度元件和一个 NTC 测温元件,通过一个 8 位 MCU 完成采样和输出。它和主控之间只有一根数据线,这根线既承担主机向传感器发送触发信号的任务,也承担传感器向主机回传数据的工作。正因为收发共用一根线,通信必须靠严格的电平时序来区分方向和数据内容。
这里要特别说一句:DHT11 的“单总线”和 DS18B20 那种 Maxim 1-Wire 协议并不是一回事。DHT11 使用的是厂商自定义的单总线时序,没有设备地址、没有 ROM 指令、不支持多设备挂接,每次通信就是主机发一个起始信号,然后 DHT11 回传 40 bit 数据。很多初学者拿 DS18B20 的协议栈去读 DHT11,结果读出来全是乱的,问题就出在这里。
单总线通信的难点不在于协议本身有多复杂,而在于它对时序精度的要求。DHT11 对电平持续时间的容忍范围相对宽一些,数据位“0”的高电平时间大约 26~28 微秒,数据位“1”的高电平时间大约 70 微秒,只要主控延时函数偏差不是特别离谱,都可以正确读到数据。这给我们这些做工程的人留了很大的余地,也是为什么我在教学和快速原型阶段特别喜欢用它。
1.2 为什么选择 DHT11 来学习单总线通信
市面上常见的数字传感器有 I2C、SPI、UART、单总线等几种接口方式。I2C 和 SPI 都有独立的时钟线,通信由主控统一控制,协议栈成熟,固件库一大堆,其实不太容易理解底层时序。单总线就不一样了,没有时钟线,只有一个 GPIO,收和发全靠延时函数的准确性。
用 DHT11 学习单总线的第一个好处是“慢”。它完整读一次需要至少 18 毫秒的起始信号,数据位也只有 40 个,整体节奏比那些高速总线慢得多,用逻辑分析仪看波形时能看得非常清楚。第二个好处是“便宜”,几块钱一片,就算调试时接反、烧坏也不心疼。第三个好处是“反馈直观”,读到的温度和湿度可以直接通过串口打印出来,代码对不对一测就知道。
我在很多实训项目里都让学生先做 DHT11,再做 DS18B20。因为 DHT11 能把单总线通信的核心思想讲透,到了 DS18B20 这种带设备寻址、带 ROM 指令、带 CRC 校验的器件时,就能专注于“协议封装”而不会被时序卡住。整个学习路径的坡度非常舒服。
1.3 整体方案设计与数据流
我常用的方案是“主控 GPIO + 上拉电阻 + DHT11”三件套。主控用 STM32F103 或者 STM32F407 都可以,代码逻辑完全一样;如果手头只有 51 单片机,也没有问题,因为 DHT11 的时序要求没那么苛刻,51 的时钟一样能跑。整体数据流大致是:
- 主控将 GPIO 配置为推挽输出,拉低数据线至少 18 毫秒,然后再拉高释放总线。这个过程叫起始信号。
- 主控把 GPIO 切换成输入模式,开始等待 DHT11 的响应。
- DHT11 检测到起始信号后,先拉低 80 微秒,再拉高 80 微秒,表示“我准备好了”。
- 随后 DHT11 连续输出 40 个数据位,每位的低电平时间相同,高电平时间不同,主控通过测量高电平时间判断“0”还是“1”。
- 主机读取完 40 bit 后,校验和验证通过,把数据拆成湿度整数、湿度小数、温度整数、温度小数四个字节。
这里有个很容易踩的坑:很多人把 GPIO 配成推挽输出就直接去读输入电平,结果发现自己发的电平把自己的读取操作干扰了。规范做法是发完起始信号后,把 GPIO 方向切换成输入模式,或者用开漏输出加外部上拉电阻。我一般直接在代码里做方向切换,这样最简单也最通用。
2. 器件原理与单总线协议核心细节解析
2.1 DHT11 内部结构与引脚定义
DHT11 最常见的封装是 4 引脚单排直插,但其实第 3 脚是空脚,真正用到的只有 4 个:VCC、DATA、NC、GND。市面上也有 3 引脚的贴片模块,通常把信号引到单独一根引脚上,方便连接。
内部结构上,DHT11 把湿敏电阻、NTC 热敏电阻和一个 8 位单片机封装在一起。湿度通过湿敏电容的容值变化反映,温度通过 NTC 的阻值变化反映,内部 MCU 负责采集和模数转换,再把结果按协议格式输出。正因为封装里集成了一块 MCU,DHT11 才能做到“单线输出数字信号”,否则就只是一个模拟量传感器。
供电方面,DHT11 的典型工作电压是 3.3V~5.5V。这里有一个很多新手容易忽略的点:如果是 5V 供电,数据线输出高电平也是 5V,而 STM32 的 GPIO 耐压一般不能直接吃 5V。所以接 3.3V 的主控时,我建议直接给 DHT11 也供 3.3V 电,虽然官方手册给的测量精度在 5V 下最理想,但实际上 3.3V 供电完全够用。如果必须用 5V 供电,数据线上要加电平转换,不能直接怼到 3.3V 的 MCU 引脚上。
2.2 40 位数据格式与校验规则
DHT11 一次完整传输一共输出 40 个 bit,也就是 5 个字节,顺序固定为:
| 字节序号 | 含义 | 示例值 |
|---|---|---|
| 1 | 湿度整数部分 | 0x24,代表 36%RH |
| 2 | 湿度小数部分 | 0x00 |
| 3 | 温度整数部分 | 0x1C,代表 28℃ |
| 4 | 温度小数部分 | 0x00 |
| 5 | 校验和 | 0x24 + 0x00 + 0x1C + 0x00 的末 8 位 |
校验规则很简单:把前四个字节相加,取低 8 位,如果和第五个字节相等,说明数据有效。比如上面这个例子,0x24 + 0x00 + 0x1C + 0x00 = 0x40,那么校验字节就应该是 0x40。如果校验失败,我建议直接丢弃本次数据,不要拿着错误数据去做后续处理。
需要说明的是,DHT11 的湿度分辨率是 1%RH,温度分辨率是 1℃,小数位在多数版本里固定为 0,所以很多人说“DHT11 的精度不高”是事实。它的定位就是廉价、易用、够看趋势,不适合做高精度计量。如果你需要更高的测量精度,可以直接考虑 DHT22(也叫 AM2302),通信时序和 DHT11 完全兼容,只是数据格式不同,后续我会顺带提一下。
2.3 完整时序拆解:起始、响应与数据位
DHT11 的时序是整个项目的灵魂。我习惯用“电平宽度”来理解它,而不是背一堆数值。整段通信可以拆成四个阶段:
第一阶段是主机发送起始信号。主控把数据线拉低,持续至少 18 毫秒,典型值是 20 毫秒左右。很多代码里写成delay_ms(20),目的就是让 DHT11 识别到主机的请求。拉低结束后,主控再拉高总线,并等待大约 20~40 微秒,然后切换成输入模式。这个拉高时间不能太长,太长的话 DHT11 会认为通信异常。
第二阶段是 DHT11 的响应信号。DHT11 检测到起始信号后,会把总线拉低 80 微秒,然后再拉高 80 微秒。主机在这段时间里会读到一次低电平再读到一次高电平。判断读到的电平是否符合预期,能初步判断传感器是否正常。
第三阶段是数据位输出。DHT11 输出每一位时都是先拉低 50 微秒,然后再拉高。区别在于拉高的持续时长:如果高电平持续 26~28 微秒,表示这一位是“0”;如果高电平持续约 70 微秒,表示这一位是“1”。主控要做的就是循环读取 40 次,每次先等待数据线变高,再测量高电平持续了多久,根据时长判定当前位是 0 还是 1。
第四阶段是结束。DHT11 发送完 40 位数据后,会释放总线,数据线由上拉电阻拉高,回到空闲状态。之后主机如果还想再读,至少需要间隔 1 到 2 秒,让传感器完成下一次采样。DHT11 本身的采样周期大约是 1 秒,频繁读取只会得到上一次的缓存数据,甚至会读失败。
2.4 上拉电阻与电平匹配的选型要点
单总线在空闲状态是高电平,靠什么拉高?要么靠主控内部上拉,要么靠外部上拉电阻。DHT11 模块上一般已经焊了 10k 欧姆或 4.7k 欧姆的上拉电阻,所以直接用模块会省很多事。
如果自己画板子或者用裸传感器,我建议外部加一个 5.1k 到 10k 欧姆的上拉电阻,接在数据线和 VCC 之间。线缆比较长的时候,分布电容会变大,可以适当减小上拉电阻到 2.2k 或 3.3k,这样上升沿会更陡。但我实测过,超过 5 米的普通杜邦线,即使减小上拉电阻也容易出现误码,所以长距离传输时最好别硬扛,改用 RS485 或者干脆换成带总线接口的传感器。
电平匹配是个非常重要但常被忽略的点。DHT11 数据线的高电平等于供电电压,如果你用 5V 给 DHT11 供电,那么信号线的高电平就是 5V。对 5V 的 51 单片机来说问题不大,但对 3.3V 的 STM32、ESP32 来说,直接读取 5V 信号悬。虽然很多 STM32 引脚标称“5V 容忍”,但我不建议长期这么干。最靠谱的方式就是统一用 3.3V 供电,千万别省这一步。
3. 驱动代码实现与关键步骤复现
3.1 环境准备与 GPIO 初始化
我以 STM32F103 + HAL 库为例子,因为这是目前学习单片机最主流的环境。如果用的是 51、Arduino、ESP-IDF,核心逻辑完全一样,只是寄存器操作方式不同。先把 GPIO 初始化写清楚:
#define DHT11_GPIO_PORT GPIOA #define DHT11_GPIO_PIN GPIO_PIN_1 #define DHT11_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() void DHT11_CtrlOutput(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); } void DHT11_CtrlInput(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); }这里我通过DHT11_CtrlOutput()和DHT11_CtrlInput()快速切换引脚方向。相比开漏输出加外部上拉的方式,这种“推挽输出 + 切输入模式”的方式在代码上更直观,也不依赖外部上拉电阻。每块模块都带了上拉电阻,所以用内部上拉其实就够,但为了更可靠,我经常在代码里把内部上拉也打开,外部电阻和内部上拉并联之后,等效上拉会更强一点,时序反而更稳定。
3.2 微秒级延时:DWT 实现方式
写 DHT11 驱动时最忌讳用HAL_Delay()这种毫秒级延时去凑微秒。DHT11 的数据位“0”和“1”之差也就是几十微秒,如果用轮询系统滴答来实现微秒延时,精度完全没法保证。我在 ARM Cortex-M 平台上的做法是直接用 DWT 计数器,这是内核里自带的一个调试计数器,不需要额外占用定时器。
static void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } static void DWT_Delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < ticks); }SystemCoreClock是当前系统主频。如果主频是 72MHz,那么 1 微秒就是 72 个时钟周期。DWT 计数器会记录从启动到现在的周期数,通过差值计算延时非常精确,而且不干扰主流程。这个函数是所有单总线驱动的地基,延时准了,后面读到的每一位才准。
如果你的平台不支持 DWT,还有一个替代方案:定时器延时。开一个 1MHz 的定时器,计数器每 1 微秒加一,然后在轮询里等待目标值。效果一样,只是要多占用一个定时器外设。我测试过,在 51 单片机上直接用_nop_()组合也能读 DHT11,但那样做可移植性差、可读性差,只适合临时验证,不适合做产品代码。
3.3 起始信号与响应检测代码
接下来是核心读取过程。我先写一个“读一个字节”的辅助函数,这样后面的主流程会非常清晰。
static uint8_t DHT11_ReadByte(void) { uint8_t data = 0; for (int i = 0; i < 8; i++) { while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_RESET); DWT_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET) { data = (data << 1) | 0x01; } else { data = (data << 1); } while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET); } return data; }这个函数的思路是从低位开始还是从高位开始?不对,从代码上看是data = data << 1,也就是先接收的位会逐步移到最高位,所以 DHT11 发来的第一位是字节的最高位。DHT11 先发湿度整数部分的最高位,因此这个顺序是正确的。
逐行解释一下:
while (data == 0)等待数据线拉低,对应数据位的 50 微秒低电平阶段。- 等到低电平结束、数据线拉高之后,延时 40 微秒。
- 如果 40 微秒后数据线仍然是高电平,说明这个位的总高电平长度超过了 40 微秒,基本可以判定是“1”;如果已经是低电平,说明高电平只有 26~28 微秒,判定为“0”。
- 最后再等待高电平结束,为下一个位做准备。
这里 40 微秒这个阈值很关键。我见过有人用 50 微秒做阈值,也能跑,但边缘情况容易误判。DHT11 数据位“0”的高电平典型值在 26~28 微秒,“1”的典型值在 70 微秒,取中间偏左的值 40 微秒最安全。
主流程如下:
uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t buf[5] = {0}; uint8_t crc = 0; DHT11_CtrlOutput(); HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET); DWT_Delay_us(20000); HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); DWT_Delay_us(30); DHT11_CtrlInput(); if (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET) { return 1; } while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN) == GPIO_PIN_SET); for (int i = 0; i < 5; i++) { buf[i] = DHT11_ReadByte(); } DHT11_CtrlOutput(); HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET); crc = buf[0] + buf[1] + buf[2] + buf[3]; if ((crc & 0xFF) != buf[4]) { return 2; } *humidity = buf[0]; *temperature = buf[2]; return 0; }启动部分的细节是:拉低 20 毫秒,释放总线 30 微秒,然后切输入。我用 30 微秒而不是更长的等待,是因为 DHT11 在主机释放总线后很快就会响应,如果等待太长,会错过响应信号的下降沿。切到输入模式后,数据线被模块上的上拉电阻拉高,然后 DHT11 会主动拉低 80 微秒,所以代码里先判断一次总线电平,如果还是高,说明 DHT11 没有响应。
响应信号的while等待会有一个潜在问题:如果传感器没接好或者损坏,电平一直不变化,代码会卡死在循环里。工程上做产品时我会加一个超时计数,比如用DWT_Delay_us(100)配合循环次数判断。这里为了教学先展示最核心的逻辑,实际项目里建议补上超时保护。
3.4 数据拼接与校验代码实现
读取完成并校验后,humidity和temperature就直接指向湿度整数和温度整数。在 main 函数里的调用方式很简单:
uint8_t humi = 0; uint8_t temp = 0; uint8_t res = 0; DWT_Delay_Init(); DHT11_CtrlOutput(); while (1) { res = DHT11_ReadData(&humi, &temp); if (res == 0) { printf("Humi: %d%%RH, Temp: %d C\r\n", humi, temp); } HAL_Delay(2000); }每次读取之间我间隔 2 秒,目的是留够 DHT11 的采样时间。DHT11 的最小采样周期是 1 秒,间隔 1 秒以上读到的数据才有效。这也是为什么你看到很多人做 DHT11 项目时,刷新率都很低,这不是能力问题,而是传感器本身的特性决定的。
4. 实测过程、常见问题与排查技巧实录
4.1 常见读数异常症状与原因速查表
我在实际项目里帮人排查过很多 DHT11 问题,多数症状集中在“读不到数据”“全是 0xFF”“数据偶尔跳变”“温度正常湿度不对”这几类。下面这张表是我长期使用的速查表,能解决绝大部分问题:
| 症状 | 直接原因 | 排查方向 |
|---|---|---|
| 读数全为 0xFF | 数据线电平一直为高,没进入数据位阶段 | 检查接线、供电、引脚是否配置错 |
| 读数全为 0x00 | 数据位被全部判定为“0” | 检查微秒延时精度、主频配置、DWT 初始化 |
| 校验和一直失败 | 时序偏移导致读错位 | 用逻辑分析仪看波形,核对每一位长度 |
| 偶尔成功偶尔失败 | 干扰、线缆过长或供电波动 | 缩短导线、加粗电源线、检查上拉电阻 |
| 温度正常湿度明显不对 | DHT11 个体差异或湿敏元件受污染 | 换个模块对比,避免在强湿/腐蚀环境使用 |
| 上电后需要很久才能读到 | 起始信号拉低时间不够 | 检查延时函数,确保拉低超过 18ms |
最典型的问题其实是主频不匹配。很多 HAL 库工程默认把SystemCoreClock设置为 72MHz,但如果有人在 CubeMX 里改了主频或者使用了外部晶振启动失败后降到了内部 RC 振荡器,SystemCoreClock变量没有同步更新,DWT 延时就会出现成倍数的误差,读出来的数据自然乱七八糟。
我在排查的时候会先打印SystemCoreClock的数值,然后接上逻辑分析仪看数据线波形。只要看一眼波形里的起始信号宽度和数据位高电平长度,就能判断问题出在哪一层。
4.2 用逻辑分析仪调试 DHT11 波形
DHT11 调试最有力的工具是逻辑分析仪。市面上几十块钱的 8 通道逻辑分析仪就够用,配合 Sigrock 软件或者 PulseView 都能很快看到波形。把通道夹到数据线上,设置采样率至少 1MHz,触发方式设为下降沿,然后手动触发一次读取,就能捕获完整的一次通信过程。
在波形上你要重点看三段:起始信号是不是稳定在 20ms 左右;DHT11 的响应信号是否是完整的 80us 低电平 + 80us 高电平;后面每一位的低电平是否一致,高电平是否明显分为“短、长”两档。如果高电平长短差异不明显,多半是传感器本身有问题或者供电太低;如果每一位的高电平长度忽长忽短,那就要考虑是不是总线干扰过大。
我遇到过一种很隐蔽的情况:DHT11 模块上自带上拉电阻,但主板 IO 上还接了下拉电阻或者 LED 指示灯,导致总线上电时电平被拉低,DHT11 根本无法正常工作。这种问题光看代码是看不出来的,必须靠波形和原理图双重排查。所以我的经验是,模块买回来先别急着接板子,直接用逻辑分析仪配合一个独立测试电路,验证传感器本身是好是坏,能省掉后面大量时间。
4.3 线缆、供电与环境干扰的坑
DHT11 的通信频率不高,但电源噪声和线缆电容会严重影响波形边沿。我给一个客户做环境监测节点时,把 DHT11 用 3 米长的双绞线接到主板,加 100 毫秒的读取周期,结果 30% 的概率校验失败。后来把供电线从数据线附近挪开,再把上拉电阻从 10k 改成 3.3k,误码率直接降到可以忽略的程度。
供电问题也很常见。DHT11 虽然耗电不大,但如果和电机、继电器共用电源,开关瞬间的电压跌落会让传感器复位或者输出异常。我建议几个做法:DHT11 的 VCC 单独走一条较粗的线,不要在传感器旁边并联大电流负载;主控和传感器共地;如果噪声实在压不住,就在传感器供电脚旁边加一个 100nF 的去耦电容,必要时串一个 10 欧姆的磁珠。
环境干扰方面,如果传感器会长时间暴露在高温高湿、油烟或者粉尘环境里,湿敏元件很可能被污染,导致湿度读数偏高甚至一直固定在某个值。这种问题没法靠代码解决,只能定期校准或者更换。做工业级产品时,我会选择把 DHT11 装在探针式的防护壳里,或者干脆改用模拟量输出的湿度传感器,配合主控的 ADC 读取,抗污染能力会强得多。
5. 应用场景与后续扩展方向
5.1 DHT11 适合的典型应用场景
DHT11 的精度虽然不高,但胜在便宜、简单、低功耗,非常适合对精度要求不高的温湿度采集场景。我梳理了几个典型的应用:
- 智能家居的室内环境监测面板:只需要知道当前温度和大致湿度,用来控制加湿器、除湿机、空调,精度要求并不高。
- 农业大棚的土壤环境辅助监测:放在大棚里测空气温湿度,用于判断通风时机,DHT11 完全够用。
- 实验室或机房的温湿度报警装置:设定阈值,超限就触发蜂鸣器或者联网告警。
- 嵌入式入门实验和课程设计:DHT11 是学习单总线时序、GPIO 操作、定时器应用的最佳载体。
在这些场景里,DHT11 最大的优势不是性能,而是容易调试。代码量少,出问题也能快速定位,属于“一次调通,长期省心”的器件。如果你是在做小批量产品而不是严格计量设备,直接焊 DHT11 模块往往比用 DHT22 或者 SHT30 更省成本,功能也能满足要求。
5.2 与 DHT22、SHT30、DS18B20 的选型对比
说到选型,很多人会把 DHT11、DHT22、SHT30、DS18B20 放在一起比。我列一张实际选型时常用的对照表,方便大家根据自己的场景快速决策:
| 传感器 | 接口 | 温度精度 | 湿度精度 | 成本 | 适合场景 |
|---|---|---|---|---|---|
| DHT11 | 单总线 | ±2℃ | ±5%RH | 最低 | 低成本环境监测、入门学习 |
| DHT22 | 单总线 | ±0.5℃ | ±2%RH | 中 | 家居环境、对湿度有一定要求 |
| SHT30 | I2C | ±0.3℃ | ±2%RH | 较高 | 高精度计量、便携设备 |
| DS18B20 | 1-Wire | ±0.5℃ | 无湿度 | 中 | 测温专用、多点测温 |
DHT11 和 DHT22 的硬件连接几乎一模一样,DHT22 的数据格式是 16 位湿度 + 16 位温度 + 8 位校验。如果你已经把 DHT11 驱动调通了,升级到 DHT22 只需要改数据解析部分,整套时序代码可以直接复用。SHT30 走的是标准 I2C,精度更高,但驱动代码量明显增加,还要考虑 I2C 地址和命令配置。DS18B20 虽然也叫“单总线”,但它属于真正的 1-Wire 协议,支持设备寻址和多点测量,适合做分布式测温。
我的建议是:如果你只需要“测个大概温湿度”,选 DHT11;如果你需要湿度数据更有参考价值,选 DHT22;如果是产品化且对成本不敏感,SHT30 是更稳的选择。
5.3 长期运行与低功耗设计的一些体会
最后分享一点我在实际产品中的经验。DHT11 平时不为零功耗设备设计,但如果要做电池供电的采集节点,可以通过切断 VCC 来降低功耗。主控 GPIO 直接给 DHT11 VCC 供电,读取前拉高、读取后拉低,这样待机电流能降到很低。需要注意的是,DHT11 上电后需要等待至少 1 秒才能稳定读取,所以每次唤醒后要先延时再触发通信。
湿度传感器长期通电会加速湿敏元件老化,所以我更推荐“按需供电”的方式。一个采集周期大概是:上电 1 秒,读取 20 毫秒,断电 59 秒,这样一个电池节点的续航可以做到数月。这个方案我在低功耗温湿度记录仪上实际跑过,数据稳定性没有任何问题。
还有一点,如果 DHT11 数据线需要走比较长的距离,我更愿意在传感器端加一个简单电平隔离,把其单总线转成标准的串口或者 RS485,再往主控传。这样虽然多了一个芯片成本,但抗干扰能力能提升一个档次,后期维护也轻松很多。
单总线通信这个东西,学的时候觉得时序难把握,真正理解了以后会发现它其实特别美,一条线就能传输完整的数据帧,省 IO、省布线,特别适合做功能简单的低成本传感节点。DHT11 只是其中一扇门,把它的时序摸透了,后面再接触各类采用单总线或者类单总线协议的器件,都会事半功倍。