从零打造STM32温湿度监测仪:DHT11+OLED全流程实战指南
在创客圈子里,STM32系列单片机一直以高性能和丰富的外设资源著称,而DHT11温湿度传感器则是环境监测项目的经典选择。本文将带你用最经济的STM32F103C8T6(俗称"蓝莓板")配合0.96寸OLED屏幕,打造一个即插即用的桌面环境监测工具。不同于学院派的系统设计论文,我们聚焦于实际制作过程中的每个细节——从元器件采购到代码调试,甚至包括那些教程里很少提及的"坑点"排查。
1. 硬件准备与连接
1.1 元器件清单与选购建议
制作这个项目需要以下核心部件(总成本约50元):
| 元器件 | 推荐型号 | 备注说明 |
|---|---|---|
| 主控板 | STM32F103C8T6最小系统板 | 注意选择带BOOT0/1按键的版本 |
| 温湿度传感器 | DHT11 | 建议选择带PCB板的型号 |
| 显示模块 | SSD1306 0.96寸OLED | 4针I2C接口版本最易用 |
| 连接线 | 杜邦线(母对母) | 准备10-15根 |
| 供电方案 | Micro USB线或5V电源 | 开发板自带稳压电路 |
特别提醒:市场上有些低价DHT11传感器存在精度问题,建议选择价格在8-12元区间的正品。OLED屏幕则要注意区分I2C和SPI接口版本,本文使用I2C接口的4针模块(GND、VCC、SCL、SDA)。
1.2 硬件连接图解
连接关系看似简单,但接错线是新手最常见的问题。以下是经过验证的可靠接法:
STM32F103C8T6 DHT11 OLED(I2C) PA0 DATA - 3.3V VCC VCC GND GND GND - - SCL→PB6 - - SDA→PB7关键提示:DHT11的DATA线需要接上拉电阻(4.7KΩ-10KΩ),如果传感器模块本身没有集成,需在DATA和VCC之间外接
实际接线时建议遵循以下顺序:
- 先连接所有GND线形成共地
- 再接VCC电源线
- 最后连接信号线(PA0、PB6、PB7)
2. 开发环境搭建
2.1 工具链配置
我们推荐两种主流的开发方式:
方案A:Keil MDK(传统方案)
- 安装Keil uVision5
- 安装STM32F1xx_DFP设备支持包
- 配置ST-Link调试器驱动
- 新建工程时选择"STM32F103C8"器件
方案B:STM32CubeIDE(现代方案)
- 下载官方集成开发环境
- 创建新项目时选择"STM32F103C8Tx"系列
- 使用内置的HAL库简化开发
- 可视化配置时钟树和引脚分配
# 示例:使用STM32CubeMX生成代码(方案B) $ stm32cubemx # 选择MCU型号 → 配置时钟 → 开启I2C1 → 配置PA0为GPIO_Input # 生成代码时勾选"生成外设初始化代码"2.2 工程文件结构
规范的工程目录能避免后期混乱,建议按如下结构组织:
Project/ ├── Core/ │ ├── Src/ │ │ ├── main.c │ │ ├── dht11.c │ │ └── oled.c │ └── Inc/ │ ├── dht11.h │ └── oled.h ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ └── STM32CubeIDE/ └── .project3. 核心代码实现
3.1 DHT11驱动开发
DHT11的通信时序是项目第一个难点。这个单总线设备对时间要求极为严格,以下是经过实战验证的驱动代码:
// dht11.h #define DHT11_GPIO_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 #define DHT11_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() uint8_t DHT11_Read(float *temperature, float *humidity); // dht11.c void DHT11_GPIO_Config(GPIO_PinState state) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(state == GPIO_PIN_SET) { // 输出模式 GPIO_InitStruct.Pin = DHT11_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); } else { // 输入模式 GPIO_InitStruct.Pin = DHT11_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct); } } uint8_t DHT11_Read(float *temp, float *humi) { uint8_t data[5] = {0}; uint8_t retry = 0; // 主机启动信号 DHT11_GPIO_Config(GPIO_PIN_SET); HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(18); // 至少18ms低电平 HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_PIN, GPIO_PIN_SET); DHT11_GPIO_Config(GPIO_PIN_RESET); // 等待从机响应 while(HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_PIN) && retry<100) { retry++; HAL_Delay(1); } if(retry >= 100) return 1; // 读取40位数据 for(int i=0; i<5; i++) { for(int j=0; j<8; j++) { while(!HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_PIN)); // 等待50us低电平结束 uint32_t start = HAL_GetTick(); while(HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_PIN)); // 测量高电平持续时间 if((HAL_GetTick() - start) > 40) data[i] |= (1 << (7-j)); } } // 校验数据 if(data[4] == (data[0]+data[1]+data[2]+data[3])) { *humi = data[0] + data[1]*0.1; *temp = data[2] + data[3]*0.1; return 0; } return 2; }3.2 OLED显示优化
OLED驱动通常使用现成库,但我们可以优化显示效果:
// oled.c void OLED_ShowTempHum(float temp, float hum) { char buffer[16]; OLED_Clear(); OLED_ShowCHinese(0, 0, 0); // 显示"温" OLED_ShowCHinese(16, 0, 1); // 显示"度" sprintf(buffer, "%.1fC", temp); OLED_ShowString(32, 0, (uint8_t*)buffer); OLED_ShowCHinese(0, 2, 2); // 显示"湿" OLED_ShowCHinese(16, 2, 1); // 显示"度" sprintf(buffer, "%.1f%%", hum); OLED_ShowString(32, 2, (uint8_t*)buffer); // 添加动态效果 static uint8_t pos = 0; OLED_DrawLine(pos, 4, pos+5, 4, 1); pos = (pos+1)%128; OLED_Refresh(); }4. 常见问题排查指南
当你的项目没有按预期工作时,可以按照以下流程排查:
4.1 DHT11无响应
检查硬件连接
- 确认DATA线接在PA0
- 测量VCC电压是否为3.3V
- 检查上拉电阻是否接好
时序调试技巧
- 在DHT11_Read函数中添加调试输出
- 用逻辑分析仪捕捉信号波形
- 尝试调整延时时间(±10us)
实测发现:某些STM32芯片需要将系统时钟配置为72MHz才能满足DHT11的时序要求
4.2 OLED显示异常
现象1:屏幕全白
- 检查I2C地址(通常0x78或0x7A)
- 确认Reset引脚已正确初始化
- 测量屏幕供电电压(3.3V-5V)
现象2:显示乱码
- 检查字体数据是否完整
- 确认I2C时钟速度不超过400kHz
- 尝试降低刷新频率
4.3 数据跳动问题
若温湿度值不稳定:
- 在传感器电源端并联100μF电容
- 添加软件滤波算法:
#define FILTER_LEN 5 float filter_buf[FILTER_LEN]; float moving_average(float new_val) { static uint8_t index = 0; filter_buf[index++] = new_val; if(index >= FILTER_LEN) index = 0; float sum = 0; for(int i=0; i<FILTER_LEN; i++) { sum += filter_buf[i]; } return sum/FILTER_LEN; }5. 项目进阶与扩展
基础功能实现后,可以考虑以下增强功能:
5.1 添加用户交互
通过GPIO按键实现功能切换:
// 在main.c中添加 while(1) { if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) { display_mode = (display_mode + 1) % 3; HAL_Delay(300); // 消抖 } switch(display_mode) { case 0: // 显示温湿度 OLED_ShowTempHum(temp, hum); break; case 1: // 显示历史最大值 OLED_ShowMaxMin(); break; case 2: // 显示趋势图 OLED_ShowTrend(); break; } }5.2 数据记录功能
利用STM32内部Flash模拟EEPROM:
#define FLASH_PAGE_ADDR 0x0801F000 // 最后一页Flash void Save_Data(float temp, float hum) { HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef erase; erase.TypeErase = FLASH_TYPEERASE_PAGES; erase.PageAddress = FLASH_PAGE_ADDR; erase.NbPages = 1; uint32_t temp_data = (uint32_t)(temp * 10); uint32_t hum_data = (uint32_t)(hum * 10); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_PAGE_ADDR, temp_data); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_PAGE_ADDR+4, hum_data); HAL_FLASH_Lock(); }5.3 低功耗优化
对于电池供电场景:
- 配置STM32进入Stop模式
- 使用RTC定时唤醒
- 优化采样间隔(如每分钟采样一次)
// 进入低功耗模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后需要重新初始化时钟 SystemClock_Config();完成这个项目后,你会发现STM32开发并没有想象中困难。实际调试时遇到最多的问题往往是硬件连接错误或时序不准,这时候逻辑分析仪就成了得力助手。建议在面包板上先验证所有功能,再考虑设计PCB制作成正式产品。