【嵌入式开发学习】第15天:综合项目实战(智能环境监测终端)

核心目标:整合前 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 分钟)

通过这个综合项目,你已掌握嵌入式开发的 “系统级思维”:

  1. 需求拆解:将复杂功能拆分为独立模块(采集、存储、通信等),降低耦合;
  2. 资源分配:根据任务重要性分配 CPU 优先级(如报警任务 > 采集任务);
  3. 容错设计:通信失败重连、存储满提示、传感器异常降级(用默认值);
  4. 优化方向:从 “能用” 到 “好用”(低功耗、日志可读性、配置灵活性)。

进阶方向

这个项目可进一步升级为工业级产品:

  • 增加甲醛、PM2.5 传感器(扩展 I2C/SPI 接口);
  • 支持 4G 模块(替代 WiFi,适合无网络环境);
  • 加入硬件加密(保护配置文件和云平台通信);
  • 实现 OTA 批量升级(通过云平台向多设备推送固件)。

嵌入式开发的终极目标是 “解决实际问题”—— 从理解需求到设计架构,从模块开发到系统优化,每一步都需要结合硬件特性和软件逻辑,这正是嵌入式开发的魅力所在!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值