475 lines
33 KiB
Markdown
475 lines
33 KiB
Markdown
# 智能水产投料机 V1.5.0(FreeRTOS · 串口日志中文版)
|
||
|
||
> **本版本基于 FreeRTOS 实时操作系统**(V10.0.1,net/app/sys 三任务架构),
|
||
> 串口日志为中文输出(**GBK 编码**,串口工具/蓝牙 APP 字符集请选 GBK)。
|
||
|
||
## 工程概述
|
||
|
||
本工程基于 **STM32F103RCT6**(兼容 GD32F103RCT6)+ **STM32Cube HAL** + **FreeRTOS V10.0.1** + **Keil MDK**,实现设备的控制、联网与投喂。
|
||
|
||
- **BootLoader**:`STM32F103_BootLoader/`,负责 OTA 固件烧录、试运行计数与失败回滚、主电源门禁
|
||
- **应用固件**:`STM32F103_App1/`,当前运行版本 V1.5.0(FreeRTOS 版,全中文串口日志)
|
||
- **双网络通道**:4G(Air780E/EG) 优先,WiFi(ESP-01S) 兜底,见下文"双通道网络"
|
||
- **蓝牙调试**:HLK-B40 BLE 透传模块(USART1),上电自动配置名称/GATT UUID,见下文"蓝牙模块配置"
|
||
|
||
> 注意:Keil 工程必须带编译选项 `--no_multibyte_chars`(App1 与 BootLoader 均已配置),
|
||
> 使 ARMCC 不解读多字节字符,中文字符串(GBK/UTF-8 均可)字节原样透传输出。
|
||
> `missing closing quote` 编译错误。
|
||
|
||
## FreeRTOS 任务架构
|
||
|
||
| 任务 | 优先级 | 栈 | 职责 |
|
||
| --- | --- | --- | --- |
|
||
| net_task | 3 | 3KB | 网络状态机 `NET_process`:4G/WiFi 建链与切换、MQTT 收发、GPS 轮询、OTA 下载 |
|
||
| app_task | 2 | 2KB | 业务:继电器命令/反馈状态机、断电记忆恢复、投喂秤轮询与保护、状态上报、OTA 确认与健康检查 |
|
||
| sys_task | 1 | 1KB | 系统:LED、电量(1s)、蓝牙调试命令、Uptime 打印(联网后每 60s) |
|
||
| idle 钩子 | - | - | 喂 IWDG(任务死循环占 CPU → idle 饿死 → 复位,正是期望行为) |
|
||
|
||
关键设计:
|
||
|
||
- **HAL 时基 = TIM2**(1ms 中断,`Src/stm32f1xx_hal_timebase_tim.c`,注意 `HAL_TIM_PeriodElapsedCallback` 里必须 `HAL_IncTick()`),SysTick 归 FreeRTOS 当 RTOS tick;it.c 不再定义 SVC/PendSV/SysTick 三个 Handler(port.c 提供,经 FreeRTOSConfig.h 宏映射)
|
||
- **bg_delay 改造**:调度器运行中 = `vTaskDelay` 让出;调度器未启动(开机初始化)= HAL_Delay + 喂狗
|
||
- **互斥锁三把**(调度器启动前全部为空操作直通):
|
||
- `s_net_at_mutex`(network.c):net 任务长 AT 会话(建链/重连/OTA)持锁,其他任务 `NET_publish_*` 尝试锁、拿不到丢弃,防 AT 应答交织
|
||
- `s_storage_mutex`(app_loop.c `storage_lock`):EEPROM(软件I2C) 与 W25Q64(软件SPI) 跨任务互斥,递归锁
|
||
- `s_log_mutex`(log.c):日志输出不交织
|
||
- **内存预算**(48KB RAM):裸机存量 ZI ~36.3KB + FreeRTOS 堆 10KB(configTOTAL_HEAP_SIZE)≈ 46.3KB;Flash ~67KB/224KB 分区。中断优先级维持原值(UART/DMA 0~3 级,不调 RTOS API,低于 5 级 syscall 阈值无冲突)
|
||
|
||
## 产品型号配置
|
||
|
||
同一套板子适配两种产品,编译前在 `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/EG MQTT+GNSS | PB10/PB11 | 921600-8N1(见下文波特率自适应) |
|
||
| UART4 | WiFi ESP-01S | PC10/PC11 | 115200→921600-8N1(见下文 WiFi 波特率同步) |
|
||
| UART5 | HLW8032 电能计量 | PD2(RX) | 4800-8E1,只收,芯片每 50ms 主动上报 |
|
||
| ESP_RST | WiFi 模块复位 | PA5 | 低电平 500ms 复位 |
|
||
| CAT_PWR | 4G 模块开机 | PC4 | 开漏输出,拉低=模块开机(上电即拉低) |
|
||
| ESP_EN | WiFi 模块使能 | PA6 | 推挽输出,高电平=使能;**默认关闭,4G 运行时拉低断电省电,切 WiFi 才拉高** |
|
||
| BT_MODE | 蓝牙 AT/透传切换 | PB0 | 开漏输出,拉低 1s 进 AT 模式(ble_at.c 控制) |
|
||
| BT_RST | 蓝牙模块复位 | PA7 | 推挽输出,低电平=工作(默认),高电平=复位 |
|
||
| OLED 屏幕 | 128x64 + GB2312 字库 IC | CLK=PC12 MOSI=PC9 DC=PC8 CS1=PC7 FSO=PC6 CS2=PB15 | GPIO 模拟 SPI,显示电压/电流/功率/电能/继电器/网络 |
|
||
| 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 | 高电平=手动模式 |
|
||
| PA1 | NET_LED 联网指示 | PA1 | 低电平点亮,见下表 |
|
||
| PC13 | LED2 心跳 / TPL5010 喂狗 | PC13 | 每 1s 翻转(见下文看门狗) |
|
||
|
||
### 指示灯含义
|
||
|
||
| 灯 | 状态 | 含义 |
|
||
| --- | --- | --- |
|
||
| NET_LED(PA1) | 100ms 快闪 | SmartConfig 智能配网中,请用 ESP-Touch APP 广播密码 |
|
||
| NET_LED(PA1) | 500ms 慢闪 | 联网中(4G/WiFi 建链、重连) |
|
||
| NET_LED(PA1) | 常亮 | 已联网(MQTT 就绪) |
|
||
| LED2(PC13) | 1s 心跳闪烁 | 系统正常运行(兼任 TPL5010 外置看门狗喂狗) |
|
||
|
||
## 看门狗(双狗架构)
|
||
|
||
| 看门狗 | 超时 | 喂狗点 | 作用 |
|
||
| --- | --- | --- | --- |
|
||
| IWDG(片内独立看门狗) | ~26s(LSI 漂移下最短 ~17s) | APP 由 FreeRTOS **idle 钩子**喂养(`vApplicationIdleHook`);BL 的擦除/拷贝循环 | 固件卡死 → 复位;BL 启动,OTA 试运行期计一次试错 |
|
||
| TPL5010(外置硬件狗) | 7 分钟 | PC13(LED2) 电平翻转:APP `activeEvents()` 每秒翻转;BL 进入时及长循环中翻转 | MCU 完全失控(含 IWDG 失效)时的最后兜底 |
|
||
|
||
注意:LED2 同时承担运行指示和外部狗喂狗,**修改 LED2 闪烁逻辑时必须保持周期性翻转**,否则 7 分钟后整板复位。
|
||
|
||
## 重启记录与断电保护
|
||
|
||
### 重启记录(reset_log.c)
|
||
|
||
- 开机时("固件版本"日志后)打印最近 **10 条**重启记录:`重启原因 + 上次运行时长`
|
||
- **存储**:W25Q64 `0x100000` 一个 4KB 扇区,10 条 32B 记录环形覆盖(序号+原因码+运行秒数+CRC16),写满一圈擦一次,断电不丢
|
||
- **原因来源**:`RCC->CSR` 由 BootLoader 读入后放共享 RAM(`BOOT_SHARE`,地址 `0x2000BF00`,热复位保持)传给 APP;软件复位的细分原因由各复位点 `reset_log_mark()` 预写(被平台踢下线/心跳超时/4G无服务/无SIM/OTA升级/OTA健康超时/4G初始化失败/串口RESET/OTA/RECOVERY命令/4G模组自重启等)
|
||
- **运行时长**:每秒写入共享 RAM(热复位不丢),看门狗/软件复位后精确到秒
|
||
|
||
### 断电保护(法拉电容 + PA0 检测)
|
||
|
||
- APP:PA0 电量检测 <10%(=外部 12V 断开)立即 50ms 快速复核 2 次(约 100ms 出结果,法拉电容 5.5F 续航有限,原 3s 去抖来不及)→ **优先上报 `pow:0`** → 写电能/断电记录 → 留 500ms 让模组把报文发完 → 复位。电量百分比做了 0~100% 限幅(电容放电时 3.3V 基准下跌会失真)
|
||
- BootLoader:跳转 APP 前读 PA0(寄存器直读 ADC),主电源未恢复(<约 11V)则每 500ms 检测原地等待,**不跳 APP**——防止单片机靠电容低压运行 APP 而外设没电的乱态;12V 恢复后正常放行
|
||
- 断电记录只被下次成功启动消费一次(`boot_seen` 位翻转标记),一次掉电 = 一条记录,BL 等待期间不产生记录
|
||
- BL 找不到有效 APP(如只刷了 BL):打印提示并每 3s 主动复位重试,刷入 APP 即恢复
|
||
|
||
## 电能计量(HLW8032)
|
||
|
||
只计量**累计消耗电量**,上报平台标识符 `energy`(单位 kWh,两位小数)。
|
||
|
||
- **硬件**:HLW8032(5V 供电)经 B0505S 隔离电源 + 光耦隔离,芯片 TXD 接 PD2(UART5_RX);电压采样 ZMPT107-1(2mA:2mA,初级 220V 串 100K,次级并 49.9Ω),电流采样 CT 100A/100mA(1000:1,输出并 2Ω),三个模拟脚各对地 33nF
|
||
- **通信**:4800bps 8E1(F1 上按 9 位帧长 + 偶校验配置),芯片每 50ms 主动上报 24 字节帧,只收不发;帧同步 = 首字节 0x55/0xAA/0xFx + 第二字节 0x5A + 校验和([2]~[22] 相加低 8 位)
|
||
- **电量算法**(手册公式):`kWh = 总脉冲 × 功率参数寄存器 × 电压系数 × 电流系数 / (10^9×3600)`;PF 寄存器 16 位溢出由数据更新寄存器 bit7 翻转计数(`总脉冲 = k×65536 + PF`),50ms 一帧不可能漏翻转;芯片断电重启(计数值倒退)自动重新基准
|
||
- **系数宏**:`main.h` 的 `HLW_U_COEF`(=100K/49.9/1000=2.004)、`HLW_I_COEF`(=1/(0.002×1000)=0.5)——改电阻/互感器只动这两个宏
|
||
- **防噪声假同步**:要求连续 3 帧且间隔 20~150ms 才认定在线(PD2 悬空时电磁干扰偶发也能凑出校验和正确的假帧,但不可能连续卡在 50ms 节奏上)
|
||
- **滤波与零漂**:电压/电流/功率经 EMA 滑动平均(系数 1/8,约 0.4s 响应)压空载跳变;电流再减零点偏移 `HLW_I_ZERO_A`(0.02A) 钳位到 0;功率死区 `HLW_P_DEAD_W`(2W) 以下显示 0、超出再减 `HLW_P_ZERO_W`(0.5W)——宏都在 main.h
|
||
- **底数 1kWh**:首次(EEPROM 空白或累计不足 1 度)同步到首帧时补足 1.00kWh 并落盘(平台过滤 0 值,保证有数据显示),只生效一次
|
||
- **持久化**:累计脉冲数(uint64)存 EEPROM 偏移 164~171;每攒约 0.1kWh 写一次(限制擦写),所有计划内复位(`reset_log_mark` 覆盖的复位点)复位前写一次,断电预警(法拉电容续航内)再写一次——除突然死机外基本不丢电量
|
||
- **上报策略**:功率 >10W 时每 30s 上报一次 `{"energy":%.2f}`;≤10W 时仅首次联网报一次(省流量);继电器开关切换随状态大报文携带 energy。串口电能日志固定 10s 一条。阈值宏 `ENERGY_REPORT_POWER_W`、间隔宏 `ENERGY_REPORT_INTERVAL_MS` 在 app_loop.c
|
||
- **调试日志**:芯片在线时每 10s 打印一条(`电能: x.xxkWh 电压: xxx.xV 电流: x.xxA 功率: xxxx.xW`),与上报节奏无关
|
||
|
||
## OLED 屏幕显示(128x64 + GB2312 字库 IC)
|
||
|
||
- 移植自《485模拟磅秤数据带CRC》的 oledisp 驱动,GPIO 模拟 SPI(屏幕 CS1 + 字库 CS2 两路片选,字库数据从 FSO 脚读回)
|
||
- 字符串直接写中文(工程源文件即 GBK 编码,字库 IC 按 GB2312 地址换算取字模)
|
||
- **已联网界面**(sys_task 每 1s 刷新):第 0 行 网络 + 运行时间(`WiFi 00:02:35`,天:时:分),第 1 行 电压/电流,第 2 行 功率/电能(计量芯片离线时显示"计量芯片离线"),第 3 行 继电器状态中文(`开关:开开关关`)
|
||
- **未联网界面**:只显示联网进度,不显示电能/开关——4G 模式显示"正在等待4G联网"+ 最近完成阶段(模块复位成功/AT指令成功/SIM卡检测成功/附着网络成功/连接服务器中/无SIM卡,状态由 `g_net_ui_phase` 从 air780e.c 各阶段更新);WiFi 固定热点显示"WiFi固定模式 正在搜索热点"+倒计时,SmartConfig 显示"智能配网 请打开APP广播"+倒计时(倒计时由 esp_wait_countdown 写入 `g_net_ui_countdown`)
|
||
- **防烧屏**:每 10s 内容整体右移 0/2/4px 轮换
|
||
- 开机欢迎页按产品模式显示标题(带秤=`智能水产投料机`,无秤=`继电器控制终端`)+ `V1.5.0 RTOS`;4G 阻塞建链期间由 bg_delay 钩子每 500ms 刷进度页(调度器启动前 sys_task 未运行,曾一直卡在欢迎页)
|
||
|
||
## 双通道网络(4G 优先,WiFi 兜底)
|
||
|
||
网络层 `HardWare/NETWORK/network.c` 对业务层屏蔽底层模组差异,业务只调 `NET_*` 接口;4G 后端(`CAR/air780e.c`,模组内置 MQTT)与 WiFi 后端(`NETWORK/net_wifi.c`,TCP 透传 + 软件 MQTT)同构,运行时互斥切换。
|
||
|
||
### 通道切换状态机
|
||
|
||
EEPROM(偏移 130)记忆**上次成功联网方式**(`4G` / `WiFi固定热点` / `WiFi配网热点`),仅方式变化时写一次;开机优先跑上次成功的方式:
|
||
|
||
```text
|
||
上次 4G 成功:4G(无 SIM/无信号每 10s 重试,3 次失败)→ WiFi → 回 4G…循环
|
||
上次 WiFi 成功:WiFi 一轮建链失败 → 4G(同上重试)→ 回 WiFi…循环
|
||
|
||
WiFi 建链子顺序(net_wifi_init,阻塞式,等待期间 vTaskDelay 让出 CPU):
|
||
① 上次是配网热点: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 心跳,连续 2 帧无应答(约 60s)判死;TCP 断开(CLOSED)/心跳超时 → 打印原因并直接整机复位重连
|
||
(CLOSED=服务器主动断开/被平台踢下线;ping 超时=服务器无响应/假在线,最坏检测延迟 ~30s)
|
||
```
|
||
|
||
> 注:已存 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)
|
||
|
||
**开关**:`Inc/main.h` 的 `#define PRODUCT_WITH_GPS`——`1` 启用定位上报(需 Air780EG 模组);`0` 完全不获取不上报(纯 Air780E 用,不产生任何 GPS 相关 AT 交互,GNSS 初始化/轮询/坐标转换全部编译期剔除)。
|
||
|
||
参考工程《air780EG-V1.1加入北斗GPS数据》移植实现(`air780e.c` 的 `air780e_gps_poll()`)。
|
||
|
||
### 工作流程
|
||
|
||
1. **GNSS 初始化**:4G 拨号(`AT+SAPBR=1,1`)成功后执行 `AT+CGNSPWR=1`(开 GNSS 电源)+ `AT+CGNSAID=31,1,1,1`(A-GPS 辅助定位)。非 EG 版本模组回 ERROR 时仅告警并关闭轮询(`s_gnss_present=0`),不影响联网
|
||
2. **周期查询**:联网 60 秒后首次查询(`GPS_FIRST_DELAY_MS`,留搜星时间),之后每 5 分钟一次(`GPS_REPORT_INTERVAL_MS`)`AT+CGNSINF`
|
||
3. **解析**:`+CGNSINF: run,fix,UTC,lat,lon,...`,手工按逗号切分(不依赖 sscanf scanset)。**定位无效(fix≠1)跳过本次上报**(室内常见),不发空串
|
||
4. **坐标转换**:WGS-84 → GCJ-02(见下节)
|
||
5. **上报**:模组内置 MQTT `AT+MPUB`,topic `/iot/data/up/<ClientID>`
|
||
|
||
### 上报格式
|
||
|
||
云平台字段标识符 **`GPS`**,**经度在前、纬度在后**(高德/GeoJSON 习惯):
|
||
|
||
```json
|
||
{"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 升级
|
||
|
||
### 流程
|
||
|
||
```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**: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 触发时先强制停止投喂(断开继电器 1)
|
||
- 下载期间其余任务(秤轮询/LED/电量/蓝牙命令)由 RTOS 调度照常并发运行,业务状态上报暂时屏蔽
|
||
- 分段接收按 `+HTTPREAD: <len>` 声明长度收齐即处理,无静默等待;40KB 固件全程约 38 秒(含模块复位重连 ~15s)
|
||
|
||
## 主要功能
|
||
|
||
1. **双通道 MQTT 联网**:4G(Air780E/EG) 优先 + WiFi(ESP-01S) 兜底自动切换,见上文"双通道网络"
|
||
2. **远程控制**:平台下发开关 `sw1/sw2/sw3/sw4`、`feeding` 指令(投料机模式下 `sw1` 被忽略,投喂只能走 `feeding`)
|
||
3. **自动投喂**(`PRODUCT_WITH_FEED_SCALE = 1`):
|
||
- 平台设置单次投喂量 `onceWt`
|
||
- 平台下发 `feeding=true` 启动投喂
|
||
- 继电器 1 吸合,RS485 秤实时读取重量
|
||
- 当重量减少 ≥ `onceWt` 时自动停止,带重量反增与 10 分钟超时保护
|
||
4. **去皮/标定**:平台设置 `zero` 空载重量,设置后按最新原始重量重新计算剩余料重并立即上报
|
||
5. **GPS 定位上报**:见上文专题(仅 4G 通道)
|
||
6. **4G 信号检测**:见上文专题
|
||
7. **OTA 升级**:见上文专题(4G/WiFi 双通道均支持)
|
||
8. **蓝牙调试**:USART1 支持 `RESET/INFO/STATUS/R1ON~R4OFF/ALLON/ALLOFF/OTA/RECOVERY/HELP` 等命令
|
||
9. **掉电安全(断电记忆)**:联网成功(MQTT 首次就绪)后按 EEPROM 恢复继电器上次状态,随后全量上报让平台与电路一致
|
||
- 无秤模式(`PRODUCT_WITH_FEED_SCALE = 0`):**四路全部恢复**(含 SW1)
|
||
- 投料机模式(`PRODUCT_WITH_FEED_SCALE = 1`):**继电器 1 不恢复且上电强制断开**(防止误投料浪费饲料),继电器 2~4 恢复
|
||
|
||
## EEPROM 布局(AT24C02,256 字节)
|
||
|
||
上电由 `eepromReadInfo()` 读取并按 Magic 校验;配置损坏/未初始化时回退到 `main.h` 的编译期默认值并重新写入。
|
||
|
||
| 偏移 | 大小 | 内容 |
|
||
| --- | --- | --- |
|
||
| 0~19 | 20B | `ClientID` MQTT 客户端 ID |
|
||
| 20~39 | 20B | `Username` |
|
||
| 40~59 | 20B | `Passward` |
|
||
| 60~79 | 20B | `ServerIP` |
|
||
| 80~99 | 20B | `Topic`(下行主题前缀 `/iot/data/down/`) |
|
||
| 100~101 | 2B | `ServerPort` |
|
||
| 102~108 | 7B | `Relay_State[7]`:[1..4] 四路继电器掉电记忆状态,[0] 保留,[5][6] 未用 |
|
||
| 112~115 | 4B | `Magic`(0xDEADBEEF,检测 EEPROM 是否已初始化;109~111 为对齐填充) |
|
||
| 116~117 | 2B | `Ver` OTA 固件版本号(确认后落盘,断电保持) |
|
||
| 120~121 | 2B | 秤参数:去皮/空载重量 `zero`(0.1KG 单位,仅称重模式) |
|
||
| 122~123 | 2B | 秤参数:单次投喂量 `onceWt`(0.1KG 单位,仅称重模式) |
|
||
| 124 | 1B | 秤参数初始化标志 |
|
||
| 130 | 1B | 上次成功联网方式:0=4G,1=WiFi固定热点,2=WiFi配网热点(仅变化时写) |
|
||
| 132~157 | 26B | 蓝牙名称配置缓存(命中则开机跳过蓝牙 AT 设置流程) |
|
||
| 160 | 1B | 日志等级(0~5:TRACE/DEBUG/INFO/WARN/ERROR/FATAL) |
|
||
| 164~171 | 8B | HLW8032 累计脉冲数(uint64,每攒 0.1kWh + 断电前写入) |
|
||
| 172 | 1B | 产品模式:0=四路继电器(无秤),1=称重投料(带秤),空白时用 main.h 宏默认值(蓝牙 SCALE0/SCALE1 切换) |
|
||
| 250 | 1B | 探测字节(上电写 0xA5 回读,验证 I2C 通信) |
|
||
|
||
> 注意:改 `MqttInfo_Str` 结构体会改变上述偏移,秤参数(120+)和联网方式(130)在
|
||
> `feed_scale.c` / `network.c` 里是独立宏定义,需同步避让。
|
||
|
||
## 平台数据字段
|
||
|
||
| 字段 | 含义 | 说明 |
|
||
| --- | --- | --- |
|
||
| `ver` | 固件版本号 | 每次状态上报携带,平台可据此识别设备是否发生回退 |
|
||
| `weight` | 剩余料重(净重) | 与平台物模型一致 |
|
||
| `zero` | 空载重量(去皮) | 与平台物模型一致 |
|
||
| `onceWt` | 单次投喂量 | 每次投喂目标重量;**无秤模式不上报、下发设置被拒绝** |
|
||
| `sw1` / `feeding` | 第一路继电器状态 | 1=投喂中,0=停止;无秤模式下两字段**同时上报**,平台两个开关同步刷新 |
|
||
| `sw2~sw4` | 备用继电器状态 | 0/1 |
|
||
| `pow` | 电量状态 | 1=正常,0=低电量 |
|
||
| `energy` | 累计消耗电量 | HLW8032 计量,单位 kWh,两位小数(>10W 每 30s,否则首次+开关切换) |
|
||
| `iccid` | SIM 卡号 | 仅 4G 通道,MQTT 连上后随 pow 上报一次,用于物联网卡套餐查询缴费 |
|
||
| `GPS` | 定位坐标 | 仅 4G 通道(Air780EG),GCJ-02 高德坐标系,格式 `"经度,纬度"`,每 5 分钟上报 |
|
||
|
||
> 注:`zero`(剩余料重)与 `onceWt` 为称重模式专用字段——`PRODUCT_WITH_FEED_SCALE=0` 时状态上报不携带这两个字段,平台下发设置会收到 `no scale on this device` 拒绝应答。
|
||
|
||
下行报文由 `m_app/proto.c` 按键名定位解析(`"sw2"` 不会误中 `"sw21"`,其他字段里的 true/false 不会造成误判)。
|
||
|
||
## 产品模式(带秤/无秤,运行时可切)
|
||
|
||
`PRODUCT_WITH_FEED_SCALE` 从纯编译宏改为**运行时可切换**:模式存 EEPROM(172),空白时用 main.h 宏做默认值。蓝牙命令:`SCALE` 查询,`SCALE0`=四路继电器(无秤),`SCALE1`=称重投料(带秤),**切换后自动重启生效**(初始化路径不同,必须重启)。影响范围:状态上报字段(zero/onceWt)、sw1/feeding 指令语义、继电器 1 断电记忆策略、秤轮询开关。
|
||
|
||
## 串口调试命令
|
||
|
||
通过 USART1(蓝牙串口)发送:
|
||
|
||
```text
|
||
RESET - 系统复位
|
||
INFO - 打印 MQTT 配置
|
||
STATUS - 打印继电器状态
|
||
R1ON - 打开继电器 1
|
||
R1OFF - 关闭继电器 1
|
||
R2ON/R2OFF ~ R4ON/R4OFF - 控制继电器 2~4
|
||
ALLON - 打开所有继电器
|
||
ALLOFF - 关闭所有继电器
|
||
OTA - 复位进入 BootLoader 检查升级
|
||
RECOVERY - WiFi 模块 AT+RESTORE 恢复出厂(忘掉已存热点)+ 整机复位,用于测试 SmartConfig
|
||
BLESET - 强制重新设置蓝牙名称/GATT UUID(清 EEPROM 缓存后重跑配置流程)
|
||
LOG - 查询当前日志等级
|
||
LOGTRACE/LOGDEBUG/LOGINFO/LOGWARN/LOGERROR/LOGFATAL
|
||
- 设置日志等级(写入 EEPROM,断电不丢)
|
||
HELP - 显示支持命令
|
||
```
|
||
|
||
SmartConfig 测试流程:关掉固定热点 → 发 `RECOVERY` → 设备复位走完 4G 重试进入 WiFi → 固定热点(60s) 失败后自动进 SmartConfig(60s 倒计时)→ 用 ESP-Touch APP 广播密码;配网成功后下次开机按记忆优先 `AT+CIPSTATUS` 秒连已存热点(无需再配)。
|
||
|
||
## 蓝牙模块配置(HLK-B40)
|
||
|
||
- **接线**:模块 UART0_TXD/RXD 接 USART1(PA10/PA9),PC5(功能键)接 **PB0**(开漏输出)
|
||
- **上电自动配置**(`ble_at.c`):拉低 PC5 1s 进 AT 模式 → 设 `AT+NAME=<BLE_NAME>` → 设 GATT UUID(默认 `FFE0` 服务 + `FFE1` 收发共用特征,JDY-31/HM-10 同款,通用 BLE 串口 APP 可连)→ `AT+REBOOT` 生效
|
||
- **零窗口设计**:配置成功后将名称缓存进 EEPROM(偏移 132),之后每次开机命中缓存就**完全跳过 AT 流程**——蓝牙 APP 不再断联,开机最早的重启原因/重启记录日志都能实时看到;只有代码里改了 `BLE_NAME`(main.h)才会自动重设一次
|
||
- **手动重设**:蓝牙命令 `BLESET`(清缓存强制重跑)
|
||
- **日志等级**:`LOG` 查询,`LOGTRACE~LOGFATAL` 六档切换,写 EEPROM(偏移 160)断电保持。档位即"输出阈值"——只打印该级及以上:
|
||
|
||
| 命令 | 级别 | 输出内容 | 适用场景 |
|
||
| --- | --- | --- | --- |
|
||
| `LOGTRACE` | 0 最细 | 全部(当前≈DEBUG) | 底层逐字节排查 |
|
||
| `LOGDEBUG` | 1 调试(默认) | AT 指令交互、连接细节、正常运行信息 | 现场排查 |
|
||
| `LOGINFO` | 2 信息 | 联网成功、继电器动作、重启记录、警告/错误 | 日常观察 |
|
||
| `LOGWARN` | 3 警告 | 仅警告/错误(AT 超时、无 SIM、掉线复位) | 稳定运行免打扰 |
|
||
| `LOGERROR` | 4 错误 | 仅错误(踢下线、心跳超时、CRC 失败) | 只看故障 |
|
||
| `LOGFATAL` | 5 致命 | 全部静音 | 极端安静 |
|
||
|
||
- USART1 接收中断带 ORE 自愈(`HAL_UART_ErrorCallback` 自动恢复接收,蓝牙模块重启乱码不会把命令通道打死)
|
||
|
||
## 代码分层(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_rtos_start(); // 创建 net/app/sys 任务 + 启动调度器,不再返回
|
||
}
|
||
```
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
STM32F103_App1/
|
||
├── Src/
|
||
│ ├── main.c # 仅 VTOR + Board_Init/app_init/app_rtos_start
|
||
│ └── stm32f1xx_hal_timebase_tim.c # TIM2 作 HAL 时基(SysTick 归 FreeRTOS)
|
||
├── Inc/
|
||
│ ├── main.h # 产品型号宏 PRODUCT_WITH_FEED_SCALE、MQTT 默认配置
|
||
│ └── FreeRTOSConfig.h # RTOS 配置(堆10KB/钩子/中断阈值,含内存预算注释)
|
||
├── Middlewares/
|
||
│ └── FreeRTOS/Source/ # FreeRTOS V10.0.1 内核(tasks/queue/list + ARM_CM3 port + heap_4)
|
||
├── m_app/
|
||
│ ├── proto.c/h # 平台 JSON 报文取值工具(按键名定位)
|
||
│ ├── ota_app.h # Flash 分区定义(BootLoader 32K + APP1 224K)
|
||
│ └── boot_share.h # BL↔APP 共享 RAM 结构(复位原因/运行时长/原因码)
|
||
├── 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/5 初始化、运行中切波特率、中断/DMA 接收、ORE 自愈
|
||
│ ├── HLW8032/ # 电能计量(UART5 帧解析、PF 脉冲累计、kWh 换算、EEPROM 持久化)
|
||
│ ├── 24C02/ # EEPROM 读写(页写入,带跨任务互斥锁)
|
||
│ ├── IIC/ # 软件 I2C(带总线恢复:SDA 被拉死时补 9 时钟释放)
|
||
│ ├── ADC/ # 电池电压检测(0~100% 限幅、掉电去抖判定)
|
||
│ ├── LED/ # 指示灯(LED2 兼任 TPL5010 喂狗)
|
||
│ ├── LOG/ # 分级日志(USART1 输出,带互斥锁;log_cn.c 中文格式串)
|
||
│ ├── SYSINIT/ # Board_Init/IWDG、app_init、RTOS 任务、reset_log 重启记录
|
||
│ └── W25Q64/ # 外部 Flash 驱动与 OTA 双槽信息区(带跨任务互斥锁)
|
||
└── MDK-ARM/ # Keil 工程文件
|
||
|
||
STM32F103_BootLoader/
|
||
├── Src/main.c # 硬件初始化 + IWDG + 入口
|
||
├── m_app/ota_boot.c # OTA 状态机:备份/烧录/回退 + 复位原因透传 + 主电源门禁 + 无APP重试
|
||
├── m_app/boot_share.h # 与 APP 共享的 RAM 结构定义(同一份拷贝)
|
||
├── 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.5.0
|
||
- Author: Helei
|