STM32 HAL库驱动DHT11:从时序解析到代码实现的温湿度采集实战
📅 2026/7/15 11:13:56
👁️ 阅读次数
📝 编程学习
1. DHT11温湿度传感器基础认知
第一次接触DHT11时,我误以为它和普通模拟传感器一样简单,结果被它的单总线协议狠狠教育了一番。这个火柴盒大小的传感器内部其实藏着两个关键元件:电容式湿度传感器和NTC热敏电阻。前者通过薄膜吸湿改变电容值来检测湿度,后者则利用电阻随温度变化的特性测量温度。
DHT11最特别的是它的数据输出方式——单总线协议。这意味着你只需要一根数据线(加上电源和地线)就能同时获取温度和湿度数据。不过别被它的简单接线迷惑,这个协议对时序的要求极其严格,误差超过几微秒就可能导致读取失败。实测中发现,当环境温度在0-50℃之间时,湿度测量误差±5%,温度误差±2℃,对于大多数室内监测场景完全够用。
2. 硬件连接与CubeMX配置
2.1 硬件连接要点
我的开发板是STM32F103C8T6,连接DHT11时踩过三个坑:
- 上拉电阻:数据线必须接4.7K-10K上拉电阻(开发板自带的可直接使用)
- 供电滤波:VCC与GND之间最好并联100nF电容,防止电源干扰
- 线长限制:当连接线超过20米时,需要减小上拉电阻阻值
典型连接方式:
DHT11 STM32 VCC -> 3.3V DATA -> PA0(任意GPIO) GND -> GND2.2 CubeMX关键配置
在CubeMX中需要配置两个关键部分:
- GPIO设置:选择推挽输出模式(初始配置,实际使用中会动态切换)
- 定时器配置:选择32位定时器(如TIM2),预分频设为(系统时钟频率-1)
以72MHz系统时钟为例:
- 预分频值设为71,得到1MHz计数频率(1us/计数)
- 自动重装载值设为最大值0xFFFFFFFF
- 启用定时器时钟
小技巧:使用32位定时器可以避免频繁溢出中断,简化微秒级延时实现
3. 单总线协议深度解析
3.1 通信时序拆解
DHT11的通信就像一场精心编排的交谊舞,主机和从机必须严格遵循以下步骤:
起始信号(主机发起)
- 拉低数据线≥18ms(实测20ms最可靠)
- 释放总线(变高电平)20-40us
- 切换到输入模式等待响应
响应信号(DHT11回应)
- 传感器拉低80us
- 再拉高80us
- 这个阶段常见问题是响应超时,通常由接线错误引起
数据传输(40bit连续发送)
- 每bit以50us低电平开始
- 26-28us高电平表示"0"
- 70us高电平表示"1"
- 完整数据格式:湿度整数(8bit)+湿度小数(8bit)+温度整数(8bit)+温度小数(8bit)+校验和(8bit)
3.2 时序异常处理
在实际项目中,我总结了三种常见异常情况:
- 无响应:检查电源、上拉电阻和起始信号时长
- 校验错误:适当增加两次读取间隔(建议≥1s)
- 数据漂移:避免在传感器附近快速吹气或触摸
4. HAL库驱动实现
4.1 微秒级延时优化
HAL库的HAL_Delay()最小只能到毫秒,我们需要用定时器实现微秒延时:
void delay_us(uint32_t us) { __HAL_TIM_SET_COUNTER(&htim2, 0); HAL_TIM_Base_Start(&htim2); while(__HAL_TIM_GET_COUNTER(&htim2) < us); HAL_TIM_Base_Stop(&htim2); }4.2 核心驱动代码
创建DHT11.h头文件:
typedef struct { uint8_t humidity; uint8_t temperature; } DHT11_Data; uint8_t DHT11_Read(DHT11_Data *data);DHT11.c关键函数实现:
uint8_t DHT11_Read(DHT11_Data *data) { uint8_t bytes[5] = {0}; uint32_t timeout = 0; // 发送起始信号 HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET); delay_us(30); // 检测响应 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct); // 等待低电平响应 while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && ++timeout < 100); if(timeout >= 100) return 0; // 读取40bit数据 for(uint8_t i=0; i<40; i++) { timeout = 0; while(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && ++timeout < 100); delay_us(40); bytes[i/8] <<= 1; if(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin)) { bytes[i/8] |= 1; while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin)); } } // 校验数据 if(bytes[4] == (bytes[0]+bytes[1]+bytes[2]+bytes[3])) { >int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); MX_USART1_UART_Init(); DHT11_Data sensor_data; char msg[50]; while(1) { if(DHT11_Read(&sensor_data)) { sprintf(msg, "Temp: %dC, Humi: %d%%\r\n", sensor_data.temperature, sensor_data.humidity); HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY); } else { HAL_UART_Transmit(&huart1, (uint8_t*)"Read failed!\r\n", 13, HAL_MAX_DELAY); } HAL_Delay(2000); } }7. 常见问题排查指南
遇到问题时,可以按照这个流程排查:
检查硬件连接
- 确认电源电压3.3-5V
- 测量DATA线空闲时为高电平
- 检查上拉电阻是否正常
验证时序
- 用逻辑分析仪捕获通信波形
- 检查起始信号是否≥18ms
- 确认响应信号80us低+80us高
代码调试
- 在关键节点插入调试输出
- 检查延时函数精度(用示波器验证)
- 确认GPIO模式切换正确
记得第一次成功读取数据时,我盯着串口输出的温湿度值看了足足五分钟——这种把物理信号转化为数字信息的成就感,正是嵌入式开发的魅力所在。虽然DHT11不是最精确的传感器,但它教会了我如何与单总线设备对话,这个经验在后续开发DS18B20等其他传感器时派上了大用场。
编程学习
技术分享
实战经验