1. 项目概述与核心价值
这个实战项目展示了如何在STM32平台上通过串口输出DHT11温湿度传感器的数据。使用HAL库和CubeMX工具的组合,可以快速搭建一个完整的温湿度监测系统原型。对于嵌入式开发者而言,这种基础外设的驱动开发能力是必备技能,特别是在物联网终端设备开发中,环境参数采集是最常见的功能需求之一。
DHT11作为经典的温湿度复合传感器,虽然精度不如高端型号(温度±2℃、湿度±5%RH),但其单总线通信协议和低廉价格使其成为入门级项目的首选。通过这个案例,开发者不仅能掌握传感器驱动开发的核心流程,还能理解HAL库的GPIO和USART模块的典型用法,为更复杂的嵌入式系统开发打下基础。
2. 硬件准备与CubeMX工程配置
2.1 硬件连接方案
DHT11传感器与STM32的连接只需要三根线:
- VCC(3.3V-5.5V)
- DATA(单总线信号线)
- GND
建议在DATA线上拉4.7KΩ电阻到VCC,确保信号稳定。接线时需特别注意:
不同厂家的DHT11引脚排列可能不同,务必确认传感器模块的丝印标识。我曾遇到过VCC和GND反接导致传感器发热损坏的情况。
2.2 CubeMX关键配置步骤
- 时钟配置:根据使用的STM32型号设置系统时钟(如STM32F103C8T6通常配置为72MHz)
- GPIO设置:
- 将连接DHT11的GPIO设为输出模式(初始状态)
- 配置为无上拉/下拉(DHT11模块已内置上拉)
- USART配置:
- 选择异步模式
- 波特率建议9600或115200
- 启用全局中断(用于接收数据)
- 生成代码:
- 工具链选择MDK-ARM或STM32CubeIDE
- 勾选"Generate peripheral initialization as a pair of .c/.h files"
配置完成后生成工程,会自动创建完整的初始化代码。这里有个实用技巧:
在Project Manager选项卡中启用"Backup previously generated files when re-generating",可以避免代码覆盖导致的手动修改丢失。
3. DHT11驱动开发详解
3.1 单总线协议时序分析
DHT11采用严格的单总线时序,主机(STM32)通过拉低DATA线启动通信:
-
起始信号:
- 主机拉低至少18ms
- 然后释放总线(改为输入模式)
- DHT11响应80us低电平+80us高电平
-
数据传输:
- 每个bit以50us低电平开始
- 高电平26-28us表示'0'
- 高电平70us表示'1'
- 40bit数据包结构:16bit湿度整数+16bit温度整数+8bit校验和
实际开发中,时序精度是关键。我推荐使用HAL库的微秒级延时函数:
c复制void DHT11_Delay_us(uint16_t us) {
__HAL_TIM_SET_COUNTER(&htimx, 0);
while(__HAL_TIM_GET_COUNTER(&htimx) < us);
}
需要先初始化一个基本定时器(如TIM2),预分频设置为系统时钟频率-1。
3.2 数据采集代码实现
完整的驱动应包含以下函数:
c复制uint8_t DHT11_ReadData(DHT11_Data *data) {
uint8_t buf[5] = {0};
uint8_t retry = 0;
// 启动信号
HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET);
DHT11_Delay_us(18000);
HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET);
// 切换为输入模式
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_Port, &GPIO_InitStruct);
// 等待响应
while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) == GPIO_PIN_SET) {
if(++retry > 100) return 0;
DHT11_Delay_us(1);
}
// 读取40bit数据
for(int i=0; i<5; i++) {
for(int j=0; j<8; j++) {
while(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin));
DHT11_Delay_us(40);
buf[i] <<= 1;
if(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin))
buf[i] |= 1;
while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin));
}
}
// 校验数据
if(buf[0] + buf[1] + buf[2] + buf[3] == buf[4]) {
data->humidity = buf[0];
data->temperature = buf[2];
return 1;
}
return 0;
}
关键点:在读取每个bit时,40us的延时判断窗口是区分'0'和'1'的核心。实际测试发现,不同批次的DHT11这个阈值可能有±5us的偏差,需要根据实际情况调整。
4. 串口输出与系统集成
4.1 串口格式化输出
利用HAL库的串口发送函数和标准库的格式化功能,可以方便地输出数据:
c复制void USART_Printf(UART_HandleTypeDef *huart, const char *fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
HAL_UART_Transmit(huart, (uint8_t *)buf, strlen(buf), HAL_MAX_DELAY);
}
// 在主循环中调用
DHT11_Data data;
if(DHT11_ReadData(&data)) {
USART_Printf(&huart1, "Temperature: %dC, Humidity: %d%%\r\n",
data.temperature, data.humidity);
} else {
USART_Printf(&huart1, "DHT11 read failed!\r\n");
}
HAL_Delay(2000); // 2秒采样间隔
4.2 低功耗优化技巧
对于电池供电的应用,可以通过以下方式优化:
- 将采样间隔延长至30秒或更长
- 在休眠期间关闭DHT11电源(通过MOS管控制)
- 使用串口DMA传输减少CPU唤醒时间
- 配置MCU进入STOP模式等待下一次采样
一个实用的电源管理实现:
c复制void Power_SaveMode(void) {
// 关闭外设时钟
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_USART1_CLK_DISABLE();
// 配置唤醒源(如RTC或EXTI)
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后重新初始化
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
}
5. 常见问题与调试技巧
5.1 典型故障排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取超时 | 接线错误/接触不良 | 检查VCC/GND/DATA连接 |
| 校验失败 | 电源噪声/时序不准 | 增加电源滤波电容,调整延时 |
| 数据全零 | 未正确切换IO模式 | 确认GPIO配置流程 |
| 温度异常 | 传感器位置不当 | 避免靠近MCU等热源 |
5.2 逻辑分析仪调试
当遇到通信问题时,使用逻辑分析仪捕获波形是最有效的调试手段。正常波形应包含:
- 18ms的低电平起始信号
- 80us的响应脉冲
- 40个规整的数据bit波形
如果发现波形畸变,可能是:
- 上拉电阻值不合适(建议4.7KΩ)
- 导线过长引入干扰(建议<20cm)
- GPIO驱动能力不足(改用开漏输出)
5.3 软件滤波处理
DHT11的测量值可能存在小幅度波动,可以通过简单的软件滤波提升稳定性:
c复制#define FILTER_SIZE 5
uint8_t DHT11_GetAverageTemp() {
uint8_t sum = 0;
uint8_t valid = 0;
DHT11_Data data;
for(int i=0; i<FILTER_SIZE; i++) {
if(DHT11_ReadData(&data)) {
sum += data.temperature;
valid++;
}
HAL_Delay(100);
}
return (valid > 0) ? (sum / valid) : 0;
}
6. 项目扩展方向
掌握了基础功能后,可以考虑以下进阶开发:
- 多传感器组网:通过单总线挂载多个DHT11(需修改协议处理)
- 无线传输:结合ESP8266或LoRa模块实现远程监控
- LCD显示:添加OLED屏本地显示实时数据
- 报警功能:设置温湿度阈值触发蜂鸣器
- 数据存储:使用EEPROM或Flash记录历史数据
一个典型的物联网扩展方案:
c复制void IoT_SendToCloud(float temp, float humi) {
char mqttMsg[128];
snprintf(mqttMsg, sizeof(mqttMsg),
"{\"dev\":\"STM32\",\"temp\":%.1f,\"humi\":%.1f}",
temp, humi);
ESP8266_Send("AT+CIPSEND=0,%d\r\n", strlen(mqttMsg));
ESP8266_Send("%s", mqttMsg);
}
// 在主循环中调用
DHT11_Data data;
if(DHT11_ReadData(&data)) {
float temp = data.temperature + 0.1*(data.temperature_decimal & 0x0F);
float humi = data.humidity + 0.1*(data.humidity_decimal & 0x0F);
IoT_SendToCloud(temp, humi);
}
在实际项目中,我发现DHT11的长期稳定性较好,但需要注意防潮处理。对于户外应用,可以在传感器外部包裹透气防水的特氟龙胶带,既能保护传感器又不影响测量精度。