
在实际嵌入式开发中温湿度传感器是环境监测类项目最基础也最常用的组件之一。DHT11 作为入门级数字温湿度传感器因其成本低、接口简单、资料丰富常被用于教学演示和原型验证。但新手在连接 DHT11 时最容易卡在时序控制、数据校验和通信稳定性上导致读出的数据全是 0 或明显异常。本文将围绕 STM32F1 系列单片机以野火指南者开发板为例驱动 DHT11 传感器的完整流程从传感器工作原理、硬件连接、时序解析到代码实现、数据校验和常见问题排查带你完成一个可稳定运行的室内温湿度查询项目。学完后你不仅能掌握 DHT11 的驱动方法还能理解单总线协议的关键要点为后续使用 DS18B20、DHT22 等单总线设备打下基础。1. 理解 DHT11 的工作机制和单总线协议1.1 DHT11 传感器是什么DHT11 是一款复合型温湿度传感器内部集成了湿度传感元件、NTC 温度测量元件和一个 8 位单片机对外提供校准后的数字信号输出。其温度测量范围 0~50°C精度 ±2°C湿度测量范围 20%~90%RH精度 ±5%RH。虽然精度一般但胜在价格低廉、接口简单适合对精度要求不高的室内环境监测场景。1.2 单总线协议如何工作DHT11 采用单总线1-Wire协议进行通信即只用一根数据线完成双向数据传输。这根数据线平时由上拉电阻拉到高电平主机MCU和从机DHT11通过拉低或释放总线来传递信号。单总线通信的核心是严格的时序要求。DHT11 的通信流程分为三步主机发起起始信号主机拉低总线至少 18ms 后释放等待 DHT11 响应。DHT11 响应信号DHT11 拉低总线 80μs 后拉高 80μs表示准备发送数据。数据传输DHT11 依次发送 40 位数据湿度整数、湿度小数、温度整数、温度小数、校验和每位数据以 50μs 的低电平起始高电平的持续时间区分 026~28μs和 170μs。注意DHT11 的小数部分实际总是 0但协议保留了该字段。校验和是前 4 个字节之和的低 8 位用于验证数据完整性。1.3 为什么时序控制如此关键由于单总线没有时钟线双方依赖时间间隔来识别比特流。如果 MCU 的延时精度不够或中断打断了关键时序就会导致数据错位。因此驱动 DHT11 时必须关闭全局中断或使用高精度定时器来保证时序严格性。2. 准备硬件环境和软件依赖2.1 所需硬件清单组件规格/型号备注主控MCUSTM32F103C8T6野火指南者其他 STM32F1 系列也可温湿度传感器DHT11注意是 3.3V 版本杜邦线母对母3 根开发板供电USB 转 TTL 或 ST-Link保证 3.3V 稳定输出上拉电阻4.7kΩ 或 5.1kΩ开发板可能已集成2.2 软件工具和库版本工具/库版本用途Keil MDK5.30代码编写和编译STM32CubeMX6.5.0引脚配置和代码生成STM32F1 HAL 库1.8.4硬件抽象层驱动串口调试助手任意查看输出数据2.3 硬件连接方式DHT11 一般有 3 个引脚4 引脚版本中 NC 悬空VCC接 3.3V注意不要接 5V除非传感器支持且 MCU 耐受 5V 输入GND接 GNDDATA接 MCU 的 GPIO 引脚如 PA0并通过 4.7kΩ 电阻上拉到 VCC在野火指南者开发板上可选择 PA0 作为数据引脚因其方便连接且不易与其他外设冲突。3. 使用 STM32CubeMX 配置工程3.1 创建新工程并选择 MCU打开 STM32CubeMX点击 New Project在 Part Number 搜索框中输入 STM32F103C8选择对应的 MCU 型号。确认引脚数为 48Flash 为 64KB然后点击 Start Project。3.2 配置系统时钟在 Pinout Configuration 标签页的 System Core 中点击 RCC将 High Speed Clock (HSE) 设置为 Crystal/Ceramic Resonator。这样外部 8MHz 晶振将被启用经 PLL 倍频后系统时钟可达 72MHz。3.3 配置 GPIO 引脚在左侧引脚图中找到 PA0点击选择 GPIO_Output。然后在右侧 GPIO 设置中GPIO output level: HighGPIO mode: Output Push PullGPIO Pull-up/Pull-down: No pull-up and no pull-downMaximum output speed: Low注意初始设置为输出模式是因为主机需要主动拉低总线发起通信。实际通信过程中会动态切换输入输出模式。3.4 配置串口用于调试输出找到 USART1PA9 为 TXPA10 为 RX点击选择 Asynchronous。在右侧配置中Baud Rate: 115200Word Length: 8 BitsParity: NoneStop Bits: 1Over Sampling: 16 Samples3.5 生成工程代码点击 Project Manager 标签页设置项目名称和存储路径在 Toolchain / IDE 中选择 MDK-ARM V5。在 Code Generator 中勾选 Generate peripheral initialization as a pair of .c/.h files per peripheral。最后点击 Generate CodeSTM32CubeMX 将生成完整的 Keil 工程文件。4. 编写 DHT11 驱动代码4.1 创建 DHT11 头文件在 Inc 文件夹下创建dht11.h定义数据类型和函数接口#ifndef __DHT11_H #define __DHT11_H #include main.h #include stdint.h // DHT11 数据类型定义 typedef struct { uint8_t humidity_int; // 湿度整数部分 uint8_t humidity_frac; // 湿度小数部分DHT11 始终为 0 uint8_t temp_int; // 温度整数部分 uint8_t temp_frac; // 温度小数部分DHT11 始终为 0 uint8_t checksum; // 校验和 } DHT11_Data; // 函数声明 void DHT11_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint8_t DHT11_Read(DHT11_Data* data); void DHT11_Delay_us(uint16_t us); #endif4.2 实现微秒级延时函数由于 DHT11 的时序要求精确到微秒级别而 HAL 库的HAL_Delay()最小单位是毫秒需要实现一个微秒级延时函数。在dht11.c中添加#include dht11.h // 基于 SysTick 的微秒延时函数 void DHT11_Delay_us(uint16_t us) { uint32_t ticks us * (SystemCoreClock / 1000000) / 8; uint32_t start_tick SysTick-VAL; uint32_t target_tick start_tick - ticks; if (target_tick start_tick) { // 处理计数器下溢 while (SysTick-VAL start_tick || SysTick-VAL target_tick); } else { while (SysTick-VAL target_tick SysTick-VAL start_tick); } }4.3 实现 DHT11 通信核心逻辑在dht11.c中继续添加通信函数static GPIO_TypeDef* DHT11_GPIO; static uint16_t DHT11_PIN; // 初始化 DHT11 引脚 void DHT11_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { DHT11_GPIO GPIOx; DHT11_PIN GPIO_Pin; } // 设置引脚为输出模式 static void DHT11_Set_Output(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(DHT11_GPIO, GPIO_InitStruct); } // 设置引脚为输入模式 static void DHT11_Set_Input(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(DHT11_GPIO, GPIO_InitStruct); } // 读取一个比特 static uint8_t DHT11_Read_Bit(void) { uint8_t result 0; // 等待 50μs 低电平开始信号结束 while (HAL_GPIO_ReadPin(DHT11_GPIO, DHT11_PIN) GPIO_PIN_RESET); // 延时 40μs 后检测高电平持续时间 DHT11_Delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO, DHT11_PIN) GPIO_PIN_SET) { result 1; // 等待高电平结束 while (HAL_GPIO_ReadPin(DHT11_GPIO, DHT11_PIN) GPIO_PIN_SET); } return result; } // 读取一个字节8 个比特 static uint8_t DHT11_Read_Byte(void) { uint8_t byte 0; for (int i 0; i 8; i) { byte 1; byte | DHT11_Read_Bit(); } return byte; } // 读取完整的 DHT11 数据 uint8_t DHT11_Read(DHT11_Data* data) { if (data NULL) return 0; // 禁用中断保证时序严格性 __disable_irq(); // 主机发起起始信号 DHT11_Set_Output(); HAL_GPIO_WritePin(DHT11_GPIO, DHT11_PIN, GPIO_PIN_RESET); DHT11_Delay_us(18000); // 拉低至少 18ms HAL_GPIO_WritePin(DHT11_GPIO, DHT11_PIN, GPIO_PIN_SET); DHT11_Delay_us(30); // 主机释放总线 // 切换为输入模式等待 DHT11 响应 DHT11_Set_Input(); // 等待 DHT11 拉低响应信号 uint32_t timeout 10000; // 超时计数器 while (HAL_GPIO_ReadPin(DHT11_GPIO, DHT11_PIN) GPIO_PIN_SET) { if (timeout-- 0) { __enable_irq(); return 0; // 响应超时 } DHT11_Delay_us(1); } // 等待 80μs 低电平结束 timeout 10000; while (HAL_GPIO_ReadPin(DHT11_GPIO, DHT11_PIN) GPIO_PIN_RESET) { if (timeout-- 0) { __enable_irq(); return 0; // 低电平超时 } DHT11_Delay_us(1); } // 等待 80μs 高电平结束 timeout 10000; while (HAL_GPIO_ReadPin(DHT11_GPIO, DHT11_PIN) GPIO_PIN_SET) { if (timeout-- 0) { __enable_irq(); return 0; // 高电平超时 } DHT11_Delay_us(1); } // 读取 40 位数据 >#include dht11.h #include stdio.h // 重定向 printf 到串口 #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; } DHT11_Data dht11_data; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 初始化 DHT11连接到 PA0 DHT11_Init(GPIOA, GPIO_PIN_0); printf(DHT11 Temperature Humidity Sensor Test\r\n); while (1) { if (DHT11_Read(dht11_data)) { printf(Humidity: %d.%d%% Temperature: %d.%d°C\r\n, dht11_data.humidity_int, dht11_data.humidity_frac, dht11_data.temp_int, dht11_data.temp_frac); } else { printf(DHT11 read failed!\r\n); } HAL_Delay(2000); // 每 2 秒读取一次 } }5.2 编译和下载在 Keil 中点击 BuildF7编译工程确认无错误后连接 ST-Link点击 DownloadF8将程序烧录到开发板。5.3 运行和验证打开串口调试助手设置波特率为 115200数据位 8停止位 1无校验位。给开发板上电后应该能看到类似以下输出DHT11 Temperature Humidity Sensor Test Humidity: 45.0% Temperature: 25.0°C Humidity: 45.0% Temperature: 25.0°C用手触摸 DHT11 传感器几分钟后应该能看到温度值缓慢上升证明传感器工作正常。6. 常见问题排查和解决方案6.1 数据读取失败或全为 0现象可能原因检查方式解决方案始终读取失败接线错误或接触不良用万用表检查 VCC、GND、DATA 电平重新插拔杜邦线确认 3.3V 供电稳定数据全为 0时序控制不精确检查延时函数精度用逻辑分析仪抓取波形优化微秒延时函数关闭全局中断偶尔读取失败响应超时增加超时判断和重试机制在读取函数中加入重试逻辑最多尝试 3 次6.2 数据明显异常如湿度 99%现象可能原因检查方式解决方案湿度值固定为 99%传感器损坏或数据引脚短路检查 DATA 引脚是否对 VCC 短路更换传感器检查 PCB 焊接温度值明显偏高/偏低传感器靠近热源或处于通风口将传感器移至标准环境测试避免阳光直射、空调出风口等位置数据跳变剧烈电源噪声或电磁干扰在 VCC 和 GND 之间加 100nF 电容增加电源去耦电容缩短接线长度6.3 通信稳定性优化在实际项目中可以增加以下稳定性措施// 带重试的读取函数 uint8_t DHT11_Read_With_Retry(DHT11_Data* data, uint8_t max_retry) { for (int i 0; i max_retry; i) { if (DHT11_Read(data)) { return 1; // 读取成功 } HAL_Delay(100); // 失败后延时 100ms 再重试 } return 0; // 所有重试都失败 }7. 生产环境注意事项和扩展方向7.1 从学习环境到生产环境的改进学习环境中的代码侧重于功能验证生产环境还需要考虑错误处理机制不是简单打印失败信息而要记录错误日志或触发告警。数据滤波算法连续读取 5 次数据去掉最大最小值后取平均避免偶发误差。低功耗设计电池供电场景下读取间隔可延长至 1-5 分钟其余时间 MCU 进入睡眠模式。看门狗保护防止程序卡死在传感器读取循环中。7.2 传感器选型建议当 DHT11 的精度无法满足需求时可以考虑传感器温度精度湿度精度接口适用场景DHT22±0.5°C±2%RH单总线一般室内监测AHT20±0.3°C±2%RHI2C智能家居设备SHT4x±0.2°C±1.8%RHI2C高精度环境监测7.3 项目扩展思路基于 DHT11 的温湿度读取能力可以扩展为智能温湿度计增加 OLED 显示屏实时显示数值和变化趋势。环境监测节点结合 ESP8266/ESP32将数据上传到云平台。智能通风系统当湿度超过阈值时自动控制风扇或除湿器。数据记录仪添加 SD 卡模块长期记录环境数据。驱动 DHT11 的关键在于理解单总线协议的时序要求并在代码中精确控制时间间隔。实际项目中建议先用逻辑分析仪验证通信波形确认时序正确后再进行软件调试。对于需要更高精度的场景I2C 接口的 AHT20、SHT4x 等传感器是更好的选择它们虽然价格稍高但通信更稳定、数据更可靠。