news 2026/9/9 11:21:12

STM32驱动DHT11温湿度传感器完整教程:时序、标准库与HAL库实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动DHT11温湿度传感器完整教程:时序、标准库与HAL库实现

搞嵌入式这几年,DHT11 应该是我用过最“亲民”的传感器了——成本低、接线少、代码量也不大,STM32 教学里十个项目有八个都从它开始。但说它“简单”也不完全对,真正上手你会发现,时序、上拉、延时、GPIO 模式切换,任何一个环节没处理好,读回来的数据就是一团乱麻。这篇教程我就把这颗传感器的完整玩法拆开讲清楚:从硬件选型、电路设计到标准库和 HAL 库两套代码实现,再到实际项目中常见的坑和排查思路,希望能帮你少走点弯路。

DHT11 属于单总线数字温湿度传感器,一条数据线既能发命令又能收数据,省引脚是它最大的优势,但代价就是时序要求比较严格,对 MCU 端微秒级延时和 GPIO 方向切换的功底是个考验。适合 STM32 初学者学习时序协议,也适合做智能家居、鱼缸温控、环境监控这类不需要太高精度的项目。

1. 项目概述与选型思路

1.1 DHT11 到底是什么,能做什么

DHT11 是一颗集成了温湿度传感元件和数字信号处理电路的复合传感器,内部由电容式湿度感应元件、NTC 热敏电阻测温元件,以及一个 8 位单片机组成。它输出的不是模拟电压,而是经过校准的串行数字信号,MCU 直接读数据线就能拿到温度和湿度值,省掉了 ADC 采样和线性化换算的麻烦。

它的核心参数我列一下:

  • 温度测量范围 0℃ ~ 50℃,精度 ±2℃
  • 湿度测量范围 20%RH ~ 90%RH,精度 ±5%RH
  • 分辨率 1℃ 和 1%RH,小数位实际意义不大
  • 单总线协议,一次传输 40 位数据
  • 供电 3.3V ~ 5V,典型工作电流约 0.5mA ~ 1mA

从参数能看出来,DHT11 的定位是“环境级监测”,不是“工业级测量”。你拿它判断房间里要不要开加湿器、鱼缸水温是否超标、机房有没有异常升温,这些场景足够用了。但如果你想做高精度气象站、培养箱恒温控制这类对误差敏感的项目,DHT11 的 ±2℃ 和 ±5%RH 就不太够看了,建议直接上 DHT22(也叫 AM2302)或者 SHT30。

1.2 为什么教程普遍选 DHT11,什么时候该换 DHT22

初学者容易陷入一个误区:传感器越便宜越简单越好,或者越贵越高级越好。其实选型看的是“够用就行”和“学习价值”。DHT11 之所以成为 STM32 入门标配,不只是因为便宜,更重要的是它的单总线协议很能锻炼人对时序的敏感度——什么时候拉低、拉低多久、什么时候采样电平、怎么在输入输出模式之间切换,这些操作几乎是所有单总线器件的通用套路,弄懂了 DHT11,以后玩 DS18B20、DHT22 都是同一个思路。

那什么时候该换 DHT22?很简单,当你发现 DHT11 的精度和量程成为项目瓶颈的时候。DHT22 的温度精度是 ±0.5℃,湿度精度 ±2%RH,量程也大不少,价格大概是 DHT11 的三倍左右。不过 DHT22 的时序和 DHT11 是兼容的,换传感器只需要改一下初始化参数和解析代码里的数值范围。还有一种思路是用 I2C 接口的 SHT30,精度更高、带报警中断,适合跟低功耗唤醒逻辑配合,但它的驱动难度比 DHT11 高一些。

实操阶段我的建议很简单:学习用 DHT11,项目如果对精度要求明确高于 DHT11 能力,直接换 DHT22,不要在 DHT11 上花太多时间做补偿校准,那不是这颗芯片擅长的事。

2. 硬件接线与电路设计

2.1 引脚定义与接线方式

DHT11 常见封装有两种:四针直插裸传感器和四针模块板。裸传感器引脚定义是 VCC、DATA、NC、GND,买回来得自己在 DATA 线上加上拉电阻;模块板一般已经把上拉电阻和滤波电容做好了,还加了电平转换逻辑,接起来更方便。

以 STM32F103C8T6 最小系统板为例,我的惯用接法:

  • VCC 接 3.3V,不要接 5V,原因下面细说
  • GND 接 GND
  • DATA 接 PB8,这个引脚空闲、没有默认复用冲突,调试方便
  • DATA 到 VCC 之间接一个 4.7kΩ ~ 10kΩ 上拉电阻

接线长度尽量控制在 20cm 以内,超过这个距离,线间电容和干扰会让单总线的高电平波形变缓,读出来的数据就开始出错。我实测过 50cm 杜邦线,现象是 10 次读取里偶尔成功一两次,一开始还以为是代码问题,后来换了短线立刻就好。

2.2 上拉电阻和供电,几个容易踩的坑

关于上拉电阻,很多模块板默认已经焊好了,你直接用就行。如果是裸传感器,DATA 线上必须加上拉电阻,否则数据线悬空时电平不确定,DHT11 根本无法正常通信。阻值选 4.7kΩ 到 10kΩ 都可以,STM32 的 GPIO 内部上拉阻值偏大(约 30kΩ ~ 50kΩ),直接依赖内部上拉读取 DHT11 通常不太稳定,所以尽量还是用外部上拉。

供电电压是另一个大头。DHT11 手册标称 3.3V ~ 5V 都能工作,但如果你用 STM32 的 3.3V 系统,强烈建议给 DHT11 也供 3.3V。原因在于:如果 DHT11 用 5V 供电,它的数据线高电平接近 5V,而 STM32 的 GPIO 多数不能容忍 5V,直接接会加大 IO 损坏风险。除非你确认模块板上有电平转换电路,或者 STM32 引脚标了 FT(Five-volt Tolerant),否则别图省事。

还要提醒一点:DHT11 的采样周期慢,手册建议两次读取间隔至少 1 秒,最好 2 秒。如果你在主循环里疯狂读取,DHT11 会直接不响应。很多人写代码时没注意这个,结果就是第一次能读到,后面全部超时失败,这个坑在下面章节详细展开。

3. 单总线协议与时序拆解

3.1 通信模型:主机发起,从机应答

DHT11 的通信是典型的半双工单总线模式,总线空闲时处于高电平,由主机(STM32)发起一次完整的数据交互。整体流程分四步:

  1. 主机将总线拉低至少 18ms,作为起始信号,让 DHT11 从待机状态唤醒
  2. 主机释放总线并拉高 20us ~ 40us,然后切换到输入模式等待 DHT11 响应
  3. DHT11 检测到起始信号后,拉低总线约 80us 作为响应信号,再拉高约 80us
  4. 响应结束后,DHT11 连续输出 40 位数据,每发送完一位总线会被拉低 50us 进行位间隔同步

整个交互过程大约需要 3ms ~ 5ms。理解这个流程很关键,因为所有驱动代码都是围绕这个状态机展开的:先输出,再切换输入,然后按时序采样。

很多人第一次写 DHT11 驱动失败,不是因为协议看不懂,而是忽略了一个细节:主机发送完起始信号后,必须把 GPIO 从推挽输出模式切换成输入模式(或者开漏带上拉),否则数据线一直被主机驱动着,DHT11 根本拉不动总线。这个模式切换是 DHT11 驱动最容易翻车的地方。

3.2 每位数据的时序判断

DHT11 输出的 40 位数据格式是固定的:8 位湿度整数 + 8 位湿度小数 + 8 位温度整数 + 8 位温度小数 + 8 位校验和。其中湿度小数、温度小数在 DHT11 上通常读出 0,但代码里还是要按格式解析,不能只读整数位。

每一位数据的传输方式是:50us 低电平(位起始 + 同步),然后是高电平。高电平持续 26us ~ 28us 表示逻辑 0,高电平持续约 70us 表示逻辑 1。接收方要做的就是在低电平结束后延时 30us 左右采样总线电平,采样到时仍为高,说明这一位是 1;如果采样到低,说明这一位已经结束,是 0。

这里有三个经验值很重要:

  • 低电平固定约 50us,这是位同步信号
  • 逻辑 0 的总周期约 76us ~ 78us
  • 逻辑 1 的总周期约 120us

判断 0 和 1 的常见方式有两种:一种是“延时采样法”,在低电平结束后延时 30us 读电平,高就是 1,低就是 0;另一种是“超时测量法”,用定时器测量高电平持续时间,超过 40us 判为 1,否则判为 0。第一种代码简单省资源,第二种抗干扰更强,适合对稳定性要求高的项目。

4. 标准库驱动实现(STM32F103 实战)

4.1 工程准备与延时方案

我用 STM32F103C8T6 + 标准外设库(StdPeriph)跑通了一套最简驱动,下面把完整思路和代码都放上来,你可以直接照着搭。

工程准备三件事:先初始化系统时钟(外部 8MHz 晶振,倍频到 72MHz),再初始化延时函数,最后配置 GPIO。这里最大的坑在于延时。DHT11 需要微秒级延时,而标准库例程里最常用的Delay_ms()只能做毫秒延时,没法处理 30us、50us 这种短延时。所以我的做法是写一个基于 SysTick 的微秒延时函数,精确度足够,也不会卡住 CPU。

SysTick 微秒延时的思路不复杂:SysTick 时钟源配置为 HCLK 9 分频,72MHz 下就是 8MHz,一个 tick 是 0.125us,延时 us 个微秒就是us * 8个 tick。但要注意,SysTick 的重装载值是 24 位,最大 16777215,直接延时长导致装载值溢出会有问题。DHT11 时序里最长的是起始信号拉低 20ms,算下来需要 160000 个 tick,没超上限,所以直接用没问题。

void delay_init(void) { // SysTick 时钟源:HCLK 9 分频,即 8MHz SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); } void delay_us(uint32_t us) { uint32_t ticks = us * 8; // 8MHz 下每个 tick 0.125us uint32_t start = SysTick->VAL; uint32_t current; SysTick->LOAD = ticks - 1; SysTick->VAL = 0; SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; do { current = SysTick->VAL; } while ((start - current) < ticks); SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; } void delay_ms(uint32_t ms) { while (ms--) { delay_us(1000); } }

这里有个小细节要提醒:SysTick->VAL是递减计数器,计算时要注意方向。我习惯用“计数器从 LOAD 开始递减,读到当前 VAL 后和起始值比较”的方式,循环里必须保证每次读取都新鲜,否则延时误差会叠加。

4.2 GPIO 配置和输入输出切换

DHT11 数据线接在 PB8,主机阶段需要输出模式发送起始信号,接收阶段需要输入模式读取电平,所以代码里要反复切换 GPIO 模式。标准库的 GPIO 配置用GPIO_InitTypeDef,我封装了两个宏,分别切到推挽输出和浮空输入,注意加上拉输入也可以,但外部已经有上拉电阻,浮空输入就够了。

#define DHT11_PORT GPIOB #define DHT11_PIN GPIO_Pin_8 #define DHT11_DQ_H() GPIO_SetBits(DHT11_PORT, DHT11_PIN) #define DHT11_DQ_L() GPIO_ResetBits(DHT11_PORT, DHT11_PIN) void DHT11_GPIO_OUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DHT11_PORT, &GPIO_InitStructure); } void DHT11_GPIO_IN(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DHT11_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DHT11_PORT, &GPIO_InitStructure); } uint8_t DHT11_DQ_READ(void) { return GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN); }

GPIO 速度一定记得设置为 50MHz。如果图省事用默认低速配置,输出边沿会变缓,起始信号和位信号的电平跳变不够利落,读取出错的概率会明显上升。这是我在实际调试中踩过的坑,所以特别写出来。

MCU 上电后先调用DHT11_GPIO_OUT(),把总线置高,维持总线空闲状态。初始化阶段不要一上来就发起始信号,给传感器留点稳定时间,一般延时 100ms 再说。

4.3 完整驱动代码

核心读取流程分三件事:发送起始信号,读取 DHT11 响应,逐位接收 40 位数据。完整代码我贴出来,每一段都有注释,方便照着移植。

uint8_t DHT11_ReadData(uint8_t *humi, uint8_t *temp) { uint8_t buf[5] = {0, 0, 0, 0, 0}; uint8_t i = 0; // 主机拉低总线,起始信号至少 18ms DHT11_GPIO_OUT(); DHT11_DQ_L(); delay_ms(20); DHT11_DQ_H(); delay_us(30); // 拉高 20us~40us // 切换输入模式,准备接收 DHT11 响应 DHT11_GPIO_IN(); // 等待 DHT11 拉低(响应起始),如果一直高说明没应答 if (DHT11_DQ_READ() == 1) { return 1; // 无响应 } // 跳过 80us 响应低电平 while (DHT11_DQ_READ() == 0); // 跳过 80us 响应高电平 while (DHT11_DQ_READ() == 1); // 接收 40 位数据:5 字节 for (i = 0; i < 5; i++) { buf[i] = DHT11_ReadByte(); } // 释放总线:切回输出,拉高 DHT11_GPIO_OUT(); DHT11_DQ_H(); // 校验:前 4 字节之和等于第 5 字节 if ((uint8_t)(buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) { *humi = buf[0]; *temp = buf[2]; return 0; // 成功 } return 2; // 校验失败 } uint8_t DHT11_ReadByte(void) { uint8_t data = 0; uint8_t i = 0; for (i = 0; i < 8; i++) { // 跳过 50us 低电平 while (DHT11_DQ_READ() == 0); // 延时 30us,采样高电平中段 delay_us(30); if (DHT11_DQ_READ() == 1) { data |= (0x80 >> i); // 高位在前 // 等待高电平结束 while (DHT11_DQ_READ() == 1); } } return data; }

有一点必须提醒:while (DHT11_DQ_READ() == 0)这类等待循环如果遇到传感器损坏或者接触不良,会变成死循环。实际项目中建议给这些等待加上超时保护,比如用一个计数器统计循环次数,超过某个阈值就返回错误。教学代码可以简单点,但做产品的同学一定要加超时。

主函数调用也很简单,每 2 秒读一次,结果通过串口打印:

int main(void) { uint8_t humi = 0, temp = 0; uint8_t ret = 0; delay_init(); Serial_Init(); // 串口初始化,波特率 115200 DHT11_GPIO_OUT(); DHT11_DQ_H(); delay_ms(100); while (1) { ret = DHT11_ReadData(&humi, &temp); if (ret == 0) { printf("温度: %d℃ 湿度: %d%%RH\n", temp, humi); } else { printf("读取失败,错误码: %d\n", ret); } delay_ms(2000); // 采样间隔 2s } }

5. HAL 库版本:读不到数据时先查这里

5.1 HAL 库 GPIO 配置差异

HAL 库的写法跟标准库不同,但核心逻辑一样,区别主要在 GPIO 初始化的结构体和 API 名字上。HAL 库的GPIO_InitTypeDef配置时钟用__HAL_RCC_GPIOB_CLK_ENABLE(),初始化用HAL_GPIO_Init(),读取引脚用HAL_GPIO_ReadPin()

需要注意的是,HAL 库切换输入输出模式时要频繁调用HAL_GPIO_Init(),而这个函数内部会检查参数合法性、重新配置,开销比标准库的GPIO_Init()大不少。DHT11 的位传输是 us 级操作,模式切换的开销虽然不至于让时序崩掉,但在追求稳定性的场景下,建议只在发送起始信号前切换一次输出,在开始读数据前切换一次输入,不要每一位都来回切换。

void DHT11_Pin_Init(GPIO_InitTypeDef *gpio_init) { __HAL_RCC_GPIOB_CLK_ENABLE(); gpio_init->Pin = GPIO_PIN_8; gpio_init->Mode = GPIO_MODE_OUTPUT_PP; gpio_init->Pull = GPIO_NOPULL; gpio_init->Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, gpio_init); } uint8_t DHT11_ReadPin(void) { return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8); }

HAL 库的GPIO_SPEED_FREQ_HIGH对应高速度,初始化后 PMOD 配置变化,写代码时不要漏了这一项,不然照样会出现信号边沿过缓的问题。

5.2 us 级延时的三种实现

HAL 库最常见的坑是HAL_Delay()只支持毫秒级延时。DHT11 需要的 30us、50us 毫秒函数根本不满足。三个替代方案:

方案一:DWT 数据观察点延时。DWT 是 Cortex-M3 内核自带的调试组件,用 CPU 周期计数做高精度延时,不占用定时器,精度高,代码也短。配置CoreDebug->DEMCR开启 TRCENA,然后使能DWT->CTRL的 CYCCNTENA,之后DWT->CYCCNT就是 CPU 周期计数器了。72MHz 主频下,延时 1us 就是 72 个周期。

void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; DWT->CYCCNT = 0; } void DWT_Delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < ticks); }

方案二:基本定时器延时。用 TIM6 或者 TIM7 做微秒时基,初始化时把定时器配成 1MHz 计数频率,每次延时只操作 CNT 计数器和标志位,不重新配置定时器,中断也不用开。这个方案更“正统”,适合严格要求不阻塞 CPU 的场景。

方案三:循环空转。for (i = 0; i < n; i++);这种简单粗暴,调试时可以用,但依赖编译器优化级别,换一个优化选项时序就变了,不建议在生产代码里用。

我自己的选择是:教学代码用 DWT,因为代码量最小;做复杂项目或需要并行任务时用定时器。

5.3 HAL 库驱动要点

HAL 库版本的读取流程跟标准库完全一致,唯一需要小心的是模式切换 API 的调用频率和超时保护。另外,HAL 库项目里SystemCoreClock这个变量如果配置不正确,DWT 延时算出来的 ticks 就是错的,读时序会整体偏移。遇到过很多人移植代码后发现读取全 0,排查半天才发现是系统时钟初始化改成外部高速时钟(HSE)后,SystemCoreClock宏还停留在默认值。

一个比较容易被忽略的工作是:HAL 库的HAL_GPIO_Init()次数如果太多,会影响后续引脚复用。比如 PB8 之前被 I2C 复用,切换成 GPIO 之后又切回 I2C 时,要重新初始化结构体。建议在程序里把 PB8 注册成专用 DHT11 引脚,避免跟其他外设来回抢。

6. 数据验证、显示与项目扩展

6.1 串口打印和校验

代码写完后,最直接的验证手段是串口打印。但如果配置不正确,串口输出全乱码,你很难判断是串口问题还是传感器问题。我的排查顺序是:先单独跑一个 GPIO 翻转测试确认主频、延时正常,再用示波器或者逻辑分析仪抓 DHT11 数据线波形,最后才看串口数值。

DHT11 输出的温度范围是 0~50℃,如果你在室内读出来 80℃,那基本是代码解析错误,不用怀疑传感器坏了。校验和失败时,错误码为 2,这时先查接线和延时,一般就能定位。

有些人习惯把小数位也显示出来:buf[1]是湿度小数,buf[3]是温度小数。但 DHT11 的分辨率只有 1,小数位长期是 0,直接忽略问题不大。如果想显示 0.1 分辨率,必须换 DHT22。

6.2 接 OLED 显示

项目里只用串口打印不够直观,很多同学会接一块 0.96 寸 OLED 屏,把温度和湿度直接显示出来。OLED 走 I2C 协议,用 PB8 和 PB9 吗?别急,这里有个冲突点:很多开发板 PB8/PB9 是 I2C1 的默认引脚,如果你 DHT11 用了 PB8,OLED 又用 I2C1,就会撞车。最简单的办法是 DHT11 换到 PA0 或者 PC13 这类不常用的引脚,OLED 的 I2C 占用 PB8/PB9。

OLED 显示温度湿度的逻辑很简单:读取 DHT11 成功后格式化字符串,调用 OLED 显示函数即可。可以把 DHT11 读取和 OLED 刷新放在同一个定时器或者主循环的 1s 周期里,注意 DHT11 采样间隔 2s,OLED 刷新频率不用跟着它走,1s 刷一次就行。

6.3 结合智能家居/鱼缸场景扩展

DHT11 最常见的项目落地场景是环境监控:智能鱼缸需要水温报警和自动加热控制,智能台灯可以根据环境温度调整色温,宿舍智能控制器用温湿度数据控制加湿器和风扇。

一个比较经典的扩展方式是加继电器和阈值逻辑:

  • 温度超过上限,继电器闭合,启动风扇
  • 湿度低于下限,继电器闭合,启动加湿器
  • 同时加蜂鸣器做超限报警

硬件上就是 STM32 驱动继电器模块,用 GPIO 输出控制继电器通断,主循环里每 2 秒读一次 DHT11,判断阈值后执行对应动作。这里有一个容易忽略的点:继电器线圈是感性负载,如果驱动电路没有加续流二极管,关断瞬间会产生反向电动势,轻则干扰 DHT11 通信,重则烧坏 GPIO 口。买继电器模块时看一眼电路板,基本成熟的模块都带了续流保护电路,但自己搭分立电路时一定要记得加。

7. 常见问题与排查技巧实录

7.1 读回来全是 0xFF 或者全是 0

全 0xFF 的典型原因是数据线一直保持高电平,DHT11 根本没把总线拉低。重点排查四点:接线是否接反、上拉电阻是否缺失、GPIO 是否成功切换成输入模式、DHT11 引脚是否被复用到其他外设。全 0 的话,基本都是起始信号没发出去,检查发送起始信号时 GPIO 是不是推挽输出模式,以及拉低时间是否够 18ms。

如果代码没问题,拿万用表量一下 VCC 和 GND 之间有没有 3.3V 电压,很多模块是坏的或者虚焊,上电后一点反应都没有。

7.2 有时能读有时卡死

“偶尔读成功一次”这个问题,90% 是时序不稳定造成的。先从波形入手:用逻辑分析仪抓 DHT11 数据线的波形,如果低电平不是标准的 50us,或者高电平时间明显偏短偏长,说明延时函数不准。我用过的开发板里,因为主频晶振配置错误导致延时偏差的情况不少见,检查一下SystemCoreClock是否和实际主频一致。

另外要检查读取间隔。DHT11 对采样间隔有硬性要求,连续读取间隔不能小于 1 秒,否则传感器内部还在处理上一次数据,根本不响应。这种问题最隐蔽——第一次能读到,循环里第二次、第三次就失败。解决办法:读取一次之后至少delay_ms(2000)再尝试下一次。

7.3 湿度数值明显偏大或温度不对

湿度偏大通常不是传感器问题,而是探头被放在密闭小空间、靠近加湿器出风口、或者手触摸了感应区域。DHT11 自身不带通风结构,周围空气流通差时湿度读数会明显偏高。温度读数偏差大,先检查 DHT11 是否离 STM32 或电源模块太近——板载 LDO、主控芯片工作发热会直接影响附近测温元件,我做过一个项目,DHT11 放在 STM32 旁边,温度读数持续比室温高 3℃~4℃,后来把探头延长线拉出来才解决。

如果温度读取值忽高忽低,还有一种可能是代码里把 buf[2] 当成了有符号数处理。DHT11 温度值只有正值,负数场景一般用 DHT22,如果强行按int8_t解析,本来就超过 0~50℃ 范围的数据会变成负值,看起来像“乱跳”。

7.4 常见问题速查表

现象可能原因解决办法
读回全 0xFF数据线一直为高,DHT11 未拉低查接线、上拉、GPIO 输入模式和引脚复用
读回全 0起始信号未发出或 GPIO 模式不对检查推挽输出、拉低时间 ≥18ms
第一次读到,后面全失败采样间隔不足 1s主循环加 2s 延时
偶尔读错、校验失败延时不准、线太长、供电不稳逻辑分析仪抓波形,换短线,3.3V 供电
温度始终偏高靠近发热源或主板探头远离 MCU/LDO
湿度明显偏大通风不畅或探头受潮改善探头位置
程序卡死在 while 等待传感器损坏或接触不良加超时保护,检测硬件

再分享一个排错小技巧:换传感器的时候,如果新传感器插上去还是同样的问题,先别急着怀疑芯片,拿万用表打一下数据线对地电压,正常空闲状态应该在 3.3V 左右。如果测量出来是 1.5V 这种中间电平,说明上拉电阻阻值选小了或者模块板上的电阻坏了,换一个 4.7kΩ 的电阻基本能解决。

8. 从 DHT11 出发,下一步还能玩什么

学会了 DHT11 的驱动,你会发现单总线协议并不是这个项目最难的部分,难的是“如何让一套驱动在各种环境下稳定运行”。我个人的经验是:把 DHT11 的读取函数封装成独立模块,接口只暴露DHT11_ReadData()这个函数,内部逻辑不要跟主业务耦合,这样后续换上 DHT22,甚至换成 I2C 接口的 SHT30,上层代码都不需要改。

接下来可以尝试的方向很明确:一是把现有驱动改成支持多传感器,比如同时监控室内和室外的温湿度;二是接入 RTOS,把 DHT11 读取放到独立任务里,用消息队列把数据传给显示任务和报警任务;三是把 DHT11 数据通过 ESP8266 或者 ESP32 上传到云平台,做成手机端可查看的物联网节点。

最后分享一个我在实际项目中的心得:DHT11 这类低精度传感器,不要在算法上过度纠结精度补偿,做到“稳定读取 + 及时报警”就够了。真到了需要高精度数据的环节,换传感器比写补偿算法划算得多。这篇教程基于 STM32F103 标准库和 HAL 库分别展开,重点都在时序和实操细节上,你照着搭完一遍,后续再去玩 DHT22、DS18B20 这些传感器,上手会快很多。

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

企业级Voice Agent两大难题:级联式三明治架构解析

这次我们来看一个偏工程落地的方向&#xff1a;企业级 Voice Agent 智能语音助手。很多人一听到“语音助手”就想到唤醒词、ASR、TTS 三个模块直接串起来&#xff0c;但真正做过项目和产品的人都知道&#xff0c;Demo 和技术演示是一回事&#xff0c;能抗住多轮对话、任务编排、…

作者头像 李华
网站建设 2026/9/9 11:17:52

从向量模到频域幅值:全面理解magnitude的工程应用

做时序算法的时候&#xff0c;我踩过一个大跟头&#xff1a;同一批振动传感器数据&#xff0c;用幅值&#xff08;magnitude&#xff09;做异常检测&#xff0c;能提前十几分钟发现设备轴承退化&#xff1b;而我只盯均值漂移&#xff0c;直到报警阈值被冲破才反应过来。从那时起…

作者头像 李华
网站建设 2026/9/9 11:13:07

基于DNA编码与混沌系统的图像加密解密Matlab实现与安全分析

干了几年图像算法&#xff0c;这类“加密解密安全性分析”的项目没少做&#xff0c;但DNA编码和混沌系统这套组合&#xff0c;说实话每次都能翻出点新坑。最近又完整跑了一遍基于DNA编码和混沌系统的图像加密解密流程&#xff0c;把数据丢失攻击测试、直方图、信息熵、PSNR、像…

作者头像 李华
网站建设 2026/9/9 11:13:02

Spring Boot多数据源实战:PostgreSQL与SQL Server动态切换全攻略

项目跑得好好的&#xff0c;突然产品提了一个需求&#xff0c;说要把 PostgreSQL 里的业务数据和另一台 SQL Server 上的历史数据放到一起看。数据量不是特别大&#xff0c;但每次都开两个连接写代码又麻烦&#xff0c;于是“Spring Boot 多数据源”这几个字就被提上了日程。我…

作者头像 李华
网站建设 2026/9/9 11:12:50

图表设计系统化实战:从信息秩序到高质量架构图的完整流程

/* 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 11:11:22

自学软件测试总半途而废?从学习路线到项目实战一次讲透

自学软件测试&#xff0c;为什么总是半途而废&#xff1f;“为什么自学软件测试很难坚持下去&#xff1f;”——这几乎是每个刚踏入这个领域的人都会问的问题。我见过太多人下载了一堆视频教程&#xff0c;收藏了十几篇面试题合集&#xff0c;甚至报了七八个网课&#xff0c;然…

作者头像 李华