6 Commits

Author SHA1 Message Date
YuanHongbin 8dfd6a6182 chore(version): 软件版本小版本迭代 V1.4 -> V1.5
ETH口升级兼容方案打通及升级链路收尾, 软件版本随本次功能迭代升级到V1.5
2026-10-12 10:44:57 +08:00
YuanHongbin 5db05d061b feat(ota): ETH口升级兼容方案与升级链路收尾, 软件版本V1.4
- ETH口参考Cat1接入0x7D升级: 下行IsHexText校验/NetUpgradeFeed第一优先/明文直通DebugAnalyze, 升级应答帧主循环最顶部直发
- 升级TX应答队列按通道绑定(TxChan/CurCh): Cat1/ETH各自取回本通道ACK, 双链路互不抢占
- ETH接收改为8KB单生产单消费环形缓冲: 修复IRQ与Rev线程单缓冲竞态丢包
- 升级会话期间ETH状态机旁路: 防RESET/OFF复位模组断链; 会话内纯hex流模式(奇数尾字符跨块拼接)
- Public: IsHexText公共化(Cat1/Eth共用), AsciiToHex内部计数器uint16修复(512字符块溢出丢帧)
- Cat1: 升级帧带CH_CAT1通道标签, 空闲5分钟保活重连只建链不上传
- 移除全部[ETHDBG]调试打印, 新增MAC不匹配提示日志
2026-10-12 10:24:44 +08:00
YuanHongbin 920bddf949 feat(cat1): 空闲保活重连/通道分流重构/远程调试命令链路, 注释编码统一UTF-8
Cat1通信稳定性:
- 新增空闲5分钟无上传自动断开重建TCP保活(只建链不上传, exe端客户端列表自动刷新)
- 注册超时不再重启模组(兼容网络调试助手等不回注册应答的对端), 连接自愈由LIPSEND失败与空闲重连兜底
- 日志流分离: 调试回应(Debug_Printf)经duchRing透传, 系统级rt_kprintf日志固定走RS485 Ch1, 消除trace/AT应答自激灌环

通道分流重构(USART1固定接Cat1):
- much/auch/duch/cuch恢复按配置分流; dbg通道选项从全部配置命令删除(ParseChannel/帮助文本/CH_DBG互斥残留)
- 下行分流规则: 0x7D升级帧第一优先(NetUpgradeFeed), 0x7A业务帧按Much/Auch回调, 其余明文直通DebugAnalyze
- Cat1升级不受分流限制: 无论Cat1配置在哪个通道均可响应0x81握手与完整升级会话
- 删除DUCH_MODE独占透传状态(duch=cat1纯调试统一走WAIT_SEND), 删除CAT1_ERR_RESET_DLY_MAX(1小时空闲重启由5分钟TCP重连替代)

远程调试命令链路:
- 服务端明文/hex命令经Cat1下行直通DebugAnalyze执行, 兼容带/不带\r\n(修复strlen<rLen误判截断URC丢弃)
- LIPURC解析增加Cat1IsHexText合法性校验(修复AsciiToHex对非hex明文静默产出乱码绕过直通分支)
- 命令回应经QISEND明文直发(网络调试助手可读, 发送前等URC流静默防吞), 升级应答帧保持LIPSEND hex(exe协议不变)

注释与编码治理:
- 全工程C/H文件统一UTF-8: CatOneATCmdStr 10处历史乱码注释还原为正确中文
- DebugCmd.c 35处函数头乱码注释(U+FFFD固化)从git 314ff67历史按函数名恢复, 5个新增函数注释按实现补写
- 修正错位/错误描述: DebugCmdTimeSync(系统时间同步)/DebugCmdRegister(重新注册)/DebugCmdCat1OnOff(调试开关)/DebugCmdSetCollectTime等4处按实际功能重写, 低功耗错别字修正
- 修复CatOneATCmdStr[16]数组17元素越界且CAT_ONE_CCLK索引错位(时间同步从未生效), 扩容[17]并归位

升级模块(承接f206b8f联调结论):
- TX队列rt_hw_interrupt_disable/enable关中断临界区(修并发越界HardFault)
- 断点续传永久取消(每次点击升级从0全新下载); 升级启动/完结均清进度记录(扇区4037)
- 暂存区按需擦除NupErasedMap(修二次升级NOR脏位损坏); 0x7D帧头MAC与本机不一致丢弃(防串台)
- AsciiToHex长度参数uint8->uint16(修432字符帧截断)
2026-10-10 14:14:16 +08:00
YuanHongbin 314ff67f26 chore(ide): 提交Keil调试器配置与Boot A/B分区升级方案文档 2026-09-23 11:22:02 +08:00
YuanHongbin 6d4aad164b chore(version): 软件版本小版本迭代 V1.3 -> V1.4 2026-09-23 11:22:02 +08:00
YuanHongbin b1cba74aa7 feat(app): 配合Boot A/B分区升级, 链接区限定A区232KB
bsp.h分区宏改A区29扇区+新增B区宏(FLASH_BINFO_BASE仍=0x7C000); GateWay_app.sct IROM 0x74000→0x3A000;
Update()增加AppSize<=FLASH_APPB_SIZE校验, 超限拒绝不置标志不复位
2026-09-23 11:16:55 +08:00
20 changed files with 1132 additions and 324 deletions
+112
View File
@@ -0,0 +1,112 @@
# Boot A/B 分区升级改造计划
## 背景
当前 Boot 升级流程:收到 0x81 升级请求后复位 → **先整片擦除 App 区(0x8000\~0x7C000, 58扇区)** → 逐包(200字节)边收边写 → 收完才做 CRC32 整包校验 → 失败/超时仅清标志,但 App 区已被擦除,**设备变砖只剩 Boot**。
改造目标:App 区分 A/B 两区。新固件先下载到 B 区暂存,**整包 CRC 校验成功后才搬运到 A 区**;任何失败(CRC 错/下载超时/断电)都能回到 A 区原有 App,彻底消除变砖风险。RS485 协议(0x81/0x82/0x01/0x02 帧)不变,主机升级工具无需修改。
## 新 Flash 分区(512KB = 64 扇区 × 8KB)
| 分区 | 地址范围 | 扇区数 | 说明 |
| ---------- | ---------------- | -------------- | --------------------------- |
| Boot | 0x00000\~0x07FFF | 4 (32KB) | 不变,MAC 常量 0x4000 不受影响 |
| **A区(运行)** | 0x08000\~0x41FFF | **29 (232KB)** | App 链接基址仍为 0x8000 |
| **B区(暂存)** | 0x42000\~0x7BFFF | **29 (232KB)** | 升级包下载区 |
| 参数区 | 0x7C000\~0x7DFFF | 1 | 不变,boot\_para\_t 结构**完全不变** |
| 备用 | 0x7E000\~0x7FFFF | 1 | 不变 |
App 当前实际 113KB,A/B 各 232KB 有约一倍余量。断电状态全部由 `UpdateFlag + AppSize + Crc32Check + B区CRC实测` 推导,**无需新增参数字段**。
## 新升级状态机
```
main() 决策(不变): AppFlag==START/BOOT_UPGRADE→跳A | UpdateFlag==UPDATE→BL_ERASE_FLASH | 其他→BL_IDLE
BL_ERASE_FLASH(改):
AppSize非法(==0 或 >232KB) → AppFlag=START_FLAG/UpdateFlag=0 → BL_RESET(回老App)
GetCrc32(B区, AppSize)==Crc32Check ? ──是(断电续传:已下载完/搬运中断电)──→ BL_COPY_TO_A
└─否→ 仅擦B区29扇区 → BL_GET_DATA(逐包写B区) → 收完 ↓
0x82 收完全部包(改):
CRC32(B区,AppSize)==Crc32Check ?
├─是→ UpDateFlag=false → BL_COPY_TO_A
└─否→ AppFlag=APP_START_FLAG / UpdateFlag=0 → 写参数 → BL_RESET (回老App, 原来是BL_IDLE变砖)
BL_COPY_TO_A(新增):
按 ceil(AppSize/8K) 扇区数逐扇区: FeedDog → 擦A扇区 → 从B读8KB到RAM缓冲 → FlashWrite写A
→ CRC32(A区,AppSize)==Crc32Check ?
├─是→ AppFlag=APP_BOOT_UPGRADE_FLAG / UpdateFlag=0 → 写参数 → BL_RESET → 跳A
└─否→ CopyRetryCnt<1 重试搬运; 仍失败→ AppFlag=0/UpdateFlag=0 → BL_IDLE
BL_GET_DATA_TIMEOUT(改): 重试3次耗尽 → AppFlag=APP_START_FLAG / UpdateFlag=0 → BL_RESET(回老App, 原来是BL_IDLE)
```
断电自愈覆盖:①下载中断电→重启 B 校验失败→重新下载;②搬运中断电→A 残缺但 B 仍有效→重启自动重搬;③擦 A 前提是 AppFlag=0 且 Boot 不会在 UpdateFlag 置位时跳转,无跳半擦区风险;④0x81 写参数中断电→参数区 0xFF→走 FirstRunFlag 分支跳 A,老 App 完好。
## Boot 工程改动(d:\keilProject\hc32f460\git\hc32f460bootloader)
**bsp.h**(分区宏,L194-206):
* `APP_A_ADDRESS = FLASH_BASE + BOOT_SIZE` (0x8000)、`APP_BANK_SECTORS 29u`、`APP_BANK_SIZE = 29*8K` (232KB)、`APP_B_ADDRESS = APP_A_ADDRESS + APP_BANK_SIZE` (0x42000)、`APP_ADDRESS` 保留别名指向 A 区、`BOOT_PARA_ADDRESS = APP_B_ADDRESS + APP_BANK_SIZE` (0x7C000 不变)
**main.h**:
* 枚举加 `BL_COPY_TO_A`;`BootLoader_t` 加 `uint32_t CopyRetryCnt`
**bsp.c**:
* `FlashWrite`/`FlashRead` 长度参数 `uint8_t` → `int`(8KB 整扇区 2048 字超出 uint8\_t,评审发现 P2)
* `FlashWrite` 尾部不足 4 字节时按 0xFF 补齐组字写入(防 AppSize 非 4 对齐时丢尾字节导致 CRC 永败)
* `EraseFlash()` 参数化为擦指定起始地址+扇区数(现仅用于擦 B 区 29 扇区,不再碰 A 区)
**main.c**:
* `GetCrc32(int dwLen)` → `GetCrc32(uint32_t BaseAddr, int dwLen)`(现有 L50 固定读 APP\_ADDRESS)
* `AppWriteFlash` 加基址参数,下载包写入 `APP_B_ADDRESS + offset`
* `AnalyzeData`:
* 0x81 分支:加 AppSize 合法性拒绝(>232KB 或 0 时忽略)
* 0x82 分支:加状态守卫 `Status 必须为 BL_GET_DATA/BL_WAIT_REV`(评审发现 P1:防迟到包破坏搬运期 B 区);收完 CRC 改算 B 区,成功→`BL_COPY_TO_A`,失败→恢复 START\_FLAG→BL\_RESET
* `BootLoaderLoopHandler`:`BL_ERASE_FLASH` 改为先校验 B 区(断电续传入口);新增 `BL_COPY_TO_A` case(调 CopyAppBtoA);`BL_GET_DATA_TIMEOUT` 耗尽改恢复 START\_FLAG+BL\_RESET
* 新增 `CopyAppBtoA()`:main.c 内实现,`static uint32_t s_CopyBuf[2048]`(8KB),逐扇区 EFM\_SectorErase(A)+读 B 缓冲+FlashWrite(A),每扇区 FeedDog,结尾 CRC32(A) 校验返回 bool
## App 工程改动(d:\keilProject\HC32F460\Git\GateWay)
**Project/GateWay/source/User/Inc/bsp.h**(L19-29,评审 P0 关键):
* `FLASH_APP_SIZE` 58→29 扇区后 `FLASH_BINFO_BASE` 会错算成 0x42000,**必须**改为:
`FLASH_APP_A_SIZE(29*8K)` + `FLASH_APPB_BASE = FLASH_APP_BASE + FLASH_APP_A_SIZE`(0x42000) + `FLASH_APPB_SIZE(29*8K)` + `FLASH_BINFO_BASE = FLASH_APPB_BASE + FLASH_APPB_SIZE`(**仍=0x7C000**)、FLASH\_CFG\_BASE 连带仍=0x7E000
**MDK/GateWay\_app.sct**:`LR_IROM1/ER_IROM1` 尺寸 `0x74000` → `0x3A000`(两个 target 共用此 sct,sct 优先于 uvprojx IROM)
**MDK/GateWay.uvprojx**:app\_boot target(L1168) IROM Size `0x6c000`→`0x3a000`(一致性,app\_dubeg 的 0x0/0x80000 本就与 sct 不一致,不动)
**source/User/Src/Update.c** `Update()`:加 `AppSize==0 || AppSize>FLASH_APPB_SIZE` 时报错返回,不置标志不复位
其余 App 代码(VTOR 动态基址、UpdateInit、升级异常检测、JumpToApp)均无需改动。
## 实施注意事项
1. **编码**:所有源文件含中文注释,一律 UTF-8。用 .NET 脚本(`UTF8Encoding(false)`)做替换修改(本会话已验证 Edit 工具会把 GBK 污染写入),改后校验 git diff 行数与编码
2. boot\_para\_t 结构、BOOT\_PARA\_ADDRESS、协议帧均不变 → 新旧 Boot/App 交叉兼容
3. 编译验证:Boot(Keil MDK)+ App(app\_boot target)分别编译通过,App 镜像 113KB < 232KB
## 验证清单
1. 正常升级全流程:新固件运行、外部参数分区按 APP\_BOOT\_UPGRADE\_FLAG 清理、传感器重新注册
2. 下载中断电 → 重启回老 App → 可再次升级
3. 下载超时(主机停发)→ 3 次重试后自动回老 App
4. CRC 注入失败 → 回老 App
5. 搬运中断电 → 重启自愈重搬 → 升级成功
6. B 区残留旧镜像 + 新 0x81(不同 AppSize/CRC)→ 不误判、重新下载
7. COPY 阶段发 0x81/0x82 → 均被拒绝,搬运不被干扰
8. AppSize=0 / >232KB → 拒绝
@@ -0,0 +1,85 @@
# Cat1 远程升级方案设计(待评审)
## 总体架构
App 扮演"接收端"角色(与 Boot 在 RS485 升级中的角色相同),服务器扮演升级主机:
```
服务器(升级主机) --TCP--> 合宙Cat1模组 --USART1--> App(RT-Thread)
│ ├─ 收0x7D帧(0x81/0x82) → 固件分包写外部SPI Flash暂存区
│←────────── 0x01 ACK(带进度) ──────────────┤
│ └─ 收完→CRC32校验→置UpdateFlag=APP_UPDATE_SPI_FLAG→复位
复位
└→ Boot: 检测APP_UPDATE_SPI_FLAG
→ SPI Flash逐扇区读 → 写内部B区 → CRC32(B)
→ 搬运B→A → CRC32(A) → 擦SPI Flash暂存区 → 置完成标志 → 复位跳App
```
**升级协议完全复用 RS485 的 0x7D 帧**:帧头 0x7D + DevMac(6) + Cmd(1) + PayloadLen(1) + Payload + CRC16(Modbus),命令 0x81 升级请求{PackageNum, AppSize, AppCrc32} / 0x82 数据{PackageIndex, PackageNum, DataLen, Data[200]} / 0x01 ACK / 0x02 请求数据。服务器端协议不变。
## 关键决策与理由
1. **Boot 不驱动 Cat1**:AT 注网 30 秒起、Boot 仅 32KB 分区(当前 Code 16KB)、模组 TCP 连接态在 MCU 复位后不可控。收包/重试/续传等复杂逻辑全部放 App(RTOS 资源丰富),Boot 只做"从外部 Flash 搬运到 B 区"这一简单确定性动作。
2. **固件暂存外部 SPI Flash**:16MB 尾部划 58×4KB=232KB 暂存区(扇区 4038~4095),日志环形写入回绕点同步从 16MB 末尾前移到扇区 4038。
3. **复用 UpdateFlag 机制**:新增标志值 `APP_UPDATE_SPI_FLAG`(建议 0xA5A55A5A,与现有 START=0x55AA5A5A / UPDATE=0xA5A5AA55 / BOOT_UPGRADE=0x5A5A55A5 不冲突),`boot_para_t` 结构不变,RS485 本地升级路径完全不受影响。
4. **依赖 TCP 可靠有序**:应用层丢包仅发生在断连/模组重启,因此用"断点续传 + 超时重发请求"而非滑动窗选择重传,状态机与 Boot 版本同构,服务器实现简单。
## 升级流程(含 Cat1 特化处理)
1. 服务器 → `0x81{PackageNum, AppSize, AppCrc32}`
2. App 校验 DevMac/合法性(AppSize==0 或 >232KB 拒绝)→ 回 `0x01 ACK`:
- 载荷建议扩展为 `{Status, ResumedIdx}`:`0x20`=全新开始(App 擦暂存区)、`0x21`=断点续传(已收 ResumedIdx 包,服务器从该包续发)。**注意:原 0x01 载荷 1 字节(0x20),扩展为 3 字节需服务器端同步适配(新版本服务器)**
3. 服务器从 ResumedIdx 起发 `0x82{PackageIndex, ...}`:
- **流控(推荐窗口式)**:服务器连发 W 包(建议 W=8~16)→ App 校验 PackageIdx 顺序写入暂存区(乱序/重复包丢弃)→ App 主动发 `0x02{期望包号}` → 服务器继续。停等式(每包一问一答)在 Cat1 上太慢:1160 包 × RTT≈2~5 分钟+,窗口式约 30~60 秒
- TCP 保证有序,App 端只需顺序校验 + 期望包号判定
4. App 收完 → `CRC32(暂存区, AppSize)`:
- 通过 → 回 `0x01{0x30 完成}` → 写 boot_para_t{AppSize, Crc32Check, UpdateFlag=APP_UPDATE_SPI_FLAG} → 200ms → 复位
- 失败 → 回 `0x01{0x31 CRC错}` → 不复位,清暂存区等待服务器重发(可多次)
5. Boot 检测 `APP_UPDATE_SPI_FLAG` → 逐 8KB:读外部 2×4KB 扇区 → 擦写内部 B 区 1×8KB 扇区 → `CRC32(B)` → `CopyAppBtoA()`(现有)→ `CRC32(A)` → **擦外部暂存区(防残留误判,同 RS485 路径 B 区擦除教训)** → 置 `APP_BOOT_UPGRADE_FLAG` → 复位跳 App
6. App 启动(`APP_BOOT_UPGRADE_FLAG`)→ 擦外部参数分区 → 上线后服务器可查版本确认升级成功
### 超时与重试参数(Cat1 特化,均为 App 侧状态机)
| 参数 | Boot RS485 值 | Cat1 版建议 | 理由 |
|------|--------------|------------|------|
| 等包超时 | 500ms | **3 秒** | Cat1 RTT + 模组缓冲 |
| 重试次数 | 3 | **5** | 偶发断连需模组重连 |
| 断连恢复 | — | 模组重连(现有逻辑)后服务器重发 0x81,ACK 带进度续传 | TCP 断开后应用层无从补偿 |
| 整体超时 | — | 30 分钟无进展 → 放弃升级(清标志,不复位) | 防止无限等待 |
### 断电自愈矩阵(Cat1 路径)
| 断电/异常时机 | 恢复行为 |
|--------------|---------|
| App 收包中断连/断电 | 暂存区+进度保留 → 重连后 0x81 续传 |
| App 置标志后、复位前 | Boot 正常走 SPI Flash→B→A |
| Boot 搬运中(写B区)断电 | UpdateFlag 仍为 SPI_FLAG → B 区 CRC 有效则重搬;无效则清标志回老 App |
| 搬运完、擦暂存区中断电 | 参数未写 → B CRC 判定重搬(B 仍在);参数已写 → 跳新 App |
## 改动清单
### App(GateWay 工程)
| 文件 | 改动 |
|------|------|
| `spiflash.h/c` | 新增 `FLASH_FW_STASH_BASE`(扇区 4038 起)+ StashErase/StashWrite/StashRead;`AddLog` 回绕点 16MB→扇区 4038;新增 CRC32(与 Boot GetCrc32 同算法 0xEDB88320) |
| `CatOneTask.c`(或新文件) | Cat1 通道增加 0x7D 帧组帧解析(`+LIPURC` hex→二进制后按 0x7D 组帧,与现有 0x7A 链路并存,按帧头区分);升级接收状态机(移植 Boot AnalyzeData+BL 状态机,超时/重试参数按上表) |
| `Update.c` | 新增 `UpdateFromStash()`:写 boot_para_t(APP_UPDATE_SPI_FLAG + AppSize + Crc32Check)→ 复位 |
| `bsp.h`(App) | `APP_UPDATE_SPI_FLAG` 宏;`boot_para_t` 不动 |
| `main.h`(App) | 版本号随迭代递增 |
### Boot(HC32F460BootLoader 工程)
| 文件 | 改动 |
|------|------|
| `bsp.c/h` | SPI Flash 驱动:与 App 相同总线(需确认外部 Flash 挂载的 SPI 外设与引脚,Boot 复用),W25Q 写使能/扇区擦/页读命令;约 +2~3KB(Boot 当前 16KB,32KB 分区内余量足够) |
| `main.c/h` | `APP_UPDATE_SPI_FLAG` 宏 + main() 决策分支(SPI_FLAG → 新状态 `BL_STASH_TO_B`);`BL_STASH_TO_B`:逐 8KB 外部读→B 区写→CRC(B) 通过进 `BL_COPY_TO_A`(现有),失败清标志回老 App;`BL_COPY_TO_A` 成功分支追加"擦外部暂存区" |
### 服务器端(不在本仓库,需同步开发)
- 实现 0x81/0x82 升级主机逻辑:窗口流控(W 包一批)、解析 0x02 期望包号、断点续传(ACK 的 ResumedIdx)、完成/失败状态处理
## 风险与待确认项
1. **调试口与 Cat1 共用 USART1(PB08 方向切换)**:升级期间必须保证 UART 方向锁定在 Cat1 侧(`CAT1_ON()`),升级状态机运行期间禁止调试打印切向(`U_DBG_LOG` 若触发 `DBG_ON()` 会破坏 TCP 数据)。实施时需审查 DEBUG=1 下所有升级路径的打印。
2. **外部 Flash 挂载总线待确认**:读 spiflash.c 确认 SPI 外设号/引脚/QSPI 模式,Boot 驱动按此实现。
3. **日志区历史数据**:回绕点前移到扇区 4038(约 15.77MB),已存历史日志超出的部分会被覆盖——现网日志增长慢,实际影响小,但需知晓。
4. **0x01 ACK 载荷扩展**(1→3 字节)需服务器端配合,若服务器不改动则退化为停等式流控(慢但兼容)。
5. **升级流量**:232KB/次,Cat1 资费可忽略。
+4 -4
View File
@@ -432,18 +432,18 @@
<Bp>
<Number>0</Number>
<Type>0</Type>
<LineNumber>569</LineNumber>
<LineNumber>503</LineNumber>
<EnabledFlag>1</EnabledFlag>
<Address>0</Address>
<Address>54178</Address>
<ByteObject>0</ByteObject>
<HtxType>0</HtxType>
<ManyObjects>0</ManyObjects>
<SizeOfObject>0</SizeOfObject>
<BreakByAccess>0</BreakByAccess>
<BreakIfRCount>0</BreakIfRCount>
<BreakIfRCount>1</BreakIfRCount>
<Filename>..\source\User\Src\CatOneTask.c</Filename>
<ExecCommand></ExecCommand>
<Expression></Expression>
<Expression>\\GateWay\../source/User/Src/CatOneTask.c\503</Expression>
</Bp>
</Breakpoint>
<WatchWindow1>
+1 -1
View File
@@ -1165,7 +1165,7 @@
<OCR_RVCT4>
<Type>1</Type>
<StartAddress>0x8000</StartAddress>
<Size>0x6c000</Size>
<Size>0x3a000</Size>
</OCR_RVCT4>
<OCR_RVCT5>
<Type>1</Type>
+3 -3
View File
@@ -1,10 +1,10 @@
; *************************************************************
; *** Scatter-Loading Description File for GateWay APP ***
; *** Flash: APP区 0x8000起 (Boot 32KB), 到参数区0x7C000前 ***
; *** Flash: A区 0x8000~0x41FFF(232KB, 运行) + B区 0x42000~0x7BFFF(232KB, 升级暂存), 参数区0x7C000 ***
; *************************************************************
LR_IROM1 0x00008000 0x00074000 {
ER_IROM1 0x00008000 0x00074000 {
LR_IROM1 0x00008000 0x0003A000 {
ER_IROM1 0x00008000 0x0003A000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
+7 -6
View File
@@ -13,6 +13,7 @@
#include "bsp.h"
#include "main.h"
#include "sx127x.h"
#include "CatOneTask.h"
extern GateWayPara_t GateWay;
@@ -76,9 +77,9 @@ void rt_hw_board_init(void)
static int uart_init(void)
{
//#error "TODO 2: Enable the hardware uart and config baudrate."
DbgOrCat1Uart_Config(500000);
// RS485Ch1_Config(500000);
/*USART1专用Cat1: 上电恒115200+CAT1方向*/
DbgOrCat1Uart_Config(115200);
CAT1_ON();
return 0;
}
INIT_BOARD_EXPORT(uart_init);
@@ -86,14 +87,14 @@ INIT_BOARD_EXPORT(uart_init);
void rt_hw_console_output(const char *str)
{
rt_enter_critical();
/*调试输出按duch配置分流; USART1固定Cat1, duch=cat1经透传缓冲上送TCP(不直写USART1防干扰AT通道)*/
switch(GateWay.ConfigPara.Duch) {
case CH_RS485_1: RS485Ch1UartSend((uint8_t *)str, strlen(str)); break;
case CH_RS485_2: RS485Ch2UartSend((uint8_t *)str, strlen(str)); break;
case CH_ETH: EthUartSend((uint8_t *)str, strlen(str)); break;
case CH_LORA: Sx1276LoRaSendBuffer((uint8_t *)str, strlen(str)); break;
case CH_CAT1: DebugOrCat1UartSend((uint8_t *)str, strlen(str)); break;
case CH_DBG: DebugOrCat1UartSend((uint8_t *)str, strlen(str)); break;
default: DebugOrCat1UartSend((uint8_t *)str, strlen(str)); break;
case CH_CAT1: RS485Ch1UartSend((uint8_t *)str, strlen(str)); break; /*系统级日志固定RS485 Ch1, 不进透传(防trace/AT应答自激灌环)*/
default: RS485Ch1UartSend((uint8_t *)str, strlen(str)); break; /*旧参数dbg/空值兜底RS485 Ch1*/
}
rt_exit_critical();
}
+2 -2
View File
@@ -2,8 +2,8 @@
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x00008000 0x0006C000 { ; load region size_region
ER_IROM1 0x00008000 0x0006C000 { ; load address = execution address
LR_IROM1 0x00008000 0x0003A000 { ; load region size_region
ER_IROM1 0x00008000 0x0003A000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
@@ -33,7 +33,6 @@ const DBGFunType DebugFun[DEBUG_CMD_CNT];
static GateWayPara GateWay;
static uint8_t DebugRxLen;
static uint8_t DebugRxBuff[DBG_RX_LEN_MAX];
static uint8_t DebugRxTimeOut1mSCnt;
static rt_sem_t DebugRev_Sem;
static char tempBuff[DEBUG_BUFF_MAX];
static bool ParaEchoCh1 = false;
@@ -43,10 +42,8 @@ void Debug_Printf(char *format, ...)
va_list args;
va_start(args, format);
vsprintf(&tempBuff[0], format, args);
if(GateWay && GateWay->ConfigPara.Duch == CH_CAT1
&& GateWay->ConfigPara.Much != CH_CAT1
&& GateWay->ConfigPara.Auch != CH_CAT1
&& !(GateWay->ConfigPara.CuchMask & CUCH_CAT1)) {
/*duch=cat1: 调试文本(命令回应)经Cat1 TCP透传(系统级rt_kprintf日志走console->RS485 Ch1, 不经此路径)*/
if(GateWay && GateWay->ConfigPara.Duch == CH_CAT1) { /*调试输出(命令回应)一律透传, 不限Much/Auch;*/
char *line = tempBuff;
while(line && *line) {
char *end = strstr(line, "\r\n");
@@ -58,9 +55,6 @@ void Debug_Printf(char *format, ...)
} else {
rt_kprintf(tempBuff);
}
if(ParaEchoCh1) {
RS485Ch1UartSend((uint8_t *)tempBuff, strlen(tempBuff));
}
va_end(args);
}
/*****************************************************************************************
@@ -84,7 +78,6 @@ void Debug_Thread_Entry(void *parameter)
result = rt_sem_take(DebugRev_Sem, RT_WAITING_FOREVER);
if(result == RT_EOK) {
DebugAnalyze(GateWay, DebugRxBuff, DebugRxLen);
DebugRxTimeOut1mSCnt = 0;
DebugRxLen = 0;
}
}
@@ -98,35 +91,15 @@ void Debug_Thread_Entry(void *parameter)
*****************************************************************************************/
void DbgOrCat1RxIrqCallback(uint8_t rData)
{
if(ChannelIsActive(GateWay->ConfigPara, CH_CAT1)) {
Cat1IrqCallback(rData);
return;
}
if(DebugRxTimeOut1mSCnt == 0)
DebugRxLen = 0;
DebugRxTimeOut1mSCnt = 3;
if(DebugRxLen < DBG_RX_LEN_MAX) {
DebugRxBuff[DebugRxLen++] = rData;
}
/*USART1专用Cat1: 接收数据无条件进Cat1处理(不再按配置分流)*/
Cat1IrqCallback(rData);
}
void DebugOrCat1RxOverhandler(void)
{
if(ChannelIsActive(GateWay->ConfigPara, CH_CAT1)) {
Cat1OverHandler();
return;
}
if(DebugRxTimeOut1mSCnt > 0)
DebugRxTimeOut1mSCnt--;
if(DebugRxLen > 2 && DebugRxTimeOut1mSCnt == 0) {
DebugRxTimeOut1mSCnt = 20;
rt_sem_release(DebugRev_Sem);
}
/*USART1固定接Cat1: 超时定时无条件服务Cat1接收链(URC分块超时/IRQ信号量释放)*/
Cat1OverHandler();
}
/*****************************************************************************************
@@ -168,7 +141,7 @@ void DebugAnalyze(GateWayPara GateWay, uint8_t *rData, uint16_t rLen)
for(int i = 0; i < DEBUG_CMD_CNT; i++) {
if(strcmp(argv[0], DebugFun[i].DBGCmd) == 0) {
DebugFun[i].DBGExec(argc, argv);
break; // 未匹配break跳出
/*USART1专用Cat1后, 调试输出统一走RS485 Ch1(经rt_kprintf→rt_hw_console_output)*/
}
}
}
@@ -203,7 +176,7 @@ void DebugCmdMainOnOff(int argc, char *argv[])
/*****************************************************************************************
* 函数名称: DebugCmdCat1OnOff
* 功能描述: CAT1通道消息控制
* 功能描述: Cat1调试信息开关(cat1dbg on/off)
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
@@ -251,10 +224,10 @@ void DebugCmdEthOnOff(int argc, char *argv[])
}
/*****************************************************************************************
* 函数名称: DebugCmdCat1OnOff
* 功能描述: CAT1通道消息控制
* 函数名称: DebugCmdRS485Ctrl
* 功能描述: RS485通道控制(通道使能/内置通讯单元使能/输出电压/波特率/升级标志)
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
*****************************************************************************************/
void DebugCmdRS485Ctrl(int argc, char *argv[])
@@ -699,14 +672,13 @@ void DebugCmdLsExclCommUnit(int argc, char *argv[])
}
/*****************************************************************************************
* 函数名称: ParseChannel
* 功能描述: 解析通道字符串
* 功能描述: 解析通道名称字符串为通道枚举值
* 参 数: str, 通道名称字符串
* 返 回 值: Channel_m通道枚举值
*****************************************************************************************/
static Channel_m ParseChannel(const char *str)
{
if(strcmp(str, "null") == 0) return CH_NULL;
if(strcmp(str, "dbg") == 0) return CH_DBG;
if(strcmp(str, "ch1") == 0) return CH_RS485_1;
if(strcmp(str, "ch2") == 0) return CH_RS485_2;
if(strcmp(str, "eth") == 0) return CH_ETH;
@@ -740,9 +712,7 @@ static void DebugCmdSetMuch(int argc, char *argv[])
DBG_LOG("Invalid channel.\r\n");
return;
}
if(ch != CH_NULL && (ChannelIsActive(GateWay->ConfigPara, ch)
|| (ch == CH_DBG && ChannelIsActive(GateWay->ConfigPara, CH_CAT1))
|| (ch == CH_CAT1 && ChannelIsActive(GateWay->ConfigPara, CH_DBG)))
if(ch != CH_NULL && ChannelIsActive(GateWay->ConfigPara, ch)
&& GateWay->ConfigPara.Much != ch) {
DBG_LOG("Channel %s is occupied.\r\n", ChannelName(ch));
return;
@@ -781,9 +751,7 @@ static void DebugCmdSetAuch(int argc, char *argv[])
DBG_LOG("Invalid channel.\r\n");
return;
}
if(ch != CH_NULL && (ChannelIsActive(GateWay->ConfigPara, ch)
|| (ch == CH_DBG && ChannelIsActive(GateWay->ConfigPara, CH_CAT1))
|| (ch == CH_CAT1 && ChannelIsActive(GateWay->ConfigPara, CH_DBG)))
if(ch != CH_NULL && ChannelIsActive(GateWay->ConfigPara, ch)
&& GateWay->ConfigPara.Auch != ch) {
DBG_LOG("Channel %s is occupied.\r\n", ChannelName(ch));
return;
@@ -845,9 +813,7 @@ static void DebugCmdSetCuch(int argc, char *argv[])
}
if(strcmp(argv[1], "add") == 0) {
if(GateWay->ConfigPara.Much == ch || GateWay->ConfigPara.Auch == ch || GateWay->ConfigPara.Duch == ch
|| (ch == CH_DBG && (GateWay->ConfigPara.Much == CH_CAT1 || GateWay->ConfigPara.Auch == CH_CAT1 || GateWay->ConfigPara.Duch == CH_CAT1))
|| (ch == CH_CAT1 && (GateWay->ConfigPara.Much == CH_DBG || GateWay->ConfigPara.Auch == CH_DBG || GateWay->ConfigPara.Duch == CH_DBG))) {
if(GateWay->ConfigPara.Much == ch || GateWay->ConfigPara.Auch == ch || GateWay->ConfigPara.Duch == ch) {
DBG_LOG("Channel %s occupied by Much/Auch/Duch.\r\n", ChannelName(ch));
return;
}
@@ -1030,8 +996,8 @@ void DebugCmdRestoreFactory(int argc, char *argv[])
DBG_LOG("Restore factory settings, GateWay Restart...\r\n");
/*仅擦除参数分区(扇区0~1),非全片擦除;擦除后参数区全0xFF,
重启后SavFlag无效自动加载Boot默认参数*/
for(uint32_t Sector = GATEWEY_PARA_SAV_SECTOR; Sector < LOG_INFO_START_SECTOR; Sector++) {
重启后SavFlag无效自动加载Boot默认参数*/
for(uint32_t Sector = GATEWEY_PARA_SAV_SECTOR; Sector < LOG_INFO_START_SECTOR; Sector++) {
SpiFlashEraseSector(Sector * SPIFLASH_SECTORSIZE);
} rt_thread_delay(1000);
NVIC_SystemReset();
@@ -1074,7 +1040,7 @@ static void DebugCmdUpload(int argc, char *argv[])
/*****************************************************************************************
* 函数名称: DebugCmdRegister
* 功能描述: 触发设备位置信息注册
* 功能描述: 触发重新向服务器注册(清注册标志并触发Cat1/ETH重连注册)
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
@@ -1109,10 +1075,10 @@ static void DebugCmdRegister(int argc, char *argv[])
}
/*****************************************************************************************
* 函数名称: DebugCmdRestoreFactory
* 功能描述: 恢复出厂设置
* 函数名称: DebugCmdReboot
* 功能描述: 重启网关(系统复位)
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
*****************************************************************************************/
void DebugCmdReboot(int argc, char *argv[])
@@ -1131,10 +1097,10 @@ void DebugCmdReboot(int argc, char *argv[])
}
/*****************************************************************************************
* 函数名称: DebugCmdSetSensorCollectTime
* 功能描述: 设置采集时间
* 函数名称: DebugCmdSetCollectTime
* 功能描述: 设置采集时间间隔(cu:通讯单元参数 / se:下发传感器)
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
*****************************************************************************************/
void DebugCmdSetCollectTime(int argc, char *argv[])
@@ -1198,7 +1164,7 @@ void DebugCmdSetCollectTime(int argc, char *argv[])
/*****************************************************************************************
* 函数名称: DebugCmdTimeSync
* 功能描述: 时间同步
* 功能描述: 系统时间同步(time命令设置系统时间)
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
@@ -1239,10 +1205,10 @@ void DebugCmdTimeSync(int argc, char *argv[])
}
/*****************************************************************************************
* 函数名称: DebugCmdTimeSync
* 功能描述: 查询各通讯单元的版本号
* 函数名称: DebugCmdQuerySensor
* 功能描述: 查询内置通讯单元的传感器列表
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
*****************************************************************************************/
extern SensorCommPara_t SComm1Para;
@@ -1289,8 +1255,7 @@ void DebugCmdSelDebugChannel(int argc, char *argv[])
if(strcmp(argv[1], "?") == 0) {
DBG_LOG("Debug Cmd: duch args\r\n");
DBG_LOG(" brief --> Debug information output channel selection.\r\n");
DBG_LOG(" args --> (dbg|ch1|ch2|eth|lora|cat1)\r\n");
DBG_LOG(" dbg: On board debugging serial port.\r\n");
DBG_LOG(" args --> (ch1|ch2|eth|lora|cat1)\r\n");
DBG_LOG(" ch1: RS485 channel 1 serial port.\r\n");
DBG_LOG(" ch2: RS485 channel 2 serial port.\r\n");
DBG_LOG(" eth: ETH serial port.\r\n");
@@ -1305,9 +1270,8 @@ void DebugCmdSelDebugChannel(int argc, char *argv[])
DBG_LOG("Invalid channel.\r\n");
return;
}
if((ch == CH_DBG && ChannelIsActive(GateWay->ConfigPara, CH_CAT1))
|| (ch == CH_CAT1 && ChannelIsActive(GateWay->ConfigPara, CH_DBG))) {
DBG_LOG("USART1 occupied by dbg/cat1 pair.\r\n");
if(ch == CH_DBG) {
DBG_LOG("DBG port removed: USART1 is dedicated to Cat1.\r\n");
return;
}
if(ChannelIsActive(GateWay->ConfigPara, ch)
@@ -1323,10 +1287,6 @@ void DebugCmdSelDebugChannel(int argc, char *argv[])
DbgOrCat1Uart_Config(115200);
CAT1_ON();
}
else if(ch == CH_DBG) {
DbgOrCat1Uart_Config(500000);
DBG_ON();
}
DBG_LOG("Set duch to %s.\r\n", ChannelName(ch));
WritePara((uint8_t *)&GateWay->ConfigPara, sizeof(GWConfigPara_t));
}
@@ -1616,7 +1576,7 @@ static void DebugCmdCali(int argc, char *argv[])
CommUnitCmd[sizeof(CommUnitFrameHeader_t)] = crc16 & 0x00ff;
CommUnitCmd[sizeof(CommUnitFrameHeader_t) + 1] = (crc16 >> 8) & 0x00ff;
if(mac[2] == 0x01) { //LORA通讯
if(mac[2] == 0x01) { //LORAͨѶ
if(GateWay->ConfigPara.CommUnitArray[ret].RegFlag == true) {
GateWay->ConfigPara.CommUnitArray[ret].CailFlag = true;
DBG_LOG("Wait for the device to respond. Mac:%02X-%02X-%02X-%02X-%02X-%02X\r\n",
@@ -1625,7 +1585,7 @@ static void DebugCmdCali(int argc, char *argv[])
TrySendFullPowerCmd(GateWay, ret);
}
}
else if(mac[2] == 0x02) { //RS485通讯
else if(mac[2] == 0x02) { //RS485ͨѶ
if(GateWay->ConfigPara.Rs485Ch1.Enable == true && !GateWay->ConfigPara.Rs485Ch1.CommUnitEnable) {
GateWay->ConfigPara.Rs485Ch1.RS485Send(Rs485Cmd, sizeof(CommUnitFrameHeader_t) + 2);
DBG_LOG("Calibrate CommUnit: %s!\r\n", argv[1]);
@@ -1735,7 +1695,7 @@ static void DebugLaserConfig(int argc, char *argv[])
CommUnitCmd[len++] = crc16 & 0x00ff;
CommUnitCmd[len++] = (crc16 >> 8) & 0x00ff;
if(mac[2] == 0x01) { //LORA通讯单元低功胀模式下直接下发
if(mac[2] == 0x01) { //LORA通讯单元低功耗模式下直接下发
if(GateWay->ConfigPara.CommUnitArray[ret].RegFlag == true)
{
if(onoff == true)
@@ -1750,11 +1710,11 @@ static void DebugLaserConfig(int argc, char *argv[])
{
GateWay->ConfigPara.CommUnitArray[ret].ContLaser = false;
GateWay->ConfigPara.CommUnitArray[ret].LaserOnOff = onoff;
Sx1276LoRaSendBuffer(CommUnitCmd, len);//直接发送
//直接发送
}
}
}
else if(mac[2] == 0x02) { //RS485通讯
else if(mac[2] == 0x02) { //RS485ͨѶ
if(GateWay->ConfigPara.Rs485Ch1.Enable == true && !GateWay->ConfigPara.Rs485Ch1.CommUnitEnable) {
GateWay->ConfigPara.Rs485Ch1.RS485Send(Rs485Cmd, len);
DBG_LOG("Control RS485 laser CommUnit: %s!\r\n", argv[1]);
@@ -1771,7 +1731,7 @@ static void DebugLaserConfig(int argc, char *argv[])
/*****************************************************************************************
* 函数名称: DebugCmdLPConfig
* 功能描述: 低功胀参数配置
* 功能描述: 低功耗参数配置
* 参 数: argc, 参数个数
argv, 参数列表
* 返 回 值: 无
@@ -1779,10 +1739,10 @@ static void DebugLaserConfig(int argc, char *argv[])
static void DebugCmdLPConfig(int argc, char *argv[])
{
DBG_LOG("\r\n");
uint16_t CollectInterval; //采集时间间隔
uint16_t ReportInterval; //上报时间间隔
uint8_t ERInterval; //紧急上报时间间隔
uint8_t ERTime; //紧急上报时间
uint16_t CollectInterval; //采集时间间隔
uint16_t ReportInterval; //上报时间间隔
uint8_t ERInterval; //紧急上报时间间隔
uint8_t ERTime; //紧急上报时间
uint8_t Mac[6];
bool ConfigAll = false;
+3 -3
View File
@@ -1,4 +1,4 @@
#ifndef __PUBLIC_H
#ifndef __PUBLIC_H
#define __PUBLIC_H
#include "bsp.h"
@@ -15,7 +15,6 @@
#define COMMUNIT_READ_SENSOR_INTERVAL_MAX (1 * 60)
#define LORA_ERR_RESET_DLY_MAX (10 * 60) //LORA通信异常复位延迟时间
#define CAT1_ERR_RESET_DLY_MAX (60 * 60) //CAT1通信异常复位延迟时间
typedef struct GateWay_t GateWayPara_t, *GateWayPara;
@@ -359,7 +358,8 @@ struct GateWay_t{
int MuchRevCallBack(GateWayPara GateWay, uint8_t *rData, uint16_t rLen);
int AuchRevCallBack(GateWayPara GateWay, uint8_t *rData, uint16_t rLen);
uint16_t CRC_Modbus(uint16_t wBase, __IO uint8_t *para, uint16_t length);
uint8_t AsciiToHex(char *ASCData, uint8_t *HexData, uint8_t sLen);
bool IsHexText(char *s, uint16_t len);
uint16_t AsciiToHex(char *ASCData, uint8_t *HexData, uint16_t sLen);
void HexToAscii(uint8_t *HexData, char *ASCData, uint8_t sLen);
int NetCommOrgData(GateWayPara GateWay, uint8_t *MegData, uint8_t *OutData);
int CommUnitAnalyze(GateWayPara GateWay, uint8_t *rData, uint16_t rLen, SendData Response);
+7
View File
@@ -32,5 +32,12 @@ typedef struct {
//输出接口
void Update(uint8_t *PayLoad);
void UpdateInit(void);
/*Cat1远程升级(0x7D帧)接口 - CatOneTask调用: 固件分包写外部SPI Flash暂存区, 收完置APP_UPDATE_SPI_FLAG复位*/
bool NetUpgradeFeed(uint8_t *data, uint16_t len, uint8_t ch); //喂入下行数据片段(带来源通道), 返回true=已被升级模块消费
bool NetUpgradePopTx(uint8_t *Out, uint16_t *OutLen, uint8_t ch); //取本通道待发应答帧(0x01/0x02, 0x7D原始帧)
bool NetUpgradeActive(void); //升级会话进行中
void NetUpgradeTick(void); //1ms轮询: 超时催发与会话放弃
void UpdateFromStash(uint32_t AppSize, uint32_t Crc32); //暂存区校验通过后置标志复位进Boot
#endif
+7 -4
View File
@@ -16,12 +16,14 @@
#define FLASH_BOOT_BASE (FLASH_BASE)
#define FLASH_BOOT_SIZE (4*FLASH_SECTOR_SIZE)
//app
//app A区(运行, 0x8000起, 232KB) + B区(升级暂存, 0x42000起, 232KB) - 与Boot A/B分区一致
#define FLASH_APP_BASE (FLASH_BOOT_BASE+ FLASH_BOOT_SIZE)
#define FLASH_APP_SIZE (58*FLASH_SECTOR_SIZE)
#define FLASH_APP_SIZE (29*FLASH_SECTOR_SIZE)
#define FLASH_APPB_BASE (FLASH_APP_BASE + FLASH_APP_SIZE)
#define FLASH_APPB_SIZE (29*FLASH_SECTOR_SIZE)
//boot info
#define FLASH_BINFO_BASE (FLASH_APP_BASE + FLASH_APP_SIZE)
//boot info (0x7C000, 位置不变)
#define FLASH_BINFO_BASE (FLASH_APPB_BASE + FLASH_APPB_SIZE)
#define FLASH_BINFO_SIZE (1*FLASH_SECTOR_SIZE)
//user config
@@ -33,6 +35,7 @@
#define APP_START_FLAG 0x55AA5A5A
#define APP_UPDATE_FLAG 0xA5A5AA55
#define APP_BOOT_UPGRADE_FLAG 0x5A5A55A5
#define APP_UPDATE_SPI_FLAG 0xA5A55A5A //Cat1远程升级: 固件已存外部SPI Flash暂存区, Boot据此搬运
#define WATCH_DOG 1
+1 -1
View File
@@ -4,7 +4,7 @@
#include "Public.h"
#include "DebugCmd.h"
#define SOFTWARE_VERSION 13
#define SOFTWARE_VERSION 15
#define HARDWARE_VERSION 12
extern bool MainDispEn;
@@ -40,6 +40,12 @@ uint32_t SpiFlashReadId(void);
#define LOG_INFO_START_SECTOR 0x02
#define LOG_SAV_START_SECTOR 0x03
/*固件升级暂存区(外部Flash尾部): 扇区4037=升级进度区, 扇区4038~4095=固件镜像(232KB,与Boot A/B分区一致)*/
#define FLASH_FW_STASH_PROG_SECTOR 4037
#define FLASH_FW_STASH_SECTOR 4038
#define FLASH_FW_STASH_SECTORS 58
#define LOG_SAV_END_ADDR (FLASH_FW_STASH_PROG_SECTOR * SPIFLASH_SECTORSIZE)
//#if (LORA_MODULE == WH_LR36_L)
//#define LOG_SAV_SIZE 78
//#define LOG_SAV_NUM_SECTOR 52 //每扇区存储数量
@@ -70,6 +76,12 @@ void AddLog(uint8_t *wData, uint32_t wLen);
int ReadLog(LogHeader Header, uint8_t **wData, uint32_t LogIdx);
int ReadPara(uint8_t *Para, uint32_t ParaLen);
int WritePara(uint8_t *Para, uint32_t ParaLen);
void FwStashEraseAll(void);
void FwStashProgRead(uint8_t *rData, uint16_t rLen);
void FwStashProgWrite(uint8_t *wData, uint16_t wLen);
void FwStashEraseSector(uint32_t SectorIdx);
int FwStashWrite(uint8_t *wData, uint32_t Offset, uint16_t wLen);
int FwStashRead(uint8_t *rData, uint32_t Offset, uint16_t rLen);
/* USER CODE END */
+209 -126
View File
@@ -1,16 +1,20 @@
#include "CatOneTask.h"
#include "Public.h"
#include "DebugCmd.h"
#include "Update.h"
static rt_sem_t CatOneRev_Sem = RT_NULL;
static rt_sem_t CatOneIRQ_Sem = RT_NULL;
static rt_thread_t Cat1Rev_Thread = RT_NULL;
static uint8_t CAT1DispEn = false;
static uint8_t CAT1DispEn = false; /*默认关闭: 正常工作不输出Cat1 AT流日志, cat1dbg命令动态开启*/
static CatOne_t CatOne;
static char CatOneRxBuff[CAT_ONE_REV_LEN_MAX];
#define CAT_ONE_URC_LEN_MAX 8192 //+LIPURC一批连发8包约3.6K, 预留8K
static char CatOneRxBuff[CAT_ONE_URC_LEN_MAX];
static uint16_t CatOneRxLen;
static volatile uint8_t Cat1RxSyncReq = 0; /*IRQ timeout-clear: Rev discards stale partial URC*/
static uint8_t CatOneTxBuff[CAT_ONE_REV_LEN_MAX];
static uint16_t CatOneTxLen;
@@ -20,8 +24,9 @@ static char CommUnitSendASCII[CAT_ONE_REV_LEN_MAX * 2];
static uint8_t Cat1RevTimeOutCnt;
static uint16_t CatSendErrCnt = 0;
static uint8_t Cat1RevErrCnt = 0; /*no-server-data watchdog: reset on any URC data*/
static uint8_t Cat1IdleReconnFlag = 0; /*空闲保活重连标志: 重连后只建链不上传(跳过注册帧)*/
bool Cat1DuchReady = false;
static char duchRing[8192];
static uint16_t duchWr, duchRd;
@@ -44,25 +49,26 @@ void Cat1DuchSend(const char *text, uint16_t len)
#define CATONE_RESET_DELAY_TIME_MAX (5 * 60 * 1000)
#define CAT1_TCP_REOPEN_RETRY_MAX 3
#define CAT1_TCP_REOPEN_RETRY_DLY 5000
#define CAT1_TCP_IDLE_RECONN_DLY_MAX (5 * 60) /*空闲无上传5分钟(秒计数): 断开重建TCP保活, exe端刷新客户端列表*/
const char *CatOneATCmdStr[16] = {
const char *CatOneATCmdStr[17] = {
"NULL",
"AT\r\n", //开机检测
"ATE0\r\n", //关回显
"AT+CPIN?\r\n", //识卡
"AT\r\n", //开机检测
"ATE0\r\n", //关回显
"AT+CPIN?\r\n", //识别卡
"AT+CGSN=1\r\n",
"AT+QCCID\r\n", //读卡号
"AT+CEREG?\r\n", //查询注册状态
"AT+QCCID\r\n", //读卡号
"AT+CEREG?\r\n", //查询注册状态
"AT+CGPADDR=1\r\n", //查询IP地址
"AT+CSQ\r\n", //查询信号强度
"AT+CSQ\r\n", //查询信号强度
"AT+LDNSGIP=gxjt.cui635.cn\r\n",
"AT+QLBS\r\n", //获取基站定位信息
"AT+QIOPEN=", //设置TCP服务器地址和端口
"AT+QLBS\r\n", //获取基站定位信息
"AT+QIOPEN=", //设置TCP服务器地址和端口号
"AT+QISEND=", //发送数据
"AT+QICLOSE=0\r\n", //断开连接
"AT+QLBSCFG=\"server\",http://locator-aep.xiot.senthink.com:80/locator/v0.1/locate\r\n",
"AT+CCLK?\r\n", //获取时间
"AT+QLBSCFG=\"token\",45B516D3078467E84DD2CFBF3532A4CB\r\n",
"AT+CCLK?\r\n", //获取时间
};
void Cat1DBGOnOff(bool OnOff)
@@ -298,41 +304,11 @@ void CatOneAtCmdAnalyze(char *RxBuff)
CatOne.ATCmdRet = CAT_ONE_RET_ERR;
break;
case CAT_ONE_AT_NULL:
p = strstr(RxBuff, "+LIPURC");
if(p != NULL) {
char rData[120];
uint8_t hData[60];
int rLen, hLen, ret;
if(strstr(p, "+LIPURC: 0,0") != NULL) {
CAT1_DBG_LOG("TCP connection disconnected.\r\n\r\n");
CatOne.TcpConnFlag = false;
CatOne.Cat1Status = CAT_ONE_OFF;
return;
}
ret = sscanf(p, "+LIPURC: 0,1,%d,%119s\r\n", &rLen, rData);
if(ret == 2) {
if(rLen < 24)
return;
memset(hData, 0x00, 60);
hLen = AsciiToHex(rData, hData, rLen);
CatSendErrCnt = 0;
if(CatOne.GateWay->ConfigPara.Much == CH_CAT1)
CatOne.GateWay->MuchRevCallBack(CatOne.GateWay, hData, hLen);
else
CatOne.GateWay->AuchRevCallBack(CatOne.GateWay, hData, hLen);
}
else {
return;
}
rt_sem_release(CatOneRev_Sem);
return;
}
if(CatOne.TcpConnFlag && RxBuff[0] > ' ') {
DebugAnalyze(CatOne.GateWay, (uint8_t *)RxBuff, strlen(RxBuff));
return;
}
break;
default:
CatOne.ATCmdRet = CAT_ONE_RET_ERR;
@@ -371,6 +347,7 @@ void CatOneAtCmdSend(CatOneATCMD_m Cmd, uint32_t Dly, CatOneStatus_m NextStatus)
Cat1RevTimeOutCnt = 0;
DebugOrCat1UartSend((uint8_t *)CatOne.CatOneSendBuff, strlen(CatOne.CatOneSendBuff));
CatOne.CatOne1mSDelayCnt = Dly;
while(rt_sem_take(CatOneRev_Sem, 0) == RT_EOK); /*清空历史信号量: 防止URC释放的旧信号量令WAIT_REV立即误返回, 掩盖模组ERROR*/
CatOne.Cat1Status = CAT_ONE_WAIT_REV;
CatOne.NextStatus = NextStatus;
}
@@ -414,7 +391,17 @@ void CatOneLoopHandler(void)
{
static rt_err_t result;
uint16_t ret;
static uint8_t Cat1RevErrCnt = 0;
static uint8_t Cat1UpgTxFlag = 0; /*当前LIPSEND是否升级应答帧: 发完即回WAIT_SEND*/
NetUpgradeTick(); /*Cat1远程升级会话超时管理*/
{ /*state trace: cat1 status enum 0=IDEL 4=SEND 5=WAIT_SEND 6=REV 8=WAIT_REV 9=RESET 10=OFF*/
static signed char LastSt = -1;
if((signed char)CatOne.Cat1Status != LastSt) {
LastSt = (signed char)CatOne.Cat1Status;
CAT1_DBG_LOG("Cat1 state -> %d\r\n", LastSt);
}
}
switch(CatOne.Cat1Status){
case CAT_ONE_IDEL:
@@ -472,16 +459,23 @@ void CatOneLoopHandler(void)
case CAT_ONE_WAIT_CONN:
if(CatOne.TcpConnFlag) {
if(CatOne.GateWay->ConfigPara.Much == CH_CAT1) {
CatOne.Cat1Status = CAT_ONE_REG_SVR;
if(Cat1IdleReconnFlag) { /*空闲保活重连: 只建链不上传, 跳过注册帧*/
Cat1IdleReconnFlag = 0;
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
}
else if(CatOne.GateWay->ConfigPara.Auch == CH_CAT1 || (CatOne.GateWay->ConfigPara.CuchMask & CUCH_CAT1)) {
else if(CatOne.GateWay->ConfigPara.Much == CH_CAT1) {
CatOne.Cat1Status = CAT_ONE_REG_SVR; /*Cat1为主上传通道: 注册上送*/
}
else if(CatOne.GateWay->ConfigPara.Auch == CH_CAT1
|| (CatOne.GateWay->ConfigPara.CuchMask & CUCH_CAT1)) {
/*Cat1为辅助/掩码通道: 仅发注册帧维持在线*/
GateWayRegister(Payload);
CatOneTxLen = NetCommOrgData(CatOne.GateWay, Payload, CatOneTxBuff);
CatOne.Cat1Status = CAT_ONE_SEND_DATA;
}
else {
CatOne.Cat1Status = CAT_ONE_DUCH_MODE;
/*duch=cat1纯调试: 统一走WAIT_SEND状态机, duchRing轮询经LIPSEND hex透传, 升级不受分流限制*/
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
}
}
break;
@@ -493,6 +487,43 @@ void CatOneLoopHandler(void)
break;
case CAT_ONE_WAIT_SEND:
/*Cat1远程升级应答帧优先(0x7D原始帧直发, 绕过0x7A打包)*/
if(NetUpgradePopTx(CatOneTxBuff, &CatOneTxLen, CH_CAT1)) {
CatSendErrCnt = 0;
Cat1UpgTxFlag = 1; /*升级应答帧标记*/
CatOne.Cat1Status = CAT_ONE_SEND_DATA;
break;
}
/*duch=cat1调试文本透传: QISEND明文直发(网络调试助手可读), 升级会话期间暂停*/
if(duchRd != duchWr && !NetUpgradeActive()) {
uint16_t avail = (duchWr - duchRd) & 0x1FFF;
uint16_t n = (avail > 240) ? 240 : avail;
if(duchRd + n <= 8192) {
memcpy(Payload, &duchRing[duchRd], n);
} else {
uint16_t part = 8192 - duchRd;
memcpy(Payload, &duchRing[duchRd], part);
memcpy(Payload + part, duchRing, n - part);
}
duchRd = (duchRd + n) & 0x1FFF;
CatSendErrCnt = 0;
{ /*wait for modem URC silence: AT cmd colliding with URC burst gets swallowed*/
uint16_t waitMs = 0;
while(Cat1RevTimeOutCnt > 0 && waitMs < 50) {
rt_thread_delay(2);
waitMs += 2;
}
}
sprintf(CatOne.CatOneSendBuff, "AT+QISEND=0,%d\r\n", n);
DebugOrCat1UartSend((uint8_t *)CatOne.CatOneSendBuff, strlen(CatOne.CatOneSendBuff));
rt_thread_delay(200);
DebugOrCat1UartSend(Payload, n);
DebugOrCat1UartSend((uint8_t *)"\x1A", 1);
rt_thread_delay(500);
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
break;
}
result = rt_mq_recv(ChannelMQ(*CatOne.GateWay, CH_CAT1), Payload, CAT_ONE_REV_LEN_MAX, 1000);
if(result == RT_EOK) {
CatSendErrCnt = 0;
@@ -507,10 +538,13 @@ void CatOneLoopHandler(void)
}
else {
CatSendErrCnt++;
if(CatSendErrCnt > CAT1_ERR_RESET_DLY_MAX) {
CatOne.Cat1Status = CAT_ONE_RESET;
CAT1_DBG_LOG("Cat1 has no data for a long time, Reset!\r\n");
if(CatSendErrCnt > CAT1_TCP_IDLE_RECONN_DLY_MAX) {
/*空闲5分钟无上传: 断开重建TCP保活, exe端重新看到客户端(不发任何数据帧)*/
CatSendErrCnt = 0;
Cat1IdleReconnFlag = 1;
CatOne.NextStatus = CAT_ONE_TCP_CONN;
CatOne.Cat1Status = CAT_ONE_CLOSE_TCP;
CAT1_DBG_LOG("Cat1 idle 5min, reconnect TCP!\r\n");
break;
}
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
@@ -520,77 +554,50 @@ void CatOneLoopHandler(void)
case CAT_ONE_SEND_DATA:
CatOne.AtCmdResendCnt = 3;
Cat1RevErrCnt++;
Cat1RevErrCnt++;
if(Cat1RevErrCnt > 10) {
CatOne.Cat1Status = CAT_ONE_RESET;
CAT1_DBG_LOG("Cat1 has no receive data for a long time, Reset!\r\n");
Cat1RevErrCnt = 0;
break;
}
CatOneAtCmdSend(CAT_ONE_LIPSEND, 5000, CAT_ONE_REV_DATA);
if(Cat1UpgTxFlag) { /*升级应答帧: 发完即回WAIT_SEND取下一帧, 不等服务器响应*/
Cat1UpgTxFlag = 0;
{ /*wait for modem URC silence: LIPSEND colliding with URC burst gets swallowed, 3 failures trigger CLOSE_TCP*/
uint16_t waitMs = 0;
while(Cat1RevTimeOutCnt > 0 && waitMs < 50) {
rt_thread_delay(2);
waitMs += 2;
}
}
CatOneAtCmdSend(CAT_ONE_LIPSEND, 5000, CAT_ONE_WAIT_SEND);
}
else {
CatOneAtCmdSend(CAT_ONE_LIPSEND, 5000, CAT_ONE_REV_DATA);
}
break;
case CAT_ONE_REV_DATA:
if(CatOne.GateWay->ConfigPara.Much != CH_CAT1) {
rt_sem_take(CatOneRev_Sem, 0);
result = rt_sem_take(CatOneRev_Sem, CatOne.CatOne1mSDelayCnt);
if(result != RT_EOK) {
/*超时: 未注册成功视为服务器无响应重新注网; 已注册则仅本周期无数据*/
if(CatOne.GateWay->MuchRegFlag == false) {
/*未注册超时: 保持连接不重启(兼容网络调试助手等不回注册应答的对端);
连接自愈由LIPSEND失败与空闲5分钟重连兜底*/
CAT1_DBG_LOG("Cat1 reg timeout, keep link!\r\n");
}
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
break;
}
result = rt_sem_take(CatOneRev_Sem, CatOne.CatOne1mSDelayCnt);
if(result != RT_EOK) {
if(CatOne.TcpConnFlag == false) {
CAT1_DBG_LOG("Cat1 Rec Server Data Timeout!\r\n");
break;
}
}
else {
Cat1RevErrCnt = 0;
}
if(CatOne.GateWay->MuchRegFlag == false)
CatOne.Cat1Status = CAT_ONE_OFF;
else
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
/*信号量唤醒: 服务器有数据到达(注册应答/业务/升级会话), 直接进发送态取待发帧*/
Cat1RevErrCnt = 0;
CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
break;
case CAT_ONE_CLOSE_TCP:
CatOne.AtCmdResendCnt = 3;
CatOneAtCmdSend(CAT_ONE_LTPCLOSE, 1000, CatOne.NextStatus);
break;
case CAT_ONE_DUCH_MODE:
Cat1DuchReady = true;
uint32_t runMs = 0;
while(ChannelIsActive(CatOne.GateWay->ConfigPara, CH_CAT1)) {
if(duchRd != duchWr) {
uint16_t avail = (duchWr - duchRd) & 0x1FFF;
uint16_t n = avail > 240 ? 240 : avail;
sprintf(CatOne.CatOneSendBuff, "AT+QISEND=0,%d\r\n", n);
DebugOrCat1UartSend((uint8_t *)CatOne.CatOneSendBuff, strlen(CatOne.CatOneSendBuff));
rt_thread_delay(200);
runMs += 200;
if(duchRd + n <= 8192) {
DebugOrCat1UartSend((uint8_t *)&duchRing[duchRd], n);
} else {
uint16_t part = 8192 - duchRd;
DebugOrCat1UartSend((uint8_t *)&duchRing[duchRd], part);
DebugOrCat1UartSend((uint8_t *)duchRing, n - part);
}
DebugOrCat1UartSend((uint8_t *)"\x1A", 1);
rt_thread_delay(500);
runMs += 500;
duchRd = (duchRd + n) & 0x1FFF;
} else {
rt_thread_delay(50);
runMs += 50;
}
if(runMs >= 1800000) {
CatOne.NextStatus = CAT_ONE_TCP_CONN;
CatOne.Cat1Status = CAT_ONE_CLOSE_TCP;
break;
}
}
Cat1DuchReady = false;
break;
case CAT_ONE_OFF:
CAT1_RESET_SET();
@@ -598,12 +605,12 @@ void CatOneLoopHandler(void)
rt_thread_delay(3000);
CAT1_PWR_KEY_SET();
CatSendErrCnt = 0;
Cat1IdleReconnFlag = 0; /*模组重启路径恢复完整注册流程*/
CatOne.RegisterDelayCnt = 0;
CAT1_DBG_LOG("Cat1 Power Off!\r\n");
CatOne.Cat1Status = CAT_ONE_IDEL;
CatOne.ResetDelayCnt = CATONE_RESET_DELAY_TIME_MAX;
DBG_ON();
break;
CatOne.ResetDelayCnt = CATONE_RESET_DELAY_TIME_MAX;
break;
case CAT_ONE_RESET:
CatSendErrCnt = 0;
@@ -644,10 +651,64 @@ static void CatOneInit(GateWayPara GateWay)
GateWay->MuchRevCallBack : GateWay->AuchRevCallBack;
}
/*+LIPURC异步事件统一分发: 与AT状态机无关, 任何状态下都能处理升级帧/业务数据*/
/*一批数据包的URC会连发, 多条可能挤进同一缓冲块: 必须循环解析每一条*/
static void CatOneUrcHandle(GateWayPara GateWay, char *RxBuff)
{
static char rData[3010]; /*static: 大缓冲+单线程访问*/
static uint8_t hData[1510];
char *p;
int rLen = 0, hLen;
if(strstr(RxBuff, "+LIPURC: 0,0") != NULL) {
CAT1_DBG_LOG("TCP connection disconnected.\r\n\r\n");
CatOne.TcpConnFlag = false;
CatOne.Cat1Status = CAT_ONE_OFF;
return;
}
p = strstr(RxBuff, "+LIPURC: 0,1,");
while(p != NULL) {
if(sscanf(p, "+LIPURC: 0,1,%d,%3000s", &rLen, rData) == 2) {
int sLen = (int)strlen(rData); /*data字段被命令内\r\n截断(%s遇空白停): 以实际可读长度为准, 兼容明文命令与hex帧*/
if(sLen > 0) {
memset(hData, 0x00, sizeof(hData)); /*ASCII hex转二进制(服务器下发为hex文本)*/
hLen = IsHexText(rData, sLen) ? AsciiToHex(rData, hData, sLen) : 0;
if(hLen == 0) {
/*非hex明文(如网络调试助手直发命令): 原样直通调试解析, 与ETH口明文直通一致*/
hLen = (sLen < (int)sizeof(hData)) ? sLen : (int)sizeof(hData) - 1;
memcpy(hData, rData, hLen);
CAT1_DBG_LOG("Cat1 rx plaintext %d bytes, to DebugAnalyze\r\n", hLen);
}
CatSendErrCnt = 0;
Cat1RevErrCnt = 0; /*server data arrived: reload watchdog*/
if(hLen > 0 && NetUpgradeFeed(hData, hLen, CH_CAT1)) {
/*0x7D frame consumed by NetUpgrade*/
}
else if(hLen > 0 && hData[0] == 0x7A) { /*0x7A业务帧: 按Much/Auch配置分流*/
if(CatOne.GateWay->ConfigPara.Much == CH_CAT1)
GateWay->MuchRevCallBack(GateWay, hData, hLen);
else
GateWay->AuchRevCallBack(GateWay, hData, hLen);
}
else if(hLen > 0) {
DebugAnalyze(GateWay, hData, hLen); /*明文调试命令: duch=cat1时服务端远程para等指令*/
}
}
}
else {
CAT1_DBG_LOG("LIPURC parse fail, drop!\r\n");
}
p = strstr(p + 13, "+LIPURC: 0,1,"); /*头部长13字节, 继续找下一条URC*/
}
rt_sem_release(CatOneRev_Sem); /*通知AT状态机: 已收到服务器数据(维持REV_DATA快速返回)*/
}
void CatOneRev_Thread_Entry(void *parameter)
{
rt_err_t result;
char RxBuffTemp[CAT_ONE_REV_LEN_MAX];
static char RxBuffTemp[CAT_ONE_URC_LEN_MAX]; /*static: 大缓冲避免拆分线程栈(单线程访问)*/
static uint16_t RxResidual = 0; /*残留半条URC长度(跨块拼接), 单线程访问*/
GateWayPara GateWay = (GateWayPara)parameter;
@@ -656,18 +717,40 @@ void CatOneRev_Thread_Entry(void *parameter)
while(1) {
result = rt_sem_take(CatOneIRQ_Sem, 500);
if(result == RT_EOK) {
memcpy(RxBuffTemp, CatOneRxBuff, CatOneRxLen);
RxBuffTemp[CatOneRxLen] = 0;
CatOneRxLen = 0;
char *urc = strstr(RxBuffTemp, "+LIPURC: 0,1,");
if(urc) {
char *data = strchr(strchr(strchr(urc, ',')+1, ',')+1, ',') + 1;
char *end = strchr(data, '\r');
if(end) *end = 0;
DebugAnalyze(GateWay, (uint8_t *)data, strlen(data));
if(Cat1RxSyncReq) { /*IRQ已超时清链: 陈旧残留作废重新同步*/
Cat1RxSyncReq = 0;
RxResidual = 0;
}
if(RxResidual + CatOneRxLen > CAT_ONE_URC_LEN_MAX - 1) {
RxResidual = 0; /*溢出保护: 丢弃残留重新同步*/
}
memcpy(RxBuffTemp + RxResidual, CatOneRxBuff, CatOneRxLen);
RxResidual += CatOneRxLen;
RxBuffTemp[RxResidual] = 0;
CatOneRxLen = 0;
if(strstr(RxBuffTemp, "+LIPURC") != NULL) {
/*按完整行切分: 最后一个\n之前逐条解析, 之后作残留等下一块拼接*/
char *lastNl = NULL;
char *scan = RxBuffTemp;
char *nl;
while((nl = strchr(scan, 0x0A)) != NULL) { lastNl = nl; scan = nl + 1; }
if(lastNl != NULL) {
int keep = (int)(RxResidual - (int)(lastNl + 1 - RxBuffTemp));
*(lastNl + 1) = 0; /*完整区以\n结尾截断*/
CatOneUrcHandle(GateWay, RxBuffTemp);
if(keep > 0) {
memmove(RxBuffTemp, lastNl + 1, keep);
RxResidual = (uint16_t)keep;
} else {
RxResidual = 0;
}
RxBuffTemp[RxResidual] = 0;
}
}
else {
CatOneAtCmdAnalyze(RxBuffTemp);
RxResidual = 0; /*AT应答整块消费*/
}
CatOneAtCmdAnalyze(RxBuffTemp);
memset(CatOneRxBuff, 0x00, CAT_ONE_REV_LEN_MAX);
}
}
}
@@ -699,20 +782,19 @@ void CatOne_Thread_Entry(void *parameter)
rt_kprintf("CatOne Thread is running!\r\n");
while(1) {
if(!ChannelIsActive(GateWay->ConfigPara, CH_CAT1)) {
rt_thread_delay(100);
continue;
}
/*USART1专用Cat1: 上电即持续注册连接, 不再依赖通道配置*/
CatOneLoopHandler();
}
}
void Cat1IrqCallback(uint8_t rData)
{
if(Cat1RevTimeOutCnt == 0)
if(Cat1RevTimeOutCnt == 0) {
CatOneRxLen = 0;
Cat1RxSyncReq = 1; /*stale partial URC: Rev side resyncs*/
}
if(CatOneRxLen < CAT_ONE_REV_LEN_MAX) {
if(CatOneRxLen < CAT_ONE_URC_LEN_MAX) {
CatOneRxBuff[CatOneRxLen++] = rData;
}
@@ -749,3 +831,4 @@ void CatOneTriggerRegister(void)
CAT1_ON();
CatOneReset();
}
+93 -27
View File
@@ -1,14 +1,30 @@
#include "EthTask.h"
#include "Public.h"
#include "DebugCmd.h"
#include "Update.h"
static rt_sem_t EthIRQ_Sem = RT_NULL;
static rt_thread_t EthRev_Thread = RT_NULL;
static EthTask_t Eth;
static char EthRxBuff[ETH_RX_LEN_MAX];
static uint16_t EthRxLen;
/*单生产(IRQ)单消费(Rev)环形缓冲: 无锁, 读写指针独立安全; 防Rev处理期间IRQ覆盖丢包*/
#define ETH_RING_SIZE 8192
static uint8_t EthRingBuf[ETH_RING_SIZE];
static volatile uint16_t EthRingW = 0, EthRingR = 0;
static uint16_t EthRingLen(void)
{
return (uint16_t)((EthRingW - EthRingR + ETH_RING_SIZE) % ETH_RING_SIZE);
}
static uint16_t EthRingGet(uint8_t *dst, uint16_t max)
{
uint16_t n = 0;
while(n < max && EthRingR != EthRingW) {
dst[n++] = EthRingBuf[EthRingR];
EthRingR = (uint16_t)((EthRingR + 1) % ETH_RING_SIZE);
}
return n;
}
static uint8_t EthRevTimeOutCnt;
static uint8_t Payload[ETH_RX_LEN_MAX];
@@ -79,7 +95,6 @@ static int GateWayRegister(uint8_t *TxBuff)
void EthLoopHandler(void)
{
rt_err_t result;
switch(Eth.Status) {
case ETH_IDEL:
rt_thread_delay(1);
@@ -156,6 +171,11 @@ void EthLoopHandler(void)
break;
case ETH_WAIT_SEND:
if(NetUpgradePopTx(Eth.TxBuff, &Eth.TxLen, CH_ETH)) { /*升级应答帧第一优先(仅取本通道帧)*/
Eth.Status = ETH_SEND_DATA;
break;
}
result = rt_mq_recv(ChannelMQ(*Eth.GateWay, CH_ETH), Payload, ETH_RX_LEN_MAX, 100);
if(result == RT_EOK) {
Eth.TxLen = NetCommOrgData(Eth.GateWay, Payload, Eth.TxBuff);
@@ -188,6 +208,7 @@ void EthLoopHandler(void)
}
HexToAscii(Eth.TxBuff, CommUnitSendASCII, Eth.TxLen);
EthRevTimeOutCnt = 0;
EthUartSend((uint8_t *)CommUnitSendASCII, Eth.TxLen * 2);
CommUnitSendASCII[Eth.TxLen * 2] = 0;
ETH_DBG_LOG("Eth Send: %s\r\n", CommUnitSendASCII);
@@ -259,13 +280,11 @@ static void EthInit(GateWayPara GateWay)
void EthIrqCallbackHandler(uint8_t rData)
{
if(EthRevTimeOutCnt == 0)
EthRxLen = 0;
if(EthRxLen < ETH_RX_LEN_MAX) {
EthRxBuff[EthRxLen++] = rData;
if((uint16_t)((EthRingW + 1) % ETH_RING_SIZE) != EthRingR) { /*未满才入, IRQ不阻塞*/
EthRingBuf[EthRingW] = rData;
EthRingW = (uint16_t)((EthRingW + 1) % ETH_RING_SIZE);
}
/*强制入环: 满时覆盖旧数据, IRQ永不阻塞*/
EthRevTimeOutCnt = 3;
}
@@ -274,7 +293,7 @@ void EthOverHandler(void)
if(EthRevTimeOutCnt > 0)
EthRevTimeOutCnt--;
if(EthRxLen > 0 && EthRevTimeOutCnt == 0) {
if(EthRingLen() > 0 && EthRevTimeOutCnt == 0) {
EthRevTimeOutCnt = 20;
rt_sem_release(EthIRQ_Sem);
}
@@ -292,24 +311,59 @@ static void EthRev_Thread_Entry(void *parameter)
while(1) {
result = rt_sem_take(EthIRQ_Sem, 500);
if(result == RT_EOK || EthRxLen > 1) {
if(EthRxLen == 0) continue;
memcpy(RxBuffTemp, EthRxBuff, EthRxLen);
RxBuffTemp[EthRxLen] = 0;
memset(HexData, 0x00, EthRxLen / 2);
uint8_t ret = AsciiToHex(RxBuffTemp, HexData, EthRxLen);
if(ret > 0 && HexData[0] == 0x7A) {
Eth.SendNoDataCnt = 0;
if(GateWay->ConfigPara.Much == CH_ETH)
GateWay->MuchRevCallBack(GateWay, HexData, ret);
else
GateWay->AuchRevCallBack(GateWay, HexData, ret);
uint16_t n = EthRingLen();
if(result == RT_EOK || n > 1) {
if(n == 0) continue;
n = EthRingGet((uint8_t *)RxBuffTemp, ETH_RX_LEN_MAX - 1);
RxBuffTemp[n] = 0;
uint16_t ret = 0;
if(NetUpgradeActive()) {
/*升级会话: 纯hex流模式, 奇数尾字符跨块拼接(模组512字节透传块会切在帧任意位置)*/
static char hexTail = 0;
static bool tailV = false;
if(tailV && n < ETH_RX_LEN_MAX) {
memmove(&RxBuffTemp[1], RxBuffTemp, n);
RxBuffTemp[0] = hexTail;
n++; tailV = false;
} else { tailV = false; }
if(n % 2 == 1) { hexTail = RxBuffTemp[n - 1]; tailV = true; n--; }
RxBuffTemp[n] = 0;
if(n > 0 && IsHexText(RxBuffTemp, n)) {
memset(HexData, 0x00, ETH_RX_LEN_MAX / 2);
ret = AsciiToHex(RxBuffTemp, HexData, n);
bool fed = (ret > 0) ? NetUpgradeFeed(HexData, ret, CH_ETH) : false;
/*注意: 会话内禁止全量hex打印, 串口打印阻塞EthRev线程导致USART RX缓冲溢出丢包*/
if(fed) Eth.SendNoDataCnt = 0; /*清看门狗防升级会话被复位*/
}
}
else if(EthRxLen > 1) {
DebugAnalyze(GateWay, (uint8_t *)RxBuffTemp, EthRxLen);
else {
/*非会话: hex业务帧/明文命令分流(0x81握手在此分支进入升级模块)*/
memset(HexData, 0x00, n / 2);
bool hexv = IsHexText(RxBuffTemp, n);
if(hexv) {
ret = AsciiToHex(RxBuffTemp, HexData, n);
}
bool fed = (ret > 0) ? NetUpgradeFeed(HexData, ret, CH_ETH) : false;
if(fed) {
Eth.SendNoDataCnt = 0;
}
else if(ret > 0 && HexData[0] == 0x7A) {
Eth.SendNoDataCnt = 0;
if(GateWay->ConfigPara.Much == CH_ETH)
GateWay->MuchRevCallBack(GateWay, HexData, ret);
else
GateWay->AuchRevCallBack(GateWay, HexData, ret);
}
else if(n > 1) {
DebugAnalyze(GateWay, (uint8_t *)RxBuffTemp, n);
}
}
memset(EthRxBuff, 0x00, ETH_RX_LEN_MAX);
EthRxLen = 0;
if(EthRingLen() > 0)
rt_sem_release(EthIRQ_Sem); /*缓冲仍有数据: 自释放信号量连续泵空*/
}
}
}
@@ -339,8 +393,20 @@ void Eth_Thread_Entry(void *parameter)
rt_kprintf("Eth Thread is running!\r\n");
while(1) {
/*升级会话应答帧: 最高优先直发, 不依赖通道配置/LINK状态/状态机(ACK原路CH_ETH返回)*/
if(NetUpgradeActive() && NetUpgradePopTx(Eth.TxBuff, &Eth.TxLen, CH_ETH)) {
HexToAscii(Eth.TxBuff, CommUnitSendASCII, Eth.TxLen);
CommUnitSendASCII[Eth.TxLen * 2] = 0;
EthUartSend((uint8_t *)CommUnitSendASCII, Eth.TxLen * 2);
}
if(!ChannelIsActive(GateWay->ConfigPara, CH_ETH)) {
rt_thread_delay(100);
rt_thread_delay(20);
continue;
}
if(NetUpgradeActive()) {
/*升级会话: 状态机旁路(防RESET/OFF复位模组断链), 应答走主循环直发+接收线程收包*/
rt_thread_delay(5);
continue;
}
EthLoopHandler();
+20 -2
View File
@@ -126,10 +126,28 @@ void HexToAscii(uint8_t *HexData, char *ASCData, uint8_t sLen)
sLen, 需要转的字符串长度
* 返 回 值: 无
*****************************************************************************************/
uint8_t AsciiToHex(char *ASCData, uint8_t *HexData, uint8_t sLen)
/*****************************************************************************************
* 函数名称: IsHexText
* 功能描述: 判断缓冲区内容是否为合法hex文本(AsciiToHex不校验字符合法性, 非hex明文须先识别)
* 参 数: s, 待判断字符串; len, 字符长度
* 返 回 值: true=合法hex文本(全字符∈[0-9a-fA-F]且偶数长度)
*****************************************************************************************/
bool IsHexText(char *s, uint16_t len)
{
if(len == 0 || len % 2 != 0)
return false;
for(uint16_t i = 0; i < len; i++) {
char c = s[i];
if(!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
return false;
}
return true;
}
uint16_t AsciiToHex(char *ASCData, uint8_t *HexData, uint16_t sLen)
{
char chr;
uint8_t HexLen = 0;
uint16_t HexLen = 0; /*uint16: 大块(512字符=256字节)时uint8溢出归0导致ret=0丢帧*/
if(sLen % 2 == 1)
return 0;
+456 -57
View File
@@ -1,61 +1,460 @@
#include "Update.h"
#include "DebugCmd.h"
#include "main.h"
#include "bsp.h"
#include "spiflash.h"
extern GateWayPara_t GateWay;
#include "Update.h"
#include "DebugCmd.h"
#include "main.h"
#include "bsp.h"
#include "spiflash.h"
#include <rthw.h> /*rt_hw_interrupt_disable/enable: TX queue critical section*/
extern GateWayPara_t GateWay;
static void NetUpgradeSavePkg(uint32_t PkgCnt); /*fwd decl: Update() calls it before its definition*/
/*****************************************************************************************
* 函数名称: Update
* 功能描述: 固件升级,此函数在解析到升级指令时调用
* 参 数: PayLoad 协议负载,需去除帧头等信息,只传负载数据
* 返 回 值: 无
*****************************************************************************************/
void Update(uint8_t *PayLoad)
{
UpDataRequset_t Req;
boot_para_t cfg = {0};
memcpy(&Req, PayLoad, sizeof(UpDataRequset_t));
/*固件大小校验: 为0或超出B区容量时拒绝升级, 不置标志不复位*/
if(Req.AppSize == 0 || Req.AppSize > FLASH_APPB_SIZE) {
U_DBG_LOG("AppSize invalid: %d (max %d)\r\n", (int)Req.AppSize, (int)FLASH_APPB_SIZE);
return;
}
/*读回现有boot参数,保留FirstRunFlag等字段*/
dev_boot_read_param(&cfg);
cfg.AppSize = Req.AppSize;
cfg.Crc32Check = Req.AppCrc32;
cfg.PackageCnt = Req.PackageNum;
cfg.AppFlag = 0;
cfg.UpdateFlag = APP_UPDATE_FLAG;
memcpy(cfg.GwMac, GateWay.ConfigPara.GwMac, 6);
U_DBG_LOG("Receive upgrade command:\r\n");
U_DBG_LOG("PackageNum: %d\r\n", cfg.PackageCnt);
U_DBG_LOG("AppSize: %d\r\n", cfg.AppSize);
U_DBG_LOG("AppCrc32: 0x%08x\r\n", cfg.Crc32Check);
U_DBG_LOG("System Reset!\r\n");
NetUpgradeSavePkg(0);
SAVE_BOOT_PARA(&cfg);
U_DELAY_MS(200);
U_SYSTEM_TESET();
}
/*****************************************************************************************
* 函数名称: UpdateInit
* 功能描述: 升级参数初始化,需要在程序启动,外设初始化完后调用
* 参 数: 无
* 返 回 值: 无
*****************************************************************************************/
void UpdateInit(void)
{
boot_para_t cfg;
READ_BOOT_PARA(&cfg);
/*参照hc32l170_app: 仅确保AppFlag=APP_START_FLAG, 使Boot下次直接跳转App
(网关MAC/服务器/Lora配置从BootPara_t获取, 由Boot配置)*/
if(cfg.AppFlag != APP_START_FLAG) {
cfg.AppFlag = APP_START_FLAG;
SAVE_BOOT_PARA(&cfg);
}
}
/*****************************************************************************************
* Cat1远程升级模块(0x7D帧, 与Boot RS485升级协议一致)
* 服务器(升级主机) --TCP--> Cat1模组 --USART1--> 本模块:
* 收0x81升级请求(校验/擦暂存区/断点续传判定) → 0x82分包顺序写入外部SPI Flash暂存区
* → 每收NUP_WINDOW包回0x02请求帧(服务端窗口流控) → 收完CRC32校验通过后置
* APP_UPDATE_SPI_FLAG复位, 由Boot完成"外部Flash→B区→A区"搬运
* 协议帧: 0x7D + DevMac(6) + Cmd + Len + Payload + CRC16(Modbus), 命令0x81/0x82/0x01/0x02
*****************************************************************************************/
#define NUP_PACKET_DATA 200 //每包数据长度(与服务端/Boot一致)
#define NUP_STASH_PROG_MAGIC 0xA5A5C35A //升级进度记录魔数
#define NUP_WINDOW 8 //每收W包回一次请求帧
#define NUP_TIMEOUT_MS 3000 //等包超时(Cat1延迟大)
#define NUP_TIMEOUT_MAX 5 //连续超时次数上限
#define NUP_IDLE_TIMEOUT_MS 1800000UL //整体无进展超时(30分钟)
#define NUP_FRAME_BUF_MAX 3072 //0x7D组帧缓冲上限(容纳URC单次喂入的多帧拼接)
#define NUP_APP_MAX (232 * 1024) //A区容量上限
/*升级进度记录(存外部Flash扇区4037): 断电/断连后据此判定断点续传*/
typedef struct {
uint32_t Magic; //NUP_STASH_PROG_MAGIC
uint32_t AppSize; //固件大小
uint32_t Crc32; //固件CRC32
uint32_t PkgCnt; //已连续收到的包数
}FwStashProg_t;
static struct {
bool Active; //升级会话进行中
uint16_t PkgCnt; //总包数
uint32_t AppSize; //固件大小
uint32_t Crc32; //固件CRC32
uint16_t ExpectIdx; //期望接收的包序号
uint16_t LastAckIdx; //最近一次0x02应答的包序号(窗口流控基准)
uint32_t LastTick; //最近收包时刻(rt_tick, 1ms)
uint32_t ErrCnt; //连续超时次数
uint8_t TxQ[4][40]; //应答帧队列(0x01/0x02连发互不挤占)
uint8_t TxQLen[4];
uint8_t TxHead;
uint8_t TxChan[4]; /*每帧来源通道: ACK从收到0x81的通道原路返回*/
uint8_t CurCh; /*当前会话通道: Feed进入时记录*/ //队首索引(出队)
uint8_t TxCnt; //队列帧数
}Nup;
static uint8_t NupFrameBuf[NUP_FRAME_BUF_MAX]; //0x7D组帧缓冲(TCP分片跨URC拼接)
static uint16_t NupFrameLen = 0;
static uint64_t NupErasedMap = 0; /*bit i: stash sector i erased this session (NOR writes only 1->0, erase-before-write required)*/
/*软件CRC32: 与Boot GetCrc32/服务端zlib.crc32同算法(0xEDB88320反射, 初末值0xFFFFFFFF)*/
static uint32_t NetUpgradeCrc32Stash(uint32_t Len)
{
uint32_t Crc = 0xFFFFFFFF;
uint32_t Done = 0;
uint8_t Buf[256];
while(Done < Len) {
uint16_t n = (Len - Done > sizeof(Buf)) ? sizeof(Buf) : (uint16_t)(Len - Done);
FwStashRead(Buf, Done, n);
for(uint16_t i = 0; i < n; i++) {
Crc ^= Buf[i];
for(int b = 0; b < 8; b++) {
Crc = (Crc & 1) ? (Crc >> 1) ^ 0xEDB88320 : (Crc >> 1);
}
}
Done += n;
}
return ~Crc;
}
/*进度记录持久化(扇区4037): PkgCnt=0表示清除*/
static void NetUpgradeSavePkg(uint32_t PkgCnt)
{
FwStashProg_t Prog;
Prog.Magic = (PkgCnt > 0) ? NUP_STASH_PROG_MAGIC : 0;
Prog.AppSize = Nup.AppSize;
Prog.Crc32 = Nup.Crc32;
Prog.PkgCnt = PkgCnt;
FwStashProgWrite((uint8_t *)&Prog, sizeof(Prog));
}
/*待发帧入队(0x7D原始帧, 由CatOne主循环经AT+LIPSEND直发, 绕过0x7A打包)*/
static void NetUpgradePushTx(uint8_t Cmd, uint8_t *Payload, uint8_t Len)
{
uint16_t crc;
rt_base_t level = rt_hw_interrupt_disable();
if(Nup.TxCnt >= 4) {
rt_hw_interrupt_enable(level);
return; /*queue full: drop*/
}
{
uint8_t Tail = (Nup.TxHead + Nup.TxCnt) % 4;
Nup.TxQ[Tail][0] = 0x7D;
memcpy(&Nup.TxQ[Tail][1], GateWay.ConfigPara.GwMac, 6);
Nup.TxQ[Tail][7] = Cmd;
Nup.TxQ[Tail][8] = Len;
memcpy(&Nup.TxQ[Tail][9], Payload, Len);
crc = CRC_Modbus(CRC16_BASE, Nup.TxQ[Tail], 9 + Len);
Nup.TxQ[Tail][9 + Len] = crc & 0xFF;
Nup.TxQ[Tail][10 + Len] = (crc >> 8) & 0xFF;
Nup.TxQLen[Tail] = 11 + Len;
Nup.TxChan[Tail] = Nup.CurCh; /*绑定来源通道*/
Nup.TxCnt++;
}
rt_hw_interrupt_enable(level);
U_DBG_LOG("NetUpgrade push: cmd=0x%02x cnt=%d\r\n", Cmd, Nup.TxCnt);
}
static void NetUpgradeSendAck(uint8_t Status, uint16_t Resumed)
{
uint8_t p[3];
p[0] = Status;
p[1] = Resumed & 0xFF;
p[2] = (Resumed >> 8) & 0xFF;
NetUpgradePushTx(0x01, p, 3); //0x20全新/0x21续传/0x32拒绝
}
static void NetUpgradeSendReq(uint16_t Idx)
{
uint8_t p[4];
p[0] = Idx & 0xFF;
p[1] = (Idx >> 8) & 0xFF;
p[2] = Nup.PkgCnt & 0xFF;
p[3] = (Nup.PkgCnt >> 8) & 0xFF;
NetUpgradePushTx(0x02, p, 4); //请求从Idx起的下一批(收满PkgCnt视为完成)
}
/*0x81升级请求: 合法性检查/回ACK并启动下载*/
static void NetUpgradeOnReq(uint8_t *Payload, uint8_t Len)
{
UpDataRequset_t Req;
if(Len < sizeof(Req)) {
return;
}
memcpy(&Req, Payload, sizeof(Req));
if(Req.AppSize == 0 || Req.AppSize > NUP_APP_MAX) {
NetUpgradeSendAck(0x32, 0);
U_DBG_LOG("NetUpgrade reject: AppSize=%d\r\n", Req.AppSize);
return;
}
/*断点续传已取消: 每次点击升级均从0%全新下载*/
NetUpgradeSavePkg(0);
NupErasedMap = 0;
Nup.Active = true;
Nup.PkgCnt = Req.PackageNum;
Nup.AppSize = Req.AppSize;
Nup.Crc32 = Req.AppCrc32;
Nup.ExpectIdx = 0;
Nup.ErrCnt = 0;
Nup.LastTick = rt_tick_get();
Nup.LastAckIdx = 0;
U_DBG_LOG("NetUpgrade req: Pkg=%d Size=%d Crc=0x%08x Resumed=0\r\n",
Nup.PkgCnt, Nup.AppSize, Nup.Crc32);
NetUpgradeSendAck(0x20, 0);
NetUpgradeSendReq(0); //请求首包, 服务端收到0x02才开始发包
}
/*0x82升级数据: 严格按期望包号顺序写暂存区, 收满窗口回请求帧, 收完CRC校验*/
static void NetUpgradeOnData(uint8_t *Payload, uint8_t Len)
{
uint16_t Idx, Total;
uint8_t DataLen;
uint32_t WriteLen;
if(!Nup.Active || Len < 5) {
return;
}
Idx = Payload[0] | (Payload[1] << 8);
Total = Payload[2] | (Payload[3] << 8);
DataLen = Payload[4];
if(Total != Nup.PkgCnt) {
return; //会话不匹配
}
if(Idx != Nup.ExpectIdx) {
return; //重复/乱序包: 丢弃(服务端按期望包号重发)
}
if(Idx == Nup.PkgCnt - 1) {
WriteLen = Nup.AppSize - (uint32_t)Idx * NUP_PACKET_DATA; //尾包实长
if(DataLen < WriteLen) {
return;
}
}
else {
if(DataLen < NUP_PACKET_DATA) {
return;
}
WriteLen = NUP_PACKET_DATA;
}
{
/*NOR only writes 1->0: erase touched stash sectors before write (one erase per sector per session), fixes stale-bit corruption on 2nd upgrade*/
uint32_t Sec0 = (uint32_t)Idx * NUP_PACKET_DATA / SPIFLASH_SECTORSIZE;
uint32_t Sec1 = ((uint32_t)Idx * NUP_PACKET_DATA + WriteLen - 1) / SPIFLASH_SECTORSIZE;
for(uint32_t s = Sec0; s <= Sec1; s++) {
if((NupErasedMap & ((uint64_t)1 << s)) == 0) {
FwStashEraseSector(s);
NupErasedMap |= (uint64_t)1 << s;
}
}
}
FwStashWrite(&Payload[5], (uint32_t)Idx * NUP_PACKET_DATA, WriteLen);
Nup.ExpectIdx++;
Nup.LastTick = rt_tick_get();
Nup.ErrCnt = 0;
if(Nup.ExpectIdx >= Nup.PkgCnt) {
if(NetUpgradeCrc32Stash(Nup.AppSize) == Nup.Crc32) {
U_DBG_LOG("NetUpgrade stash verified, reboot to bootloader\r\n");
UpdateFromStash(Nup.AppSize, Nup.Crc32); //置APP_UPDATE_SPI_FLAG并复位
}
U_DBG_LOG("NetUpgrade stash CRC failed, restart download\r\n");
//校验失败: 从0重下(写前按需擦除保证暂存区干净)
NetUpgradeSavePkg(0);
NupErasedMap = 0;
Nup.ExpectIdx = 0;
NetUpgradeSendReq(0);
return;
}
if(Nup.ExpectIdx - Nup.LastAckIdx >= NUP_WINDOW) {
NetUpgradeSendReq(Nup.ExpectIdx); //收满窗口, 请求下一批(与断点起点无关)
Nup.LastAckIdx = Nup.ExpectIdx;
}
if(Nup.ExpectIdx % (NUP_WINDOW * 4) == 0) {
NetUpgradeSavePkg(Nup.ExpectIdx); //定期持久化进度(32包一次)
}
}
static void NetUpgradeFrameHandler(uint8_t Cmd, uint8_t *Payload, uint8_t Len)
{
U_DBG_LOG("NetUpgrade frame: cmd=0x%02x len=%d\r\n", Cmd, Len);
switch(Cmd) {
case 0x81:
NetUpgradeOnReq(Payload, Len);
break;
case 0x82:
NetUpgradeOnData(Payload, Len);
break;
default:
break;
}
}
/*****************************************************************************************
* 函数名称: NetUpgradeFeed
* 功能描述: 喂入Cat1下行数据片段(+LIPURC二进制), 流式拼接提取完整0x7D升级帧
* 参 数: data/len, 二进制数据片段(TCP分片有序)
* 返 回 值: true=数据已被升级模块消费(帧头0x7D); false=非0x7D流, 调用方交还原0x7A路径
*****************************************************************************************/
bool NetUpgradeFeed(uint8_t *data, uint16_t len, uint8_t ch)
{
Nup.CurCh = ch; /*记录会话通道*/
if(NupFrameLen == 0) {
uint16_t skip = 0;
while(skip < len && data[skip] != 0x7D) {
skip++;
}
if(skip >= len) {
return false;
}
data += skip;
len -= skip;
}
if(NupFrameLen + len > NUP_FRAME_BUF_MAX) {
NupFrameLen = 0; //溢出保护, 重新同步
return true;
}
memcpy(&NupFrameBuf[NupFrameLen], data, len);
NupFrameLen += len;
while(NupFrameLen >= 11) {
if(NupFrameBuf[0] != 0x7D) {
uint16_t i;
for(i = 1; i < NupFrameLen; i++) {
if(NupFrameBuf[i] == 0x7D) break;
}
if(i >= NupFrameLen) {
NupFrameLen = 0;
break;
}
memmove(NupFrameBuf, &NupFrameBuf[i], NupFrameLen - i);
NupFrameLen -= i;
continue;
}
uint16_t total = 11 + NupFrameBuf[8];
if(NupFrameLen < total) {
break; //帧未收齐, 等下一分片
}
uint16_t calc = CRC_Modbus(CRC16_BASE, NupFrameBuf, total - 2);
uint16_t recv = NupFrameBuf[total - 2] | (NupFrameBuf[total - 1] << 8);
if(calc == recv) {
/*MAC filter: only the device whose own GwMac matches the 0x7D frame header handles the frame,
broadcast 0x81 is answered by the target device only, other devices drop it silently*/
if(memcmp(&NupFrameBuf[1], GateWay.ConfigPara.GwMac, 6) == 0) {
NetUpgradeFrameHandler(NupFrameBuf[7], &NupFrameBuf[9], NupFrameBuf[8]);
}
else {
U_DBG_LOG("NetUpgrade MAC mismatch, check upgrade dialog MAC\r\n");
}
memmove(NupFrameBuf, &NupFrameBuf[total], NupFrameLen - total);
NupFrameLen -= total;
}
else {
memmove(NupFrameBuf, &NupFrameBuf[1], NupFrameLen - 1);
NupFrameLen -= 1;
}
}
return true;
}
/*****************************************************************************************
* 函数名称: Update
* 功能描述: 固件升级,此函数在解析到升级指令时调用
* 参 数: PayLoad 协议负载,需去除帧头等信息,只传负载数据
* 返 回 值: 无
* 函 数 名: NetUpgradePopTx
* 功 能: 取出本通道应答帧(0x01 ACK/0x02请求), 供CatOne主循环/EthTask经AT+LIPSEND直发
*****************************************************************************************/
void Update(uint8_t *PayLoad)
bool NetUpgradePopTx(uint8_t *Out, uint16_t *OutLen, uint8_t ch)
{
UpDataRequset_t Req;
boot_para_t cfg = {0};
memcpy(&Req, PayLoad, sizeof(UpDataRequset_t));
/*读回现有boot参数,保留FirstRunFlag等字段*/
dev_boot_read_param(&cfg);
cfg.AppSize = Req.AppSize;
cfg.Crc32Check = Req.AppCrc32;
cfg.PackageCnt = Req.PackageNum;
cfg.AppFlag = 0;
cfg.UpdateFlag = APP_UPDATE_FLAG;
memcpy(cfg.GwMac, GateWay.ConfigPara.GwMac, 6);
U_DBG_LOG("Receive upgrade command:\r\n");
U_DBG_LOG("PackageNum: %d\r\n", cfg.PackageCnt);
U_DBG_LOG("AppSize: %d\r\n", cfg.AppSize);
U_DBG_LOG("AppCrc32: 0x%08x\r\n", cfg.Crc32Check);
U_DBG_LOG("System Reset!\r\n");
SAVE_BOOT_PARA(&cfg);
U_DELAY_MS(200);
U_SYSTEM_TESET();
}
/*****************************************************************************************
* 函数名称: UpdateInit
* 功能描述: 升级参数初始化,需要在程序启动,外设初始化完后调用
* 参 数: 无
* 返 回 值: 无
*****************************************************************************************/
void UpdateInit(void)
{
boot_para_t cfg;
READ_BOOT_PARA(&cfg);
/*参照hc32l170_app: 仅确保AppFlag=APP_START_FLAG, 使Boot下次直接跳转App
(网关MAC/服务器/Lora配置从BootPara_t获取, 由Boot配置)*/
if(cfg.AppFlag != APP_START_FLAG) {
cfg.AppFlag = APP_START_FLAG;
SAVE_BOOT_PARA(&cfg);
bool ret = false;
rt_base_t level = rt_hw_interrupt_disable();
for(uint8_t idx = 0; idx < Nup.TxCnt; idx++) {
uint8_t slot = (Nup.TxHead + idx) % 4;
if(Nup.TxChan[slot] != ch) continue; /*跳过其他通道的帧(Cat1/ETH各自取自己的ACK)*/
memcpy(Out, Nup.TxQ[slot], Nup.TxQLen[slot]);
*OutLen = Nup.TxQLen[slot];
uint8_t keep = Nup.TxCnt - idx - 1; /*slot之后的帧数: 依次前移紧凑*/
for(uint8_t m2 = 0; m2 < keep; m2++) {
uint8_t curSlot = (slot + m2) % 4;
uint8_t nxt = (slot + m2 + 1) % 4;
memcpy(Nup.TxQ[curSlot], Nup.TxQ[nxt], 40);
Nup.TxChan[curSlot] = Nup.TxChan[nxt];
Nup.TxQLen[curSlot] = Nup.TxQLen[nxt];
}
Nup.TxCnt--;
ret = true;
break;
}
}
rt_hw_interrupt_enable(level);
return ret;
}/*****************************************************************************************
* 函 数 名: NetUpgradeActive
* 功 能: 升级会话状态查询
*****************************************************************************************/
bool NetUpgradeActive(void)
{
return Nup.Active;
}
/*****************************************************************************************
* 函数名称: NetUpgradeTick
* 功能描述: 1ms轮询(CatOne主循环调用): 等包超时催发请求, 长期无进展放弃会话
*****************************************************************************************/
void NetUpgradeTick(void)
{
if(!Nup.Active) {
if(NupFrameLen > 0) {
NupFrameLen = 0; /*非会话期清理残片, 防止后续0x7A业务流被组帧器吞掉*/
}
return;
}
uint32_t Now = rt_tick_get();
if(Now - Nup.LastTick > NUP_IDLE_TIMEOUT_MS) {
Nup.Active = false;
NupFrameLen = 0;
U_DBG_LOG("NetUpgrade idle timeout(30min), abort session\r\n");
return;
}
if(Now - Nup.LastTick > NUP_TIMEOUT_MS) {
Nup.LastTick = Now;
NupFrameLen = 0; /*等包超时: 残片必为损坏流, 清掉重同步*/
Nup.ErrCnt++;
if(Nup.ErrCnt > NUP_TIMEOUT_MAX) {
Nup.Active = false; //放弃会话(不复位), 服务器可重新0x81发起
NupFrameLen = 0;
U_DBG_LOG("NetUpgrade timeout, abort session\r\n");
return;
}
NetUpgradeSendReq(Nup.ExpectIdx); //催发: 服务端重发当前批次
}
}
/*****************************************************************************************
* 函数名称: UpdateFromStash
* 功能描述: 暂存区校验通过后, 置APP_UPDATE_SPI_FLAG并复位, Boot从外部Flash搬运升级
*****************************************************************************************/
void UpdateFromStash(uint32_t AppSize, uint32_t Crc32)
{
boot_para_t cfg;
READ_BOOT_PARA(&cfg);
cfg.AppSize = AppSize;
cfg.Crc32Check = Crc32;
cfg.PackageCnt = (uint16_t)((AppSize + NUP_PACKET_DATA - 1) / NUP_PACKET_DATA);
cfg.AppFlag = 0;
cfg.UpdateFlag = APP_UPDATE_SPI_FLAG;
NetUpgradeSavePkg(0); /*清除进度记录:本次升级已完结,下次升级从0重新开始*/
U_DBG_LOG("Upgrade ready: Size=%d Crc=0x%08x, reboot\r\n", AppSize, Crc32);
SAVE_BOOT_PARA(&cfg);
U_DELAY_MS(200);
U_SYSTEM_TESET();
}
+7
View File
@@ -400,9 +400,14 @@ static void RS485Ch1UsartErrIrqCallback(void)
* 参 数: 无
* 返 回 值: 无
*****************************************************************************************/
static volatile bool RS485Ch1TxFlag = false; /*RS485Ch1发送期间置位: RX中断丢弃回环自收字节*/
static void RS485Ch1UsartRxIrqCallback(void)
{
uint8_t rData = RS485Ch1UartRec();
if(RS485Ch1TxFlag) {
return; /*发送回环自收字节丢弃*/
}
RS485Ch1RxIrqCallback(rData);
}
@@ -502,11 +507,13 @@ void RS485Ch1_Config(uint32_t BaudRate)
void RS485Ch1UartSend(uint8_t *sData, uint16_t sLen)
{
RS485_CH1_TX();
RS485Ch1TxFlag = true;
for(int i = 0; i < sLen; i++) {
USART_SendData(RS485_CH1_USART_CH, sData[i]);
while(USART_GetStatus(RS485_CH1_USART_CH, UsartTxComplete) == Reset);
}
RS485_CH1_RX();
RS485Ch1TxFlag = false;
}
/*****************************************************************************************
+3 -3
View File
@@ -285,10 +285,10 @@ void GateWayInit(void)
}
else
rt_kprintf("History Num: 0\r\n");
bool cat1Active = ChannelIsActive(GateWay.ConfigPara, CH_CAT1);
/*USART1专用Cat1: 上电恒115200+CAT1方向, 调试输出统一走RS485 Ch1(见rt_hw_console_output)*/
bool ethActive = ChannelIsActive(GateWay.ConfigPara, CH_ETH);
if(cat1Active) { DbgOrCat1Uart_Config(115200); CAT1_ON(); }
else { DBG_ON(); DbgOrCat1Uart_Config(500000); }
DbgOrCat1Uart_Config(115200);
CAT1_ON();
if(ethActive) { ETH_ON(); rt_thread_delay(1000); ETHReset(); }
}
+59 -4
View File
@@ -323,7 +323,7 @@ void CheckDelSecter(uint32_t wAddr, uint16_t wLen)
uint32_t DelSectorNum = 0;
uint32_t SurplusSpace = SPIFLASH_SECTORSIZE - (wAddr % SPIFLASH_SECTORSIZE);
if((wAddr + wLen) > SPIFLASH_SIZE) {
if((wAddr + wLen) > LOG_SAV_END_ADDR) {
DelOffsetAddr = LOG_SAV_START_SECTOR * SPIFLASH_SECTORSIZE;
SetLastLogEndAddr(DelOffsetAddr + wLen);
SetLoopSavFlag(1);
@@ -342,7 +342,7 @@ void CheckDelSecter(uint32_t wAddr, uint16_t wLen)
SpiFlashEraseSector(DelOffsetAddr);
DelOffsetAddr += SPIFLASH_SECTORSIZE;
}
if(DelOffsetAddr == SPIFLASH_SIZE) {
if(DelOffsetAddr == LOG_SAV_END_ADDR) {
DelOffsetAddr = LOG_SAV_START_SECTOR * SPIFLASH_SECTORSIZE;
}
FindFirstLogAddr = DelOffsetAddr;
@@ -363,7 +363,7 @@ void CheckDelSecter(uint32_t wAddr, uint16_t wLen)
}
if(i == LOG_SAV_SIZE_MAX) {
ReadAddr += LOG_SAV_SIZE_MAX;
if(ReadAddr >= SPIFLASH_SIZE) { //找不到下一条数据,重置flash
if(ReadAddr >= LOG_SAV_END_ADDR) { //找不到下一条数据,重置flash
Debug_Printf("Add LOG Error, Next Data No Find!\r\n");
SetLogNum(0);
SetLoopSavFlag(0);
@@ -407,7 +407,7 @@ void AddLog(uint8_t *wData, uint32_t wLen)
//判断是否跨扇区,跨扇区需要先擦除下一扇区
CheckDelSecter(LLEndAddr, LogSize);
if(LogH->LogEndAddr > SPIFLASH_SIZE) {
if(LogH->LogEndAddr > LOG_SAV_END_ADDR) {
LLEndAddr = LOG_SAV_START_SECTOR * SPIFLASH_SECTORSIZE;
LogH->LogEndAddr = ReadLastLogEndAddr();
}
@@ -419,6 +419,61 @@ void AddLog(uint8_t *wData, uint32_t wLen)
}
/*升级进度记录读写(扇区4037): 记录AppSize/Crc32/已收包数, 断电断连后支持断点续传*/
void FwStashProgRead(uint8_t *rData, uint16_t rLen)
{
FlashLock();
SpiFlashReadAnyLengthData(rData, FLASH_FW_STASH_PROG_SECTOR * SPIFLASH_SECTORSIZE, rLen);
FlashUnlock();
}
void FwStashProgWrite(uint8_t *wData, uint16_t wLen)
{
FlashLock();
SpiFlashEraseSector(FLASH_FW_STASH_PROG_SECTOR * SPIFLASH_SECTORSIZE);
SpiFlashWriteAnyLengthData(wData, FLASH_FW_STASH_PROG_SECTOR * SPIFLASH_SECTORSIZE, wLen);
FlashUnlock();
}
/*固件升级暂存区操作: 内部带互斥, 避免与AddLog/WritePara的SPI事务交错*/
void FwStashEraseAll(void)
{
FlashLock();
for(int i = 0; i < FLASH_FW_STASH_SECTORS; i++) {
SpiFlashEraseSector(FLASH_FW_STASH_SECTOR * SPIFLASH_SECTORSIZE + i * SPIFLASH_SECTORSIZE);
}
FlashUnlock();
}
void FwStashEraseSector(uint32_t SectorIdx)
{
FlashLock();
SpiFlashEraseSector(FLASH_FW_STASH_SECTOR * SPIFLASH_SECTORSIZE + SectorIdx * SPIFLASH_SECTORSIZE);
FlashUnlock();
}
int FwStashWrite(uint8_t *wData, uint32_t Offset, uint16_t wLen)
{
if(Offset + wLen > FLASH_FW_STASH_SECTORS * SPIFLASH_SECTORSIZE) {
return -1;
}
FlashLock();
SpiFlashWriteAnyLengthData(wData, FLASH_FW_STASH_SECTOR * SPIFLASH_SECTORSIZE + Offset, wLen);
FlashUnlock();
return 0;
}
int FwStashRead(uint8_t *rData, uint32_t Offset, uint16_t rLen)
{
if(Offset + rLen > FLASH_FW_STASH_SECTORS * SPIFLASH_SECTORSIZE) {
return -1;
}
FlashLock();
SpiFlashReadAnyLengthData(rData, FLASH_FW_STASH_SECTOR * SPIFLASH_SECTORSIZE + Offset, rLen);
FlashUnlock();
return 0;
}
/*****************************************************************************************
* 函数名称: ReadLog
* 功能描述: 读取日志