news 2026/9/9 5:35:05

STM32驱动DHT11温湿度传感器:单总线时序与实战避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动DHT11温湿度传感器:单总线时序与实战避坑指南

我最早接触DHT11是在帮一个学弟调试毕业设计的时候,他的温湿度数据读出来永远是0,整块板子连初始化都过不去。当时我第一反应是"这模块不是有手就行?",结果连我自己也栽了一个下午。后来发现,DHT11看着简单,实际上它那个单总线协议对时序极度敏感,稍微有点偏差就全军覆没。这篇教程就是把我从硬件选型、原理图设计、底层驱动到踩坑排查的完整经验整理出来,给正在用STM32做温湿度采集的开发者,尤其是用标准库或HAL库写驱动时总被时序问题折磨的人,一条能直接走通的路。

1. 为什么2024年还在用DHT11:先从选型逻辑说起

市面上温湿度传感器多得是,SHT30、AHT20、BME280,随便拉一个出来精度都比DHT11强。那为什么DHT11至今还是教学板、毕业设计、DIY小项目里的常客?我自己的看法是:它的定位从来就不是"性能王者",而是"学习曲线最平滑的入门传感器"。

先看一张我常用的对比表:

型号精度(温度/湿度)通信方式典型价格驱动难度
DHT11±2°C / ±5%RH单总线3-5元
DHT22±0.5°C / ±2%RH单总线10-15元
SHT30±0.3°C / ±2%RHI2C6-10元
AHT20±0.3°C / ±2%RHI2C3-6元
BME280±1°C / ±3%RHI2C/SPI10-20元中高

I2C传感器确实更简单——主机发地址,从机回数据,时序标准,不用微秒级掐表。但DHT11的"单总线协议"反而更能锻炼人对数字时序的敏感度。你在DHT11上学到的电平翻转、延时窗口、起始信号、响应时序,后面玩DS18B20、红外遥控、甚至是自定义单总线协议时全部用得上。

还有一个非常现实的原因:DHT11模块版便宜到可以忽略成本,坏了直接换,不需要重新画板子。做产品选型我不会推荐DHT11,但做学习项目、实验室原型验证、或者给学生练手,它依然是最顺手的那个。

另外一个常被忽略的点:DHT11的单总线协议对MCU的GPIO操作能力有很好的测试作用。如果你的SPI、I2C经常莫名失败,先用DHT11把引脚操作练熟,再回头排查硬件问题,往往事半功倍。

2. 硬件设计的三件小事:上拉电阻、电源去耦和布线细节

很多新手直接买现成的DHT11模块,插上杜邦线就开始写代码。但我建议至少要知道模块上那几个元件是干嘛的,因为你迟早要自己画板子。

2.1 上拉电阻:为什么必须接,阻值怎么选

DHT11的数据引脚是开漏输出结构——它本身只能把电平拉低,不能主动输出高电平。要让总线在空闲时处于高电平,必须在数据线上接一个上拉电阻到VCC。

模块版上通常已经焊好了上拉电阻,一般4.7kΩ或10kΩ。如果你用的是散装DHT11传感器,裸芯片只有四个引脚(VCC、DATA、GND、还有一个NC空脚),这时上拉电阻必须自己加。

阻值选4.7k还是10k?DHT11的单总线通信速率不高,边沿要求也不苛刻,4.7k、5.1k、10k都能正常工作。我习惯用4.7k,理由有两个:一是抗干扰能力比10k更强,二是DHT11数据线稍长(超过20cm)时,4.7k的上升沿陡峭度更好,读取成功率更高。

2.2 电源去耦:一个104电容解决大问题

单独给DHT11供电的场合,VCC和GND之间跨接一个0.1μF(104)的瓷片电容,再并联一个10μF电解电容会更好。这个不是玄学——DHT11在发出数据时,内部电路会在每个bit的边沿产生短暂的电流冲击。如果电源纹波大,这个冲击可能影响内部RC振荡器的稳定性,导致输出的脉冲宽度漂移。脉冲宽度漂移的直接后果就是MCU误判bit是0还是1。

特别是当你把DHT11和电机、继电器、蜂鸣器这类感性负载共用电源时,不加去耦电容,读取乱码的概率会明显上升。

2.3 3.3V还是5V:宽电压不是免死金牌

模块版的DHT11(蓝色那种)内部通常带了一个电平转换电路,可以工作在3.3V或5V。但如果你用的是散装传感器,情况就不一样了。

散装DHT11虽然标称是3.3V~5.5V宽电压供电,但市面上不少批次实际上更适应5V逻辑。我实测过一批散装的DHT11,在3.3V供电下,输出高电平大约只有2.4V左右。STM32的GPIO输入高电平阈值是0.7×VDD,也就是3.3V供电下需要超过2.31V才算高电平——2.4V勉强越过阈值,但余量很小,线一长就出错。

我的建议是:散装DHT11优先用5V供电,数据线通过电阻分压(比如1k+2k)或者电平转换芯片降到3.3V再接STM32,这样最稳。模块版则无所谓,3.3V直接干就行。

2.4 布线长度和走线位置的影响

传感器和MCU之间的杜邦线尽量控制在20cm以内。超过这个距离,建议把上拉电阻的阻值适当调小到2.2k~3.3k,补偿线缆电容对上升沿的拖累。

还有个实战中容易踩的坑:DHT11的数据线不要和SPI时钟线、USB差分线平行走太长距离。强耦合会让DHT11的时序出现毛刺,单片机读到错误bit后依然可能通过校验(因为校验位只有8位,碰撞概率1/256),导致温度突然跳变几十度。这种故障最难查,因为绝大多数时间数据是正常的,偶尔抽风一次。

3. 通信协议拆解:DHT11的单总线时序到底在说什么

网上关于DHT11的时序分析文章不少,但很多都只说"拉低18ms然后读数据",没有讲清楚协议的精髓。这里我从底层把整个通信过程完整拆一遍。

3.1 一次完整通信的四个阶段

DHT11的单总线通信以主机发起起始信号为开始,整个过程分成四段:

  1. 主机发送起始信号:主机把总线拉低至少18ms(通常我拉低20ms),然后释放总线(拉高)。这个低电平时间必须足够长,让DHT11的内部电路检测到并进入工作状态。

  2. DHT11应答信号:主机释放总线后,DHT11会在20~40μs内把总线拉低,持续约80μs,表示"我准备好了";随后释放总线,总线被上拉电阻拉高,再持续约80μs,准备发送数据。

  3. 数据位传输:40位数据,按序发送,每位都由一个50μs左右的低电平起始,然后是高电平。高电平持续时间的长短决定了该位是0还是1。

  4. 总线释放:40位数据发送完后,DHT11释放总线,回到空闲状态,等待下一次起始信号。

3.2 位0和位1的区别:一切都在于高电平宽度

这是整个DHT11协议最核心也最容易搞混的点。每一位数据的格式是:50μs低电平 + 一定的的高电平

  • 逻辑0:高电平持续约26~28μs
  • 逻辑1:高电平持续约70μs

MCU怎么判断收到的是0还是1?没有捷径,只能是:先把低电平等完(因为每一位都以50μs低电平开头,但实际长度有波动,26到38μs都算正常),然后开始计时高电平的时间。如果高电平持续超过某个阈值(通常取40~50μs),判为1;否则判为0。

这里有一个很多教程没提到的关键点:低电平的起始时刻并不精确,所以不能等到低电平结束才采样,而是在检测到下降沿后要等足够的时间跨过低电平区间,再开始测量高电平宽度。

3.3 40位数据布局与校验逻辑

DHT11一次上报40位数据,顺序是:

  • 湿度整数部分(8位)
  • 湿度小数部分(8位)
  • 温度整数部分(8位)
  • 温度小数部分(8位)
  • 校验和(8位)

所有数据都是高位在前(MSB first)。校验和数据等于前四个字节之和的低8位。

比如你读到的40位是:

  • 湿度整数:0x1F(十进制的31)
  • 湿度小数:0x00
  • 温度整数:0x1C(十进制的28)
  • 温度小数:0x00
  • 校验和:0x3B

验证一下:0x1F + 0x00 + 0x1C + 0x00 = 0x3B,校验和一致,数据有效。如果校验失败,说明本次读取过程中有时序错误,直接丢弃重读即可。DHT11的更新周期是1Hz(0.5Hz到2Hz不等),重读间隔至少要等1秒以上,否则大概率还是读到旧数据或错误数据。

3.4 为什么DHT11的时序对延时函数这么敏感

这里要解释一个问题:为什么同样一份代码,在别人的板子上跑得好好的,在你的板子上就死机、卡死或者数据全零?

因为DHT11协议要求的延时是微秒级的(26μs、50μs、80μs),这取决于:

  • 系统时钟频率(8MHz还是72MHz,精确延时数值完全不同)
  • 编译器优化等级
  • MCU执行循环的指令周期数
  • 中断是否干扰了时序

很多人直接用了一个不精确的Delay函数,比如for(i=0; i<30; i++);,这个循环在不同优化级别下耗时差异巨大。我在-O2优化下写的延时函数,换到-O0就完全跑不通,就是这个原因。

4. 标准库实战:从发送起始信号到校验完整驱动

下面给一套我实测稳定的标准库驱动代码。开发环境是Keil MDK5 + STM32F103C8T6(蓝色Pill板),系统时钟72MHz。核心逻辑是发送起始信号、读取响应、逐位采集40位数据、校验和确认。

如果你的硬件配置和我不同,重点看延时函数怎么改,不要照抄延时数值。

4.1 引脚配置与宏定义

#include "stm32f10x.h" #include "delay.h" // 自己实现的延时函数,提供 delay_us 和 delay_ms #define DHT11_GPIO_PORT GPIOB #define DHT11_GPIO_CLK RCC_APB2Periph_GPIOB #define DHT11_GPIO_PIN GPIO_Pin_0 // 两行关键宏:切换输入输出模式 #define DHT11_OUT_MODE() { GPIOB->CRL &= 0xFFFFFFF0; GPIOB->CRL |= 0x00000003; } // PB0 推挽输出50MHz #define DHT11_IN_MODE() { GPIOB->CRL &= 0xFFFFFFF0; GPIOB->CRL |= 0x00000004; } // PB0 浮空输入 #define DHT11_SET_HIGH() GPIO_SetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN) #define DHT11_SET_LOW() GPIO_ResetBits(DHT11_GPIO_PORT, DHT11_GPIO_PIN) #define DHT11_READ() GPIO_ReadInputDataBit(DHT11_GPIO_PORT, DHT11_GPIO_PIN)

这里有个细节:DHT11的数据线在通信过程中需要切换方向——主机发送起始信号时要输出模式,读取应答和数据时要切换为输入模式。GPIO方向切换是关键,不要偷懒。

在标准库中,我通常直接操作CRL寄存器而不是调用GPIO_Init,因为GPIO_Init函数体较长,在需要快速切换模式的场景下效率偏低,而且寄存器操作指令数固定,时序更可控。

4.2 微秒级延时函数:不要裸用for循环

我在delay.h里自己实现了微秒延时,基于SysTick定时器,精度在±2μs以内。DHT11对50μs级别的窗口要求没那么变态,±2μs完全够用。这里给出一种基于SysTick的实现方式:

void delay_us(uint32_t nus) { uint32_t temp; SysTick->LOAD = nus * (SystemCoreClock / 1000000); // 设置重装载值 SysTick->VAL = 0; // 清空计数器 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 使能SysTick do { temp = SysTick->CTRL; } while ((temp & SysTick_CTRL_ENABLE_Msk) && !(temp & SysTick_CTRL_COUNTFLAG_Msk)); SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭SysTick SysTick->VAL = 0; }

注意,SystemCoreClock在标准库的system_stm32f10x.c中定义,72MHz主频下就是72000000。这个延时函数会被频繁调用,如果觉得每次都要开关SysTick开销太大,也可以用DWT计数器或者裸机while递减配合校准。我主要图它直观、好移植。

4.3 DHT11完整的读取函数

uint8_t DHT11_ReadData(uint8_t *humidity_int, uint8_t *humidity_dec, uint8_t *temp_int, uint8_t *temp_dec) { uint8_t buf[5] = {0, 0, 0, 0, 0}; uint8_t i, j; uint8_t retry = 0; // 1. 主机发送起始信号:拉低至少18ms DHT11_OUT_MODE(); DHT11_SET_LOW(); delay_ms(20); // 2. 释放总线,模式切为输入 DHT11_SET_HIGH(); delay_us(30); // 释放后等待一小段时间,让上拉电阻把电平拉高 DHT11_IN_MODE(); // 3. 等待DHT11应答:先低后高各约80us retry = 0; while (DHT11_READ() == 1 && retry < 100) { // 等待低电平(应答开始) retry++; delay_us(1); } if (retry >= 100) return 1; // 超时,传感器未响应 retry = 0; while (DHT11_READ() == 0 && retry < 100) { // 等待低电平结束 retry++; delay_us(1); } if (retry >= 100) return 1; retry = 0; while (DHT11_READ() == 1 && retry < 100) { // 等待高电平结束,准备读数据 retry++; delay_us(1); } if (retry >= 100) return 1; // 4. 读取40位数据 for (j = 0; j < 5; j++) { for (i = 0; i < 8; i++) { // 跳过50us低电平 retry = 0; while (DHT11_READ() == 0 && retry < 80) { retry++; delay_us(1); } // 测量高电平宽度 retry = 0; while (DHT11_READ() == 1 && retry < 80) { retry++; delay_us(1); } // 如果高电平持续超过40us,判为逻辑1 if (retry > 40) { buf[j] |= (0x80 >> i); // 注意高位在前 } } } // 5. 校验 if ((uint8_t)(buf[0] + buf[1] + buf[2] + buf[3]) != buf[4]) { return 2; // 校验失败 } *humidity_int = buf[0]; *humidity_dec = buf[1]; *temp_int = buf[2]; *temp_dec = buf[3]; return 0; }

这段代码里的两个关键点:

为什么用retry > 40而不是retry > 35判1?因为我的delay_us(1)实际耗时可能略大于1μs,加上while循环的判断开销,实际循环周期大约是1.5μs。如果我阈值设得太小,逻辑0的高电平(约26μs)可能也被判成1。用40作为阈值,实测在72MHz下区分度为:逻辑0约17次循环,逻辑1约46次循环,非常清晰。

为什么每个等待循环都有超时上限?如果传感器没接好、损坏或者总线被拉死,没有超时的while会变成死循环,程序直接卡死。这也是很多人说"DHT11卡死程序"的根本原因。加超时是所有单总线驱动的基本素养。

4.4 调用示例

int main(void) { uint8_t hum_i, hum_d, temp_i, temp_d; uint8_t ret; delay_init(); // 时钟初始化 GPIO_Config(); // 端口时钟使能和引脚配置 while (1) { ret = DHT11_ReadData(&hum_i, &hum_d, &temp_i, &temp_d); if (ret == 0) { printf("Humidity: %d.%d%%RH Temp: %d.%d°C\r\n", hum_i, hum_d, temp_i, temp_d); } else { printf("DHT11 read error: %d\r\n", ret); } delay_ms(2000); // 注意:间隔至少1秒,DHT11更新速率限制 } }

5. HAL库移植:从标准库思维切换到HAL库的三个变化

现在STM32CubeMX已经成为主流初始化工具,很多人的工程直接从HAL库起步。HAL库封装了底层寄存器操作,但这几个点会让DHT11的HAL实现踩坑。

5.1 GPIO方向切换:HAL函数的开销问题

HAL库切换GPIO输入输出方向的标准写法是:

GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

问题在于:HAL_GPIO_Init函数非常庞大,它会重设整个GPIO配置寄存器的所有位,每次调用耗时几十微秒。而DHT11协议里,起始信号释放后只有20~40μs的应答窗口,你用HAL_GPIO_Init切一次模式,窗口就错过了。

我的HAL库方案是:初始化时用HAL正常配置,切换方向时直接操作寄存器。在HAL库工程里,GPIOB的CRL寄存器地址依然相同,直接改寄存器完全兼容。

#define DHT11_OUT_MODE() { GPIOB->CRL &= 0xFFFFFFF0; GPIOB->CRL |= 0x00000003; } #define DHT11_IN_MODE() { GPIOB->CRL &= 0xFFFFFFF0; GPIOB->CRL |= 0x00000004; }

这种写法在HAL库工程里完全合法,因为HAL库也是基于同一套寄存器映射的。我用它在STM32F407上验证过,没问题。

5.2 HAL_Delay的精度陷阱

HAL提供的HAL_Delay()是基于SysTick中断实现的毫秒级延时。如果你要微秒级延时,不要尝试用HAL_Delay拆——它的节拍就是1ms,拆不出微秒精度。

在HAL库工程里,我依然使用自己实现的SysTick微秒延时。但要注意:如果HAL已经占用了SysTick(HAL_Init默认会),你自己再用SysTick会冲突。解决办法有两种:

  1. 改HAL的时基源:在CubeMX的SYS配置里,把Timebase Source改成TIM6或其他定时器,把SysTick腾出来给你的微秒延时用。
  2. 用DWT计数器实现延时:DWT是Cortex-M内核的调试组件,有一个32位的CYCCNT寄存器,专门用来记录CPU周期数,不依赖SysTick,也不受HAL影响:
void delay_us_DWT(uint32_t us) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; uint32_t cycles = us * (SystemCoreClock / 1000000); uint32_t start = DWT->CYCCNT; while (DWT->CYCCNT - start < cycles); }

我后来在F4上长期使用DWT方案,稳定性很好,推荐直接把这段代码封装到你的bsp层里。

5.3 中断优先级:为什么你的数据偶尔跳变

在标准库时代,我很少遇到DHT11读取被中断干扰的情况,因为F103本身没有多少中断源。但换到F4、H7后,外设丰富,如果DHT11读取过程中被UART中断、定时器中断打断几百微秒,时序窗口就被破坏了。

一个最典型的场景:你用串口每秒打印一次温湿度,同时DHT11每次读取都失败。原因是USART的TXE中断在DHT11读取的50μs高电平时间内频繁触发,导致循环变长,阈值判断全部失真。

解决办法:

  1. DHT11读取函数整体放进临界区:读取前__disable_irq(),读完后__enable_irq()。这个粗暴但有效,因为DHT11一次完整读取只有几毫秒,关中断对系统影响很小。
  2. 如果你要读取的同时还要保证串口不丢数据,把DHT11读取放到定时器中断里,并且把该定时器中断优先级设为最低。
  3. 最优雅的解法:用DMA传输串口数据,让串口中断根本不触发。

6. 实测问题排查:六种翻车现场与修复链路

6.1 数据全为0或校验永远失败

这是最常见的问题。排查链路应该是:

  1. 先确认接线:VCC是3.3还是5V?DATA接对引脚没有?GND是不是共地?如果模块输出电压低于STM32高电平阈值,数据线就用示波器或万用表量高电平幅值。
  2. 再查上拉:裸传感器没有上拉电阻时,数据线空闲状态是浮空的,读回的值随机,几乎必然失败。
  3. 然后用逻辑分析仪或示波器看起始信号:主机有没有正常拉低20ms?释放后DHT11有没有拉低80μs作为应答?如果完全没看到应答波形,大概率传感器已经损坏或者供电异常。
  4. 最后检查延时:你用的延时准确吗?用delay_us(50)实际延时了多少?有时我直接用HAL_Delay(1)想"顺便延时1微秒",结果延时了1毫秒,整个时序全部错乱。

6.2 第一次读取卡死

很多人写的主循环第一次调用读取函数就死机,这是因为起始信号发出后,DHT11没有响应,程序在等待低电平的while(DHT11_READ()==1)里死循环了。前面也提过,所有等待循环必须加超时退出。没有超时机制的DHT11驱动代码,网上能找到一大把,抄的时候务必自己加上。

6.3 一直返回超时错误,但传感器是好的

还有一种情况:传感器在第一次成功读取后,第二次读取必须间隔1秒以上。如果你在循环里50ms就调用一次读取,DHT11根本来不及刷新数据,会一直处在"忙"状态,不响应起始信号。

解决方案:把读取间隔设为2秒。如果需要更快的刷新率,考虑DHT22或SHT30,它们在高速轮询下表现好得多。

6.4 温度湿度偶发乱跳

温度从25°C突然跳到80°C,然后又跳回来,这种偶发故障通常是电磁干扰或者电源毛刺。

我曾经遇到过一个小项目,DHT11数据线走到了一块开关电源附近,只要电源启动,温湿度就开始乱跳。后面加了一根屏蔽线(把信号线用外皮接地包裹),并在线路入口加了一个100nF滤波电容,故障消失。

另一个排查点:检查你选择的GPIO是不是被其他外设复用。比如STM32F103的PB0在默认情况下是ADC的通道8输入引脚。如果你初始化了ADC外设但没有配置引脚复用,DHT11读到的是被ADC内部电路干扰后的电平,数据就会时好时坏。

6.5 使用JTAG引脚导致的下载失败

我见过一个典型案例:有人把DHT11接在PA13(JTMS引脚)上,然后发现程序下载不进去,或者下载一次后再也连不上调试器。原因是PA13/PA14默认被JTAG占用,你配置成普通GPIO后,调试口被禁用,SWD也就失效了。

解决办法:在代码里加上这个:

GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

然后按复位键的同时点下载,抢在用户程序运行前把芯片擦掉。或者干脆换一个不带调试功能的GPIO引脚,别给自己挖坑。

6.6 板子能跑但读出来是恒定的28°C 60%RH

这个温度恒定地异常"合理",大概率是传感器没有真正更新数据——除了1秒间隔问题外,还有可能是因为DHT11的DATA引脚和VCC之间短路了,或者传感器内部晶振损坏。用另一块已知正常的模块交叉测试,能快速定位问题。

7. 进阶玩法:一条总线上挂多个DHT11与低功耗设计

DHT11的单总线协议从原理上讲,一条总线上可以挂多个传感器,因为它每个bit都有明确的电平窗口。但实际上很少人这么干,原因有二:一是DHT11没有芯片地址,所有挂在同一总线上的传感器会同时响应,数据互相冲突;二是DHT11没有冲突检测机制,不能像DS18B20那样通过序列号寻址。

所以如果你想采集多个位置的温湿度,我的建议是:

  1. 每个DHT11独占一个GPIO引脚,读取逻辑完全独立。STM32的引脚资源充足,几个传感器还占得起。
  2. 引脚紧张时,可以用一个8选1模拟开关(比如CD4051)轮流把不同DHT11的数据线接到同一个GPIO。注意切换后要给传感器留足够的稳定时间(至少100ms),避免电平冲突。
  3. 如果你追求极低功耗,DHT11本身就不是好选择——它的上拉电阻一直在耗电。低功耗场景我更推荐把DHT11的VCC引脚也接到MCU的GPIO上,读取前拉高供电,读完再拉低,这样待机电流几乎为零。不过要提醒:上电后DHT11需要1秒左右的稳定时间才能可靠通信,这个特性决定了它做不了高频采集。

8. 两个实战经验补充:关于移植和PCB设计

8.1 关于"国产替代"芯片的移植注意事项

最近看到有人用APM32、GD32替代STM32跑DHT11驱动。这些芯片在引脚和寄存器层次上兼容STM32F1,标准库代码基本可以直接用,但有一个被我同事踩过的坑:

有一批GD32F103的GPIO速度档位和STM32定义不完全一样,如果照搬标准库的GPIO初始化,引脚速度配置可能没生效,导致GPIO翻转速度不够,DHT11起始信号的下降沿不够陡,传感器完全收不到起始信号。

解决办法很简单:把GPIO速度档位强制设为最高档(在标准库中是GPIO_Speed_50MHz,在HAL库中是GPIO_SPEED_FREQ_HIGH)。不要用LOW或MEDIUM,DHT11对边沿斜率有隐性要求。

另外,如果碰到"起始信号发出后传感器一直无响应"的情况,先把时间参数整体放大到1.5倍试试。部分国产芯片的指令执行周期和ST原厂有细微差异,延时会偏短。

8.2 PCB设计时给DHT11留的位置建议

如果你在画板子,给DHT11留位置时注意三点:

  • 传感器开口方向要朝外,方便空气流通,不要在它正上方放遮挡物或大功率发热元件。
  • 数据线走线远离电源开关管和电感类器件,实在躲不开就在数据线上串一个330Ω电阻,限制高频干扰灌入。
  • 在DATA和GND之间加一个1nF的滤波电容。虽然这个电容会略微拖慢上升沿,但只要总线长度不长(小于10cm),没影响。我在产品原型中验证过,这个电容能显著减少数据跳变。

9. 从DHT11到DHT22:一个驱动函数的通用化改造

最后说一个实用技巧:如果你以后要升级到DHT22,DHT11的驱动代码可以复用90%。DHT22的通信协议结构与DHT11几乎一致,不同的地方在于:

  • 数据位逻辑1的高电平宽度更长,约75μs
  • 温湿度分辨率是16位,前两个字节是湿度,按0.1%RH为单位;接下来两个字节是温度,最高位是符号位,按0.1°C为单位
  • 校验和同样是前四个字节之和的低8位

所以你可以把读取函数抽象成:

typedef struct { float humidity; float temperature; } DHT_Data_t; uint8_t DHT_GenericRead(DHT_Data_t *data);

在读取完40位原始数据后,根据传感器类型(用宏或参数区分)做不同的数据解码。我当年在项目里就是靠这个抽象,把DHT11/DHT22的驱动维护成本降到了零。

这套改造做完,你的温湿度模块基本就到了"可复用到下一个项目"的程度了。从最基础的时序原理,到标准库和HAL库两套实现,再到干扰排查和PCB设计注意事项,DHT11这条线吃透之后,再去碰其他单总线设备或者I2C传感器,都会顺手很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 5:34:19

AI Agent加持的SSH终端JC Shell:跨平台运维实战体验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:33:33

2026年电动车头盔怎么选?三款高性价比实测推荐

骑电动车这六年&#xff0c;我换过的头盔不下十顶&#xff0c;从最早随手买的工地盔&#xff0c;到后来跟风入的几百块蓝牙智能盔&#xff0c;坑踩了不少&#xff0c;经验也确实攒了一肚子。身边朋友经常问我&#xff1a;“到底哪款电动车头盔最值得买&#xff1f;”说实话&…

作者头像 李华
网站建设 2026/9/9 5:31:24

车载扬尘监测站完整指南:原理、选型与实战部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:31:20

基于ATML与IEEE 1671的自动化测试系统数据驱动架构设计

1. 项目背景&#xff1a;传统设备自动化测试系统开发&#xff0c;最痛的是什么先交代一下背景。过去几年我一直在做设备自动化测试系统的设计与集成&#xff0c;这里的“设备”既有电路板组件、电源模块&#xff0c;也有整机终端。做这套东西的人应该都有同感&#xff1a;早期项…

作者头像 李华
网站建设 2026/9/9 5:30:29

CrewAI多智能体实战:从任务拆解到调参避坑的全记录

开头先说句公道话。很多刚接触AI开发的朋友&#xff0c;总以为只要把提示词写得足够详细&#xff0c;单次调用大模型就能解决一切问题。我刚开始做AI工具时也是这个思路&#xff0c;结果写着写着就发现&#xff0c;单靠一个LLM调用&#xff0c;处理稍微复杂的任务就会出现上下文…

作者头像 李华
网站建设 2026/9/9 5:30:24

嵌入式固件启动流程深度拆解与OTA故障定位实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华