fix(flash,upgrade): 注册应答/Flash并发/参数校验修复+升级后擦参数分区+注释统一UTF-8

- 注册应答先发后写Flash,重复注册参数无变化时跳过写入;SX1276发送前等待上一帧TxDone,防止应答帧被下一帧截断
- SPI Flash操作增加互斥锁(ReadPara/WritePara/AddLog/SavLogNum),修复多线程并发写坏参数镜像
- WritePara自行擦除参数区全部扇区并加越界保护;移除通用写函数中的日志簿记副作用(双擦+LastLogEndAddr污染)
- 开机参数加载增加CRC校验与通讯单元表语义清洗;修复忽略ReadPara返回值导致坏镜像放行并被回写洗白的问题
- RS485Send函数指针每次开机重设,不再从Flash恢复旧固件代码地址
- Boot升级完成置APP_BOOT_UPGRADE_FLAG,App启动时仅擦外部参数分区(扇区0~1,历史日志保留)并恢复标志
- 参数SavFlag绑定PARA_LAYOUT_VER布局版本戳,布局变更自动回退默认参数
- res gxjt改为仅擦参数分区,不再触发全片扫描
- 全部源码注释统一UTF-8(无BOM),修复历史GBK/UTF-8混合编码及乱码字符
- 软件版本 1.2 -> 1.3
This commit is contained in:
2026-09-03 19:00:24 +08:00
parent a6c8cd8a71
commit fe4b9fabf8
7 changed files with 158 additions and 40 deletions
@@ -1029,9 +1029,11 @@ void DebugCmdRestoreFactory(int argc, char *argv[])
}
DBG_LOG("Restore factory settings, GateWay Restart...\r\n");
memset((uint8_t *)&GateWay->ConfigPara, 0x00, sizeof(GWConfigPara_t));
WritePara((uint8_t *)&GateWay->ConfigPara, sizeof(GWConfigPara_t));
rt_thread_delay(1000);
/*仅擦除参数分区(扇区0~1),非全片擦除;擦除后参数区全0xFF,
重启后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();
while(1);
}
@@ -1623,7 +1625,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]);
@@ -1752,7 +1754,7 @@ static void DebugLaserConfig(int argc, char *argv[])
}
}
}
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]);
@@ -418,6 +418,14 @@ void Sx1276LoRaLoopHandler(GateWayPara GateWay)
}
void Sx1276LoRaSendBuffer(uint8_t* pucBuff, uint16_t ucLen)
{
uint16_t TxDoneWaitCnt = 0;
/*LoRa发送是异步的(TxDone中断结束,IsrSx1276LoRaTxRx中将State恢复为IDLE)。
若上一帧仍在发送时再次调用本函数,下面设置STANDBY会把正在发送的帧截断,
导致先发出的应答丢失(传感器收不到注册应答),故先等待上一帧发送完成*/
while(LoRaPara.State == SX1276_TX && TxDoneWaitCnt < 2000) {
Ddl_Delay1ms(1);
TxDoneWaitCnt++;
}
LoRaPara.State = SX1276_BUSY;
if (ucLen > LORA_BUFF_SIZE){
ucLen = LORA_BUFF_SIZE;
@@ -667,3 +675,4 @@ bool LoraSetFreqCent(uint32_t FreqCent)
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef __PUBLIC_H
#ifndef __PUBLIC_H
#define __PUBLIC_H
#include "bsp.h"
+1 -1
View File
@@ -4,7 +4,7 @@
#include "Public.h"
#include "DebugCmd.h"
#define SOFTWARE_VERSION 12
#define SOFTWARE_VERSION 13
#define HARDWARE_VERSION 12
extern bool MainDispEn;
+25 -16
View File
@@ -615,22 +615,30 @@ int CommUnitAnalyze(GateWayPara GateWay, uint8_t *rData, uint16_t rLen, SendData
// if(CUHeader->PayloadLen < 2)
// return -1;
if(CommUnitIdx >= 0) { //设备存在
if(CommUnitIdx >= 0) { //设备存在
MAIN_DBG_LOG("\r\nCommUnit has already been registered! Mac:%02X-%02X-%02X-%02X-%02X-%02X\r\n",
CUHeader->DevMac[0], CUHeader->DevMac[1], CUHeader->DevMac[2], CUHeader->DevMac[3], CUHeader->DevMac[4], CUHeader->DevMac[5]);
uint8_t ret = 1;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].CommErrCnt = 0;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].ConfigFlag = true;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].CailFlag = false;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].TimeSyncFlag = false;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].ContLaser = false;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].LaserOnOff = false;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].FPOTimeFlag = false;
GateWay->ConfigPara.CommUnitArray[CommUnitIdx].FPOTimeConfirmed = true;
WritePara((uint8_t *)&GateWay->ConfigPara, sizeof(GWConfigPara_t));
CommUnitCmdSend(GateWay, CUHeader->DevMac, COMM_UNIT_CMD_REG, &ret, 1, Response);
return CommUnitIdx;
}
CommUnitPara_t *pCU = &GateWay->ConfigPara.CommUnitArray[CommUnitIdx];
bool ParaChanged = (pCU->CommErrCnt != 0 || pCU->ConfigFlag != true || pCU->CailFlag != false ||
pCU->TimeSyncFlag != false || pCU->ContLaser != false || pCU->LaserOnOff != false ||
pCU->FPOTimeFlag != false || pCU->FPOTimeConfirmed != true);
pCU->CommErrCnt = 0;
pCU->ConfigFlag = true;
pCU->CailFlag = false;
pCU->TimeSyncFlag = false;
pCU->ContLaser = false;
pCU->LaserOnOff = false;
pCU->FPOTimeFlag = false;
pCU->FPOTimeConfirmed = true;
/*先发应答再写FlashWritePara需擦除2个扇区并写入约7KB参数(耗时百毫秒级),
若先写Flash,应答延迟过大,会超出传感器注册请求后的接收窗口导致其收不到回应;
参数无变化时跳过写入,避免传感器反复重注册时频繁擦写Flash*/
CommUnitCmdSend(GateWay, CUHeader->DevMac, COMM_UNIT_CMD_REG, &ret, 1, Response);
if(ParaChanged)
WritePara((uint8_t *)&GateWay->ConfigPara, sizeof(GWConfigPara_t));
return CommUnitIdx;
} }
for(int i = 0; i < COMMUNIT_NUM_MAX; i++) {
if(GateWay->ConfigPara.CommUnitArray[i].RegFlag == false) {
CommUnitIdx = i;
@@ -668,11 +676,11 @@ int CommUnitAnalyze(GateWayPara GateWay, uint8_t *rData, uint16_t rLen, SendData
GateWay->ConfigPara.CommUnitArray[i].CurFPOTimeStart = LW_DEV_FPO_TIME_START_DEFAULT;
GateWay->ConfigPara.CommUnitArray[i].CurFPOTime = LW_DEV_FPO_TIME_DEFAULT;
GateWay->ConfigPara.CommUnitArray[i].FPOTimeFlag = false;
GateWay->ConfigPara.CommUnitArray[i].FPOTimeConfirmed = true;
WritePara((uint8_t *)&GateWay->ConfigPara, sizeof(GWConfigPara_t));
GateWay->ConfigPara.CommUnitArray[i].FPOTimeConfirmed = true;
uint8_t ret = 1;
//先发应答再写Flash,避免WritePara耗时导致应答延迟过大(原因同上)
CommUnitCmdSend(GateWay, CUHeader->DevMac, COMM_UNIT_CMD_REG, &ret, 1, Response);
WritePara((uint8_t *)&GateWay->ConfigPara, sizeof(GWConfigPara_t));
if(GateWay->MuchRegFlag) { //网关已注册,向服务发送新设备添加指令
PayLoadLen = (GateWay->ConfigPara.CommUnitArray[i].SensorN * 6) + 2;
MegData = rt_malloc(PayLoadLen + 3);
@@ -1440,3 +1448,4 @@ int AuchRevCallBack(GateWayPara GateWay, uint8_t *rData, uint16_t rLen)
{
return _NetRevCallBack(GateWay, rData, rLen, &GateWay->AuchRegFlag);
}
+63 -7
View File
@@ -75,8 +75,19 @@ void GateWayInit(void)
boot_para_t bootParam;
dev_boot_read_param(&bootParam);
rt_kprintf("AppFlag: 0x%08x\r\n", bootParam.AppFlag);
/*升级完成后Boot会设AppFlag=APP_START_FLAG并清UpdateFlag=0,
检测到升级异常(UpdateFlag仍为APP_UPDATE_FLAG 或 非首次且AppFlag无效)时擦外置Flash*/
/*升级完成后BootAppFlag=APP_BOOT_UPGRADE_FLAG(每次升级均置位,不限首次)。
App启动时据此擦除参数分区(仅扇区0~1,非全片擦除)并恢复APP_START_FLAG
随后ReadPara读到0xFF→SavFlag无效→自动加载默认参数,传感器重新注册。
须在下方升级异常检测之前处理,避免AppFlag!=APP_START_FLAG误触发全片擦除*/
if(bootParam.AppFlag == APP_BOOT_UPGRADE_FLAG) {
rt_kprintf("Upgrade done, erasing parameter partition...\r\n");
for(uint32_t Sector = GATEWEY_PARA_SAV_SECTOR; Sector < LOG_INFO_START_SECTOR; Sector++) {
SpiFlashEraseSector(Sector * SPIFLASH_SECTORSIZE);
}
bootParam.AppFlag = APP_START_FLAG;
dev_boot_write_param(bootParam);
}
/*检测升级异常(UpdateFlag仍为APP_UPDATE_FLAG 或 非首次且AppFlag无效)时擦外置Flash*/
if(bootParam.UpdateFlag == APP_UPDATE_FLAG
|| (bootParam.FirstRunFlag == 0 && bootParam.AppFlag != APP_START_FLAG)) {
rt_kprintf("Boot upgrade detected, erasing external Flash...\r\n");
@@ -85,8 +96,21 @@ void GateWayInit(void)
dev_boot_write_param(bootParam);
}
bool NeedSavePara = false;
int ret = ReadPara((uint8_t *)&GateWay.ConfigPara, sizeof(GWConfigPara_t));
if(GateWay.ConfigPara.SavFlag != LOG_SAV_FLAG) {
/*ReadPara返回-1表示CRC校验失败(参数镜像损坏)。原实现只检查SavFlag:
SavFlag位于结构体最前部,"部分损坏"的镜像(前部有效后部乱码)恰好保留合法
SavFlag,坏数据会被整体装入RAM——正是culs显示后面设备参数乱码的根源。
CRC失败必须回退默认参数,让传感器走重新注册流程*/
if(ret != 0) {
rt_kprintf("Para CRC Error, reset to default!\r\n");
}
if(ret != 0 || GateWay.ConfigPara.SavFlag != LOG_SAV_FLAG) {
/*CRC正常但SavFlag无效:flash参数为空(全新设备/曾执行res gxjt/Boot整片擦除后)
打印区分于CRC错误路径,便于现场排障判断走的是默认参数还是正常恢复*/
if(ret == 0)
rt_kprintf("Para empty, load default!\r\n");
NeedSavePara = true;
memset((uint8_t *)&GateWay.ConfigPara, 0x00, sizeof(GWConfigPara_t));
GateWay.ConfigPara.SavFlag = LOG_SAV_FLAG;
/*通道/服务器等默认参数全部从Boot参数区(BootPara_t)获取, 由Boot直接配置*/
@@ -111,9 +135,7 @@ void GateWayInit(void)
GateWay.ConfigPara.Rs485Ch2.UpgradeEnable = false;
GateWay.ConfigPara.Rs485Ch2.CommUnitEnable = false;
GateWay.ConfigPara.Rs485Ch2.Power = false;
GateWay.ConfigPara.Rs485Ch1.RS485Send = RS485Ch1UartSend;
GateWay.ConfigPara.Rs485Ch2.RS485Send = RS485Ch2UartSend;
GateWay.ConfigPara.Lora.OnOff = true;
GateWay.ConfigPara.Lora.ucChannel = 8;
GateWay.ConfigPara.Lora.ucPower = 20;
@@ -125,6 +147,36 @@ void GateWayInit(void)
GateWay.ConfigPara.OutageFlag = false;
}
else {
/*防御被"洗白"的脏数据(镜像部分损坏但CRC合法):对通讯单元表做语义校验,
清除无效条目,避免乱码条目占用注册槽位导致新设备注册不上*/
for(int i = 0; i < COMMUNIT_NUM_MAX; i++) {
CommUnitPara_t *CU = &GateWay.ConfigPara.CommUnitArray[i];
uint8_t RegByte;
memcpy(&RegByte, &CU->RegFlag, 1); //绕过编译器对bool取值0/1的假定,读取原始字节
bool EntryValid = (RegByte == 0 || RegByte == 1);
if(EntryValid && RegByte == 1) {
bool MacAllFF = true, MacAll00 = true;
for(int j = 0; j < 6; j++) {
if(CU->Mac[0][j] != 0xFF) MacAllFF = false;
if(CU->Mac[0][j] != 0x00) MacAll00 = false;
}
if(CU->SensorN == 0 || CU->SensorN > SENSOR_NUM_MAX || MacAllFF || MacAll00)
EntryValid = false;
}
if(!EntryValid) {
memset(CU, 0x00, sizeof(CommUnitPara_t));
NeedSavePara = true;
}
}
if(NeedSavePara)
rt_kprintf("CommUnit table dirty, cleaned!\r\n");
}
/*函数指针不允许从Flash恢复:正常加载路径下RS485Send来自旧固件写入Flash的
代码地址,固件升级后代码布局变化会使该指针失效(野指针)。每次开机统一重设*/
GateWay.ConfigPara.Rs485Ch1.RS485Send = RS485Ch1UartSend;
GateWay.ConfigPara.Rs485Ch2.RS485Send = RS485Ch2UartSend;
/*网关MAC编码从Boot参数区(BootPara_t.GwMac)获取, 由Boot从固定地址0x4000同步
(协议规定: Mac[0]=VerInfo, Mac[1:3]=PrjNum, Mac[4:5]=GwNum)*/
@@ -212,7 +264,11 @@ void GateWayInit(void)
//WritePara((uint8_t *)&GateWay.ConfigPara, sizeof(GWConfigPara_t));
}
WritePara((uint8_t *)&GateWay.ConfigPara, sizeof(GWConfigPara_t));
/*原实现开机无条件回写参数:一方面无谓增加一次擦写磨损;另一方面若读出的
是坏数据,回写会重新计算CRC把脏数据"洗白"成永久合法,无法自愈。
仅在参数被重置或清洗过时才落盘*/
if(NeedSavePara)
WritePara((uint8_t *)&GateWay.ConfigPara, sizeof(GWConfigPara_t));
LogInit();
+52 -10
View File
@@ -9,6 +9,28 @@
uint8_t flashtestbuff[SPIFLASH_SIZE];
#endif
/*SPI Flash操作互斥保护:ReadPara/WritePara/AddLog/SavLogNum会被多个线程
(Lora/RS485/Cat1/Eth/调试/主线程)并发调用,无保护时SPI事务交错会写坏参数镜像
(传感器注册风暴与网关向服务器注册并发写Flash即触发此问题)。
RT-Thread互斥量支持同线程递归持有(AddLog内调SavLogNum不会死锁)*/
static rt_mutex_t FlashMutex = RT_NULL;
static void FlashLock(void)
{
if(FlashMutex == RT_NULL) {
rt_enter_critical();
if(FlashMutex == RT_NULL)
FlashMutex = rt_mutex_create("flsmux", RT_IPC_FLAG_PRIO);
rt_exit_critical();
}
if(FlashMutex != RT_NULL)
rt_mutex_take(FlashMutex, RT_WAITING_FOREVER);
}
static void FlashUnlock(void)
{
if(FlashMutex != RT_NULL)
rt_mutex_release(FlashMutex);
}
static uint8_t SpiFlashReadByte(void)
{
return Spi1SendReceive(0);
@@ -107,7 +129,7 @@ void SpiFlashWriteAnyLengthData(uint8_t* wData, uint32_t wAddr, uint16_t wLen)
uint16_t FirstPageRemainSpace = SPIFLASH_PAGESIZE - (WriteAddr % SPIFLASH_PAGESIZE);
// //判断是否跨扇区,跨扇区需要先擦除下一扇区
CheckDelSecter(wAddr, wLen);
// CheckDelSecter(wAddr, wLen);
if(wLen < FirstPageRemainSpace) {
FirstPageWriteSize = wLen; //计算首页写入长度
@@ -260,7 +282,8 @@ void LogInit(void)
void SavLogNum(uint32_t LogNum)
{
LogNumSav_t CLogSav;
FlashLock();
if(LogNum == 0) {
LogNumIdx = 0;
SetLogNum(0);
@@ -268,21 +291,21 @@ void SavLogNum(uint32_t LogNum)
SetFirstLogStartAddr(LOG_SAV_START_SECTOR * SPIFLASH_SECTORSIZE);
SetLastLogEndAddr(LOG_SAV_START_SECTOR * SPIFLASH_SECTORSIZE);
SpiFlashEraseSector(LOG_INFO_START_SECTOR * SPIFLASH_SECTORSIZE);
FlashUnlock();
return;
}
SetLogNum(LogNum);
CLogSav.Flag = LOG_INFO_SAV_FLAG;
CLogSav.LogCnt = LogNum;
CLogSav.FirstLogStartAddr = ReadFirstLogStartAddr();
CLogSav.LastLogEndAddr = ReadLastLogEndAddr();
if(LogNumIdx == 256) {
SpiFlashEraseSector(LOG_INFO_START_SECTOR * SPIFLASH_SECTORSIZE);
LogNumIdx = 0;
}
SpiFlashWriteData((uint8_t *)&CLogSav, LogNumIdx * sizeof(LogNumSav_t) + LOG_INFO_START_SECTOR * SPIFLASH_SECTORSIZE, sizeof(LogNumSav_t));
LogNumIdx++;
FlashUnlock();
}
/*****************************************************************************************
@@ -363,6 +386,7 @@ void AddLog(uint8_t *wData, uint32_t wLen)
// uint32_t SectorN;
LogHeader LogH;
FlashLock();
LogH = (LogHeader)LogBuffTemp;
LogNum = ReadLogNum();
@@ -390,6 +414,7 @@ void AddLog(uint8_t *wData, uint32_t wLen)
SpiFlashWriteAnyLengthData(LogBuffTemp, LLEndAddr, LogSize);
SavLogNum(LogNum);
FlashUnlock();
//Debug_Printf("Add LOG: %d,endaddr = %d\r\n", LogNum, ReadLastLogEndAddr());
}
@@ -468,8 +493,10 @@ int ReadLog(LogHeader Header, uint8_t **rData, uint32_t LogIdx)
* 返 回 值: 无
*****************************************************************************************/
int ReadPara(uint8_t *Para, uint32_t ParaLen)
{
SpiFlashReadAnyLengthData(Para, GATEWEY_PARA_SAV_ADDR, ParaLen); //读取片尾数据
{
FlashLock();
SpiFlashReadAnyLengthData(Para, GATEWEY_PARA_SAV_ADDR, ParaLen);
FlashUnlock();
uint16_t Check = CRC_Modbus(CRC16_BASE, Para, ParaLen - 2);
GWConfigPara CfgPara = (GWConfigPara)Para;
if(CfgPara->crc16 != Check)
@@ -485,14 +512,29 @@ int ReadPara(uint8_t *Para, uint32_t ParaLen)
* 返 回 值: 无
*****************************************************************************************/
int WritePara(uint8_t *Para, uint32_t ParaLen)
{
SpiFlashEraseSector(GATEWEY_PARA_SAV_ADDR);
{
FlashLock();
/*参数区只能占用扇区0~1(8KB),扇区2为日志信息区(LogNumArray)。
参数越界会擦除日志簿记数据,此处直接拒绝写入*/
if(GATEWEY_PARA_SAV_ADDR + ParaLen > LOG_INFO_START_SECTOR * SPIFLASH_SECTORSIZE) {
FlashUnlock();
return -1;
}
uint16_t Check = CRC_Modbus(CRC16_BASE, Para, ParaLen - 2);
GWConfigPara CfgPara = (GWConfigPara)Para;
CfgPara->crc16 = Check;
SpiFlashWriteAnyLengthData(Para, GATEWEY_PARA_SAV_ADDR, ParaLen); //读取片尾数据
/*擦除参数区覆盖的所有扇区(当前GWConfigPara_t约7.5KB,跨扇区0和1)。
原实现只擦扇区0,跨扇区部分依赖SpiFlashWriteAnyLengthData内部的
CheckDelSecter擦除,该函数会顺带改写日志系统的LastLogEndAddr等簿记
变量并可能误擦日志信息区,现已移除该调用,扇区擦除由本函数自行完成*/
uint32_t SectorCnt = (GATEWEY_PARA_SAV_ADDR + ParaLen + SPIFLASH_SECTORSIZE - 1) / SPIFLASH_SECTORSIZE;
for(uint32_t i = 0; i < SectorCnt; i++) {
SpiFlashEraseSector((GATEWEY_PARA_SAV_SECTOR + i) * SPIFLASH_SECTORSIZE);
}
SpiFlashWriteAnyLengthData(Para, GATEWEY_PARA_SAV_ADDR, ParaLen);
FlashUnlock();
return 0;
}
/* USER CODE END */