STM32F103C8T6驱动DHT22温湿度传感器,OLED显示数据(附完整代码及接线图)
STM32F103C8T6与DHT22温湿度传感器的实战开发指南
第一次接触嵌入式开发时,传感器数据采集总是让人既兴奋又忐忑。记得我刚开始用STM32读取DHT22数据时,因为时序问题折腾了整整两天。本文将分享一个完整的项目实践——用STM32F103C8T6驱动DHT22温湿度传感器,并通过OLED实时显示数据。不同于简单的代码展示,我会重点讲解硬件连接中的"坑点"、时序控制的精髓,以及如何优化显示效果。
1. 硬件准备与电路设计
1.1 元器件选型与特性分析
选择STM32F103C8T6作为主控有几个实际考虑:首先它价格亲民(约10元),其次72MHz的主频完全能满足传感器数据处理需求,最重要的是它丰富的GPIO和内置硬件I2C非常适合这个项目。
DHT22是项目中关键的传感器元件,与DHT11相比,它的精度更高:
- 温度测量范围:-40~80℃ ±0.5℃
- 湿度测量范围:0~100%RH ±2%RH
- 单总线通信,节省IO资源
OLED显示屏我们选用0.96寸SSD1306驱动的I2C接口型号,对比SPI版本,它只需要4根线就能工作,特别适合引脚资源紧张的场景。
1.2 硬件连接详解
核心接线图如下:
| 设备引脚 | STM32对应引脚 | 备注 |
|---|---|---|
| DHT22 VCC | 3.3V/5V | 建议使用3.3V降低功耗 |
| DHT22 GND | GND | 共地非常重要 |
| DHT22 DATA | PB9 | 需配置为上拉输入/推挽输出 |
| OLED VCC | 3.3V | 超过3.3V可能损坏屏幕 |
| OLED GND | GND | 必须与MCU共地 |
| OLED SCL | PB6 | 硬件I2C1_SCL |
| OLED SDA | PB7 | 硬件I2C1_SDA |
实际布线时,DHT22的信号线建议不超过20cm,过长的导线会导致信号衰减。我曾遇到因使用50cm杜邦线导致数据不稳定的情况,缩短线材后问题立即解决。
1.3 常见硬件问题排查
- OLED不显示:首先检查I2C地址是否正确(通常是0x78或0x7A),用万用表测量SCL/SDA电压(正常应为3.3V脉冲)
- DHT22无响应:确认上拉电阻(4.7KΩ)已接好,测量DATA线在空闲时的电压(应为高电平)
- 数据跳动严重:检查电源是否稳定,可在VCC与GND之间加一个100μF电容
2. 开发环境搭建与工程配置
2.1 工具链选择
推荐两种开发环境方案:
方案一:Keil MDK
- 安装Keil uVision5(需注册)
- 安装STM32F1xx_DFP设备支持包
- 配置工程时勾选"Use MicroLIB"以减小代码体积
方案二:STM32CubeIDE
- 官网下载集成开发环境
- 新建工程时选择STM32F103C8系列
- 在.ioc配置文件中启用I2C1和外设时钟
// 关键时钟配置代码(CubeMX生成)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
2.2 工程文件结构
合理的文件组织能大幅提高开发效率:
/Project
├── /CMSIS // 内核支持文件
├── /Drivers
│ ├── OLED // OLED驱动
│ └── DHT22 // 温湿度传感器驱动
├── /Inc
│ ├── main.h
│ ├── oled.h
│ └── dht22.h
└── /Src
├── main.c
├── oled.c
└── dht22.c
2.3 编译配置要点
- 在"Options for Target"中设置正确的Flash下载算法(STM32F10x Medium-density)
- 优化等级建议选择-O1,兼顾性能和调试便利性
- 勾选"Create HEX File"以便烧录
3. DHT22驱动开发详解
3.1 单总线通信时序剖析
DHT22采用严格的单总线协议,时序误差必须小于20μs。下面是关键时序参数:
| 操作 | 时间要求 | 容错范围 |
|---|---|---|
| 主机拉低 | ≥1ms | 0.8-20ms |
| 主机释放 | 20-40μs | 必须严格 |
| 从机响应 | 80μs低电平 | 60-100μs |
| 数据0 | 26-28μs高电平 | ±5μs |
| 数据1 | 70μs高电平 | ±10μs |
// 关键时序实现代码
void DHT22_Rst(void) {
DHT22_IO_OUT(); // 设置为输出模式
DHT22_DQ_OUT = 0; // 拉低总线
delay_ms(1); // 保持1ms
DHT22_DQ_OUT = 1; // 释放总线
delay_us(30); // 等待30μs
}
3.2 数据读取优化技巧
原始代码中的轮询方式会占用大量CPU资源,我们可以用定时器中断来改进:
- 配置一个基本定时器(如TIM4)
- 设置1μs的时间基准
- 在下降沿触发外部中断
- 在定时器中断中记录高电平持续时间
// 改进的数据位判断逻辑
uint8_t DHT22_Read_Bit(void) {
uint16_t cnt = 0;
while(DHT22_DQ_IN && cnt<200) cnt++; // 等待低电平
cnt = 0;
while(!DHT22_DQ_IN && cnt<200) cnt++; // 等待高电平
delay_us(40); // 等待40μs
return DHT22_DQ_IN ? 1 : 0;
}
3.3 数据校验与错误处理
DHT22传输的40位数据包含校验和,正确的处理流程应该是:
- 读取5字节原始数据(湿度高8位、湿度低8位、温度高8位、温度低8位、校验和)
- 计算前4字节的和
- 比对校验字节
uint8_t DHT22_Read_Data(uint16_t *temp, uint16_t *humi) {
uint8_t buf[5], sum;
// ...读取数据代码...
sum = (buf[0] + buf[1] + buf[2] + buf[3]) & 0xFF;
if(sum == buf[4]) { // 校验通过
*humi = buf[0]<<8 | buf[1];
*temp = buf[2]<<8 | buf[3];
return 0;
}
return 1; // 校验失败
}
4. OLED显示优化实战
4.1 I2C驱动深度优化
标准的I2C库函数调用开销较大,我们可以直接操作寄存器提升速度:
void I2C_WriteByte(uint8_t addr, uint8_t data) {
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
}
4.2 显示缓存管理技巧
SSD1306支持显存(GRAM)操作,合理利用可以大幅减少I2C通信:
- 定义128x64的显示缓存数组
- 修改像素点时只更新数组
- 定期调用刷新函数统一更新屏幕
uint8_t OLED_GRAM[128][8]; // 每字节存储8个垂直像素
void OLED_Refresh(void) {
for(uint8_t page=0; page<8; page++) {
I2C_WriteByte(0x00, 0xB0+page); // 设置页地址
I2C_WriteByte(0x00, 0x00); // 列地址低4位
I2C_WriteByte(0x00, 0x10); // 列地址高4位
for(uint8_t col=0; col<128; col++) {
I2C_WriteByte(0x40, OLED_GRAM[col][page]);
}
}
}
4.3 用户界面设计建议
- 温度显示:使用16x16大字体突出关键数据
- 湿度显示:添加百分比符号和小数点
- 状态指示:当温度超过阈值时显示警告图标
- 刷新策略:每2秒更新一次数据,避免屏幕闪烁
void Display_TempHum(uint16_t temp, uint16_t humi) {
char buf[16];
// 温度显示
sprintf(buf, "%2d.%1d C", temp/10, temp%10);
OLED_ShowStr(20, 2, buf, 16, 1);
// 湿度显示
sprintf(buf, "%2d.%1d %%", humi/10, humi%10);
OLED_ShowStr(20, 5, buf, 16, 1);
// 添加单位标签
OLED_ShowStr(0, 2, "Temp:", 16, 1);
OLED_ShowStr(0, 5, "Humi:", 16, 1);
}
5. 系统整合与性能优化
5.1 主循环架构设计
避免在main函数中使用阻塞式延迟,采用状态机模式更高效:
typedef enum {
STATE_READ_SENSOR,
STATE_UPDATE_DISPLAY,
STATE_DELAY
} SystemState;
SystemState state = STATE_READ_SENSOR;
uint32_t lastTick = 0;
while(1) {
switch(state) {
case STATE_READ_SENSOR:
if(DHT22_Read_Data(&temperature, &humidity) == 0) {
state = STATE_UPDATE_DISPLAY;
}
break;
case STATE_UPDATE_DISPLAY:
Display_TempHum(temperature, humidity);
lastTick = HAL_GetTick();
state = STATE_DELAY;
break;
case STATE_DELAY:
if(HAL_GetTick() - lastTick >= 2000) {
state = STATE_READ_SENSOR;
}
break;
}
}
5.2 低功耗优化策略
- 在延迟期间将MCU切换到Sleep模式
- 降低系统时钟频率(从72MHz降到36MHz)
- 关闭未使用的外设时钟
- 间隔式刷新OLED(如每5秒刷新一次)
void Enter_LowPowerMode(void) {
__WFI(); // 等待中断唤醒
SystemCoreClockUpdate(); // 唤醒后重新配置时钟
}
5.3 抗干扰设计
- 在DHT22数据线加10KΩ上拉电阻
- VCC与GND之间并联0.1μF和100μF电容
- 避免将传感器放置在发热元件附近
- 软件上采用多次读取取平均值的策略
#define SAMPLE_TIMES 3
uint16_t Get_Average_Temperature(void) {
uint32_t sum = 0;
uint16_t temp, humi;
uint8_t valid = 0;
for(uint8_t i=0; i<SAMPLE_TIMES; i++) {
if(DHT22_Read_Data(&temp, &humi) == 0) {
sum += temp;
valid++;
}
delay_ms(100);
}
return valid ? (sum/valid) : 0xFFFF;
}
6. 进阶功能扩展
6.1 添加历史数据记录
利用STM32内部Flash模拟EEPROM存储历史数据:
#define FLASH_PAGE_ADDR 0x0801F000 // 最后一页Flash
void Save_To_Flash(uint16_t temp, uint16_t humi) {
FLASH_Unlock();
FLASH_ErasePage(FLASH_PAGE_ADDR);
FLASH_ProgramHalfWord(FLASH_PAGE_ADDR, temp);
FLASH_ProgramHalfWord(FLASH_PAGE_ADDR+2, humi);
FLASH_Lock();
}
void Read_From_Flash(uint16_t *temp, uint16_t *humi) {
*temp = *(__IO uint16_t*)FLASH_PAGE_ADDR;
*humi = *(__IO uint16_t*)(FLASH_PAGE_ADDR+2);
}
6.2 通过串口输出数据
方便调试和数据采集:
void USART_Send_TempHum(uint16_t temp, uint16_t humi) {
char buffer[64];
sprintf(buffer, "Temperature: %d.%d C, Humidity: %d.%d %%\r\n",
temp/10, temp%10, humi/10, humi%10);
for(uint16_t i=0; i<strlen(buffer); i++) {
USART_SendData(USART1, buffer[i]);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
}
6.3 添加蓝牙传输功能
使用HC-05模块实现无线监控:
- 连接蓝牙模块的TXD到STM32的USART2_RX
- 连接蓝牙模块的RXD到STM32的USART2_TX
- 修改串口初始化代码
- 手机安装蓝牙串口APP即可接收数据
void Bluetooth_Init(void) {
USART_InitTypeDef USART_InitStruct;
// 时钟使略...
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStruct);
USART_Cmd(USART2, ENABLE);
}
更多推荐



所有评论(0)