18 KiB
智能水产投料机 V1.0.3
工程概述
本工程基于 STM32F103RCT6 + STM32Cube HAL + Keil MDK,实现设备的控制、联网与投喂。
- BootLoader:
STM32F103_BootLoader/,负责 OTA 固件烧录、试运行计数与失败回滚 - 应用固件:
STM32F103_App1/,当前运行版本 V1.0.3 - 双网络通道:4G(Air780E/EG) 优先,WiFi(ESP-01S) 兜底,见下文"双通道网络"
产品型号配置
同一套板子适配两种产品,编译前在 Inc/main.h 顶部通过一个宏二选一:
#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/EG MQTT+GNSS | PB10/PB11 | 921600-8N1(见下文波特率自适应) |
| UART4 | WiFi ESP-01S | PC10/PC11 | 115200→921600-8N1(见下文 WiFi 波特率同步) |
| ESP_RST | WiFi 模块复位 | PA5 | 低电平 500ms 复位 |
| 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 优先,WiFi 兜底)
网络层 HardWare/NETWORK/network.c 对业务层屏蔽底层模组差异,业务只调 NET_* 接口;4G 后端(CAR/air780e.c,模组内置 MQTT)与 WiFi 后端(NETWORK/net_wifi.c,TCP 透传 + 软件 MQTT)同构,运行时互斥切换。
通道切换状态机
EEPROM(偏移 130)记忆上次成功联网方式(4G / WiFi固定热点 / WiFi配网热点),仅方式变化时写一次;开机优先跑上次成功的方式:
上次 4G 成功:4G(无 SIM/无信号每 10s 重试,3 次失败)→ WiFi → 回 4G…循环
上次 WiFi 成功:WiFi 一轮建链失败 → 4G(同上重试)→ 回 WiFi…循环
WiFi 建链子顺序(net_wifi_init,阻塞式,期间 bg_delay 喂狗):
① 上次是配网热点:AT+CIPSTATUS 查已存 AP 自动连接(10s,STATUS:2=已拿到IP)
② AT+CWJAP 连代码里固定的 SSID/PASS(esp8266.h 的 WIFI_SSID/WIFI_PASS,60s 倒计时)
③ ESP-Touch SmartConfig 智能配网(60s 倒计时,配到的热点由模块保存)
全部失败 → 回 4G
WiFi 运行:30s MQTT PINGREQ 心跳;TCP 断开(CLOSED)/心跳超时 → 自动重连,重连失败回 4G
注:已存 AP 的判断用
AT+CIPSTATUS主动查询而非被动等WIFI GOT IP打印—— 后者在波特率同步阶段就会被清缓冲擦掉,永远等不到。
WiFi 通道实现要点
- TCP 透传 + 软件 MQTT:ESP-01S 用
AT+CIPMODE=1+AT+CIPSEND进透传,MQTT 3.1.1 协议栈在 MCU 软件实现(HardWare/WIFI/mqtt_client.c),不用 AT+MQTT - WiFi 波特率同步:ESP-01S 出厂/RESTORE 后 115200,初始化时探测并用
AT+UART_DEF=921600固化(掉电保存),MCU 侧UART4_SetBaudRate()同步;失败自动回滚维持 115200 - 接收缓冲共享:WiFi 与 4G 互斥运行,共用
Rx_Buf/U2_CopyBuff(stm32f1xx_it.c两路 IDLE 均带激活后端守卫),RAM 增量 <2KB - AT 应答严格匹配:发送后才清接收缓冲(防上一条应答迟到尾巴造成"假成功")、二进制安全匹配、收到
ERROR/FAIL立即失败;进透传前先AT+CIPCLOSE清残留连接、CIPMUX=0先于CIPMODE=1 - ORE 自愈:921600 高波特率下 UART 溢出会让 HAL 中止 DMA 接收,
HAL_UART_ErrorCallback里按当前激活通道立即重新武装;DMA 填满(连续流无 IDLE 间隙)时在HAL_UART_RxCpltCallback里搬运并重启,防接收停摆 - WiFi 通道 OTA:支持,透传 TCP 上软件实现 HTTP Range 分块下载,见下文"OTA 升级"章节
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。
4G 信号检测(AT+CSQ)
- 网络附着(CGATT)成功后查询一次
AT+CSQ,rssi(0~31) 转百分比串口输出:> 4G: signal 77% (rssi=24) - 无信号(rssi=99 或 <2):置
g_no_signal_detected,与无 SIM 走同一套重试/回退 WiFi 逻辑 - 信号弱(<20%):告警但继续联网
GPS 定位上报(仅 4G 通道,Air780EG)
参考工程《air780EG-V1.1加入北斗GPS数据》移植实现(air780e.c 的 air780e_gps_poll())。
工作流程
- GNSS 初始化:4G 拨号(
AT+SAPBR=1,1)成功后执行AT+CGNSPWR=1(开 GNSS 电源)+AT+CGNSAID=31,1,1,1(A-GPS 辅助定位)。非 EG 版本模组回 ERROR 时仅告警并关闭轮询(s_gnss_present=0),不影响联网 - 周期查询:联网 60 秒后首次查询(
GPS_FIRST_DELAY_MS,留搜星时间),之后每 5 分钟一次(GPS_REPORT_INTERVAL_MS)AT+CGNSINF - 解析:
+CGNSINF: run,fix,UTC,lat,lon,...,手工按逗号切分(不依赖 sscanf scanset)。定位无效(fix≠1)跳过本次上报(室内常见),不发空串 - 坐标转换:WGS-84 → GCJ-02(见下节)
- 上报:模组内置 MQTT
AT+MPUB,topic/iot/data/up/<ClientID>
上报格式
云平台字段标识符 GPS,经度在前、纬度在后(高德/GeoJSON 习惯):
{"header":"iot.prop.post","body":{"GPS":"115.924092,28.679470"}}
坐标系转换(WGS-84 → GCJ-02)
GPS 模块输出 WGS-84 原始坐标,直接输入高德地图会有 100~600m 加密偏移(国内地图强制使用 GCJ-02"火星坐标系")。固件在上报前用公开标准算法完成转换(wgs84_to_gcj02(),三角函数近似,中国境外自动跳过),上报的坐标可直接输入高德地图/坐标拾取器。
注意:若改用百度地图,需要再转一层 GCJ-02 → BD-09。
保护机制
- OTA 下载期间(
g_net_tx_busy)禁止 GPS 查询插队,避免污染 HTTP 数据流 - 查询等待期间有平台下行命令(
+MSUB:)插队时主动让行,不吞继电器指令
使用注意
- GPS 天线需能看到天空:室内基本无法定位(
> GPS: no fix, skip属正常),窗边/阳台通常可定,室外最佳;冷启动首次定位约 1~3 分钟 - 验证:串口日志
> GPS: 115.924092,28.679470,或直接看平台GPS字段
OTA 升级
流程
平台下发 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:4G 用模组内置 HTTP(
AT+HTTP*分块);WiFi 通道由 MCU 在透传 TCP 上软件实现 HTTP Range 分块下载(NETWORK/net_wifi_ota.c):- 流程:收 OTA 指令 → 停投喂 →
+++退透传(MQTT 连接被顶替,单连接限制)→ 连固件服务器 → 每 3072B 一个GET(Range: bytes=off-end,MCU 自定节奏,防止服务器一次灌爆接收缓冲)→ 每块写 W25Q64 槽 B + 回读校验 + 累计 CRC16 → 整包 CRC + 固件头校验 → 重连 MQTT 上报 result → 写信息区复位进 BL - 下载期间 MQTT 断开属正常,进度只在开始/结束上报;失败自动重连 MQTT 恢复业务
- 流程:收 OTA 指令 → 停投喂 →
- OTA 触发时先强制停止投喂(断开继电器 1)
- 下载期间蓝牙命令、秤轮询、LED 心跳、电量检测照常运行(
bg_delay切片让出),业务状态上报暂时屏蔽 - 分段接收按
+HTTPREAD: <len>声明长度收齐即处理,无静默等待;40KB 固件全程约 38 秒(含模块复位重连 ~15s)
主要功能
- 双通道 MQTT 联网:4G(Air780E/EG) 优先 + WiFi(ESP-01S) 兜底自动切换,见上文"双通道网络"
- 远程控制:平台下发开关
sw1/sw2/sw3/sw4、feeding指令(投料机模式下sw1被忽略,投喂只能走feeding) - 自动投喂(
PRODUCT_WITH_FEED_SCALE = 1):- 平台设置单次投喂量
onceWt - 平台下发
feeding=true启动投喂 - 继电器 1 吸合,RS485 秤实时读取重量
- 当重量减少 ≥
onceWt时自动停止,带重量反增与 10 分钟超时保护
- 平台设置单次投喂量
- 去皮/标定:平台设置
zero空载重量,设置后按最新原始重量重新计算剩余料重并立即上报 - GPS 定位上报:见上文专题(仅 4G 通道)
- 4G 信号检测:见上文专题
- OTA 升级:见上文专题(仅 4G 通道)
- 蓝牙调试:USART1 支持
RESET/INFO/STATUS/R1ON~R4OFF/ALLON/ALLOFF/OTA/RECOVERY/HELP等命令 - 掉电安全(断电记忆):联网成功(MQTT 首次就绪)后按 EEPROM 恢复继电器上次状态,随后全量上报让平台与电路一致
- 无秤模式(
PRODUCT_WITH_FEED_SCALE = 0):四路全部恢复(含 SW1) - 投料机模式(
PRODUCT_WITH_FEED_SCALE = 1):继电器 1 不恢复且上电强制断开(防止误投料浪费饲料),继电器 2~4 恢复
- 无秤模式(
平台数据字段
| 字段 | 含义 | 说明 |
|---|---|---|
ver |
固件版本号 | 每次状态上报携带,平台可据此识别设备是否发生回退 |
weight |
空载重量 | 平台显示为去皮重量 zero |
zero |
剩余料重 | 平台显示为当前剩余料重 |
onceWt |
单次投喂量 | 每次投喂目标重量 |
sw1 / feeding |
第一路继电器状态 | 1=投喂中,0=停止;无秤模式下两字段同时上报,平台两个开关同步刷新 |
sw2~sw4 |
备用继电器状态 | 0/1 |
pow |
电量状态 | 1=正常,0=低电量 |
GPS |
定位坐标 | 仅 4G 通道(Air780EG),GCJ-02 高德坐标系,格式 "经度,纬度",每 5 分钟上报 |
下行报文由 m_app/proto.c 按键名定位解析("sw2" 不会误中 "sw21",其他字段里的 true/false 不会造成误判)。
串口调试命令
通过 USART1(蓝牙串口)发送:
RESET - 系统复位
INFO - 打印 MQTT 配置
STATUS - 打印继电器状态
R1ON - 打开继电器 1
R1OFF - 关闭继电器 1
R2ON/R2OFF ~ R4ON/R4OFF - 控制继电器 2~4
ALLON - 打开所有继电器
ALLOFF - 关闭所有继电器
OTA - 复位进入 BootLoader 检查升级
RECOVERY - WiFi 模块 AT+RESTORE 恢复出厂(忘掉已存热点)+ 整机复位,用于测试 SmartConfig
HELP - 显示支持命令
SmartConfig 测试流程:关掉固定热点 → 发 RECOVERY → 设备复位走完 4G 重试进入 WiFi → 已存热点(20s)、固定热点(60s) 都失败后自动进 SmartConfig(60s 倒计时)→ 用 ESP-Touch APP 广播密码。
代码分层(APP)
main.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)
}
}
目录结构
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/ # 网络层:network.c 通道切换/统一分发 + net_wifi.c WiFi 后端 + net_wifi_ota.c WiFi OTA
│ ├── WIFI/ # esp8266.c ESP-01S AT驱动/透传/SmartConfig + mqtt_client.c 软件MQTT
│ ├── CAR/ # Air780E/EG AT 驱动、波特率自适应、CSQ 信号检测、GNSS 定位、OTA 下载
│ ├── UART/ # USART1/2/3+UART4 初始化、运行中切波特率、中断/DMA 接收、ORE 自愈
│ ├── 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:
- 打开
MDK-ARM/STM32F103rb_App1.uvprojx(BootLoader 工程同理) - 按产品形态设置
Inc/main.h中的PRODUCT_WITH_FEED_SCALE - 编译下载(先烧 BootLoader 到
0x08000000,APP1 起始地址0x08008000) - 首次启动会自动把默认 MQTT 配置写入 AT24C02
命令行(CI/脚本可用):
# 编译(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