别再混用了!手把手教你用STM32CubeMX搞定DHT11和DHT22(附代码避坑)
STM32CubeMX实战DHT11与DHT22传感器的高效驱动开发在嵌入式开发中温湿度传感器的应用场景非常广泛。DHT11和DHT22作为两种常见的数字温湿度传感器虽然都采用单总线协议但在实际使用中存在不少差异。本文将带你使用STM32CubeMX和HAL库从零开始构建一个兼容这两种传感器的驱动方案。1. 传感器选型与CubeMX工程准备DHT11和DHT22虽然外观相似但性能参数和通信细节有显著区别。我们先通过一个对比表格了解它们的关键差异参数DHT11DHT22温度范围0-50°C (±2°C)-40-80°C (±0.5°C)湿度范围20-90%RH (±5%)0-100%RH (±2%)分辨率1°C / 1%RH0.1°C / 0.1%RH采样周期≥1秒≥2秒供电电压3-5.5V3-6V起始信号要求拉低≥18ms拉低≥1ms在STM32CubeMX中创建工程时需要注意以下配置要点选择正确的STM32系列和具体型号配置系统时钟建议使用外部晶振为传感器数据线配置GPIO模式初始化为输出模式输出类型推挽输出上拉/下拉无速度建议选择High以提高时序精度// CubeMX生成的GPIO初始化代码示例 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT_PORT, GPIO_InitStruct);2. 精准时序控制与HAL库适配单总线协议对时序要求严格而HAL库的HAL_Delay()函数基于SysTick实现最小精度通常为1ms。对于DHT22需要的微秒级延时我们需要实现更精确的延时函数。2.1 微秒级延时实现基于SysTick的微秒延时实现void DHT_DelayUs(uint16_t us) { uint32_t start DWT-CYCCNT; uint32_t clock SystemCoreClock / 1000000; uint32_t cycles us * clock; while((DWT-CYCCNT - start) cycles); }使用前需要初始化DWT计数器void DHT_DelayInit(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; }2.2 起始信号生成DHT11和DHT22的起始信号要求不同我们需要实现兼容的发送函数void DHT_StartSignal(DHT_Type type) { // 设置为输出模式 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT_PORT, GPIO_InitStruct); // 拉低总线 HAL_GPIO_WritePin(DHT_PORT, DHT_PIN, GPIO_PIN_RESET); // 根据传感器类型选择延时时间 if(type DHT11) { HAL_Delay(18); // DHT11需要18ms } else { DHT_DelayUs(1500); // DHT22需要1-2ms } // 释放总线 HAL_GPIO_WritePin(DHT_PORT, DHT_PIN, GPIO_PIN_SET); DHT_DelayUs(30); // 等待30us }3. 数据读取与处理优化传感器返回40位数据包含湿度、温度及校验和。读取时需要特别注意时序和数据处理方式。3.1 数据位读取实现uint8_t DHT_ReadByte(void) { uint8_t data 0; // 切换为输入模式 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(DHT_PORT, GPIO_InitStruct); for(int i0; i8; i) { // 等待低电平开始 while(HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN) GPIO_PIN_RESET); // 测量高电平持续时间 uint32_t start DWT-CYCCNT; while(HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN) GPIO_PIN_SET); uint32_t duration DWT-CYCCNT - start; // 判断是0还是1 data 1; if(duration 40 * (SystemCoreClock / 1000000)) { data | 1; } } return data; }3.2 数据解析与校验DHT11和DHT22的数据格式不同需要分别处理typedef struct { uint8_t humi_int; uint8_t humi_deci; uint8_t temp_int; uint8_t temp_deci; uint8_t check_sum; } DHT_Data; int DHT_ReadData(DHT_Type type, DHT_Data *data) { DHT_StartSignal(type); // 检查传感器响应 if(HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN) GPIO_PIN_SET) { return DHT_ERROR_NO_RESPONSE; } // 读取5字节数据 >// dht_driver.h #ifndef DHT_DRIVER_H #define DHT_DRIVER_H #include stm32f1xx_hal.h typedef enum { DHT11, DHT22 } DHT_Type; typedef enum { DHT_OK, DHT_ERROR_NO_RESPONSE, DHT_ERROR_CHECKSUM, DHT_ERROR_TIMEOUT } DHT_Status; typedef struct { uint8_t humi_int; uint8_t humi_deci; uint8_t temp_int; uint8_t temp_deci; uint8_t check_sum; } DHT_Data; void DHT_Init(GPIO_TypeDef *port, uint16_t pin); DHT_Status DHT_Read(DHT_Type type, DHT_Data *data); #endif4.2 使用示例#include dht_driver.h // 定义传感器连接的GPIO #define DHT_PORT GPIOA #define DHT_PIN GPIO_PIN_1 int main(void) { HAL_Init(); SystemClock_Config(); DHT_Init(DHT_PORT, DHT_PIN); DHT_Data data; while(1) { if(DHT_Read(DHT22, data) DHT_OK) { printf(Temperature: %d.%d°C, Humidity: %d.%d%%\n, data.temp_int, data.temp_deci, data.humi_int, data.humi_deci); } HAL_Delay(2000); // DHT22需要至少2秒间隔 } }4.3 常见问题排查在实际项目中可能会遇到以下问题读取失败或数据错误检查GPIO配置是否正确特别是输出速度和上下拉设置确保供电稳定必要时增加滤波电容验证延时函数的准确性DHT22偶尔读取失败将起始信号的低电平时间从1ms增加到2ms确保两次读取间隔大于2秒HAL_Delay精度不足对于DHT1118ms延时可以使用HAL_Delay对于DHT22的微秒级延时必须使用基于DWT的精确延时通过STM32CubeMX和HAL库开发DHT传感器驱动虽然需要处理一些时序精度问题但整体开发效率高代码可移植性强。在实际项目中建议将驱动封装为独立模块方便在不同项中复用。