# 智能水产投料机 V1.0.3 ## 工程概述 本工程基于 **STM32F103RCT6** + **STM32Cube HAL** + **Keil MDK**,实现设备的控制、联网与投喂。 - **BootLoader**:`STM32F103_BootLoader/`,负责 OTA 固件烧录、试运行计数与失败回滚 - **应用固件**:`STM32F103_App1/`,当前运行版本 V1.0.3 ## 产品型号配置 同一套板子适配两种产品,编译前在 `Inc/main.h` 顶部通过一个宏二选一: ```c #define PRODUCT_WITH_FEED_SCALE 0 ``` | 取值 | 产品 | 投喂行为 | | --- | --- | --- | | `1` | 智能水产投料机(接 RS485 磅秤) | `feeding` 按 `onceWt` 定量自动投喂,带重量反增/超时保护;`sw1` 指令被忽略 | | `0` | 基础四路继电器(不接磅秤) | `feeding` / `sw1` 直接开关投喂继电器 1,由平台侧自行计时 | ## 硬件接口 | 接口 | 用途 | 引脚 | 参数 | | --- | --- | --- | --- | | USART1 | 蓝牙串口调试 / 日志输出 | PA9/PA10 | 115200-8N1 | | USART2 | RS485 喂料秤 | PA2/PA3 | 9600-8N1 | | USART3 | 4G Air780E MQTT | PB10/PB11 | 921600-8N1(见下文波特率自适应) | | Relay1 | 投喂电机控制 | PB7 | 高电平吸合 | | Relay2~4 | 备用控制 | PB8/PC0/PC2 | 高电平吸合 | | I2C(软件) | AT24C02 配置存储 | PA11/PA12 | 页写入 | | SPI(软件) | W25Q64 外部 Flash | PB3/PB4/PA15/PB5 | OTA 固件暂存 | | ADC1 | 电池电压检测 | PA0 | 低电量上报 `pow:0` | | PA8 | 手动模式检测 | PA8 | 高电平=手动模式 | | PC13 | LED2 心跳 / TPL5010 喂狗 | PC13 | 每 1s 翻转(见下文看门狗) | ## 看门狗(双狗架构) | 看门狗 | 超时 | 喂狗点 | 作用 | | --- | --- | --- | --- | | IWDG(片内独立看门狗) | ~26s(LSI 漂移下最短 ~17s) | APP 主循环、`app_bg_service()`(所有 AT/OTA 等待每 20ms 让出一次);BL 的擦除/拷贝循环 | 固件卡死 → 复位;BL 启动,OTA 试运行期计一次试错 | | TPL5010(外置硬件狗) | 7 分钟 | PC13(LED2) 电平翻转:APP `activeEvents()` 每秒翻转;BL 进入时及长循环中翻转 | MCU 完全失控(含 IWDG 失效)时的最后兜底 | 注意:LED2 同时承担运行指示和外部狗喂狗,**修改 LED2 闪烁逻辑时必须保持周期性翻转**,否则 7 分钟后整板复位。 ## 4G 波特率自适应(115200 → 921600) - Air780E 出厂/自适应模式只覆盖 9600~115200;921600 必须用 `AT+IPR` 固定。 - APP 联网时**永远先按 115200 探测**(避免向自适应侦测窗口发送其他波特率的乱码): - 通 → 发 `AT+IPR=921600` + `AT&W` 固化,切换到 921600(日志出现 `baud -> 921600`); - 不通(模块已固化 921600)→ 直接切 921600 连接。 - 切换由 `USART3_SetBaudRate()` 运行中重写 BRR 完成,不影响 GPIO/DMA/NVIC。 ## OTA 升级 ### 流程 ```text 平台下发 iot.ota.upgrade.post(url, crc16, ver) → APP: 模块硬复位 → HTTP HEAD 拿大小 → 擦 W25Q64 槽 B → 3072 字节/块分段下载(AT+HTTPPARA BREAK/BREAKEND + HTTPACTION + HTTPREAD) → 每块写 W25Q64 并回读校验,全程累计 CRC16 → 整包 CRC + 栈顶/复位向量校验 → 可靠上报 result.post(等 OK + 重试) → 写 OTA 信息区(状态 DOWNLOADED) → 复位进 BootLoader BootLoader: → 擦除备份槽 → 备份当前 APP1 到槽 A(失败则中止更新,留在旧版本) → 擦 APP1 → 写入新固件 → CRC + 向量校验 → 状态 INSTALLED → 跳转 新 APP 试运行: → MQTT 就绪 + 首次发布成功 + 3s → 上报 confirm.post → 确认标志写 W25Q64(回读验证 + 3 次重试)→ 版本号存 EEPROM → 下次启动 BL 清理 OTA 标志,升级完成 ``` ### Flash 分区 内部 Flash(256KB): | 区域 | 地址 | 大小 | | --- | --- | --- | | BootLoader | `0x08000000` | 32KB | | APP1 运行区 | `0x08008000` | 224KB | W25Q64 外部 Flash(8MB): | 区域 | 地址 | 用途 | | --- | --- | --- | | 槽 A | `0x000000` (256KB) | 当前固件备份(回滚镜像) | | 槽 B | `0x040000` (256KB) | 新固件下载暂存 | | 信息区 A/B | `0x080000` / `0x081000` (各 4KB) | OTA 状态双副本(magic + 序号 + checksum) | ### 健康检查与回退 新固件"健康" = 4G 附着 → MQTT 连接 → 首次发布成功。回退判据(**3 次真实故障**才回退): - **看门狗复位**(IWDG/WWDG,固件真的卡死)→ BL 计一次试错(读 `RCC->CSR` 判别); - **健康超时**(MQTT 就绪 1 分钟仍无首次发布)→ APP 复位前自行计一次; - **上电/按键/普通软件复位不计次**——现场断电、断网不会误触发回退; - 试运行期间完全断网 → 不计次、不回退,持续等待网络恢复。 ### 掉电/断网安全性 | 故障点 | 结果 | | --- | --- | | 下载中断网/掉电 | 信息区未更新,旧固件照常运行,平台可重推 | | BL 备份阶段掉电 | `backup_valid=0`,重启后重新备份 | | BL 擦写 APP1 中途掉电 | 状态 `COPYING` 视为 `DOWNLOADED`,重启后完整重做(备份不会被坏镜像覆盖) | | 备份创建失败 | **中止更新**,留在旧版本,下次重启重试 | | 回退过程掉电 | 重启后重新执行回退拷贝 | ### 其他要点 - OTA 触发时先强制停止投喂(断开继电器 1) - 下载期间蓝牙命令、秤轮询、LED 心跳、电量检测照常运行(`bg_delay` 切片让出),业务状态上报暂时屏蔽 - 分段接收按 `+HTTPREAD: ` 声明长度收齐即处理,无静默等待;40KB 固件全程约 38 秒(含模块复位重连 ~15s) ## 主要功能 1. **MQTT 联网**:Air780E 4G 模块,通过 MQTT 连接云平台 2. **远程控制**:平台下发开关 `sw1/sw2/sw3/sw4`、`feeding` 指令(投料机模式下 `sw1` 被忽略,投喂只能走 `feeding`) 3. **自动投喂**(`PRODUCT_WITH_FEED_SCALE = 1`): - 平台设置单次投喂量 `onceWt` - 平台下发 `feeding=true` 启动投喂 - 继电器 1 吸合,RS485 秤实时读取重量 - 当重量减少 ≥ `onceWt` 时自动停止,带重量反增与 10 分钟超时保护 4. **去皮/标定**:平台设置 `zero` 空载重量,设置后按最新原始重量重新计算剩余料重并立即上报 5. **OTA 升级**:见上文专题 6. **蓝牙调试**:USART1 支持 `RESET/INFO/STATUS/R1ON~R4OFF/ALLON/ALLOFF/OTA/RECOVERY/HELP` 等命令 7. **掉电安全**:继电器 2~4 联网后从 EEPROM 恢复上次状态;继电器 1 上电强制断开,防止误投料 ## 平台数据字段 | 字段 | 含义 | 说明 | | --- | --- | --- | | `ver` | 固件版本号 | 每次状态上报携带,平台可据此识别设备是否发生回退 | | `weight` | 空载重量 | 平台显示为去皮重量 `zero` | | `zero` | 剩余料重 | 平台显示为当前剩余料重 | | `onceWt` | 单次投喂量 | 每次投喂目标重量 | | `feeding` / `sw1` | 第一路继电器状态 | 1=投喂中,0=停止,两个标识符等效 | | `sw2~sw4` | 备用继电器状态 | 0/1 | | `pow` | 电量状态 | 1=正常,0=低电量 | 下行报文由 `m_app/proto.c` 按键名定位解析(`"sw2"` 不会误中 `"sw21"`,其他字段里的 true/false 不会造成误判)。 ## 串口调试命令 通过 USART1(蓝牙串口)发送: ```text RESET - 系统复位 INFO - 打印 MQTT 配置 STATUS - 打印继电器状态 R1ON - 打开继电器 1 R1OFF - 关闭继电器 1 R2ON/R2OFF ~ R4ON/R4OFF - 控制继电器 2~4 ALLON - 打开所有继电器 ALLOFF - 关闭所有继电器 OTA - 复位进入 BootLoader 检查升级 RECOVERY - 复位进入 BootLoader HELP - 显示支持命令 ``` ## 代码分层(APP) `main.c` 只保留三行初始化和主循环,逻辑全部下沉到模块: ```c int main(void) { SCB->VTOR = FLASH_BASE | Application_1_Addr; Board_Init(); // 基础硬件:HAL/时钟/GPIO/DMA/UART/看门狗(SYSINIT/system.c) app_init(); // 业务模块初始化(SYSINIT/app_loop.c) app_loop_init(); while (1) { IWDG_Feed(); app_loop_process(); // 业务调度(SYSINIT/app_loop.c) } } ``` ## 目录结构 ```text STM32F103_App1/ ├── Src/ │ └── main.c # 仅 VTOR + Board_Init/app_init/主循环 ├── Inc/ │ └── main.h # 产品型号宏 PRODUCT_WITH_FEED_SCALE、MQTT 默认配置 ├── m_app/ │ ├── proto.c/h # 平台 JSON 报文取值工具(按键名定位) │ └── ota_app.h # Flash 分区定义(BootLoader 32K + APP1 224K) ├── HardWare/ │ ├── USER_CMD/ # 蓝牙串口命令处理 │ ├── FEED_SCALE/ # RS485 喂料秤与定量投喂状态机 │ ├── RELAY/ # 继电器控制、反馈检测、平台指令执行 │ ├── NETWORK/ # 网络抽象层:状态上报/响应/发送互斥 │ ├── CAR/ # Air780E AT 驱动、波特率自适应、OTA 下载 │ ├── UART/ # USART1/2/3 初始化、运行中切波特率、中断/DMA 接收 │ ├── 24C02/ # EEPROM 读写(页写入) │ ├── IIC/ # 软件 I2C │ ├── ADC/ # 电池电压检测 │ ├── LED/ # 指示灯(LED2 兼任 TPL5010 喂狗) │ ├── LOG/ # 分级日志(USART1 输出) │ ├── SYSINIT/ # Board_Init/IWDG、app_init、主循环调度、后台任务切片 │ └── W25Q64/ # 外部 Flash 驱动与 OTA 双槽信息区 └── MDK-ARM/ # Keil 工程文件 STM32F103_BootLoader/ ├── Src/main.c # 硬件初始化 + IWDG + 入口 ├── m_app/ota_boot.c # OTA 状态机:备份/烧录/试运行计数/回退/跳转 ├── HardWare/W25Q64/ # 外部 Flash 驱动(与 APP 共用设计) ├── HardWare/LOG/ # 分级日志(与 APP 相同) └── MDK-ARM/ # Keil 工程文件 ``` ## 编译烧录 **Keil GUI**: 1. 打开 `MDK-ARM/STM32F103rb_App1.uvprojx`(BootLoader 工程同理) 2. 按产品形态设置 `Inc/main.h` 中的 `PRODUCT_WITH_FEED_SCALE` 3. 编译下载(先烧 BootLoader 到 `0x08000000`,APP1 起始地址 `0x08008000`) 4. 首次启动会自动把默认 MQTT 配置写入 AT24C02 **命令行**(CI/脚本可用): ```bash # 编译(Keil 安装目录的 UV4,-r 全量 -b 增量,-o 输出日志) /c/Keil_v5/UV4/UV4.exe -r -j0 STM32F103rb_App1.uvprojx -o build.txt # 烧录(J-Link 命令脚本,工程 MDK-ARM 目录下有现成的 jlink_flash.jlink) JLink.exe -CommanderScript jlink_flash.jlink ``` ## 版本 - Version: 1.0.3 - Author: Helei