STM32 HAL库驱动DHT11:从时序解析到代码实现的温湿度采集实战
2026/7/15 11:06:40 网站建设 项目流程

1. DHT11温湿度传感器基础认知

第一次接触DHT11时,我误以为它和普通模拟传感器一样简单,结果被它的单总线协议狠狠教育了一番。这个火柴盒大小的传感器内部其实藏着两个关键元件:电容式湿度传感器NTC热敏电阻。前者通过薄膜吸湿改变电容值来检测湿度,后者则利用电阻随温度变化的特性测量温度。

DHT11最特别的是它的数据输出方式——单总线协议。这意味着你只需要一根数据线(加上电源和地线)就能同时获取温度和湿度数据。不过别被它的简单接线迷惑,这个协议对时序的要求极其严格,误差超过几微秒就可能导致读取失败。实测中发现,当环境温度在0-50℃之间时,湿度测量误差±5%,温度误差±2℃,对于大多数室内监测场景完全够用。

2. 硬件连接与CubeMX配置

2.1 硬件连接要点

我的开发板是STM32F103C8T6,连接DHT11时踩过三个坑:

  1. 上拉电阻:数据线必须接4.7K-10K上拉电阻(开发板自带的可直接使用)
  2. 供电滤波:VCC与GND之间最好并联100nF电容,防止电源干扰
  3. 线长限制:当连接线超过20米时,需要减小上拉电阻阻值

典型连接方式:

DHT11 STM32 VCC -> 3.3V DATA -> PA0(任意GPIO) GND -> GND

2.2 CubeMX关键配置

在CubeMX中需要配置两个关键部分:

  1. GPIO设置:选择推挽输出模式(初始配置,实际使用中会动态切换)
  2. 定时器配置:选择32位定时器(如TIM2),预分频设为(系统时钟频率-1)

以72MHz系统时钟为例:

  • 预分频值设为71,得到1MHz计数频率(1us/计数)
  • 自动重装载值设为最大值0xFFFFFFFF
  • 启用定时器时钟

小技巧:使用32位定时器可以避免频繁溢出中断,简化微秒级延时实现

3. 单总线协议深度解析

3.1 通信时序拆解

DHT11的通信就像一场精心编排的交谊舞,主机和从机必须严格遵循以下步骤:

  1. 起始信号(主机发起)

    • 拉低数据线≥18ms(实测20ms最可靠)
    • 释放总线(变高电平)20-40us
    • 切换到输入模式等待响应
  2. 响应信号(DHT11回应)

    • 传感器拉低80us
    • 再拉高80us
    • 这个阶段常见问题是响应超时,通常由接线错误引起
  3. 数据传输(40bit连续发送)

    • 每bit以50us低电平开始
    • 26-28us高电平表示"0"
    • 70us高电平表示"1"
    • 完整数据格式:湿度整数(8bit)+湿度小数(8bit)+温度整数(8bit)+温度小数(8bit)+校验和(8bit)

3.2 时序异常处理

在实际项目中,我总结了三种常见异常情况:

  1. 无响应:检查电源、上拉电阻和起始信号时长
  2. 校验错误:适当增加两次读取间隔(建议≥1s)
  3. 数据漂移:避免在传感器附近快速吹气或触摸

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. 常见问题排查指南

遇到问题时,可以按照这个流程排查:

  1. 检查硬件连接

    • 确认电源电压3.3-5V
    • 测量DATA线空闲时为高电平
    • 检查上拉电阻是否正常
  2. 验证时序

    • 用逻辑分析仪捕获通信波形
    • 检查起始信号是否≥18ms
    • 确认响应信号80us低+80us高
  3. 代码调试

    • 在关键节点插入调试输出
    • 检查延时函数精度(用示波器验证)
    • 确认GPIO模式切换正确

记得第一次成功读取数据时,我盯着串口输出的温湿度值看了足足五分钟——这种把物理信号转化为数字信息的成就感,正是嵌入式开发的魅力所在。虽然DHT11不是最精确的传感器,但它教会了我如何与单总线设备对话,这个经验在后续开发DS18B20等其他传感器时派上了大用场。

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

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

立即咨询