diff --git a/.trae/documents/cat1-remote-upgrade-plan.md b/.trae/documents/cat1-remote-upgrade-plan.md
new file mode 100644
index 0000000..95b350c
--- /dev/null
+++ b/.trae/documents/cat1-remote-upgrade-plan.md
@@ -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 资费可忽略。
diff --git a/Project/GateWay/MDK/GateWay.uvoptx b/Project/GateWay/MDK/GateWay.uvoptx
index 1dcfca5..d5c10e9 100644
--- a/Project/GateWay/MDK/GateWay.uvoptx
+++ b/Project/GateWay/MDK/GateWay.uvoptx
@@ -432,18 +432,18 @@
0
0
- 569
+ 503
1
- 0
+ 54178
0
0
0
0
0
- 0
+ 1
..\source\User\Src\CatOneTask.c
-
+ \\GateWay\../source/User/Src/CatOneTask.c\503
diff --git a/Project/GateWay/MDK/RTE/RTOS/board.c b/Project/GateWay/MDK/RTE/RTOS/board.c
index 9bbfa65..e7d9228 100644
--- a/Project/GateWay/MDK/RTE/RTOS/board.c
+++ b/Project/GateWay/MDK/RTE/RTOS/board.c
@@ -76,9 +76,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,15 +86,8 @@ INIT_BOARD_EXPORT(uart_init);
void rt_hw_console_output(const char *str)
{
rt_enter_critical();
- 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;
- }
+ /*USART1专用Cat1后, 调试输出统一走RS485 Ch1*/
+ RS485Ch1UartSend((uint8_t *)str, strlen(str));
rt_exit_critical();
}
diff --git a/Project/GateWay/source/Module/GateWay_Debug/DebugCmd.c b/Project/GateWay/source/Module/GateWay_Debug/DebugCmd.c
index 835f0d1..8317a94 100644
--- a/Project/GateWay/source/Module/GateWay_Debug/DebugCmd.c
+++ b/Project/GateWay/source/Module/GateWay_Debug/DebugCmd.c
@@ -43,24 +43,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)) {
- char *line = tempBuff;
- while(line && *line) {
- char *end = strstr(line, "\r\n");
- uint16_t n = end ? (uint16_t)(end - line + 2) : (uint16_t)strlen(line);
- if(n > 240) n = 240;
- if(n > 0) Cat1DuchSend(line, n);
- line = end ? end + 2 : NULL;
- }
- } else {
- rt_kprintf(tempBuff);
- }
- if(ParaEchoCh1) {
- RS485Ch1UartSend((uint8_t *)tempBuff, strlen(tempBuff));
- }
+ /*USART1专用Cat1后, 调试输出统一走RS485 Ch1(经rt_kprintf→rt_hw_console_output)*/
+ rt_kprintf(tempBuff);
va_end(args);
}
/*****************************************************************************************
@@ -98,18 +82,8 @@ 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);
}
@@ -1305,9 +1279,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 || ch == CH_CAT1) {
+ DBG_LOG("USART1 is dedicated to Cat1, duch switch denied.\r\n");
return;
}
if(ChannelIsActive(GateWay->ConfigPara, ch)
diff --git a/Project/GateWay/source/User/Inc/Public.h b/Project/GateWay/source/User/Inc/Public.h
index d4b7edc..efd2764 100644
--- a/Project/GateWay/source/User/Inc/Public.h
+++ b/Project/GateWay/source/User/Inc/Public.h
@@ -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,7 @@ 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);
+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);
diff --git a/Project/GateWay/source/User/Inc/Update.h b/Project/GateWay/source/User/Inc/Update.h
index 626f523..2513f86 100644
--- a/Project/GateWay/source/User/Inc/Update.h
+++ b/Project/GateWay/source/User/Inc/Update.h
@@ -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); //喂入Cat1下行数据片段, 返回true=已被升级模块消费
+bool NetUpgradePopTx(uint8_t *Out, uint16_t *OutLen); //取待发应答帧(0x01/0x02, 0x7D原始帧)
+bool NetUpgradeActive(void); //升级会话进行中
+void NetUpgradeTick(void); //1ms轮询: 超时催发与会话放弃
+void UpdateFromStash(uint32_t AppSize, uint32_t Crc32); //暂存区校验通过后置标志复位进Boot
#endif
diff --git a/Project/GateWay/source/User/Inc/bsp.h b/Project/GateWay/source/User/Inc/bsp.h
index d47c579..61194b4 100644
--- a/Project/GateWay/source/User/Inc/bsp.h
+++ b/Project/GateWay/source/User/Inc/bsp.h
@@ -35,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
diff --git a/Project/GateWay/source/User/Inc/spiflash.h b/Project/GateWay/source/User/Inc/spiflash.h
index 4228fed..065f808 100644
--- a/Project/GateWay/source/User/Inc/spiflash.h
+++ b/Project/GateWay/source/User/Inc/spiflash.h
@@ -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 */
diff --git a/Project/GateWay/source/User/Src/CatOneTask.c b/Project/GateWay/source/User/Src/CatOneTask.c
index 55146f2..a9cad8e 100644
--- a/Project/GateWay/source/User/Src/CatOneTask.c
+++ b/Project/GateWay/source/User/Src/CatOneTask.c
@@ -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 = true; /*默认常开: Cat1调试信息始终输出, 可由Cat1DBGOnOff()动态关闭*/
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,6 +24,8 @@ 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];
@@ -44,25 +50,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] = {
"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+CGPADDR=1\r\n", //查询IP地址
- "AT+CSQ\r\n", //查询信号强度
+ "AT+QCCID\r\n", //璇诲崱鍙?
+ "AT+CEREG?\r\n", //鏌ヨ?㈡敞鍐岀姸鎬?
+ "AT+CGPADDR=1\r\n", //鏌ヨ??IP鍦板潃
+ "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+QICLOSE=0\r\n", //鏂?寮€杩炴帴
"AT+QLBSCFG=\"server\",http://locator-aep.xiot.senthink.com:80/locator/v0.1/locate\r\n",
"AT+QLBSCFG=\"token\",45B516D3078467E84DD2CFBF3532A4CB\r\n",
- "AT+CCLK?\r\n", //获取时间
+ "AT+CCLK?\r\n", //鑾峰彇鏃堕棿
};
void Cat1DBGOnOff(bool OnOff)
@@ -298,41 +305,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 +348,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 +392,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:
@@ -471,17 +459,14 @@ void CatOneLoopHandler(void)
break;
case CAT_ONE_WAIT_CONN:
+ /*USART1 dedicated to Cat1: register immediately on connect, independent of channel config*/
if(CatOne.TcpConnFlag) {
- if(CatOne.GateWay->ConfigPara.Much == CH_CAT1) {
- CatOne.Cat1Status = CAT_ONE_REG_SVR;
- }
- else if(CatOne.GateWay->ConfigPara.Auch == CH_CAT1 || (CatOne.GateWay->ConfigPara.CuchMask & CUCH_CAT1)) {
- GateWayRegister(Payload);
- CatOneTxLen = NetCommOrgData(CatOne.GateWay, Payload, CatOneTxBuff);
- CatOne.Cat1Status = CAT_ONE_SEND_DATA;
+ if(Cat1IdleReconnFlag) { /*空闲保活重连: 只建链不上传, 跳过注册帧*/
+ Cat1IdleReconnFlag = 0;
+ CatOne.Cat1Status = CAT_ONE_WAIT_SEND;
}
else {
- CatOne.Cat1Status = CAT_ONE_DUCH_MODE;
+ CatOne.Cat1Status = CAT_ONE_REG_SVR;
}
}
break;
@@ -493,6 +478,13 @@ void CatOneLoopHandler(void)
break;
case CAT_ONE_WAIT_SEND:
+ /*Cat1远程升级应答帧优先(0x7D原始帧直发, 绕过0x7A打包)*/
+ if(NetUpgradePopTx(CatOneTxBuff, &CatOneTxLen)) {
+ CatSendErrCnt = 0;
+ Cat1UpgTxFlag = 1; /*升级应答帧标记*/
+ CatOne.Cat1Status = CAT_ONE_SEND_DATA;
+ 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 +499,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;
@@ -527,31 +522,39 @@ void CatOneLoopHandler(void)
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) {
+ if(CatOne.TcpConnFlag == false)
+ CAT1_DBG_LOG("Cat1 Rec Server Data Timeout!\r\n");
+ CatOne.Cat1Status = CAT_ONE_OFF;
+ break;
+ }
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);
@@ -598,12 +601,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 +647,54 @@ 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) {
+ if(rLen >= 24) {
+ if((int)strlen(rData) >= rLen) {
+ memset(hData, 0x00, sizeof(hData)); /*ASCII hex转二进制(服务器下发为hex文本)*/
+ hLen = AsciiToHex(rData, hData, rLen);
+ CatSendErrCnt = 0;
+ Cat1RevErrCnt = 0; /*server data arrived: reload watchdog*/
+ if(hLen > 0 && NetUpgradeFeed(hData, hLen)) {
+ /*0x7D frame consumed by NetUpgrade*/
+ }
+ else
+ GateWay->MuchRevCallBack(GateWay, hData, hLen); /*Cat1 is the master uplink: route all business to Much*/
+ }
+ else {
+ CAT1_DBG_LOG("LIPURC trunc, drop!\r\n");
+ }
+ }
+ }
+ 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 +703,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 +768,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 +817,4 @@ void CatOneTriggerRegister(void)
CAT1_ON();
CatOneReset();
}
+
diff --git a/Project/GateWay/source/User/Src/EthTask.c b/Project/GateWay/source/User/Src/EthTask.c
index 0aa8249..d91fe52 100644
--- a/Project/GateWay/source/User/Src/EthTask.c
+++ b/Project/GateWay/source/User/Src/EthTask.c
@@ -297,7 +297,7 @@ static void EthRev_Thread_Entry(void *parameter)
memcpy(RxBuffTemp, EthRxBuff, EthRxLen);
RxBuffTemp[EthRxLen] = 0;
memset(HexData, 0x00, EthRxLen / 2);
- uint8_t ret = AsciiToHex(RxBuffTemp, HexData, EthRxLen);
+ uint16_t ret = AsciiToHex(RxBuffTemp, HexData, EthRxLen);
if(ret > 0 && HexData[0] == 0x7A) {
Eth.SendNoDataCnt = 0;
if(GateWay->ConfigPara.Much == CH_ETH)
diff --git a/Project/GateWay/source/User/Src/Public.c b/Project/GateWay/source/User/Src/Public.c
index 672074f..035b292 100644
--- a/Project/GateWay/source/User/Src/Public.c
+++ b/Project/GateWay/source/User/Src/Public.c
@@ -126,7 +126,7 @@ void HexToAscii(uint8_t *HexData, char *ASCData, uint8_t sLen)
sLen, 需要转的字符串长度
* 返 回 值: 无
*****************************************************************************************/
-uint8_t AsciiToHex(char *ASCData, uint8_t *HexData, uint8_t sLen)
+uint16_t AsciiToHex(char *ASCData, uint8_t *HexData, uint16_t sLen)
{
char chr;
uint8_t HexLen = 0;
diff --git a/Project/GateWay/source/User/Src/Update.c b/Project/GateWay/source/User/Src/Update.c
index 3f17192..07ee712 100644
--- a/Project/GateWay/source/User/Src/Update.c
+++ b/Project/GateWay/source/User/Src/Update.c
@@ -3,8 +3,10 @@
#include "main.h"
#include "bsp.h"
#include "spiflash.h"
+#include /*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
@@ -40,6 +42,7 @@ void Update(uint8_t *PayLoad)
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();
@@ -64,4 +67,380 @@ void UpdateInit(void)
}
}
+/*****************************************************************************************
+* 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 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.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)
+{
+ 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]);
+ }
+ memmove(NupFrameBuf, &NupFrameBuf[total], NupFrameLen - total);
+ NupFrameLen -= total;
+ }
+ else {
+ memmove(NupFrameBuf, &NupFrameBuf[1], NupFrameLen - 1);
+ NupFrameLen -= 1;
+ }
+ }
+ return true;
+}
+
+/*****************************************************************************************
+* 函数名称: NetUpgradePopTx
+* 功能描述: 取待发应答帧(0x01 ACK/0x02请求), 由CatOne主循环经AT+LIPSEND直发
+*****************************************************************************************/
+bool NetUpgradePopTx(uint8_t *Out, uint16_t *OutLen)
+{
+ bool ret = false;
+ uint8_t cmdLog = 0;
+ rt_base_t level = rt_hw_interrupt_disable();
+ if(Nup.TxCnt > 0) {
+ cmdLog = Nup.TxQ[Nup.TxHead][7];
+ memcpy(Out, Nup.TxQ[Nup.TxHead], Nup.TxQLen[Nup.TxHead]);
+ *OutLen = Nup.TxQLen[Nup.TxHead];
+ Nup.TxHead = (Nup.TxHead + 1) % 4;
+ Nup.TxCnt--;
+ ret = true;
+ }
+ rt_hw_interrupt_enable(level);
+ if(ret) {
+ U_DBG_LOG("NetUpgrade pop: cmd=0x%02x\r\n", cmdLog);
+ }
+ return ret;
+}
+
+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();
+}
diff --git a/Project/GateWay/source/User/Src/bsp.c b/Project/GateWay/source/User/Src/bsp.c
index b2ac7f7..9f52364 100644
--- a/Project/GateWay/source/User/Src/bsp.c
+++ b/Project/GateWay/source/User/Src/bsp.c
@@ -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;
}
/*****************************************************************************************
diff --git a/Project/GateWay/source/User/Src/main.c b/Project/GateWay/source/User/Src/main.c
index 383398a..dfdb1bd 100644
--- a/Project/GateWay/source/User/Src/main.c
+++ b/Project/GateWay/source/User/Src/main.c
@@ -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(); }
}
diff --git a/Project/GateWay/source/User/Src/spiflash.c b/Project/GateWay/source/User/Src/spiflash.c
index 1f21e00..b705eea 100644
--- a/Project/GateWay/source/User/Src/spiflash.c
+++ b/Project/GateWay/source/User/Src/spiflash.c
@@ -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
* 功能描述: 读取日志