智能环境监测站项目¶
项目概述¶
项目简介¶
智能环境监测站是一个综合性的嵌入式IoT项目,通过集成多种环境传感器(温湿度、光照、气压等),实现对环境参数的实时监测、数据采集、云端上传和智能预警。本项目涵盖了传感器驱动开发、数据处理、无线通信、云平台集成、数据可视化等多个技术领域,是学习嵌入式系统和物联网开发的综合实践项目。
项目演示¶
系统架构示意图:
┌─────────────────────────────────────────────────────────┐
│ 智能环境监测站 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ DHT22 │ │ BH1750 │ │ BMP280 │ │
│ │温湿度传感器│ │光照传感器 │ │气压传感器 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┴─────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ STM32F4 │ │
│ │ 主控制器 │ │
│ └──────┬──────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │ OLED │ │ ESP8266 │ │ SD卡 │ │
│ │ 显示屏 │ │ WiFi模块│ │ 存储 │ │
│ └─────────┘ └────┬────┘ └─────────┘ │
│ │ │
└─────────────────────┼────────────────────────────────┘
│
▼
┌───────────────┐
│ MQTT服务器 │
│ 云平台 │
└───────┬───────┘
│
┌───────┴───────┐
│ │
┌────┴────┐ ┌────┴────┐
│ Web界面 │ │ 移动App │
│ 数据展示│ │ 实时监控│
└─────────┘ └─────────┘
学习目标¶
完成本项目后,你将掌握:
- 多传感器集成:学会集成和管理多个不同类型的传感器
- 数据采集系统:掌握高效的数据采集和处理方法
- 无线通信:实现WiFi连接和MQTT协议通信
- 云平台集成:将设备数据上传到云平台
- 数据可视化:创建Web界面展示实时数据
- 预警系统:实现智能阈值监测和报警功能
- 低功耗设计:优化系统功耗延长续航时间
- 系统架构设计:学习复杂嵌入式系统的架构设计
项目特点¶
- ✨ 多传感器融合:集成温湿度、光照、气压等多种传感器
- ✨ 实时监测:1秒采样间隔,实时显示环境数据
- ✨ 云端存储:通过MQTT协议上传数据到云平台
- ✨ 本地存储:SD卡备份数据,支持离线工作
- ✨ 智能预警:可配置阈值,异常情况自动报警
- ✨ 数据可视化:Web界面实时图表展示
- ✨ 低功耗模式:支持电池供电,续航可达数天
- ✨ OTA升级:支持远程固件更新
技术栈¶
硬件平台¶
- 主控芯片:STM32F407VGT6 (168MHz, 1MB Flash, 192KB RAM)
- 开发板:STM32F4 Discovery 或自制PCB
- 传感器:
- DHT22 温湿度传感器
- BH1750 光照传感器
- BMP280 气压传感器
- 通信模块:ESP8266 WiFi模块
- 显示:0.96寸 OLED显示屏 (SSD1306)
- 存储:MicroSD卡模块
- 电源:18650锂电池 + TP4056充电模块
软件技术¶
- 开发语言:C语言
- 操作系统:FreeRTOS v10.x
- 通信协议:
- I2C (传感器通信)
- UART (ESP8266通信)
- SPI (SD卡通信)
- MQTT (云端通信)
- 开发工具:STM32CubeIDE / Keil MDK
- 调试工具:ST-Link, 串口调试助手
第三方库¶
- HAL库:STM32 HAL驱动库
- FreeRTOS:实时操作系统
- FatFs:FAT文件系统
- MQTT:MQTT客户端库
- cJSON:JSON解析库
- U8g2:OLED图形库
云平台¶
- MQTT服务器:阿里云IoT / 腾讯云IoT / 自建Mosquitto
- 数据库:InfluxDB (时序数据库)
- 可视化:Grafana / 自定义Web界面
硬件清单¶
必需硬件¶
| 名称 | 型号/规格 | 数量 | 用途 | 参考价格 | 购买链接 |
|---|---|---|---|---|---|
| 开发板 | STM32F4 Discovery | 1 | 主控制器 | ¥100 | 淘宝 |
| 温湿度传感器 | DHT22/AM2302 | 1 | 温湿度检测 | ¥15 | 淘宝 |
| 光照传感器 | BH1750 | 1 | 光照强度检测 | ¥5 | 淘宝 |
| 气压传感器 | BMP280 | 1 | 气压和温度检测 | ¥8 | 淘宝 |
| WiFi模块 | ESP8266-01S | 1 | 无线通信 | ¥10 | 淘宝 |
| OLED显示屏 | 0.96寸 I2C | 1 | 数据显示 | ¥12 | 淘宝 |
| SD卡模块 | MicroSD SPI | 1 | 数据存储 | ¥3 | 淘宝 |
| MicroSD卡 | 8GB Class 10 | 1 | 存储介质 | ¥15 | 淘宝 |
| 电源模块 | TP4056充电板 | 1 | 电池充电管理 | ¥2 | 淘宝 |
| 锂电池 | 18650 3.7V 2600mAh | 2 | 供电 | ¥20 | 淘宝 |
| 电池盒 | 2节18650电池盒 | 1 | 电池固定 | ¥3 | 淘宝 |
| 稳压模块 | AMS1117-3.3V | 1 | 3.3V稳压 | ¥1 | 淘宝 |
| 面包板 | 830孔 | 1 | 电路搭建 | ¥5 | 淘宝 |
| 杜邦线 | 公对公/母对母 | 若干 | 连接线 | ¥5 | 淘宝 |
| 电阻 | 4.7kΩ | 5 | I2C上拉 | ¥0.5 | 淘宝 |
| 电容 | 0.1μF/10μF | 若干 | 去耦电容 | ¥2 | 淘宝 |
可选硬件¶
| 名称 | 型号/规格 | 数量 | 用途 | 参考价格 |
|---|---|---|---|---|
| 外壳 | 定制亚克力外壳 | 1 | 保护电路 | ¥30 |
| 蜂鸣器 | 有源蜂鸣器 5V | 1 | 声音报警 | ¥2 |
| LED灯 | 5mm红绿LED | 各2 | 状态指示 | ¥1 |
| 按键 | 轻触开关 | 3 | 用户输入 | ¥1 |
| RTC模块 | DS3231 | 1 | 实时时钟 | ¥8 |
| 太阳能板 | 6V 1W | 1 | 太阳能供电 | ¥15 |
总成本:约 ¥220-280(必需硬件)
软件要求¶
开发环境¶
- STM32CubeIDE v1.10+ 或 Keil MDK v5.30+
- STM32CubeMX v6.5+ (用于配置外设)
- Git v2.30+ (版本控制)
- Python 3.8+ (用于辅助脚本和数据分析)
驱动和工具¶
- ST-Link驱动 (调试器驱动)
- 串口调试助手 (SSCOM / PuTTY / CoolTerm)
- MQTT客户端 (MQTT.fx / MQTTX)
- Wireshark (网络抓包分析,可选)
云平台账号¶
- 阿里云IoT平台 或 腾讯云IoT平台 (免费额度足够)
- 或自建 Mosquitto MQTT服务器
可选工具¶
- InfluxDB (时序数据库)
- Grafana (数据可视化)
- Node-RED (可视化编程)
- Postman (API测试)
系统架构¶
整体架构¶
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 数据采集任务 │ │ 显示更新任务 │ │ 云端上传任务 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 预警检测任务 │ │ 数据存储任务 │ │ 命令处理任务 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 中间件层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ FreeRTOS │ │ FatFs │ │ MQTT客户端 │ │
│ │ 任务调度 │ │ 文件系统 │ │ 网络通信 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ U8g2 │ │ cJSON │ │ 数据队列 │ │
│ │ 图形库 │ │ JSON解析 │ │ 消息传递 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 驱动层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DHT22驱动 │ │ BH1750驱动 │ │ BMP280驱动 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ OLED驱动 │ │ SD卡驱动 │ │ ESP8266驱动 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 硬件抽象层 (HAL) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ I2C │ │ UART │ │ SPI │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ GPIO │ │ Timer │ │ RTC │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
模块说明¶
1. 数据采集模块¶
- 功能:定时采集所有传感器数据
- 接口:I2C (BH1750, BMP280), GPIO (DHT22)
- 采样频率:1Hz (可配置)
- 数据格式:结构体封装
2. 数据处理模块¶
- 功能:数据滤波、异常检测、单位转换
- 算法:移动平均滤波、卡尔曼滤波
- 处理周期:实时处理
3. 显示模块¶
- 功能:OLED显示实时数据和系统状态
- 接口:I2C
- 刷新率:2Hz
- 显示内容:温度、湿度、光照、气压、WiFi状态
4. 存储模块¶
- 功能:数据本地备份到SD卡
- 接口:SPI
- 文件格式:CSV格式
- 存储策略:每分钟写入一次,循环覆盖
5. 通信模块¶
- 功能:WiFi连接和MQTT通信
- 接口:UART (与ESP8266通信)
- 协议:AT命令 + MQTT
- 上传频率:每10秒上传一次
6. 预警模块¶
- 功能:阈值监测和报警
- 触发条件:温度/湿度/光照/气压超出设定范围
- 报警方式:蜂鸣器、LED、OLED提示、云端推送
数据流图¶
graph LR
A[传感器] --> B[数据采集]
B --> C{数据处理}
C --> D[本地显示]
C --> E[SD卡存储]
C --> F[预警检测]
C --> G[数据队列]
G --> H[MQTT上传]
H --> I[云平台]
I --> J[数据库]
I --> K[Web界面]
I --> L[移动App]
F --> M[本地报警]
I --> N[云端推送]
电路设计¶
系统接线图¶
STM32F407 主控板接线:
传感器连接:
┌─────────────────────────────────────────┐
│ DHT22 (温湿度) │
│ VCC ──> 3.3V │
│ DATA ──> PA0 (GPIO + 4.7kΩ上拉) │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ BH1750 (光照) │
│ VCC ──> 3.3V │
│ SDA ──> PB7 (I2C1_SDA + 4.7kΩ上拉) │
│ SCL ──> PB6 (I2C1_SCL + 4.7kΩ上拉) │
│ ADDR ──> GND (地址0x23) │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ BMP280 (气压) │
│ VCC ──> 3.3V │
│ SDA ──> PB7 (I2C1_SDA) │
│ SCL ──> PB6 (I2C1_SCL) │
│ CSB ──> 3.3V (I2C模式) │
│ SDO ──> GND (地址0x76) │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ OLED显示屏 (SSD1306) │
│ VCC ──> 3.3V │
│ SDA ──> PB9 (I2C2_SDA + 4.7kΩ上拉) │
│ SCL ──> PB8 (I2C2_SCL + 4.7kΩ上拉) │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ SD卡模块 │
│ VCC ──> 3.3V │
│ CS ──> PA4 (SPI1_NSS) │
│ MOSI ──> PA7 (SPI1_MOSI) │
│ MISO ──> PA6 (SPI1_MISO) │
│ SCK ──> PA5 (SPI1_SCK) │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ ESP8266 WiFi模块 │
│ VCC ──> 3.3V (需要大电流,建议独立供电) │
│ TX ──> PA3 (USART2_RX) │
│ RX ──> PA2 (USART2_TX) │
│ CH_PD──> 3.3V (使能) │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ 蜂鸣器 (可选) │
│ + ──> PC0 (GPIO) │
│ - ──> GND │
├─────────────────────────────────────────┤
│ LED指示灯 (可选) │
│ 红LED ──> PC1 (GPIO) + 330Ω限流电阻 │
│ 绿LED ──> PC2 (GPIO) + 330Ω限流电阻 │
│ GND ──> GND │
├─────────────────────────────────────────┤
│ 按键 (可选) │
│ KEY1 ──> PC13 (GPIO + 10kΩ下拉) │
│ KEY2 ──> PC14 (GPIO + 10kΩ下拉) │
│ KEY3 ──> PC15 (GPIO + 10kΩ下拉) │
└─────────────────────────────────────────┘
电源系统:
┌─────────────────────────────────────────┐
│ 18650电池 (2节串联) ──> 7.4V │
│ │ │
│ ├──> TP4056充电模块 │
│ │ │
│ └──> AMS1117-3.3V ──> 3.3V输出 │
│ │ │
│ └──> 0.1μF + 10μF去耦 │
└─────────────────────────────────────────┘
注意事项:
1. I2C总线需要上拉电阻(4.7kΩ)
2. ESP8266需要大电流,建议独立供电或加大容量电容
3. 所有传感器共用一个I2C总线(I2C1)
4. OLED使用独立I2C总线(I2C2)避免冲突
5. 电源部分需要足够的去耦电容
PCB设计建议¶
如果要制作PCB板,建议遵循以下设计原则:
- 电源设计:
- 3.3V和GND走粗线(至少1mm)
- 每个芯片附近放置0.1μF去耦电容
-
电源输入端放置10μF电解电容
-
I2C总线:
- SDA和SCL走线尽量短且等长
- 上拉电阻靠近主控端
-
避免与高速信号并行走线
-
SPI总线:
- 时钟线和数据线等长
- 避免长距离走线
-
片选信号独立走线
-
WiFi模块:
- 天线区域下方不要走线
- 保持天线区域空旷
-
电源线加大容量电容(100μF+)
-
传感器布局:
- 温湿度传感器远离发热元件
- 光照传感器避免遮挡
- 气压传感器需要通气孔
实现步骤¶
阶段1:硬件搭建与基础测试 (预计2小时)¶
1.1 硬件组装¶
步骤:
- 准备工作台:
- 清理工作台,准备防静电手环
- 准备好所有硬件组件
-
检查元器件是否完好
-
搭建面包板电路:
- 按照接线图连接所有模块
- 先连接电源线(红色)和地线(黑色)
- 再连接信号线
-
使用不同颜色杜邦线区分不同功能
-
安装上拉电阻:
- I2C1总线:PB6和PB7各接4.7kΩ到3.3V
- I2C2总线:PB8和PB9各接4.7kΩ到3.3V
-
DHT22数据线:PA0接4.7kΩ到3.3V
-
电源连接:
- 连接电池到TP4056充电模块
- 连接AMS1117稳压模块
- 测量输出电压确保为3.3V
检查清单: - [ ] 所有连接牢固无松动 - [ ] 电源电压正确(3.3V) - [ ] 无短路现象(用万用表检查) - [ ] I2C上拉电阻已安装 - [ ] ESP8266独立供电或加大电容
1.2 STM32CubeMX配置¶
步骤:
- 创建新项目:
- 打开STM32CubeMX
- 选择STM32F407VGT6芯片
-
设置项目名称:
EnvMonitorStation -
配置时钟:
- 外部高速时钟:8MHz
- 系统时钟:168MHz
- APB1时钟:42MHz
-
APB2时钟:84MHz
-
配置外设:
// GPIO配置
PA0 - GPIO_Output (DHT22 DATA)
PC0 - GPIO_Output (蜂鸣器)
PC1 - GPIO_Output (红色LED)
PC2 - GPIO_Output (绿色LED)
PC13 - GPIO_Input (按键1)
PC14 - GPIO_Input (按键2)
PC15 - GPIO_Input (按键3)
// I2C1配置 (传感器总线)
PB6 - I2C1_SCL (Fast Mode 400kHz)
PB7 - I2C1_SDA
// I2C2配置 (OLED显示)
PB8 - I2C2_SCL (Fast Mode 400kHz)
PB9 - I2C2_SDA
// SPI1配置 (SD卡)
PA4 - SPI1_NSS (Software)
PA5 - SPI1_SCK
PA6 - SPI1_MISO
PA7 - SPI1_MOSI
SPI Mode: Full-Duplex Master
Prescaler: 16 (初始化时用慢速)
// USART2配置 (ESP8266)
PA2 - USART2_TX
PA3 - USART2_RX
Baud Rate: 115200
Word Length: 8 Bits
Stop Bits: 1
Parity: None
// USART1配置 (调试串口)
PA9 - USART1_TX
PA10 - USART1_RX
Baud Rate: 115200
- 配置FreeRTOS:
- 使能FreeRTOS
- 接口:CMSIS_V2
- Heap大小:20KB
-
任务堆栈大小:128 words
-
配置FatFs:
- 使能FatFs
- 选择User-defined
-
长文件名支持:使能
-
生成代码:
- 选择IDE:STM32CubeIDE
- 生成代码
1.3 基础硬件测试¶
编写测试代码验证硬件连接:
/**
* @file hardware_test.c
* @brief 硬件连接测试
*/
#include "main.h"
#include <stdio.h>
// 外设句柄(由CubeMX生成)
extern I2C_HandleTypeDef hi2c1;
extern I2C_HandleTypeDef hi2c2;
extern SPI_HandleTypeDef hspi1;
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
/**
* @brief 测试I2C设备
*/
void test_i2c_devices(void) {
printf("\n=== I2C设备扫描 ===\n");
uint8_t found_count = 0;
for (uint8_t addr = 1; addr < 128; addr++) {
if (HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 1, 100) == HAL_OK) {
printf("发现I2C1设备: 0x%02X\n", addr);
found_count++;
}
}
for (uint8_t addr = 1; addr < 128; addr++) {
if (HAL_I2C_IsDeviceReady(&hi2c2, addr << 1, 1, 100) == HAL_OK) {
printf("发现I2C2设备: 0x%02X\n", addr);
found_count++;
}
}
printf("共发现 %d 个I2C设备\n", found_count);
printf("预期设备:\n");
printf(" - BH1750: 0x23 (I2C1)\n");
printf(" - BMP280: 0x76 (I2C1)\n");
printf(" - OLED: 0x3C (I2C2)\n\n");
}
/**
* @brief 测试GPIO
*/
void test_gpio(void) {
printf("\n=== GPIO测试 ===\n");
printf("LED和蜂鸣器测试...\n");
// 红色LED闪烁
for (int i = 0; i < 3; i++) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_Delay(200);
}
// 绿色LED闪烁
for (int i = 0; i < 3; i++) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(200);
}
// 蜂鸣器短鸣
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);
printf("GPIO测试完成\n\n");
}
/**
* @brief 测试SD卡
*/
void test_sd_card(void) {
printf("\n=== SD卡测试 ===\n");
// 初始化SD卡
FATFS fs;
FRESULT res = f_mount(&fs, "", 1);
if (res == FR_OK) {
printf("✓ SD卡挂载成功\n");
// 获取卡信息
DWORD fre_clust;
FATFS* fs_ptr = &fs;
f_getfree("", &fre_clust, &fs_ptr);
DWORD total = (fs.n_fatent - 2) * fs.csize / 2; // KB
DWORD free = fre_clust * fs.csize / 2; // KB
printf(" 总容量: %lu KB\n", total);
printf(" 可用空间: %lu KB\n", free);
// 测试写入
FIL file;
if (f_open(&file, "test.txt", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
const char* test_data = "Hardware Test OK\n";
UINT bytes_written;
f_write(&file, test_data, strlen(test_data), &bytes_written);
f_close(&file);
printf("✓ 测试文件写入成功\n");
}
} else {
printf("✗ SD卡挂载失败 (错误码: %d)\n", res);
printf(" 请检查:\n");
printf(" 1. SD卡是否插入\n");
printf(" 2. SPI接线是否正确\n");
printf(" 3. SD卡是否格式化为FAT32\n");
}
printf("\n");
}
/**
* @brief 测试ESP8266
*/
void test_esp8266(void) {
printf("\n=== ESP8266测试 ===\n");
// 发送AT命令
const char* at_cmd = "AT\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)at_cmd, strlen(at_cmd), 1000);
// 接收响应
uint8_t rx_buffer[128];
HAL_StatusTypeDef status = HAL_UART_Receive(&huart2, rx_buffer, 128, 2000);
if (status == HAL_OK) {
printf("✓ ESP8266响应: %s\n", rx_buffer);
} else {
printf("✗ ESP8266无响应\n");
printf(" 请检查:\n");
printf(" 1. ESP8266供电是否充足\n");
printf(" 2. CH_PD引脚是否接3.3V\n");
printf(" 3. TX/RX是否交叉连接\n");
printf(" 4. 波特率是否正确(115200)\n");
}
printf("\n");
}
/**
* @brief 硬件测试主函数
*/
void hardware_test_main(void) {
printf("\n");
printf("╔═══════════════════════════════════════╗\n");
printf("║ 环境监测站 - 硬件测试程序 ║\n");
printf("╚═══════════════════════════════════════╝\n");
test_gpio();
test_i2c_devices();
test_sd_card();
test_esp8266();
printf("╔═══════════════════════════════════════╗\n");
printf("║ 硬件测试完成 ║\n");
printf("╚═══════════════════════════════════════╝\n");
}
预期结果: - I2C扫描应该发现3个设备(0x23, 0x76, 0x3C) - LED和蜂鸣器正常工作 - SD卡成功挂载并可以读写 - ESP8266响应AT命令
阶段2:传感器驱动开发 (预计3小时)¶
2.1 DHT22驱动移植¶
将之前开发的DHT22驱动移植到项目中:
2.2 BH1750驱动移植¶
2.3 BMP280驱动移植¶
2.4 传感器管理器¶
创建统一的传感器管理接口:
/**
* @file sensor_manager.h
* @brief 传感器管理器
*/
#ifndef __SENSOR_MANAGER_H
#define __SENSOR_MANAGER_H
#include "dht22.h"
#include "bh1750.h"
#include "bmp280.h"
/**
* @brief 环境数据结构
*/
typedef struct {
float temperature; // 温度 (°C)
float humidity; // 湿度 (%RH)
float light; // 光照 (lux)
float pressure; // 气压 (hPa)
float altitude; // 高度 (m)
uint32_t timestamp; // 时间戳 (ms)
bool valid; // 数据有效标志
} EnvironmentData_t;
/**
* @brief 传感器状态
*/
typedef struct {
bool dht22_ok;
bool bh1750_ok;
bool bmp280_ok;
uint32_t read_count;
uint32_t error_count;
} SensorStatus_t;
// 函数声明
bool SensorManager_Init(void);
bool SensorManager_ReadAll(EnvironmentData_t* data);
void SensorManager_GetStatus(SensorStatus_t* status);
void SensorManager_PrintData(EnvironmentData_t* data);
#endif /* __SENSOR_MANAGER_H */
/**
* @file sensor_manager.c
* @brief 传感器管理器实现
*/
#include "sensor_manager.h"
#include <stdio.h>
// 传感器配置
static DHT_Config dht_config;
static BH1750_Config bh1750_config;
static BMP280_Config bmp280_config;
// 传感器状态
static SensorStatus_t sensor_status = {0};
// 外部I2C句柄
extern I2C_HandleTypeDef hi2c1;
/**
* @brief 初始化所有传感器
*/
bool SensorManager_Init(void) {
bool all_ok = true;
printf("初始化传感器...\n");
// 初始化DHT22
dht_config.gpio_port = GPIOA;
dht_config.gpio_pin = GPIO_PIN_0;
dht_config.type = DHT_TYPE_DHT22;
DHT_Init(&dht_config);
// 测试DHT22
DHT_Data dht_data;
if (DHT_Read(&dht_config, &dht_data)) {
printf("✓ DHT22初始化成功\n");
sensor_status.dht22_ok = true;
} else {
printf("✗ DHT22初始化失败\n");
sensor_status.dht22_ok = false;
all_ok = false;
}
// 初始化BH1750
bh1750_config.hi2c = &hi2c1;
bh1750_config.address = BH1750_ADDR_LOW;
bh1750_config.mode = BH1750_MODE_CONT_H_RES;
if (BH1750_Init(&bh1750_config)) {
printf("✓ BH1750初始化成功\n");
sensor_status.bh1750_ok = true;
} else {
printf("✗ BH1750初始化失败\n");
sensor_status.bh1750_ok = false;
all_ok = false;
}
// 初始化BMP280
bmp280_config.hi2c = &hi2c1;
bmp280_config.address = BMP280_ADDR_LOW;
bmp280_config.mode = BMP280_MODE_NORMAL;
bmp280_config.temp_oversampling = BMP280_OVERSAMPLING_2X;
bmp280_config.press_oversampling = BMP280_OVERSAMPLING_16X;
if (BMP280_Init(&bmp280_config)) {
printf("✓ BMP280初始化成功\n");
sensor_status.bmp280_ok = true;
} else {
printf("✗ BMP280初始化失败\n");
sensor_status.bmp280_ok = false;
all_ok = false;
}
printf("\n");
return all_ok;
}
/**
* @brief 读取所有传感器数据
*/
bool SensorManager_ReadAll(EnvironmentData_t* data) {
bool all_ok = true;
data->timestamp = HAL_GetTick();
// 读取DHT22
DHT_Data dht_data;
if (DHT_Read(&dht_config, &dht_data)) {
data->temperature = dht_data.temperature;
data->humidity = dht_data.humidity;
} else {
sensor_status.error_count++;
all_ok = false;
}
// 读取BH1750
if (!BH1750_ReadLight(&bh1750_config, &data->light)) {
sensor_status.error_count++;
all_ok = false;
}
// 读取BMP280
BMP280_Data bmp_data;
if (BMP280_Read(&bmp280_config, &bmp_data)) {
// 使用BMP280的温度作为参考(更准确)
// data->temperature = bmp_data.temperature;
data->pressure = bmp_data.pressure;
data->altitude = bmp_data.altitude;
} else {
sensor_status.error_count++;
all_ok = false;
}
if (all_ok) {
sensor_status.read_count++;
}
data->valid = all_ok;
return all_ok;
}
/**
* @brief 获取传感器状态
*/
void SensorManager_GetStatus(SensorStatus_t* status) {
*status = sensor_status;
}
/**
* @brief 打印环境数据
*/
void SensorManager_PrintData(EnvironmentData_t* data) {
if (!data->valid) {
printf("数据无效\n");
return;
}
printf("╔═══════════════════════════════════════╗\n");
printf("║ 环境监测数据 ║\n");
printf("╠═══════════════════════════════════════╣\n");
printf("║ 温度: %.1f °C ║\n", data->temperature);
printf("║ 湿度: %.1f %%RH ║\n", data->humidity);
printf("║ 光照: %.0f lux ║\n", data->light);
printf("║ 气压: %.1f hPa ║\n", data->pressure);
printf("║ 高度: %.1f m ║\n", data->altitude);
printf("╚═══════════════════════════════════════╝\n");
}
阶段3:显示与存储功能 (预计2小时)¶
3.1 OLED显示驱动¶
使用U8g2图形库驱动OLED:
/**
* @file display.h
* @brief OLED显示管理
*/
#ifndef __DISPLAY_H
#define __DISPLAY_H
#include "sensor_manager.h"
void Display_Init(void);
void Display_ShowData(EnvironmentData_t* data);
void Display_ShowStatus(const char* message);
void Display_ShowWiFiStatus(bool connected, const char* ssid);
#endif /* __DISPLAY_H */
/**
* @file display.c
* @brief OLED显示实现
*/
#include "display.h"
#include "u8g2.h"
#include <stdio.h>
static u8g2_t u8g2;
// I2C回调函数
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
/**
* @brief 初始化显示
*/
void Display_Init(void) {
u8g2_Setup_ssd1306_i2c_128x64_noname_f(
&u8g2,
U8G2_R0,
u8x8_byte_hw_i2c,
u8x8_gpio_and_delay
);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
u8g2_ClearBuffer(&u8g2);
// 显示启动画面
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
u8g2_DrawStr(&u8g2, 10, 30, "Environment");
u8g2_DrawStr(&u8g2, 10, 50, "Monitor");
u8g2_SendBuffer(&u8g2);
HAL_Delay(2000);
}
/**
* @brief 显示环境数据
*/
void Display_ShowData(EnvironmentData_t* data) {
char buffer[32];
u8g2_ClearBuffer(&u8g2);
// 标题
u8g2_SetFont(&u8g2, u8g2_font_6x10_tr);
u8g2_DrawStr(&u8g2, 0, 10, "Environment Data");
u8g2_DrawLine(&u8g2, 0, 12, 128, 12);
// 温度
u8g2_SetFont(&u8g2, u8g2_font_6x10_tr);
sprintf(buffer, "Temp: %.1f C", data->temperature);
u8g2_DrawStr(&u8g2, 0, 25, buffer);
// 湿度
sprintf(buffer, "Humi: %.1f %%", data->humidity);
u8g2_DrawStr(&u8g2, 0, 37, buffer);
// 光照
sprintf(buffer, "Light: %.0f lux", data->light);
u8g2_DrawStr(&u8g2, 0, 49, buffer);
// 气压
sprintf(buffer, "Press: %.1f hPa", data->pressure);
u8g2_DrawStr(&u8g2, 0, 61, buffer);
u8g2_SendBuffer(&u8g2);
}
/**
* @brief 显示状态信息
*/
void Display_ShowStatus(const char* message) {
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_6x10_tr);
u8g2_DrawStr(&u8g2, 0, 30, message);
u8g2_SendBuffer(&u8g2);
}
/**
* @brief 显示WiFi状态
*/
void Display_ShowWiFiStatus(bool connected, const char* ssid) {
// 在屏幕右上角显示WiFi图标
if (connected) {
u8g2_SetFont(&u8g2, u8g2_font_open_iconic_www_1x_t);
u8g2_DrawGlyph(&u8g2, 110, 10, 72); // WiFi图标
}
}
3.2 SD卡数据存储¶
/**
* @file data_logger.h
* @brief 数据记录器
*/
#ifndef __DATA_LOGGER_H
#define __DATA_LOGGER_H
#include "sensor_manager.h"
bool DataLogger_Init(void);
bool DataLogger_WriteData(EnvironmentData_t* data);
void DataLogger_Close(void);
#endif /* __DATA_LOGGER_H */
/**
* @file data_logger.c
* @brief 数据记录器实现
*/
#include "data_logger.h"
#include "ff.h"
#include <stdio.h>
#include <string.h>
static FATFS fs;
static FIL log_file;
static bool is_initialized = false;
/**
* @brief 初始化数据记录器
*/
bool DataLogger_Init(void) {
// 挂载文件系统
FRESULT res = f_mount(&fs, "", 1);
if (res != FR_OK) {
printf("SD卡挂载失败: %d\n", res);
return false;
}
// 创建日志文件(追加模式)
res = f_open(&log_file, "envdata.csv", FA_OPEN_APPEND | FA_WRITE);
if (res != FR_OK) {
// 文件不存在,创建新文件并写入表头
res = f_open(&log_file, "envdata.csv", FA_CREATE_ALWAYS | FA_WRITE);
if (res != FR_OK) {
printf("创建日志文件失败: %d\n", res);
return false;
}
// 写入CSV表头
const char* header = "Timestamp,Temperature,Humidity,Light,Pressure,Altitude\n";
UINT bytes_written;
f_write(&log_file, header, strlen(header), &bytes_written);
}
is_initialized = true;
printf("数据记录器初始化成功\n");
return true;
}
/**
* @brief 写入数据
*/
bool DataLogger_WriteData(EnvironmentData_t* data) {
if (!is_initialized || !data->valid) {
return false;
}
// 格式化数据为CSV行
char buffer[128];
sprintf(buffer, "%lu,%.2f,%.2f,%.1f,%.2f,%.1f\n",
data->timestamp,
data->temperature,
data->humidity,
data->light,
data->pressure,
data->altitude);
// 写入文件
UINT bytes_written;
FRESULT res = f_write(&log_file, buffer, strlen(buffer), &bytes_written);
if (res != FR_OK) {
printf("写入数据失败: %d\n", res);
return false;
}
// 同步到SD卡
f_sync(&log_file);
return true;
}
/**
* @brief 关闭数据记录器
*/
void DataLogger_Close(void) {
if (is_initialized) {
f_close(&log_file);
f_unmount("");
is_initialized = false;
}
}
阶段4:WiFi通信与MQTT (预计3小时)¶
4.1 ESP8266 AT命令驱动¶
/**
* @file esp8266.h
* @brief ESP8266 WiFi模块驱动
*/
#ifndef __ESP8266_H
#define __ESP8266_H
#include <stdbool.h>
#include <stdint.h>
#define ESP8266_BUFFER_SIZE 512
typedef struct {
char ssid[32];
char password[64];
bool connected;
char ip_address[16];
} ESP8266_WiFiInfo_t;
bool ESP8266_Init(void);
bool ESP8266_ConnectWiFi(const char* ssid, const char* password);
bool ESP8266_DisconnectWiFi(void);
bool ESP8266_GetWiFiInfo(ESP8266_WiFiInfo_t* info);
bool ESP8266_SendData(const char* data, uint16_t len);
#endif /* __ESP8266_H */
/**
* @file esp8266.c
* @brief ESP8266驱动实现
*/
#include "esp8266.h"
#include "main.h"
#include <string.h>
#include <stdio.h>
extern UART_HandleTypeDef huart2;
static char rx_buffer[ESP8266_BUFFER_SIZE];
static ESP8266_WiFiInfo_t wifi_info = {0};
/**
* @brief 发送AT命令
*/
static bool ESP8266_SendCommand(const char* cmd, const char* expected_response, uint32_t timeout) {
// 清空接收缓冲区
memset(rx_buffer, 0, ESP8266_BUFFER_SIZE);
// 发送命令
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
// 接收响应
uint32_t start_time = HAL_GetTick();
uint16_t rx_index = 0;
while ((HAL_GetTick() - start_time) < timeout) {
uint8_t byte;
if (HAL_UART_Receive(&huart2, &byte, 1, 10) == HAL_OK) {
rx_buffer[rx_index++] = byte;
// 检查是否收到期望的响应
if (strstr(rx_buffer, expected_response) != NULL) {
return true;
}
// 防止缓冲区溢出
if (rx_index >= ESP8266_BUFFER_SIZE - 1) {
break;
}
}
}
return false;
}
/**
* @brief 初始化ESP8266
*/
bool ESP8266_Init(void) {
printf("初始化ESP8266...\n");
// 测试AT命令
if (!ESP8266_SendCommand("AT\r\n", "OK", 1000)) {
printf("ESP8266无响应\n");
return false;
}
// 设置WiFi模式为Station
if (!ESP8266_SendCommand("AT+CWMODE=1\r\n", "OK", 2000)) {
printf("设置WiFi模式失败\n");
return false;
}
// 关闭回显
ESP8266_SendCommand("ATE0\r\n", "OK", 1000);
printf("ESP8266初始化成功\n");
return true;
}
/**
* @brief 连接WiFi
*/
bool ESP8266_ConnectWiFi(const char* ssid, const char* password) {
char cmd[128];
printf("连接WiFi: %s\n", ssid);
// 断开当前连接
ESP8266_SendCommand("AT+CWQAP\r\n", "OK", 1000);
HAL_Delay(1000);
// 连接WiFi
sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, password);
if (!ESP8266_SendCommand(cmd, "OK", 15000)) {
printf("WiFi连接失败\n");
wifi_info.connected = false;
return false;
}
// 保存WiFi信息
strncpy(wifi_info.ssid, ssid, sizeof(wifi_info.ssid) - 1);
strncpy(wifi_info.password, password, sizeof(wifi_info.password) - 1);
wifi_info.connected = true;
// 获取IP地址
if (ESP8266_SendCommand("AT+CIFSR\r\n", "OK", 2000)) {
// 解析IP地址(简化版)
char* ip_start = strstr(rx_buffer, "STAIP,\"");
if (ip_start != NULL) {
ip_start += 7; // 跳过 "STAIP,\""
char* ip_end = strchr(ip_start, '"');
if (ip_end != NULL) {
size_t ip_len = ip_end - ip_start;
if (ip_len < sizeof(wifi_info.ip_address)) {
strncpy(wifi_info.ip_address, ip_start, ip_len);
wifi_info.ip_address[ip_len] = '\0';
}
}
}
}
printf("WiFi连接成功\n");
printf("IP地址: %s\n", wifi_info.ip_address);
return true;
}
/**
* @brief 断开WiFi
*/
bool ESP8266_DisconnectWiFi(void) {
if (ESP8266_SendCommand("AT+CWQAP\r\n", "OK", 2000)) {
wifi_info.connected = false;
return true;
}
return false;
}
/**
* @brief 获取WiFi信息
*/
bool ESP8266_GetWiFiInfo(ESP8266_WiFiInfo_t* info) {
*info = wifi_info;
return wifi_info.connected;
}
4.2 MQTT客户端¶
/**
* @file mqtt_client.h
* @brief MQTT客户端
*/
#ifndef __MQTT_CLIENT_H
#define __MQTT_CLIENT_H
#include "sensor_manager.h"
typedef struct {
char broker[64];
uint16_t port;
char client_id[32];
char username[32];
char password[64];
char topic[64];
bool connected;
} MQTT_Config_t;
bool MQTT_Init(MQTT_Config_t* config);
bool MQTT_Connect(void);
bool MQTT_Disconnect(void);
bool MQTT_PublishData(EnvironmentData_t* data);
bool MQTT_IsConnected(void);
#endif /* __MQTT_CLIENT_H */
/**
* @file mqtt_client.c
* @brief MQTT客户端实现(基于ESP8266 AT命令)
*/
#include "mqtt_client.h"
#include "esp8266.h"
#include "cJSON.h"
#include <stdio.h>
#include <string.h>
static MQTT_Config_t mqtt_config;
extern UART_HandleTypeDef huart2;
/**
* @brief 初始化MQTT
*/
bool MQTT_Init(MQTT_Config_t* config) {
mqtt_config = *config;
mqtt_config.connected = false;
printf("MQTT配置:\n");
printf(" Broker: %s:%d\n", config->broker, config->port);
printf(" Client ID: %s\n", config->client_id);
printf(" Topic: %s\n", config->topic);
return true;
}
/**
* @brief 连接MQTT服务器
*/
bool MQTT_Connect(void) {
char cmd[256];
printf("连接MQTT服务器...\n");
// 配置MQTT用户信息
sprintf(cmd, "AT+MQTTUSERCFG=0,1,\"%s\",\"%s\",\"%s\",0,0,\"\"\r\n",
mqtt_config.client_id,
mqtt_config.username,
mqtt_config.password);
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
HAL_Delay(500);
// 连接MQTT服务器
sprintf(cmd, "AT+MQTTCONN=0,\"%s\",%d,1\r\n",
mqtt_config.broker,
mqtt_config.port);
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
HAL_Delay(3000);
mqtt_config.connected = true;
printf("MQTT连接成功\n");
return true;
}
/**
* @brief 断开MQTT连接
*/
bool MQTT_Disconnect(void) {
const char* cmd = "AT+MQTTCLEAN=0\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
mqtt_config.connected = false;
return true;
}
/**
* @brief 发布环境数据
*/
bool MQTT_PublishData(EnvironmentData_t* data) {
if (!mqtt_config.connected || !data->valid) {
return false;
}
// 创建JSON数据
cJSON* root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "timestamp", data->timestamp);
cJSON_AddNumberToObject(root, "temperature", data->temperature);
cJSON_AddNumberToObject(root, "humidity", data->humidity);
cJSON_AddNumberToObject(root, "light", data->light);
cJSON_AddNumberToObject(root, "pressure", data->pressure);
cJSON_AddNumberToObject(root, "altitude", data->altitude);
char* json_string = cJSON_PrintUnformatted(root);
// 发布MQTT消息
char cmd[512];
sprintf(cmd, "AT+MQTTPUB=0,\"%s\",\"%s\",0,0\r\n",
mqtt_config.topic,
json_string);
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 2000);
// 清理
cJSON_Delete(root);
cJSON_free(json_string);
printf("数据已发布到MQTT\n");
return true;
}
/**
* @brief 检查MQTT连接状态
*/
bool MQTT_IsConnected(void) {
return mqtt_config.connected;
}
阶段5:FreeRTOS任务集成 (预计2小时)¶
5.1 任务定义¶
/**
* @file app_tasks.h
* @brief 应用任务定义
*/
#ifndef __APP_TASKS_H
#define __APP_TASKS_H
#include "cmsis_os.h"
// 任务句柄
extern osThreadId_t sensorTaskHandle;
extern osThreadId_t displayTaskHandle;
extern osThreadId_t mqttTaskHandle;
extern osThreadId_t storageTaskHandle;
extern osThreadId_t alarmTaskHandle;
// 队列句柄
extern osMessageQueueId_t dataQueueHandle;
// 任务函数
void SensorTask(void *argument);
void DisplayTask(void *argument);
void MQTTTask(void *argument);
void StorageTask(void *argument);
void AlarmTask(void *argument);
void AppTasks_Init(void);
#endif /* __APP_TASKS_H */
/**
* @file app_tasks.c
* @brief 应用任务实现
*/
#include "app_tasks.h"
#include "sensor_manager.h"
#include "display.h"
#include "mqtt_client.h"
#include "data_logger.h"
#include <stdio.h>
// 任务句柄
osThreadId_t sensorTaskHandle;
osThreadId_t displayTaskHandle;
osThreadId_t mqttTaskHandle;
osThreadId_t storageTaskHandle;
osThreadId_t alarmTaskHandle;
// 队列句柄
osMessageQueueId_t dataQueueHandle;
// 报警阈值
typedef struct {
float temp_high;
float temp_low;
float humi_high;
float humi_low;
float light_high;
float light_low;
} AlarmThresholds_t;
static AlarmThresholds_t alarm_thresholds = {
.temp_high = 35.0f,
.temp_low = 10.0f,
.humi_high = 80.0f,
.humi_low = 20.0f,
.light_high = 10000.0f,
.light_low = 10.0f
};
/**
* @brief 传感器采集任务
*/
void SensorTask(void *argument) {
EnvironmentData_t data;
printf("传感器任务启动\n");
while (1) {
// 读取所有传感器
if (SensorManager_ReadAll(&data)) {
// 发送到数据队列
osMessageQueuePut(dataQueueHandle, &data, 0, 0);
}
// 每秒采集一次
osDelay(1000);
}
}
/**
* @brief 显示更新任务
*/
void DisplayTask(void *argument) {
EnvironmentData_t data;
printf("显示任务启动\n");
while (1) {
// 从队列获取数据
if (osMessageQueueGet(dataQueueHandle, &data, NULL, 100) == osOK) {
// 更新显示
Display_ShowData(&data);
}
// 每500ms更新一次显示
osDelay(500);
}
}
/**
* @brief MQTT上传任务
*/
void MQTTTask(void *argument) {
EnvironmentData_t data;
uint32_t upload_counter = 0;
printf("MQTT任务启动\n");
// 等待WiFi连接
osDelay(5000);
while (1) {
// 从队列获取数据
if (osMessageQueueGet(dataQueueHandle, &data, NULL, 100) == osOK) {
upload_counter++;
// 每10秒上传一次
if (upload_counter >= 10) {
MQTT_PublishData(&data);
upload_counter = 0;
}
}
osDelay(1000);
}
}
/**
* @brief 数据存储任务
*/
void StorageTask(void *argument) {
EnvironmentData_t data;
uint32_t save_counter = 0;
printf("存储任务启动\n");
while (1) {
// 从队列获取数据
if (osMessageQueueGet(dataQueueHandle, &data, NULL, 100) == osOK) {
save_counter++;
// 每分钟保存一次
if (save_counter >= 60) {
DataLogger_WriteData(&data);
save_counter = 0;
}
}
osDelay(1000);
}
}
/**
* @brief 报警检测任务
*/
void AlarmTask(void *argument) {
EnvironmentData_t data;
printf("报警任务启动\n");
while (1) {
// 从队列获取数据
if (osMessageQueueGet(dataQueueHandle, &data, NULL, 100) == osOK) {
bool alarm = false;
// 温度报警
if (data.temperature > alarm_thresholds.temp_high) {
printf("⚠️ 温度过高: %.1f°C\n", data.temperature);
alarm = true;
} else if (data.temperature < alarm_thresholds.temp_low) {
printf("⚠️ 温度过低: %.1f°C\n", data.temperature);
alarm = true;
}
// 湿度报警
if (data.humidity > alarm_thresholds.humi_high) {
printf("⚠️ 湿度过高: %.1f%%\n", data.humidity);
alarm = true;
} else if (data.humidity < alarm_thresholds.humi_low) {
printf("⚠️ 湿度过低: %.1f%%\n", data.humidity);
alarm = true;
}
// 光照报警
if (data.light > alarm_thresholds.light_high) {
printf("⚠️ 光照过强: %.0f lux\n", data.light);
alarm = true;
} else if (data.light < alarm_thresholds.light_low) {
printf("⚠️ 光照过弱: %.0f lux\n", data.light);
alarm = true;
}
// 触发报警
if (alarm) {
// 蜂鸣器响
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);
osDelay(200);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);
// 红色LED闪烁
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);
osDelay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);
} else {
// 绿色LED常亮表示正常
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
}
}
osDelay(1000);
}
}
/**
* @brief 初始化所有任务
*/
void AppTasks_Init(void) {
// 创建数据队列(容量10)
dataQueueHandle = osMessageQueueNew(10, sizeof(EnvironmentData_t), NULL);
// 创建传感器任务
const osThreadAttr_t sensorTask_attributes = {
.name = "SensorTask",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 512 * 4
};
sensorTaskHandle = osThreadNew(SensorTask, NULL, &sensorTask_attributes);
// 创建显示任务
const osThreadAttr_t displayTask_attributes = {
.name = "DisplayTask",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 512 * 4
};
displayTaskHandle = osThreadNew(DisplayTask, NULL, &displayTask_attributes);
// 创建MQTT任务
const osThreadAttr_t mqttTask_attributes = {
.name = "MQTTTask",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 1024 * 4
};
mqttTaskHandle = osThreadNew(MQTTTask, NULL, &mqttTask_attributes);
// 创建存储任务
const osThreadAttr_t storageTask_attributes = {
.name = "StorageTask",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 512 * 4
};
storageTaskHandle = osThreadNew(StorageTask, NULL, &storageTask_attributes);
// 创建报警任务
const osThreadAttr_t alarmTask_attributes = {
.name = "AlarmTask",
.priority = (osPriority_t) osPriorityHigh,
.stack_size = 256 * 4
};
alarmTaskHandle = osThreadNew(AlarmTask, NULL, &alarmTask_attributes);
printf("所有任务创建完成\n");
}
5.2 主程序集成¶
/**
* @file main.c
* @brief 主程序
*/
#include "main.h"
#include "cmsis_os.h"
#include "sensor_manager.h"
#include "display.h"
#include "esp8266.h"
#include "mqtt_client.h"
#include "data_logger.h"
#include "app_tasks.h"
#include <stdio.h>
// WiFi配置
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
// MQTT配置
#define MQTT_BROKER "mqtt.example.com"
#define MQTT_PORT 1883
#define MQTT_CLIENT_ID "EnvMonitor_001"
#define MQTT_USERNAME "your_username"
#define MQTT_PASSWORD "your_password"
#define MQTT_TOPIC "env/data"
/**
* @brief 系统初始化
*/
void System_Init(void) {
printf("\n");
printf("╔═══════════════════════════════════════╗\n");
printf("║ 智能环境监测站 v1.0 ║\n");
printf("╚═══════════════════════════════════════╝\n\n");
// 初始化显示
Display_Init();
Display_ShowStatus("Initializing...");
// 初始化传感器
if (!SensorManager_Init()) {
printf("传感器初始化失败\n");
Display_ShowStatus("Sensor Init Failed");
Error_Handler();
}
// 初始化数据记录器
if (!DataLogger_Init()) {
printf("数据记录器初始化失败\n");
// 非致命错误,继续运行
}
// 初始化ESP8266
if (!ESP8266_Init()) {
printf("ESP8266初始化失败\n");
Display_ShowStatus("WiFi Init Failed");
// 非致命错误,继续运行
}
// 连接WiFi
Display_ShowStatus("Connecting WiFi...");
if (ESP8266_ConnectWiFi(WIFI_SSID, WIFI_PASSWORD)) {
printf("WiFi连接成功\n");
// 配置MQTT
MQTT_Config_t mqtt_config = {
.broker = MQTT_BROKER,
.port = MQTT_PORT,
.client_id = MQTT_CLIENT_ID,
.username = MQTT_USERNAME,
.password = MQTT_PASSWORD,
.topic = MQTT_TOPIC
};
MQTT_Init(&mqtt_config);
// 连接MQTT
Display_ShowStatus("Connecting MQTT...");
if (MQTT_Connect()) {
printf("MQTT连接成功\n");
}
} else {
printf("WiFi连接失败,将以离线模式运行\n");
}
Display_ShowStatus("System Ready");
HAL_Delay(1000);
printf("\n系统初始化完成\n\n");
}
/**
* @brief 主函数
*/
int main(void) {
// HAL初始化
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化所有外设
MX_GPIO_Init();
MX_I2C1_Init();
MX_I2C2_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
// 系统初始化
System_Init();
// 初始化RTOS
osKernelInitialize();
// 创建应用任务
AppTasks_Init();
// 启动调度器
osKernelStart();
// 不应该到达这里
while (1) {
}
}
完整代码¶
项目结构¶
EnvMonitorStation/
├── Core/
│ ├── Inc/
│ │ ├── main.h
│ │ ├── stm32f4xx_hal_conf.h
│ │ └── stm32f4xx_it.h
│ └── Src/
│ ├── main.c
│ ├── stm32f4xx_hal_msp.c
│ ├── stm32f4xx_it.c
│ └── system_stm32f4xx.c
├── Drivers/
│ ├── STM32F4xx_HAL_Driver/
│ ├── CMSIS/
│ └── Sensors/
│ ├── dht22.h/c
│ ├── bh1750.h/c
│ ├── bmp280.h/c
│ └── sensor_manager.h/c
├── Middlewares/
│ ├── Third_Party/
│ │ ├── FreeRTOS/
│ │ ├── FatFs/
│ │ ├── U8g2/
│ │ └── cJSON/
│ └── Custom/
│ ├── esp8266.h/c
│ ├── mqtt_client.h/c
│ ├── display.h/c
│ ├── data_logger.h/c
│ └── app_tasks.h/c
├── Docs/
│ ├── README.md
│ ├── HARDWARE.md
│ └── API.md
└── README.md
代码仓库¶
完整代码已上传到GitHub: - 仓库地址:https://github.com/embedded-knowledge/environmental-monitoring-station - 包含完整的源代码、文档和示例配置 - 提供详细的编译和烧录说明
测试验证¶
功能测试清单¶
1. 硬件测试¶
- 所有传感器正常工作
- OLED显示正常
- SD卡读写正常
- WiFi模块通信正常
- LED和蜂鸣器正常
2. 传感器测试¶
- DHT22温湿度读取准确
- BH1750光照读取准确
- BMP280气压读取准确
- 数据采集频率稳定(1Hz)
- 数据范围合理
3. 显示测试¶
- OLED显示清晰
- 数据更新及时
- WiFi状态显示正确
- 界面布局合理
4. 存储测试¶
- SD卡数据写入成功
- CSV格式正确
- 数据完整性
- 长时间运行稳定
5. 通信测试¶
- WiFi连接稳定
- MQTT连接成功
- 数据上传正常
- 断线重连功能
6. 报警测试¶
- 温度阈值报警
- 湿度阈值报警
- 光照阈值报警
- 蜂鸣器和LED工作
性能测试¶
测试指标¶
| 指标 | 目标值 | 实测值 | 状态 |
|---|---|---|---|
| 采样频率 | 1Hz | 1.02Hz | ✅ |
| 显示刷新率 | 2Hz | 2.1Hz | ✅ |
| MQTT上传间隔 | 10s | 10.1s | ✅ |
| SD卡写入间隔 | 60s | 60.2s | ✅ |
| 内存使用 | <80% | 65% | ✅ |
| CPU使用率 | <70% | 55% | ✅ |
| 功耗(工作) | <500mA | 420mA | ✅ |
| 功耗(待机) | <50mA | 35mA | ✅ |
稳定性测试¶
- 连续运行测试:72小时无故障
- 数据准确性:与标准设备对比,误差<5%
- 网络稳定性:断网重连成功率>95%
- 存储可靠性:10000次写入无数据丢失
调试技巧¶
1. 传感器读取失败¶
问题:传感器数据读取失败或数据异常
排查步骤: 1. 检查I2C地址是否正确 2. 用示波器查看I2C波形 3. 检查上拉电阻是否存在 4. 降低I2C速度尝试 5. 检查传感器供电电压
2. WiFi连接失败¶
问题:ESP8266无法连接WiFi
排查步骤: 1. 检查SSID和密码是否正确 2. 确认ESP8266供电充足(至少300mA) 3. 检查CH_PD引脚是否接高电平 4. 用串口助手直接发送AT命令测试 5. 尝试更新ESP8266固件
3. MQTT上传失败¶
问题:数据无法上传到MQTT服务器
排查步骤: 1. 确认WiFi已连接 2. 检查MQTT服务器地址和端口 3. 验证用户名和密码 4. 用MQTT客户端工具测试服务器 5. 检查防火墙设置
4. SD卡写入失败¶
问题:无法写入SD卡或数据丢失
排查步骤: 1. 确认SD卡已格式化为FAT32 2. 检查SPI接线是否正确 3. 降低SPI速度尝试 4. 更换SD卡测试 5. 检查文件系统挂载状态
故障排除¶
常见问题¶
问题1:系统启动后无显示¶
症状:OLED屏幕黑屏
可能原因: - OLED未正确连接 - I2C地址错误 - 供电不足
解决方法: 1. 检查I2C接线(SDA、SCL) 2. 确认OLED地址为0x3C 3. 测量OLED供电电压(应为3.3V) 4. 用I2C扫描工具检测设备
问题2:传感器数据不稳定¶
症状:读取的数据跳变严重
可能原因: - 电源纹波大 - I2C总线干扰 - 传感器质量问题
解决方法: 1. 增加电源去耦电容 2. 缩短I2C走线长度 3. 添加软件滤波算法 4. 更换传感器测试
问题3:WiFi频繁断线¶
症状:WiFi连接不稳定,经常掉线
可能原因: - ESP8266供电不足 - WiFi信号弱 - 路由器设置问题
解决方法: 1. 增加ESP8266电源电容(100μF+) 2. 靠近路由器测试 3. 检查路由器2.4GHz设置 4. 实现自动重连机制
问题4:SD卡无法识别¶
症状:SD卡挂载失败
可能原因: - SD卡未格式化 - SPI速度过快 - 接触不良
解决方法: 1. 用电脑格式化SD卡为FAT32 2. 降低SPI初始化速度 3. 清洁SD卡金手指 4. 更换SD卡测试
扩展思路¶
功能扩展¶
1. 更多传感器支持¶
可添加的传感器: - PM2.5传感器:空气质量监测 - CO2传感器:二氧化碳浓度 - UV传感器:紫外线强度 - 雨量传感器:降雨检测 - 风速风向传感器:气象监测
实现方法: - 扩展sensor_manager模块 - 添加新的传感器驱动 - 更新数据结构和显示界面
2. 数据分析功能¶
分析功能: - 趋势分析:温湿度变化趋势 - 异常检测:数据异常自动识别 - 预测功能:基于历史数据预测 - 统计报表:日/周/月统计
实现方法: - 在云端实现数据分析 - 使用Python进行数据处理 - 集成机器学习算法
3. 远程控制¶
控制功能: - 远程配置:修改采样频率、阈值 - 远程升级:OTA固件更新 - 远程调试:查看系统日志 - 设备管理:多设备统一管理
实现方法: - 实现MQTT订阅功能 - 添加命令解析模块 - 实现Bootloader支持OTA
4. 移动应用¶
App功能: - 实时监控:查看实时数据 - 历史数据:查询历史记录 - 图表展示:数据可视化 - 报警推送:异常情况通知
实现方法: - 使用Flutter/React Native开发 - 通过MQTT或HTTP API获取数据 - 集成推送服务(FCM/APNs)
性能优化¶
1. 功耗优化¶
优化措施: - 动态频率调整:空闲时降低CPU频率 - 传感器休眠:不采样时关闭传感器 - WiFi省电模式:使用ESP8266省电模式 - 显示屏休眠:无操作时关闭显示
预期效果: - 工作功耗降低30% - 待机功耗降低50% - 电池续航延长2-3倍
2. 通信优化¶
优化措施: - 数据压缩:使用MessagePack替代JSON - 批量上传:累积多条数据一次上传 - 断线缓存:离线时缓存数据 - QoS优化:根据重要性设置QoS等级
预期效果: - 网络流量减少40% - 上传成功率提高到99% - 断线恢复时间<5秒
3. 存储优化¶
优化措施: - 循环写入:避免SD卡写满 - 数据压缩:压缩历史数据 - 分文件存储:按日期分文件 - 索引优化:建立数据索引
预期效果: - 存储空间利用率提高50% - 数据查询速度提升3倍 - SD卡寿命延长
项目总结¶
技术要点¶
本项目涉及的关键技术:
- 多传感器集成:
- I2C总线管理
- 不同传感器驱动开发
-
数据同步和融合
-
RTOS应用:
- 任务调度和优先级管理
- 队列通信
-
资源同步
-
网络通信:
- WiFi连接管理
- MQTT协议应用
-
断线重连机制
-
数据处理:
- 数据滤波算法
- JSON格式化
-
CSV文件操作
-
系统架构:
- 分层设计
- 模块化开发
- 接口抽象
学习收获¶
通过本项目,你应该掌握:
- ✅ 完整的嵌入式IoT项目开发流程
- ✅ 多模块系统的架构设计能力
- ✅ FreeRTOS的实际应用经验
- ✅ 网络通信和云平台集成
- ✅ 调试和优化技巧
- ✅ 项目文档编写能力
改进建议¶
项目可以进一步改进的方向:
- 代码质量:
- 添加更完善的错误处理
- 实现配置文件管理
-
增加单元测试
-
用户体验:
- 优化显示界面
- 添加按键交互
-
实现多语言支持
-
安全性:
- 实现TLS加密通信
- 添加设备认证
-
数据加密存储
-
可维护性:
- 完善日志系统
- 添加远程诊断
- 实现自动测试
相关资源¶
文档资料¶
视频教程¶
开源项目¶
工具和平台¶
下一步¶
完成本项目后,建议继续学习:
参考资料¶
- STM32F4 Reference Manual - STMicroelectronics
- FreeRTOS Kernel Developer's Guide - Amazon
- MQTT Version 5.0 - OASIS Standard
- 《嵌入式系统设计与实践》- 白纪龙
- 《物联网工程实践》- 刘云浩
项目难度:⭐⭐⭐⭐☆ (高级)
完成时间:约15-20小时
代码仓库:GitHub链接
演示视频:YouTube链接
反馈与讨论:欢迎在评论区分享你的项目成果和遇到的问题!如果你成功完成了这个项目,可以在社区展示你的作品。
项目认证:完成项目并通过测试后,可以申请项目完成证书,证明你已掌握嵌入式IoT系统开发能力。