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不匹配提示日志
This commit is contained in:
2026-10-12 10:24:44 +08:00
parent 920bddf949
commit 5db05d061b
7 changed files with 572 additions and 485 deletions
+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
View File
@@ -358,6 +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);
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);
+2 -2
View File
@@ -34,8 +34,8 @@ 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 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
+3 -15
View File
@@ -488,7 +488,7 @@ void CatOneLoopHandler(void)
case CAT_ONE_WAIT_SEND:
/*Cat1远程升级应答帧优先(0x7D原始帧直发, 绕过0x7A打包)*/
if(NetUpgradePopTx(CatOneTxBuff, &CatOneTxLen)) {
if(NetUpgradePopTx(CatOneTxBuff, &CatOneTxLen, CH_CAT1)) {
CatSendErrCnt = 0;
Cat1UpgTxFlag = 1; /*升级应答帧标记*/
CatOne.Cat1Status = CAT_ONE_SEND_DATA;
@@ -651,18 +651,6 @@ static void CatOneInit(GateWayPara GateWay)
GateWay->MuchRevCallBack : GateWay->AuchRevCallBack;
}
/*hex文本合法性: 全部字符∈[0-9a-fA-F]且偶数长度(AsciiToHex不校验字符合法性, 非hex明文须先识别直通)*/
static bool Cat1IsHexText(char *s, int len)
{
if(len <= 0 || len % 2 != 0)
return false;
for(int 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;
}
/*+LIPURC异步事件统一分发: 与AT状态机无关, 任何状态下都能处理升级帧/业务数据*/
/*一批数据包的URC会连发, 多条可能挤进同一缓冲块: 必须循环解析每一条*/
static void CatOneUrcHandle(GateWayPara GateWay, char *RxBuff)
@@ -686,7 +674,7 @@ static void CatOneUrcHandle(GateWayPara GateWay, char *RxBuff)
if(sLen > 0) {
memset(hData, 0x00, sizeof(hData)); /*ASCII hex转二进制(服务器下发为hex文本)*/
hLen = Cat1IsHexText(rData, sLen) ? AsciiToHex(rData, hData, sLen) : 0;
hLen = IsHexText(rData, sLen) ? AsciiToHex(rData, hData, sLen) : 0;
if(hLen == 0) {
/*非hex明文(如网络调试助手直发命令): 原样直通调试解析, 与ETH口明文直通一致*/
hLen = (sLen < (int)sizeof(hData)) ? sLen : (int)sizeof(hData) - 1;
@@ -695,7 +683,7 @@ static void CatOneUrcHandle(GateWayPara GateWay, char *RxBuff)
}
CatSendErrCnt = 0;
Cat1RevErrCnt = 0; /*server data arrived: reload watchdog*/
if(hLen > 0 && NetUpgradeFeed(hData, hLen)) {
if(hLen > 0 && NetUpgradeFeed(hData, hLen, CH_CAT1)) {
/*0x7D frame consumed by NetUpgrade*/
}
else if(hLen > 0 && hData[0] == 0x7A) { /*0x7A业务帧: 按Much/Auch配置分流*/
+88 -22
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);
uint16_t ret = AsciiToHex(RxBuffTemp, HexData, EthRxLen);
if(ret > 0 && HexData[0] == 0x7A) {
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 {
/*非会话: 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(EthRxLen > 1) {
DebugAnalyze(GateWay, (uint8_t *)RxBuffTemp, EthRxLen);
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();
+19 -1
View File
@@ -126,10 +126,28 @@ void HexToAscii(uint8_t *HexData, char *ASCData, uint8_t sLen)
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;
+30 -16
View File
@@ -103,7 +103,9 @@ static struct {
uint32_t ErrCnt; //连续超时次数
uint8_t TxQ[4][40]; //应答帧队列(0x01/0x02连发互不挤占)
uint8_t TxQLen[4];
uint8_t TxHead; //队首索引(出队)
uint8_t TxHead;
uint8_t TxChan[4]; /*每帧来源通道: ACK从收到0x81的通道原路返回*/
uint8_t CurCh; /*当前会话通道: Feed进入时记录*/ //队首索引(出队)
uint8_t TxCnt; //队列帧数
}Nup;
@@ -162,6 +164,7 @@ static void NetUpgradePushTx(uint8_t Cmd, uint8_t *Payload, uint8_t 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);
@@ -305,8 +308,9 @@ static void NetUpgradeFrameHandler(uint8_t Cmd, uint8_t *Payload, uint8_t Len)
* 参 数: data/len, 二进制数据片段(TCP分片有序)
* 返 回 值: true=数据已被升级模块消费(帧头0x7D); false=非0x7D流, 调用方交还原0x7A路径
*****************************************************************************************/
bool NetUpgradeFeed(uint8_t *data, uint16_t len)
bool NetUpgradeFeed(uint8_t *data, uint16_t len, uint8_t ch)
{
Nup.CurCh = ch; /*记录会话通道*/
if(NupFrameLen == 0) {
uint16_t skip = 0;
@@ -350,6 +354,9 @@ bool NetUpgradeFeed(uint8_t *data, uint16_t len)
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;
@@ -362,29 +369,36 @@ bool NetUpgradeFeed(uint8_t *data, uint16_t len)
return true;
}
* 函数名称: NetUpgradePopTx
* 功能描述: 取待发应答帧(0x01 ACK/0x02请求), 由CatOne主循环经AT+LIPSEND直发
/*****************************************************************************************
* 函 数 名: NetUpgradePopTx
* 功 能: 取出本通道应答帧(0x01 ACK/0x02请求), 供CatOne主循环/EthTask经AT+LIPSEND直发
bool NetUpgradePopTx(uint8_t *Out, uint16_t *OutLen)
*****************************************************************************************/
bool NetUpgradePopTx(uint8_t *Out, uint16_t *OutLen, uint8_t ch)
{
uint8_t cmdLog = 0;
bool ret = false;
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;
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;
}
if(ret) {
U_DBG_LOG("NetUpgrade pop: cmd=0x%02x\r\n", cmdLog);
}
rt_hw_interrupt_enable(level);
}
return ret;
}/*****************************************************************************************
* 函 数 名: NetUpgradeActive
* 功 能: 升级会话状态查询
*****************************************************************************************/
bool NetUpgradeActive(void)
{