湿敏传感器设计实战:DHT11原理图与STM32时序程序全解析
2026/9/13 15:31:55 网站建设 项目流程

简介:面向电子设计竞赛、毕业设计及小团队预研,这份湿敏传感器模块技术资料围绕湿度检测开关的方案设计、电路实现与TTL输出调试提供完整参照,适合中初级电子工程师、嵌入式学习者快速上手。压缩包为zip格式,共21个文件,整体仅734KB;内容以单片机C语言工程为主,包含C语言源程序、可烧录的HEX文件、Keil工程配置文件,以及编译生成的中间文件;同时提供原理图图片、设计文档与产品使用手册,能够覆盖从方案设计、编译烧录到硬件联调的主要链路。资料中可直接找到湿敏传感器原理图、TTL输出测试程序、芯片资料和产品使用手册,便于对照电路与代码理解湿度检测开关的阈值判断逻辑和输出方式;HEX文件可用于实际烧录验证,减少重复设计时间。目前已有141人学习下载,适合个人学习做项目参考,也可作为毕业设计或小团队开发时的低成本技术参照,能够有效缩短前期方案探索时间。

1. 湿敏传感器电子设计资料,先看时序再看原理图

一套名为“湿敏传感器(设计方案+原理图+程序+芯片资料)”的资料包,在电子设计圈里并不罕见。你从网上下到的多半是一个压缩包,里面有方案说明、原理图文件、单片机源码和一些 PDF 格式的芯片手册。很多人第一件事是打开原理图看电路,结果看完还是不知道程序怎么写,原因很简单:湿敏传感器这类器件,硬件电路往往只有几个电阻电容,真正的技术门槛在传感器和单片机之间的通信时序上。

这篇内容会按照一套可复现的湿敏传感器方案来讲:怎么选芯片、怎么把原理图上的接口画对、怎么写程序把湿度读出来、读到异常值怎么排查。适合正在做课程设计、准备电子竞赛,或者要在工控机箱、仓储环境里做湿度采集的开发者。看完之后,你拿到类似的资料包,也知道先看什么、改什么、哪些参数不能动。

2. 湿敏传感器选型:芯片资料先看时序图和电气参数

2.1 湿敏电阻与数字式湿度传感器,设计的复杂度差一个量级

市面上常见的湿敏传感器可以分成两类。第一类是电阻式湿敏元件,比如 HR202、湿敏电阻,它的阻值随环境相对湿度变化,通常需要配合一个由运放构成的振荡电路或分压电路,把电阻变化转换成电压或频率信号,再交给单片机的 ADC 或计数器处理。这类元件成本低,但非线性明显,需要在程序里做查表或曲线拟合。

第二类是数字式温湿度传感器,典型代表是 DHT11、DHT22 和 SHT30。这类传感器内部已经集成了感湿元件和信号调理电路,直接输出数字信号。单片机只需要一根 IO 口就能读取数据,程序的复杂度和模拟方案的标定工作量都小很多。做课程设计或产品原型时,优先选数字式。

标题里提到的“湿敏传感器设计方案”资料包,绝大多数情况下就是指 DHT11 这类数字式方案。原因很实际:51 单片机、STM32 都能直接驱动,资料里给的程序可以直接移植,原理图也简单,布线不容易出错。

2.2 DHT11 芯片资料里,重点看响应时序和供电电压

拿到芯片资料后,不要从头到尾读一遍数据手册,直接翻到“时序特性”和“电气参数”两页。对你写程序有用的信息就三类:

  • 供电电压范围。DHT11 常见规格是 3.3V 到 5V,但不同批次或不同封装的传感器,工作电压范围会有差异。资料里若标注 3.3V 到 5.5V,你接在 STM32 的 3.3V 供电上也安全。
  • 数据输出的电气特性。注意数据引脚的高电平最低值是多少,这决定它能不能被 3.3V 的单片机直接识别。多数 DHT11 模块自带 10k 上拉电阻,接 3.3V 单片机可以直接通信。
  • 一次完整读数需要的时间。DHT11 从主机发出启动信号到传感器返回 40 bit 数据,整个过程约 4ms。这意味着单片机在读数据期间不能被中断打断太久,否则时序会错。

资料包里给出的“程序”往往是某人用 51 单片机写的,移植到 STM32 时,必须把原来的延时函数重新标定,这是最容易翻车的地方。

2.3 常用湿敏传感器参数对比,按项目场景选型

以下是我在设计湿度采集节点时常用的选型依据,直接拿来做方案对比:

型号输出方式湿度量程典型精度通信协议适用场景
DHT11数字单总线20%~90%RH±5%RH单总线,40 bit 数据低成本环境监测、课程设计
DHT22数字单总线0%~100%RH±2%RH单总线,16 bit 湿度数据对精度有一定要求的记录仪
SHT30数字 I2C0%~100%RH±2%RHI2C,带 CRC 校验产品原型、高可靠采集
HR202模拟电阻20%~95%RH依赖校准分压/振荡电路成本敏感、批量标定能力强的方案

选型时还要看一个容易忽略的点:采样频率。DHT11 的数据手册建议两次读取间隔不小于 1 秒,室内环境监测没问题,但如果要做通风管道里的快速变化检测,就要换响应时间更短的 SHT30 或电容式传感器。程序里的轮询周期要和传感器规格匹配,不要为了界面刷新快就把 DHT11 读得很频繁。

3. 湿敏传感器原理图设计:接口电路与多页图纸规范

3.1 传感器接口电路,就四个引脚但上拉不能省

以 DHT11 模块为例,它的接口在原理图上通常只有 4 个网络:VDD、DATA、NC、GND。数据引脚 DATA 是漏极开路输出,所以模块上会有一颗 4.7k 到 10k 的上拉电阻到 VDD。如果你用的是裸传感器而不是模块,原理图上必须自己补这颗上拉电阻。

我一般这样设计传感器接口电路:VDD 接 3.3V 或 5V,取决于单片机的 IO 电平;DATA 接单片机的一个普通 GPIO,并加上拉电阻;NC 悬空;GND 与系统地单点连接。如果传感器和单片机距离超过 20cm,我会在 DATA 线上串一个 100 欧姆电阻,并在传感器端加 0.1uF 去耦电容,目的是抑制线缆上的高频干扰,这不会影响单总线通信。

原理图里还要注意电源滤波。湿敏传感器对供电纹波不敏感,但同一路电源上如果有继电器或电机,建议用磁珠把传感器电源单独隔开。否则每次继电器动作时,湿度读数就可能跳变。

3.2 从 STM32 最小系统板到湿敏传感器,引脚分配要避开下载口

很多资料包里的原理图是基于 STM32F103C8T6 最小系统板画的,因为这种板子便宜、资料多,而且可以直接用串口下载程序。传感器数据脚接哪个 GPIO,看似随意,实际要避开两个引脚:PA9 和 PA10 是串口下载和调试常用的引脚,PB2 是 BOOT1 引脚,复位期间电平会被采样,不适合接传感器。

我常用的引脚分配是:PB5 接 DHT11 DATA,PA0 接一个按键用于手动触发采集,PB8 和 PB9 预留为 I2C 接口以防后续想换 SHT30。这样分配的好处是,PB5 在最小系统板上引出了排针,方便杜邦线连接;同时它和 SPI1 的 SCK 功能复用,如果你不用 SPI,完全不用担心冲突。

3.2.1 原理图中传感器的符号和封装核对

从资料包里的原理图库拷贝传感器符号时,一定要核对封装。DHT11 裸传感器的封装是四脚直插或贴片,而市面上常见的 DHT11 模块是四针排母,占用的 PCB 面积完全不同。如果原理图上用的是模块符号,PCB 封装却画成裸传感器,打样回来就装不上。

核对方法是把原理图符号的 Pin 定义和实际器件的 Pin 排列对照。DHT11 的引脚顺序并不统一,有的模块脚位是 VDD、DATA、NC、GND,有的则是 GND、DATA、VDD 的顺序,必须以器件丝印为准。原理图里网络名正确不代表 PCB 上丝印位置正确。

3.3 多页原理图的页码和跨页连接,避免 DRC 报一堆错

稍微完整一点的湿敏传感器方案,原理图会分成两三页:电源页、单片机最小系统页、传感器接口页。这时候最容易出的问题是页码重复。有人从别的工程里复制页面,图内位置相同的 Title Block 还留着原来的页码,DRC 检查时就会提示页号重复。比如 OrCAD 里常见的检查提示,说有两个或以上页面把 Page Number 都设成了 1。

在 OrCAD Capture 里,把 Title Block 的页码属性改成系统变量,让它自动按页面顺序编号,而不是在每一页手动填数字。在 Altium Designer 里,则要确认图纸的 Sheet Number 没有冲突。跨页连接时,我习惯把传感器接口的 DATA 网络命名为 SENSOR_DATA,而不是简写为 DATA,这样在另一页图纸里搜索时不会和别的通信接口混淆。

3.3.1 AD 里检查原理图没连接好的方法

用 Altium Designer 检查跨页连接,最直接的方法是跑 Engineering Change Order 前的 DRC 检查,重点看 Unconnected Pin 和 Off-Sheet Connector 这两类报告。还有一个实用技巧:在原理图编辑器里按快捷键 Shift+S,可以在单层显示模式下看到同一网络的所有连线是否闭合。如果发现飞线没有连接,多半是 Net Label 拼写不一致,比如 SENSOR_DATA 和 SENSOR-DATA 在肉眼看来相同,但在网络表里是两个不同的网络。

4. 湿敏传感器程序实现:单总线时序的稳定读取

4.1 GPIO 初始化与延时基准,先用定时器不用 for 循环

程序的起点是初始化传感器引脚。下面代码以 STM32 标准外设库为例,用 PB5 作为 DHT11 数据引脚。

#include "stm32f10x.h" #define DHT11_PORT GPIOB #define DHT11_PIN GPIO_Pin_5 static void DHT11_GPIO_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); gpio.GPIO_Pin = DHT11_PIN; gpio.GPIO_Speed = GPIO_Speed_50MHz; gpio.GPIO_Mode = GPIO_Mode_Out_PP; // 先配置为推挽输出 GPIO_Init(DHT11_PORT, &gpio); GPIO_SetBits(DHT11_PORT, DHT11_PIN); // 空闲状态保持高电平 }

读取过程中引脚需要在输出和输入之间切换,所以再封装一个模式切换函数:

static void DHT11_SetMode(uint8_t output_en) { GPIO_InitTypeDef gpio; gpio.GPIO_Pin = DHT11_PIN; gpio.GPIO_Speed = GPIO_Speed_50MHz; if (output_en) { gpio.GPIO_Mode = GPIO_Mode_Out_PP; // 驱动总线,拉低产生启动信号 } else { gpio.GPIO_Mode = GPIO_Mode_IPU; // 释放总线,输入模式读取数据 } GPIO_Init(DHT11_PORT, &gpio); }

这里有个细节:如果模块上已有上拉电阻,读数据时最好配成 IPU(上拉输入)而不是浮空输入,避免在总线空闲时读到不稳定的电平。延时函数用定时器的微秒延时,最高优先级。软件延时不是不行,但要考虑编译器优化会把空循环优化掉,所以我不建议用 for 循环做微秒级延时。

4.1.1 用 DWT 实现微秒延时

Cortex-M3 内核的 DWT 模块可以直接使用,比 SysTick 更省事,也不占用定时器资源。

static volatile uint32_t us_tick; static void delay_init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } static void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; while (DWT->CYCCNT - start < us * 72) ; // 72MHz 主频下,1us 约 72 个周期 }

72 这个系数是主频决定的,如果你的 Stm32 跑在 64MHz,就要改成 64。换主频后忘记改延时系数,是程序读不出湿度的首要原因。

4.2 启动信号与应答检测,时序差 20 微秒都不行

DHT11 的通信过程分四段:主机拉低总线、主机拉高释放总线、传感器响应、传感器发送数据。主机拉低的时间必须大于 18ms,否则传感器不会应答。拉高后等待 20us 到 40us,传感器会把总线拉低一段时间表示应答,然后拉高 80us,再开始发送 40 个数据位。

static uint8_t DHT11_Start(void) { uint8_t retry = 0; DHT11_SetMode(1); // 切到输出模式 GPIO_ResetBits(DHT11_PORT, DHT11_PIN); // 拉低总线 delay_us(20000); // 持续 20ms,超过最低要求 GPIO_SetBits(DHT11_PORT, DHT11_PIN); // 拉高总线 delay_us(30); // 等待传感器响应 DHT11_SetMode(0); // 释放总线,改输入模式 // 等待低电平应答,加超时保护 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET) { if (++retry > 100) return 0; // 无应答,返回失败 } return 1; }

拉低时间我写 20000us,也就是 20ms,而不是 18ms,因为留出余量应对不同批次的传感器。拉高后到切输入模式之间的 30us 是整个时序里最敏感的部分,如果这段时间被中断打断,传感器已经发出的应答信号就会被错过。所以读取流程要放在临界区内,或者用关中断的方式执行完整帧读取。

4.3 湿敏传感器数据位的读取:40us 处采样判定 0 和 1

DHT11 传输 40 bit 数据,每一位都以低电平开始,然后是高电平。高电平持续 26us 到 28us 表示 0,持续 70us 表示 1。所以主机在低电平结束后等待 40us 再采样电平,就能稳定区分两种数值。

static uint8_t DHT11_ReadByte(void) { uint8_t byte = 0; uint8_t i; for (i = 0; i < 8; i++) { uint32_t timeout = 0; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == RESET) { if (++timeout > 10000) return 0xFF; // 防止死循环 } delay_us(40); // 等待超过 28us,进入稳定电平区域 if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET) { byte |= (0x80 >> i); // 高电平持续时为 1 } timeout = 0; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET) { if (++timeout > 10000) return 0xFF; } } return byte; }

40us 采样点不是随便定的。数据 0 的高电平持续 28us,数据 1 的高电平持续 70us,在 40us 这个时刻,数据 0 已经结束高电平,而数据 1 还在高电平中,因此能可靠区分。采样点太晚,数据 0 所在的位周期可能出现误判;采样点太早,传感器还在输出高电平上升沿附近,稳定性差。

完整读一帧的代码组织如下:

static int DHT11_ReadData(uint8_t *humi, uint8_t *temp) { uint8_t buf[5] = {0}; uint8_t i; if (!DHT11_Start()) return -1; // 等待传感器应答后的低电平结束 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == RESET); while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) == SET); for (i = 0; i < 5; i++) { buf[i] = DHT11_ReadByte(); } // 校验:前四个字节相加取低 8 位,应等于第五字节 if ((uint8_t)(buf[0] + buf[1] + buf[2] + buf[3]) != buf[4]) { return -2; // 校验失败 } *humi = buf[0]; // 湿度整数部分 *temp = buf[2]; // 温度整数部分 return 0; }

4.4 湿敏传感器的读取频率,必须给传感器留出恢复时间

DHT11 数据手册给出的最小读取周期是 1 秒。这个参数不能随便压缩,原因是传感器内部测量湿度需要时间,连续快速读取会让数据引脚上的电平变化过快,传感器内部状态机来不及复位。程序里可以用一个简单的状态机,在每秒钟的定时器中断里设置一个标志位,主循环检测到标志后执行一次读取。

我也见过有人在 200ms 周期里读 DHT11,读出来的数据前几十次都正常,后面就固定成 0x00,重新上电后又恢复。这不是芯片坏了,而是违反了读取间隔要求。如果应用确实需要更快的采样率,换用 I2C 接口的 SHT30 是更合理的选择,它的读取周期可以做到 10ms 级别。

5. 湿敏传感器调试:串口读数与常见故障定位

5.1 用串口把湿度和温度值打印出来,先确认通信正常

程序写完后第一步,不是看 OLED 显示,而是用串口把裸数据发到电脑上观察。串口打印能区分问题是出在传感器还是出在后面的数据处理逻辑上。把 USART1 重定向到 printf,然后每秒打印一次原始数据:

printf("DHT11: humi=%d.%d%% temp=%d.%dC ret=%d\r\n", humi_int, humi_dec, temp_int, temp_dec, ret);

这里的 humi_dec 是湿度小数部分,对应 buf[1];temp_dec 对应 buf[3]。DHT11 官方规格里,湿度整数部分直接是相对湿度百分比,小数部分在大多数资料包程序中会被忽略,因为精度只有 1%RH。但打印出来能帮你判断校验是否正常:如果小数部分持续打印 0x00,而且整数部分稳定,说明传感器本身工作正常,只是程序没有处理小数位。

5.2 读数是固定 0x00 或 0xFF,从这三个位置查

  • 传感器供电没有达到工作电压。DHT11 最低工作电压有的批次是 3.3V,有的标称 5V,你用 3.3V 单片机板载稳压给传感器供电,在传感器启动瞬间压降可能低于阈值。用万用表在读取瞬间量一下传感器 VDD 引脚,低于 3.0V 就要换电源路径。
  • 数据线没有正确上拉。如果用的是裸传感器而不是模块,原理图上漏画上拉电阻,数据引脚在输入模式下就是浮空状态,读进来的是随机电平,表现出来就是 0xFF。
  • 引脚模式切换没有生效。有的程序把 GPIO 配置成开漏输出,但忘了在外部加上拉电阻,拉高时电平爬升慢,传感器应答信号跟不上。

排查方法是在 DHT11_Start() 返回后,用示波器或者逻辑分析仪抓 DATA 引脚的波形。正常波形应该看到主机拉低 20ms、拉高 30us,然后是传感器拉低 80us 再拉高 80us。只要这段波形不正常,问题就在主机侧或上拉电阻。

5.3 湿敏传感器读数和实际环境偏差大,分析这三点

第一,传感器安装位置太靠近发热元件。PC 板上 MCU 或电源芯片的温升会让传感器周围局部温度升高,湿度读数随之偏低。设计 PCB 时把传感器放在板边,远离发热区,开窗露出外壳。第二,焊接时间过长。湿敏元件里的感湿膜受热后特性会漂移,手工焊接时每个引脚不要超过 2 秒。第三,传感器表面被手摸过或者有助焊剂残留,会直接影响感湿响应,用无水酒精擦拭后晾干再校准。

还有一种情况是读数整体偏移固定值,比如实际湿度 55%RH,传感器一直读 50%RH。这类偏差如果在产品阶段出现,可以做一个两点的湿度校准:在 33%RH 和 75%RH 的饱和盐溶液中放置 30 分钟,分别记下读数,然后在程序里做线性矫正。

6. 湿敏传感器方案落地:自动除湿控制与采样周期设计

6.1 阈值判断加上回差,避免继电器频繁动作

读完湿度数据后的典型应用是控制除湿设备,比如继电器驱动风扇或加热器。设计方案的思路并不复杂,但直接比较会导致一个问题:当湿度在阈值附近波动时,继电器会以秒级频率通断,严重影响寿命。解决办法是引入回差控制,这是工业控制里最常见的做法。

#define HUMI_SET 60.0f // 目标湿度阈值 #define HUMI_HYST 5.0f // 回差 5%RH static float humidity = 0.0f; static uint8_t relay_state = 0; if (!relay_state && humidity > HUMI_SET) { relay_state = 1; // 超过阈值,启动除湿 GPIO_SetBits(RELAY_PORT, RELAY_PIN); } else if (relay_state && humidity < (HUMI_SET - HUMI_HYST)) { relay_state = 0; // 降到阈值以下且回差足够,停止除湿 GPIO_ResetBits(RELAY_PORT, RELAY_PIN); }

回差的意义在于:启动条件是湿度高于 60%RH,停止条件是湿度低于 55%RH,中间 5%RH 的区间内继电器保持原有状态。这样即使湿度在 59% 到 61% 之间波动,继电器也不会来回翻转。回差参数的大小取决于被控设备的启停成本和环境的湿度波动幅度,实验室环境取 3%RH 到 5%RH,大型仓库可以放到 10%RH。

6.2 采样和控制的节奏,按传感器的极限来编排

控制周期要和 DHT11 的读取间隔匹配。程序结构可以这样安排:定时器每 500ms 产生一次中断,在中断里计数到 2 时置位采集标志,主循环检测到标志后执行一次 DHT11 读取、滤波、阈值判断和继电器控制。整个控制周期是 1 秒,刚好满足 DHT11 的最小读取间隔。

滤波方面,我不建议对这种低频采集做滑动平均滤波,因为会引入额外的相位延迟,导致除湿设备启停滞后。更有效的方法是做异常值剔除:连续读 5 次,去掉最大值和最小值后取平均,或者只保留校验通过的数据,校验失败的这次采集直接丢弃并计数,连续失败 10 次才上报传感器故障。这样系统在传感器偶发性错误时仍然能保持稳定。

最后留一个实用技巧:如果你的应用对湿度准确性没有严格要求,DHT11 读数不一定要做多点校准,但要在程序里固定采样周期,并且把传感器放在空气流通的位置。很多湿敏传感器方案最后“测不准”,并不是芯片精度不够,而是安装位置挡住了气流。把这两个点处理好,这套方案的可靠性能达到量产水平。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询