1. 项目概述:STM32驱动DHT11实现OLED温湿度显示
这个项目展示了如何使用STM32微控制器读取DHT11温湿度传感器的数据,并将测量结果实时显示在OLED屏幕上。作为嵌入式开发的经典案例,它完美融合了硬件接口、通信协议和显示控制三大核心技术要点。
我选择这个方案进行讲解,主要基于以下几个实际考量:
- 硬件成本极低 - DHT11传感器和0.96寸OLED都是淘宝上10元以内能买到的器件
- 学习价值高 - 涵盖了GPIO控制、时序协议、数据校验等嵌入式开发核心技能
- 可视效果好 - OLED显示直观,比串口打印更适合教学演示
- 扩展性强 - 可作为智能家居、环境监测等项目的传感器模块
硬件选型提示:虽然DHT22精度更高,但对于初学者而言,DHT11更便宜且协议相同,是更好的入门选择。OLED建议使用SSD1306驱动的I2C接口版本,节省IO资源。
2. 硬件连接与原理分析
2.1 系统组成框图
code复制[STM32F103C8T6] <-单总线-> [DHT11]
|
I2C
|
[OLED]
2.2 引脚连接详解
根据提供的图片资料,典型连接方式如下:
| 设备 | 引脚 | STM32引脚 | 备注 |
|---|---|---|---|
| DHT11 | DATA | PA0 | 需接4.7K上拉电阻 |
| VCC | 3.3V | 也可接5V | |
| GND | GND | ||
| OLED | SCL | PB6 | I2C1时钟线 |
| SDA | PB7 | I2C1数据线 | |
| VCC | 3.3V | ||
| GND | GND |
布线经验:DHT11的数据线长度最好控制在20cm以内,过长容易导致信号失真。I2C总线上建议加100nF去耦电容。
2.3 DHT11工作原理深度解析
DHT11采用单总线协议,其通信本质上是通过精确的时序来传递数据。传感器内部包含:
- 电容式湿度传感元件
- 热敏电阻温度传感元件
- 8位单片机进行模数转换
工作时序可分为四个阶段:
- 主机发送起始信号(拉低≥18ms)
- 传感器响应信号(80μs低电平+80μs高电平)
- 数据传输阶段(40位数据,每位约50μs)
- 空闲状态(总线保持高电平)
3. 软件实现详解
3.1 开发环境配置
推荐使用以下工具链:
- IDE: Keil MDK 或 STM32CubeIDE
- 库: HAL库或标准外设库
- 调试工具: ST-Link V2
工程需要包含:
c复制#include "stm32f1xx_hal.h"
#include "dht11.h"
#include "ssd1306.h"
#include "fonts.h" // OLED显示字体
3.2 DHT11驱动实现
3.2.1 GPIO模式动态切换
这是单总线设备驱动的关键技巧:
c复制// 设置为推挽输出模式
void DHT11_Output_Mode(void) {
GPIO_InitTypeDef gpio = {0};
gpio.Pin = DHT11_PIN;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &gpio);
}
// 设置为上拉输入模式
void DHT11_Input_Mode(void) {
GPIO_InitTypeDef gpio = {0};
gpio.Pin = DHT11_PIN;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = GPIO_PULLUP;
HAL_GPIO_Init(DHT11_PORT, &gpio);
}
3.2.2 核心通信函数优化版
这是我优化后的读取函数,增加了超时检测:
c复制#define DHT11_TIMEOUT 100 // 100us超时
uint8_t DHT11_Read_Byte(void) {
uint8_t data = 0;
for(int i=0; i<8; i++) {
// 等待下降沿
uint32_t timeout = DHT11_TIMEOUT;
while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET) {
if(--timeout == 0) return 0xFF; // 超时错误
}
// 精确延时40us后采样
Delay_us(40);
if(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) {
data |= (1 << (7-i)); // MSB first
timeout = DHT11_TIMEOUT;
while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) {
if(--timeout == 0) return 0xFF;
}
}
}
return data;
}
3.2.3 完整数据读取流程
c复制uint8_t DHT11_Read(float *temp, float *humi) {
uint8_t data[5] = {0};
// 1. 主机发送起始信号
DHT11_Output_Mode();
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);
Delay_ms(18);
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);
// 2. 切换输入模式等待响应
DHT11_Input_Mode();
Delay_us(30);
// 3. 检查传感器响应
if(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) return DHT11_ERROR_NO_RESPONSE;
Delay_us(80);
if(!HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN)) return DHT11_ERROR_NO_RESPONSE;
Delay_us(80);
// 4. 读取40位数据
for(int i=0; i<5; i++) {
data[i] = DHT11_Read_Byte();
if(data[i] == 0xFF) return DHT11_ERROR_TIMEOUT;
}
// 5. 校验数据
if(data[4] != (data[0]+data[1]+data[2]+data[3])) {
return DHT11_ERROR_CHECKSUM;
}
// 6. 转换数据
*humi = data[0] + data[1]/10.0f;
*temp = data[2] + data[3]/10.0f;
return DHT11_OK;
}
3.3 OLED显示实现
3.3.1 显示界面设计
建议采用以下布局:
code复制+----------------+
| 温度: 25.6°C |
| 湿度: 45.2% |
| |
| 最后更新: |
| 15:30:23 |
+----------------+
3.3.2 显示刷新逻辑
c复制void Update_Display(float temp, float humi) {
char buf[20];
SSD1306_Clear();
// 显示温度
sprintf(buf, "Temp: %.1fC", temp);
SSD1306_GotoXY(0, 0);
SSD1306_Puts(buf, &Font_7x10, SSD1306_COLOR_WHITE);
// 显示湿度
sprintf(buf, "Humi: %.1f%%", humi);
SSD1306_GotoXY(0, 12);
SSD1306_Puts(buf, &Font_7x10, SSD1306_COLOR_WHITE);
// 显示更新时间
RTC_TimeTypeDef time;
HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
sprintf(buf, "Updated: %02d:%02d:%02d",
time.Hours, time.Minutes, time.Seconds);
SSD1306_GotoXY(0, 24);
SSD1306_Puts(buf, &Font_7x10, SSD1306_COLOR_WHITE);
SSD1306_UpdateScreen();
}
4. 系统整合与优化
4.1 主程序架构
c复制int main(void) {
// 硬件初始化
HAL_Init();
SystemClock_Config();
DHT11_Init();
SSD1306_Init();
float temp, humi;
uint8_t status;
while(1) {
status = DHT11_Read(&temp, &humi);
if(status == DHT11_OK) {
Update_Display(temp, humi);
} else {
Show_Error(status); // 错误处理函数
}
HAL_Delay(2000); // 2秒更新一次
}
}
4.2 实测性能优化技巧
-
时序精度提升:
- 使用硬件定时器替代软件延时
- 将关键延时函数放在RAM中执行(避免Flash访问延迟)
-
错误处理增强:
c复制void Show_Error(uint8_t err) {
switch(err) {
case DHT11_ERROR_NO_RESPONSE:
SSD1306_Puts("Error: No response", ...);
break;
case DHT11_ERROR_CHECKSUM:
SSD1306_Puts("Error: Checksum", ...);
break;
// 其他错误类型...
}
SSD1306_UpdateScreen();
}
- 低功耗优化:
- 在两次读取之间将MCU进入Sleep模式
- 关闭OLED背光(需要硬件支持)
5. 常见问题与解决方案
5.1 DHT11读取失败排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 一直返回无响应 | 接线错误或传感器损坏 | 检查VCC/GND连接,更换传感器 |
| 偶尔读取失败 | 时序不精确或信号干扰 | 缩短数据线,添加滤波电容 |
| 校验和经常错误 | 电源不稳定或采样间隔不足 | 增加电源电容,确保>1s间隔 |
| 数据明显不准 | 传感器老化或环境因素 | 校准或更换更高精度传感器 |
5.2 OLED显示异常处理
-
屏幕不亮:
- 检查I2C地址是否正确(通常0x3C或0x3D)
- 测量VCC电压(3.3V或5V根据型号)
-
显示乱码:
- 确认初始化序列完整
- 检查字体数据是否损坏
-
刷新闪烁:
- 使用双缓冲机制
- 降低刷新频率(如500ms)
5.3 进阶调试技巧
-
逻辑分析仪抓包:
- 捕获单总线实际波形
- 验证时序参数是否符合规格书
-
变量实时监控:
- 通过SWD接口实时查看变量
- 使用SEGGER RTT输出调试信息
-
温度补偿算法:
c复制// 简单的温度补偿示例
float compensated_humi(float raw_humi, float temp) {
// 温度每高于25°C 1度,湿度降低0.5%
if(temp > 25.0f) {
return raw_humi - (temp - 25.0f) * 0.5f;
}
return raw_humi;
}
6. 项目扩展思路
-
多传感器网络:
- 通过单总线挂载多个DHT11
- 为每个传感器分配唯一ID
-
无线传输功能:
- 添加ESP8266实现WiFi上传
- 使用HC-05模块实现蓝牙传输
-
历史数据记录:
- 添加SPI Flash存储历史数据
- 实现温度变化曲线显示
-
报警功能:
- 设置温湿度阈值
- 超过阈值时触发蜂鸣器
这个项目最让我惊喜的是DHT11的性价比 - 虽然精度一般,但对于大多数日常应用已经完全够用。在实际部署时,建议将传感器远离热源和直接阳光照射,这样可以获得更准确的环境参数。