核心目标:整合前 14 天所有核心技术(传感器采集、多任务管理、本地存储、网络通信、工业协议、低功耗),开发一个 “智能环境监测终端”—— 实现温湿度实时采集、本地日志存储、工业总线(Modbus)与云平台(阿里云)双路数据上传、远程 / 本地阈值控制、低功耗优化的完整系统,体验从 “模块开发” 到 “系统集成” 的全流程,模拟真实嵌入式产品的开发逻辑。
一、项目需求与整体架构(30 分钟)
1. 核心功能清单
- 数据采集:AHT10 传感器 1 秒采集一次温湿度,支持本地校准(±0.5℃/±2% RH);
- 存储功能:SD 卡日志(按小时生成文件,格式:
20251106_10.log),记录时间戳 + 温湿度 + 报警状态; - 双路上传:
- 工业侧:作为 Modbus 从机(地址 0x01),支持 PLC 通过 RS485 读取数据、修改阈值;
- 云端:通过 ESP8266 每 30 秒向阿里云 IoT 平台上传数据,支持手机 APP 查看;
- 报警功能:温湿度超阈值时,LED 闪烁 + 蜂鸣器报警,同时日志标记 “ALARM”;
- 低功耗优化:无操作时 30 秒后进入停止模式(功耗≤50μA),可通过按键或 Modbus 指令唤醒;
- 配置管理:SD 卡
config.ini存储 WiFi 密码、Modbus 地址、报警阈值等,掉电保留。
2. 硬件架构(整合多外设)
| 外设模块 | 核心功能 | 与 STM32 连接方式 | 关键参数 |
|---|---|---|---|
| AHT10 | 温湿度采集 | I2C1(PB6=SCL,PB7=SDA) | 精度 ±0.3℃/±2% RH |
| SD 卡模块 | 日志存储 | SPI1(PA5=SCK,PA6=MISO,PA7=MOSI,PA4=CS) | FAT32 格式,最大 32GB |
| ESP8266 | 阿里云上传 | USART2(PA2=TX,PA3=RX) | 波特率 115200,支持 MQTT |
| RS485 模块 | Modbus 从机通信 | USART1(PA9=TX,PA10=RX,PA8=DE/RE) | 波特率 9600,地址 0x01 |
| 按键 | 唤醒 / 校准 | PA0(唤醒)、PA1(校准) | 下拉输入,长按触发校准 |
| LED + 蜂鸣器 | 报警指示 | PC13(LED)、PB8(蜂鸣器) | 低电平触发 |
| RTC | 时间戳生成 | LSE(32.768kHz 晶振) | 误差≤1 分钟 / 天 |
3. 软件架构(FreeRTOS 任务分配)
采用 “高内聚低耦合” 设计,用 5 个任务实现功能拆分,通过全局变量 + 消息队列通信:
| 任务名称 | 优先级 | 核心功能 | 周期 / 触发方式 |
|---|---|---|---|
| 数据采集任务 | 3 | 读 AHT10、校准补偿、更新全局数据 | 1 秒定时 |
| 存储日志任务 | 2 | 将数据写入 SD 卡,按小时切分文件 | 10 秒定时 |
| 通信转发任务 | 2 | 向 Modbus 主机和阿里云双路上传数据 | 30 秒定时 |
| 报警处理任务 | 4 | 对比数据与阈值,触发 LED + 蜂鸣器报警 | 200ms 轮询 |
| 系统管理任务 | 1 | 处理按键输入、低功耗控制、配置文件读取 | 500ms 轮询 |
任务间通信:
- 全局变量:
env_data(存储最新温湿度、时间戳、报警状态); - 消息队列:
cmd_queue(接收 Modbus / 云端的控制指令,如修改阈值)。
二、核心模块代码实现(90 分钟,整合关键逻辑)
1. 全局数据结构与初始化
c
/* USER CODE BEGIN 0 */
#include <string.h>
#include <stdio.h>
#include "ff.h"
#include "rtc.h"
#include "aht10.h" // 复用AHT10驱动
#include "modbus.h" // 复用Modbus从机代码
#include "esp8266.h" // 复用ESP8266控制代码
#include "fatfs.h" // 复用FATFS文件操作
// 环境数据结构体(任务间共享)
typedef struct {
float temp; // 温度(℃)
float humi; // 湿度(%RH)
RTC_TimeTypeDef time; // 时间
RTC_DateTypeDef date; // 日期
uint8_t alarm; // 报警状态(0=正常,1=温度超标,2=湿度超标,3=双超标)
} EnvData;
EnvData env_data = {0};
// 系统配置结构体(从config.ini读取)
typedef struct {
float temp_th; // 温度阈值(℃)
float humi_th; // 湿度阈值(%RH)
char wifi_ssid[32]; // WiFi名称
char wifi_pwd[32]; // WiFi密码
uint8_t modbus_addr; // Modbus地址
} SysConfig;
SysConfig sys_cfg = {30.0, 60.0, "IoT_AP", "12345678", 0x01};
// FreeRTOS消息队列(存储控制指令)
QueueHandle_t cmd_queue;
/* USER CODE END 0 */
2. 数据采集任务(含校准逻辑)
c
// 数据采集任务:读传感器→校准→更新全局数据
void DataCollect_Task(void const * argument) {
AHT10_Init(&hi2c1); // 初始化传感器
float temp_offset = 0.0, humi_offset = 0.0; // 校准偏移量(默认0)
for(;;) {
// 1. 读取原始温湿度
AHT10_ReadData(&hi2c1, &env_data.temp, &env_data.humi);
// 2. 应用校准偏移(通过按键触发校准,此处简化为固定值)
env_data.temp += temp_offset;
env_data.humi += humi_offset;
// 3. 获取当前时间戳
HAL_RTC_GetTime(&hrtc, &env_data.time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &env_data.date, RTC_FORMAT_BIN);
vTaskDelay(1000); // 1秒周期
}
}
3. 存储日志任务(按小时切分文件)
c
// 存储日志任务:按时间创建文件,写入带报警标记的数据
void LogStore_Task(void const * argument) {
FIL log_file;
FRESULT fr;
UINT bw;
char file_name[32], log_buf[128];
uint8_t last_hour = 25; // 初始值(大于23)
for(;;) {
// 1. 检查是否需要创建新文件(小时变化时)
if (env_data.time.Hours != last_hour) {
// 格式化文件名:20251106_10.log(年月日_小时)
sprintf(file_name, "0:%04d%02d%02d_%02d.log",
2000+env_data.date.Year, env_data.date.Month, env_data.date.Date,
env_data.time.Hours);
// 关闭之前的文件(若存在)
if (last_hour != 25) f_close(&log_file);
// 打开新文件(追加模式)
fr = f_open(&log_file, file_name, FA_WRITE | FA_OPEN_APPEND | FA_CREATE_ALWAYS);
if (fr != FR_OK) {
HAL_UART_Transmit(&huart1, (uint8_t*)"日志文件创建失败!\r\n", 18, 100);
}
last_hour = env_data.time.Hours;
}
// 2. 格式化日志内容(含报警标记)
sprintf(log_buf, "%04d-%02d-%02d %02d:%02d:%02d, temp=%.1f, humi=%.1f, alarm=%d\r\n",
2000+env_data.date.Year, env_data.date.Month, env_data.date.Date,
env_data.time.Hours, env_data.time.Minutes, env_data.time.Seconds,
env_data.temp, env_data.humi, env_data.alarm);
// 3. 写入当前日志文件
if (f_write(&log_file, log_buf, strlen(log_buf), &bw) != FR_OK) {
HAL_UART_Transmit(&huart1, (uint8_t*)"日志写入失败!\r\n", 16, 100);
}
vTaskDelay(10000); // 10秒周期
}
}
4. 通信转发任务(双路上传)
c
// 通信转发任务:向Modbus主机和阿里云上传数据
void CommForward_Task(void const * argument) {
// 初始化Modbus从机和ESP8266
Modbus_Init(sys_cfg.modbus_addr); // 从配置读取地址
ESP8266_Init(sys_cfg.wifi_ssid, sys_cfg.wifi_pwd); // 从配置读取WiFi信息
Aliyun_Connect(); // 连接阿里云
char mqtt_payload[128];
for(;;) {
// 1. Modbus从机:更新保持寄存器(供PLC读取)
modbus_hold_reg[0] = (uint16_t)(env_data.temp * 10); // 温度放大10倍
modbus_hold_reg[1] = (uint16_t)(env_data.humi * 10); // 湿度放大10倍
modbus_hold_reg[2] = sys_cfg.temp_th * 10; // 温度阈值
modbus_hold_reg[3] = sys_cfg.humi_th * 10; // 湿度阈值
// 2. 阿里云:发布JSON格式数据
sprintf(mqtt_payload, "{\"temp\":%.1f,\"humi\":%.1f,\"alarm\":%d}",
env_data.temp, env_data.humi, env_data.alarm);
Aliyun_Publish(mqtt_payload); // 发布到云平台
vTaskDelay(30000); // 30秒周期
}
}
5. 报警处理任务(实时响应)
c
// 报警处理任务:对比阈值,触发报警
void AlarmHandle_Task(void const * argument) {
uint8_t last_alarm = 0;
for(;;) {
// 1. 判断报警状态
env_data.alarm = 0;
if (env_data.temp > sys_cfg.temp_th) env_data.alarm |= 1;
if (env_data.humi > sys_cfg.humi_th) env_data.alarm |= 2;
// 2. 报警状态变化时执行动作
if (env_data.alarm != last_alarm) {
if (env_data.alarm != 0) {
// 超标:LED闪烁+蜂鸣器鸣叫
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); // 蜂鸣器响
} else {
// 恢复正常:关闭报警
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); // 蜂鸣器关
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // LED灭
}
last_alarm = env_data.alarm;
}
// 3. 报警中:LED闪烁(200ms周期)
if (env_data.alarm != 0) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
vTaskDelay(200); // 200ms周期(快速响应)
}
}
6. 系统管理任务(配置 + 低功耗)
c
// 系统管理任务:处理配置、按键、低功耗
void SysManage_Task(void const * argument) {
uint8_t key_press = 0;
uint32_t idle_time = 0; // 空闲计时(毫秒)
// 初始化:读取配置文件
ReadConfigFromSD(&sys_cfg);
for(;;) {
// 1. 检测按键(唤醒/校准)
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0) { // 唤醒按键
idle_time = 0; // 重置空闲计时
key_press = 1;
}
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == 0) { // 校准按键(长按2秒)
// 执行校准逻辑(此处简化为记录当前值为基准)
HAL_UART_Transmit(&huart1, (uint8_t*)"开始校准...\r\n", 14, 100);
vTaskDelay(2000); // 等待长按
}
// 2. 处理消息队列(Modbus/云端指令)
char cmd[32];
if (xQueueReceive(cmd_queue, cmd, 0) == pdPASS) {
// 解析指令(如"temp_th=32.0")
if (strstr(cmd, "temp_th=")) {
sscanf(cmd, "temp_th=%f", &sys_cfg.temp_th);
WriteConfigToSD(&sys_cfg); // 保存到SD卡
} else if (strstr(cmd, "humi_th=")) {
sscanf(cmd, "humi_th=%f", &sys_cfg.humi_th);
WriteConfigToSD(&sys_cfg);
}
idle_time = 0; // 有指令,不进入低功耗
}
// 3. 低功耗控制(30秒无操作进入停止模式)
if (idle_time >= 30000 && !key_press) {
HAL_UART_Transmit(&huart1, (uint8_t*)"进入低功耗模式...\r\n", 20, 100);
EnterStopMode(); // 进入停止模式(唤醒后重新计时)
idle_time = 0;
} else {
idle_time += 500; // 500ms周期累加
}
key_press = 0;
vTaskDelay(500);
}
}
三、系统测试与优化(60 分钟)
1. 模块测试(逐个验证)
- 数据采集:用手捂住 AHT10,观察串口输出温湿度是否上升;
- 日志存储:SD 卡生成按小时命名的日志文件,内容包含时间戳和数据;
- Modbus 通信:PLC(或 Modbus Poll)读取寄存器 0x0000-0x0003,验证温湿度和阈值正确;
- 云平台上传:阿里云 IoT 控制台查看实时数据,手机 APP 同步显示;
- 报警功能:温湿度超过阈值时,LED 闪烁 + 蜂鸣器鸣叫,日志标记 “alarm=1”;
- 低功耗:30 秒无操作后,电流从 50mA 降至 50μA 以下,按键可唤醒。
2. 系统联调(解决协同问题)
- 任务冲突:调整报警任务优先级(4 最高),确保报警响应不被其他任务阻塞;
- 通信超时:在 ESP8266 和 Modbus 代码中添加重连机制(失败后 10 秒重试);
- 存储效率:日志文件按小时切分,避免单文件过大导致写入缓慢;
- 功耗优化:空闲时关闭 SPI、I2C 等外设时钟,仅保留 RTC 和中断唤醒源。
四、第十五天总结:嵌入式系统开发的核心思维(20 分钟)
通过这个综合项目,你已掌握嵌入式开发的 “系统级思维”:
- 需求拆解:将复杂功能拆分为独立模块(采集、存储、通信等),降低耦合;
- 资源分配:根据任务重要性分配 CPU 优先级(如报警任务 > 采集任务);
- 容错设计:通信失败重连、存储满提示、传感器异常降级(用默认值);
- 优化方向:从 “能用” 到 “好用”(低功耗、日志可读性、配置灵活性)。
进阶方向
这个项目可进一步升级为工业级产品:
- 增加甲醛、PM2.5 传感器(扩展 I2C/SPI 接口);
- 支持 4G 模块(替代 WiFi,适合无网络环境);
- 加入硬件加密(保护配置文件和云平台通信);
- 实现 OTA 批量升级(通过云平台向多设备推送固件)。
嵌入式开发的终极目标是 “解决实际问题”—— 从理解需求到设计架构,从模块开发到系统优化,每一步都需要结合硬件特性和软件逻辑,这正是嵌入式开发的魅力所在!


被折叠的 条评论
为什么被折叠?



