STM32F103C8T6与DHT22温湿度传感器的实战开发指南
1. 硬件准备与连接
在开始编码之前,确保你已准备好以下硬件组件:
- STM32F103C8T6最小系统板(蓝色药丸开发板)
- DHT22温湿度传感器模块
- 0.96寸OLED显示屏(I2C接口)
- 杜邦线若干
- 面包板(可选,方便连接)
硬件连接示意图:
| 设备引脚 | STM32引脚 | 备注 |
|---|---|---|
| DHT22 VCC | 3.3V | 电源正极 |
| DHT22 GND | GND | 电源地 |
| DHT22 DATA | PB9 | 数据线 |
| OLED VCC | 3.3V | 电源正极 |
| OLED GND | GND | 电源地 |
| OLED SCL | PB6 | I2C时钟线 |
| OLED SDA | PB7 | I2C数据线 |
注意:DHT22的数据线需要连接一个4.7KΩ上拉电阻到3.3V,大多数模块已经内置此电阻
2. 开发环境配置
2.1 软件工具准备
要完成本项目,你需要:
- Keil MDK-ARM(或STM32CubeIDE)
- STM32标准外设库(或HAL库)
- 串口调试工具(如Putty、Tera Term)
# 示例:使用STM32CubeMX生成项目基础代码 stm32cubemx -m STM32F103C8Tx -p "GPIO,I2C,TIM"2.2 工程文件结构
建议的项目目录结构:
Project/ ├── CMSIS/ # 核心支持文件 ├── StdPeriph_Driver/ # 标准外设驱动 ├── User/ │ ├── main.c # 主程序 │ ├── dht22.c # DHT22驱动 │ ├── dht22.h # DHT22头文件 │ ├── oled.c # OLED驱动 │ └── oled.h # OLED头文件 └── stm32f10x_conf.h # 库配置文件3. DHT22驱动开发
3.1 理解DHT22通信协议
DHT22采用单总线协议,通信过程分为三个步骤:
- 主机启动信号:拉低数据线至少1ms,然后释放
- 从机响应:DHT22拉低80μs,然后拉高80μs
- 数据传输:40位数据(16位湿度+16位温度+8位校验和)
时序关键参数:
| 阶段 | 时间要求 | 容错范围 |
|---|---|---|
| 主机启动低电平 | ≥1ms | 1-20ms |
| 主机释放等待 | 20-40μs | - |
| 从机响应低电平 | 80μs | 75-85μs |
| 从机响应高电平 | 80μs | 75-85μs |
| 数据位0高电平 | 26-28μs | - |
| 数据位1高电平 | 70μs | - |
3.2 GPIO模拟单总线实现
// dht22.h 头文件关键定义 #ifndef __DHT22_H #define __DHT22_H #include "stm32f10x.h" #define DHT22_GPIO_PORT GPIOB #define DHT22_GPIO_PIN GPIO_Pin_9 #define DHT22_RCC RCC_APB2Periph_GPIOB // 方向控制宏 #define DHT22_INPUT() GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU #define DHT22_OUTPUT() GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP uint8_t DHT22_Init(void); uint8_t DHT22_Read_Data(float *temperature, float *humidity); #endif3.3 数据读取实现
// dht22.c 核心读取函数 uint8_t DHT22_Read_Data(float *temp, float *humi) { uint8_t buf[5] = {0}; uint8_t retry = 0; // 1. 主机启动信号 DHT22_OUTPUT(); GPIO_ResetBits(DHT22_GPIO_PORT, DHT22_GPIO_PIN); Delay_ms(2); // 拉低至少1ms GPIO_SetBits(DHT22_GPIO_PORT, DHT22_GPIO_PIN); Delay_us(30); // 等待20-40μs // 2. 切换为输入模式等待响应 DHT22_INPUT(); // 3. 检测DHT22响应 while(GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) && retry<100) { retry++; Delay_us(1); } if(retry>=100) return 1; retry = 0; while(!GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) && retry<100) { retry++; Delay_us(1); } if(retry>=100) return 1; // 4. 开始接收40位数据 for(uint8_t i=0; i<5; i++) { for(uint8_t j=0; j<8; j++) { retry = 0; while(!GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) && retry<100) { retry++; Delay_us(1); } Delay_us(40); // 判断高低电平 if(GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN)) { buf[i] |= (1 << (7-j)); while(GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) && retry<100) { retry++; Delay_us(1); } } } } // 5. 校验数据 if(buf[0] + buf[1] + buf[2] + buf[3] == buf[4]) { *humi = (float)((buf[0]<<8)|buf[1]) / 10.0; *temp = (float)((buf[2]<<8)|buf[3]) / 10.0; return 0; } return 1; }4. OLED显示实现
4.1 I2C初始化配置
// oled.c - I2C初始化 void OLED_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 2. 配置GPIO GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 3. 配置I2C I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 400000; // 400kHz I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); // 4. OLED初始化序列 OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0xD5); // 设置时钟分频 OLED_WriteCmd(0x80); OLED_WriteCmd(0xA8); // 设置多路复用率 OLED_WriteCmd(0x3F); OLED_WriteCmd(0xD3); // 设置显示偏移 OLED_WriteCmd(0x00); // ... 更多初始化命令 OLED_WriteCmd(0xAF); // 开启显示 }4.2 温湿度显示界面设计
// 显示刷新函数 void OLED_Refresh_Data(float temp, float humi) { char str[16]; // 温度显示 sprintf(str, "Temp: %.1fC", temp); OLED_ShowString(0, 2, (uint8_t *)str, 16); // 湿度显示 sprintf(str, "Humi: %.1f%%", humi); OLED_ShowString(0, 4, (uint8_t *)str, 16); // 添加简单的图形指示 static uint8_t pos = 0; OLED_DrawLine(pos, 63, pos+10, 63, 1); pos = (pos + 5) % 118; }5. 系统集成与优化
5.1 主程序框架
// main.c #include "stm32f10x.h" #include "dht22.h" #include "oled.h" #include "delay.h" int main(void) { float temperature = 0, humidity = 0; // 硬件初始化 Delay_Init(); DHT22_Init(); OLED_Init(); OLED_Clear(); // 显示初始界面 OLED_ShowString(0, 0, (uint8_t *)"STM32 DHT22 Demo", 16); OLED_ShowString(0, 2, (uint8_t *)"Initializing...", 16); while(1) { if(DHT22_Read_Data(&temperature, &humidity) == 0) { OLED_Refresh_Data(temperature, humidity); } else { OLED_ShowString(0, 6, (uint8_t *)"Read Error!", 16); } Delay_ms(2000); // 2秒更新一次 } }5.2 常见问题排查
问题1:DHT22无响应
- 检查电源连接(3.3V-5V)
- 确认数据线已正确连接且上拉
- 检查时序是否精确(特别是启动信号)
- 尝试更换DHT22模块
问题2:OLED显示异常
- 确认I2C地址是否正确(通常0x78或0x7A)
- 检查SCL/SDA线是否接反
- 确保初始化序列完整
- 尝试降低I2C时钟速度
问题3:数据读取不稳定
- 增加读取失败重试机制
- 在数据线靠近DHT22端添加滤波电容(100nF)
- 避免在读取过程中被中断打断
- 确保供电稳定(可并联100μF电容)
5.3 性能优化技巧
精确延时优化:
// 使用SysTick实现微秒级延时 void Delay_us(uint32_t nus) { uint32_t temp; SysTick->LOAD = SystemCoreClock/1000000 * nus; SysTick->VAL = 0x00; SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; do { temp = SysTick->CTRL; } while((temp&0x01) && !(temp&(1<<16))); SysTick->CTRL = 0x00; SysTick->VAL = 0x00; }低功耗设计:
- 在两次读取之间让MCU进入睡眠模式
- 使用定时器唤醒代替Delay延时
- 适当降低系统时钟频率
数据平滑处理:
#define SAMPLE_SIZE 5 float smooth_temp[SAMPLE_SIZE] = {0}; uint8_t index = 0; float Get_Smooth_Temperature(float new_temp) { smooth_temp[index] = new_temp; index = (index + 1) % SAMPLE_SIZE; float sum = 0; for(uint8_t i=0; i<SAMPLE_SIZE; i++) { sum += smooth_temp[i]; } return sum / SAMPLE_SIZE; }
6. 扩展功能实现
6.1 温湿度数据记录
// 简单的数据记录功能 #define MAX_RECORDS 24 // 记录24组数据 typedef struct { float temp; float humi; uint32_t time; } Record; Record records[MAX_RECORDS]; uint8_t record_index = 0; void Add_Record(float temp, float humi) { records[record_index].temp = temp; records[record_index].humi = humi; records[record_index].time = Get_System_Tick(); // 需要实现获取系统tick的函数 record_index = (record_index + 1) % MAX_RECORDS; } void Display_History() { char str[20]; for(uint8_t i=0; i<8 && i<MAX_RECORDS; i++) { uint8_t idx = (record_index - i - 1) % MAX_RECORDS; sprintf(str, "%2d:%.1fC %.1f%%", i+1, records[idx].temp, records[idx].humi); OLED_ShowString(0, i*2, (uint8_t *)str, 12); } }6.2 阈值报警功能
// 报警阈值设置 float temp_high_limit = 30.0; float temp_low_limit = 10.0; float humi_high_limit = 80.0; float humi_low_limit = 30.0; void Check_Alarm(float temp, float humi) { static uint8_t alarm_state = 0; uint8_t new_alarm = 0; if(temp > temp_high_limit) { new_alarm |= 0x01; OLED_ShowString(90, 0, (uint8_t *)"HOT!", 12); } if(temp < temp_low_limit) { new_alarm |= 0x02; OLED_ShowString(90, 0, (uint8_t *)"COLD", 12); } if(humi > humi_high_limit) { new_alarm |= 0x04; OLED_ShowString(90, 2, (uint8_t *)"WET!", 12); } if(humi < humi_low_limit) { new_alarm |= 0x08; OLED_ShowString(90, 2, (uint8_t *)"DRY!", 12); } if(new_alarm && !alarm_state) { // 触发报警动作(如点亮LED、蜂鸣器等) GPIO_SetBits(GPIOA, GPIO_Pin_0); } else if(!new_alarm && alarm_state) { // 关闭报警 GPIO_ResetBits(GPIOA, GPIO_Pin_0); } alarm_state = new_alarm; }6.3 通过串口输出数据
// 串口初始化 void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX(PA9) RX(PA10) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } // 串口发送数据 void Send_Sensor_Data(float temp, float humi) { char buffer[64]; int len = sprintf(buffer, "Temperature: %.1fC, Humidity: %.1f%%\r\n", temp, humi); for(int i=0; i<len; i++) { USART_SendData(USART1, buffer[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } }