ginlong_upgrade.c
0xC4)与 DSP 写固件帧(0xC6)的组帧4.3 应答接收解析(inv_recv_upgrade_data,行 83-191)4.4 ARM 升级状态机(ginlong_upgrade_arm_handle,行 367-589)4.5 DSP 升级状态机(ginlong_upgrade_dsp_handle,行 598-883)4.6 发送流控(ginlong_send_upgrade_data,行 58-75)4.7 调度时序与 uCOS-II 配合4.8 关键全局变量的读写5. 对外接口6. 依赖关系7. 备注与风险点严重中等轻微源文件路径:
midware/inverter/ginlong/ginlong_upgrade.c所属模块:midware / inverter / ginlong(锦浪逆变器固件升级) 代码行数:884
1. 文件职责
本文件实现锦浪(Ginlong/Solis)储能逆变器的在线固件升级流程,覆盖三个升级对象:ARM 控制单元(UPDT_INV3)、DSP 从机(UPDT_INV2)、DSP 主机(UPDT_INV1)。升级数据由云端经 net/upd_download.c 下载到 SDRAM 的两个固件区(g_u8_fw_sdram1 / g_u8_fw_sdram2)并解析为二进制镜像,本文件负责把镜像按私有协议分帧,经 USART2 写入逆变器并逐包确认。它包含两套独立的状态机(ginlong_upgrade_arm_handle() 与 ginlong_upgrade_dsp_handle()),以及组帧/校验、应答接收解析和进度查询接口。
驱动方是 midware/inverter/ginlong/ginlong_comm_usart.c:当检测到固件下载完成或服务器置位升级请求时,常规 Modbus 通信被挂起,改为在每次任务轮询里推进一次升级状态机(ginlong_comm_usart.c:4875、4885)。进度百分比由 inverter.c:3816-3999 的厂商分发函数读取并上报给 HMI/云端。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
GINLONG_UPGRADE_DEBUG | 宏 | 调试打印总开关,默认 0(ginlong_upgrade.h:15) |
DAT_PCK_UPGRADE_INV | 宏 | 300,连续升级包之间的最小间隔毫秒数(流控)(ginlong_upgrade.h:16) |
INV_RECV_UPGRADE_BUFFER_SIZE | 宏 | 200,应答接收缓冲区字节数(ginlong_upgrade.h:17) |
SEND_UPGRADE_DATA_SIZE | 宏 | 1024,每包固件负载字节数(ginlong_upgrade.h:18) |
GINLONG_UPGRADE_OBJECT_E | 枚举 | 升级对象:GINLONG_NONE / GINLONG_MASTER / GINLONG_SLAVE / GINLONG_ARM(ginlong_upgrade.h:20-26) |
GINLONG_UPGRADE_ARM_STATE_E | 枚举 | ARM 升级状态机 6 态(ginlong_upgrade.h:46-55) |
GINLONG_UPGRADE_DSP_STATE_E | 枚举 | DSP 升级状态机 8 态(ginlong_upgrade.h:57-68) |
GINLONG_UPGRADE_TABLE_T | 结构体 | 升级过程表(ginlong_upgrade.h:71-81),静态实例 s_t_upgrade_tab |
s_t_upgrade_tab | static 全局变量 | 唯一的升级过程表实例,本文件第 24 行定义,生命周期贯穿整个升级 |
g_u8_receive_upgrade_buf[200] | 全局数组 | 升级应答接收缓冲区,本文件第 25 行定义(非 static,但头文件未 extern) |
g_u8_jump_boot_command[25] | static 数组 | ARM 跳转 BOOT 指令(命令字 0xC3),第 28-29 行 |
g_u8_query_erase_instruction[16] | static 数组 | ARM 查询 Flash 擦除状态指令(0xC4),第 31-32 行 |
g_u8_off_grid_command[55] | static 数组 | 离网指令(命令字 0x03,长度 0xDE),第 35-38 行 |
g_u8_dsp_jump_boot_command[15] | static 数组 | DSP 跳转 BOOT 指令(0xC5),第 40-41 行 |
g_u8_query_dsp_erase_flash_instruction[15] | static 数组 | 查询 DSP 擦写状态指令(0xC7),第 43-44 行 |
s_u8_query_dsp_erase_flash[27] | static 数组 | 27 字节变体查询指令(前 15 字节与上者相同,紧跟 0xA7 后再拼接一帧),第 46-48 行 |
g_u8_exit_dsp_upgrade[15] | static 数组 | 退出 DSP 升级指令(0xC5,数据域全 0),第 51-52 行 |
g_u8_fw_sdram1 / g_u8_fw_sdram2 | 全局数组 | SDRAM 固件缓存区,来自 net/server.h:258-259 |
ENUpdateType / UPDT_INV1 / UPDT_INV2 / UPDT_INV3 | 枚举/枚举成员 | 升级任务类型:1=DSP 主机、2=DSP 从机、3=ARM(net/server.h:190-192) |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
ginlong_send_upgrade_data | 全局 | 带 300 ms 流控的 USART2 发送封装:不足间隔先延时补齐,然后清空接收缓冲并发送 |
inv_recv_upgrade_data | 全局 | 升级应答接收与解析:轮询等待 → 按协议剥头/校验长度 → 逐字节读入缓冲区 → 校验和比对,返回有效长度 |
fill_arm_upgrade_data_accl_code | 全局 | 填充 ARM 升级帧(0xC4)的负载与累加校验码到 u8_upgrade_buf |
fill_dsp_upgrade_data_accl_code | 全局 | 填充 DSP 升级帧(0xC6)的帧头、长度、行号、负载与累加校验码 |
ginlong_read_upgrade_flag_master | 全局 | 返回当前升级对象是否为逆变器主机(DSP 主机) |
ginlong_read_upgrade_percent_master | 全局 | 返回主机升级百分比(offset*100/file_len),非主机时返回 0 |
ginlong_read_upgrade_flag_slave | 全局 | 返回当前升级对象是否为逆变器从机(DSP 从机) |
ginlong_read_upgrade_percent_slave | 全局 | 返回从机升级百分比,非从机时返回 0 |
ginlong_read_upgrade_flag_arm | 全局 | 返回当前升级对象是否为 ARM 单元 |
ginlong_read_upgrade_percent_arm | 全局 | 返回 ARM 升级百分比,非 ARM 时返回 0 |
ginlong_upgrade_arm_handle | 全局 | ARM 升级状态机主函数(6 态),单次调用推进一步 |
ginlong_upgrade_dsp_handle | 全局 | DSP 升级状态机主函数(8 态),单次调用推进一步 |
共 12 个函数,其中只有前 4 个被声明在头文件里(ginlong_upgrade.h:88-91 声明了 ginlong_upgrade_arm_handle、ginlong_upgrade_dsp_handle、ginlong_upgrade_handle 与 ginlong_send_upgrade_data);inv_recv_upgrade_data、fill_*_accl_code 是全局函数但无任何声明,属于隐式外部链接。
4. 核心逻辑解析
4.1 私有升级协议帧格式
协议与 Modbus 无关,是锦浪专用的"7E 开头 + 命令字 + 长度 + 负载 + 累加和"格式。
帧结构通式:
7E | 01 | CMD | [LEN_L [LEN_H]] | DATA[...] | CHECK
| | | 长度域 数据域 1 字节累加校验
固定头 固定值为 0x01
- 起始码固定
0x7E。 - 第 1 字节固定
0x01(设备地址)。 - 第 2 字节为命令字:
0x03离网、0xC3ARM 跳 BOOT、0xC4ARM 擦写/写固件、0xC5DSP 跳 BOOT/退出、0xC6DSP 写固件、0xC7查询 DSP 擦写状态。 - 长度域字节数依命令字而定:接收解析器把
0xC4视为 2 字节长度(ginlong_upgrade.c:131),其他命令字视为 1 字节。 CHECK= 从第 1 字节(跳过0x7E)到负载末字节的算术累加和取低 8 位:CHECK = (sum(buf[1..N-2])) & 0xFF。
已用静态数组验证的自洽性(示例):
- 离网指令
7E 01 03 00 DE ...共 55 字节,末字节0xE2 = (0x01+0x03+0x00+0xDE+0x00×50) & 0xFF。 - ARM 跳 BOOT
7E 01 C3 14 FF 00 00 04 ...共 25 字节,末字节0xDF。 - ARM 擦除查询
7E 01 C4 0A 00 ... 01 01 01 01 D3共 16 字节,末字节0xD3 = (0x01+0xC4+0x0A+0x00+负载) & 0xFF。
4.2 ARM 写固件帧(0xC4)与 DSP 写固件帧(0xC6)的组帧
ARM 帧(ginlong_upgrade.c:502-533 + fill_arm_upgrade_data_accl_code):
偏移 0 1 2 3 4 5 6 7 8 9 ... 9+len
值 7E 01 C4 LEN_L LEN_H LINE_L LINE_H FLAG 00 payload... CHECK
- 中间包:
LEN_L=0x04、LEN_H=0x04→buf[3..4]作为 16 位小端值 =0x0404 = 1028,l_u16_data_len = 1028 - 4 = 1024(ginlong_upgrade.c:207)。 - 末包:
LEN = (file_len - offset + 4),即剩余字节数 + 4,同样减 4 得到真实负载长度。 FLAG:中间包0x00,末包0x01。末包标记决定状态机何时收尾(ginlong_upgrade.c:549-553)。- 校验和
CHECK=sum(buf[1..8]) + sum(payload)的低 8 位,写入buf[9 + data_len](ginlong_upgrade.c:220)。 - 发送长度
l_u16_send_data_len = ((buf[4]<<8)|buf[3]) + 6,即data_len + 10(ginlong_upgrade.c:531)。
DSP 帧(ginlong_upgrade.c:770-782 + fill_dsp_upgrade_data_accl_code):
偏移 0 1 2 3 4 5 6 7 ... 7+len
值 7E 01 C6 LEN_L LEN_H LINE_L LINE_H payload... CHECK
LEN = data_len + 2(ginlong_upgrade.c:247-248),小端两字节。- 校验和 =
sum(buf[1..6]) + sum(payload),写入buf[7 + data_len](ginlong_upgrade.c:263)。 - 发送长度 =
LEN + 6 = data_len + 8(ginlong_upgrade.c:780)。
两帧的分包地址计算方式一致:addr = u8_upgrade_addr + (u16_send_line_num - 1) * SEND_UPGRADE_DATA_SIZE(ginlong_upgrade.c:205、256)。
4.3 应答接收解析(inv_recv_upgrade_data,行 83-191)
这是全文件最关键的底层函数,执行五步:
第 1 步 — 轮询等待(:98-107)
do {
OSTimeDlyHMSM(0, 0, 0, 50); // 每次睡 50 ms
l_u16_len = usart2_get_data_len();
l_u32_timeout += 50;
if (16 == l_u16_len || 15 == l_u16_len) break;
} while (l_u32_timeout < timeout);
只有收到 15 或 16 字节才提前退出;其他长度一律等到 timeout 耗尽。这与协议中 15 字节(0xC5/0xC6/0xC7 应答)和 16 字节(0xC4 应答)两种固定应答长度对应。
第 2 步 — 长度钳位与头部解析(:112-153)
l_u16_len超过 200 时截断为 200(INV_RECV_UPGRADE_BUFFER_SIZE)。- 循环读前 4 字节:第 0 字节必须为
0x7E否则直接*len = 0返回;第 2 字节若为0xC4置l_u8_len_tick = 1;第 3 字节存入l_u16_recv_len作为低字节。 - 若
l_u8_len_tick == 1,再读一字节作为长度高字节。
第 3 步 — 按长度读负载(:156-161):读 l_u16_recv_len 个字节。
第 4 步 — 读校验字节并比对(:164-180):累加 buf[1] 到负载末字节(不含 0x7E),取低 8 位与末字节比较;相等则 *len = l_u16_len,否则 *len = 0。
第 5 步 — 无数据分支(:182-190):l_u16_len == 0 时走 else if(buf[l_u16_len-1] == 0x14 || 0xD0 == ...),命中则 *len = 0,否则 *len = 0——该分支无论走哪个结果都一样(见第 7 节风险点)。
4.4 ARM 升级状态机(ginlong_upgrade_arm_handle,行 367-589)
状态变量:static GINLONG_UPGRADE_ARM_STATE_E s_u8_upgrade_sate = UPGRADE_ARM_IDLE,三个静态错误计数器 s_u8_upgrade_send_err_cnt、s_u8_erase_failed_cnt、s_u8_upgrade_data_err_cnt。
UPDT_INV3==TRUE ┌──────────────────┐
UPGRADE_ARM_IDLE ──────────────────────► ENTER_ARM_UPGRADE_BOOT ──┐
│ UPDT_INV3==FALSE │ 收到 C3,50/70
▼ ▼
UPGRADE_ARM_FINISH ◄──── err>20 ──────────────────── WAIT_ARM_ERASE_RESPONSE
▲ │ 收到 C4 + buf[9]==01
│ err>30 ▼
└────────────────────── WRITE_ARM_FIRMWARE_CONTENT ◄───────┘
│ 末包(FLAG=0x01)写入成功
▼
UPGRADE_ARM_FINISH
| 状态 | 动作 | 迁移条件 | ||
|---|---|---|---|---|
UPGRADE_ARM_IDLE(:379) | 检查 Get_Status_Is_Updated_From_Server(UPDT_INV3);成立则设 u8_upgrade_object=GINLONG_ARM、u8_upgrade_addr=g_u8_fw_sdram1、u32_upgrade_file_len=get_length_file_updating(UPDT_INV3) | 有升级任务 → ENTER_ARM_UPGRADE_BOOT;无 → UPGRADE_ARM_FINISH | ||
ENTER_ARM_UPGRADE_BOOT(:425) | 若 send_err_cnt == 10 先 usart2_init(57600) 切波特率;发 25 字节 0xC3 跳 BOOT;收 300 ms | `buf[2]==0xC3 && (buf[3]==0x50 \ | \ | buf[3]==0x70) → WAIT_ARM_ERASE_RESPONSE(并再次 usart2_init(57600)、清计数器);否则 err_cnt++,>20 → UPGRADE_ARM_FINISH` |
WAIT_ARM_ERASE_RESPONSE(:461) | 先 OSTimeDlyHMSM(0,0,1,0) 等 1 s;发 16 字节 0xC4 擦除查询;收 2500 ms | recv_num != 0 && buf[2]==0xC4 && buf[9]==0x01 → WRITE_ARM_FIRMWARE_CONTENT(erase_failed_cnt=0);否则 erase_failed_cnt++,>30 → UPGRADE_ARM_FINISH | ||
WRITE_ARM_FIRMWARE_CONTENT(:493) | 若 send_line_num == upgrade_line_num(上包已确认)则 send_line_num++;按末包/中间包分别填 buf[3..8];fill_arm_upgrade_data_accl_code();发送;收 300 ms | 应答 buf[2]==0xC4 && buf[3]==0x0A && buf[5]==发送行号低字节 && buf[9]==0x01:offset += LEN-4,upgrade_line_num++,err=0;若发送帧 buf[7]==0x01(末包)→ UPGRADE_ARM_FINISH。任一步不匹配则 err++,>10 → UPGRADE_ARM_FINISH | ||
SEND_ARM_OFF_GRID_COMMAND(:394) | 内层 for(i=0;i<3;i++) 发送 55 字节离网指令,收 300 ms,判断 buf[1]==0x01 && buf[2]==0x03 | 命中 → ENTER_ARM_UPGRADE_BOOT;err_cnt > 10 → ENTER_ARM_UPGRADE_BOOT(无实现入口,死状态) | ||
UPGRADE_ARM_FINISH(:577) | 状态复位为 IDLE,三个计数器清零,memset(&s_t_upgrade_tab,0,sizeof),usart2_init(9600),usart2_clear_buffer(),Set_Status_Is_Updated_From_Server(UPDT_INV3, FALSE) | 无条件回到 UPGRADE_ARM_IDLE |
关键细节:
- 波特率三段式:常规通信 9600 → 进入 BOOT 后 57600 → 收尾回 9600。
if(10 == err_cnt) usart2_init(57600)表示"先在 9600 下尝试 10 次,失败后再切 57600 重试",是对部分逆变器 BOOT 波特率识别顺序的妥协。 - 行号驱动的丢包恢复:
WRITE_ARM_FIRMWARE_CONTENT首行的if(send_line_num == upgrade_line_num) send_line_num++保证只有在上一包被确认后行号才前进。因此重发时行号不变,地址(line-1)*1024也不变,天然支持重传。 - 末包标记
buf[7] == 0x01是 ARM 流程唯一的正常结束出口。
4.5 DSP 升级状态机(ginlong_upgrade_dsp_handle,行 598-883)
DSP 升级比 ARM 多两个阶段:先整体上传"API 数据区",再擦除、再逐包写固件,最后显式退出 BOOT。
UPGRADE_DSP_IDLE ──(UPDT_INV1 && UPDT_INV2)──► SEND_OFF_GRID_COMMAND
▲ │ 收到 01/03
│ ▼
UPGRADE_DSP_FINISH ◄────────────── ENTER_DSP_UPGRADE_BOOT
▲ ▲ │ 收到 C5 / buf[4]==0xFF
│ │ err>20 ▼
│ └──── EXIT_DSP_UPGRADE ◄── WRITE_DSP_FIRMWARE_DATA
│ │ 收到 C7/03 → 发 C5 退出 → 收到 C5/00 ▲
│ └────────────────────► UPGRADE_DSP_FINISH │ 擦除确认 C7/02
│ │
│ WAIT_DSP_ERASE_RESPONSE ────────┘
│ │ err_cnt==10 回退重传 API 数据
└──────────────────────────┴──────► WRITE_DSP_API_DATA
| 状态 | 动作 | 迁移条件 |
|---|---|---|
UPGRADE_DSP_IDLE(:612) | 需 Get_Status_Is_Updated_From_Server(UPDT_INV1) && ...(UPDT_INV2) 同时成立;设对象 GINLONG_SLAVE、地址 g_u8_fw_sdram2、长度 get_length_file_updating(UPDT_INV2) | 成立 → SEND_OFF_GRID_COMMAND;否则 → UPGRADE_DSP_FINISH(注意:DSP 升级要求 ARM 与从机任务同时挂起) |
SEND_OFF_GRID_COMMAND(:634) | 内层循环 3 次发送 55 字节离网指令,收 300 ms,判 buf[1]==0x01 && buf[2]==0x03 | 命中 → ENTER_DSP_UPGRADE_BOOT;err_cnt > 10 → ENTER_DSP_UPGRADE_BOOT |
ENTER_DSP_UPGRADE_BOOT(:666) | OSTimeDlyHMSM(0,0,1,0) 1 s;发 15 字节 0xC5 跳 BOOT;收 500 ms | buf[2]==0xC5 && buf[4]==0xFF → WRITE_DSP_API_DATA,并把对象重置为 SLAVE/sdram2/UPDT_INV2、offset=0、行号=0、memset(upgrade_buf)、usart2_init(19200);否则 err_cnt++,>10 → UPGRADE_DSP_FINISH |
WRITE_DSP_API_DATA(:706) | 一次性把整段数据 ginlong_send_upgrade_data(u8_upgrade_addr, u32_upgrade_file_len) 发出(不过 fill_* 组帧,原始字节流);然后 OSTimeDlyHMSM(0,0,5,0) 等 5 s | 无条件 → WAIT_DSP_ERASE_RESPONSE |
WAIT_DSP_ERASE_RESPONSE(:722) | erase_failed_cnt == 0 或 10 时发 27 字节变体查询,否则发 15 字节标准查询;收 1500 ms | buf[2]==0xC7 && buf[4]==0x02 → WRITE_DSP_FIRMWARE_DATA,对象切到 MASTER/sdram1/UPDT_INV1、偏移与行号归零、memset(upgrade_buf)、erase_failed_cnt=0、延时 1 s;否则延时 1 s 后 erase_failed_cnt++,==10 → 退回 WRITE_DSP_API_DATA(重传 API 数据),>20 → UPGRADE_DSP_FINISH |
WRITE_DSP_FIRMWARE_DATA(:770) | 行号推进同 ARM;fill_dsp_upgrade_data_accl_code();发送;收 300 ms | buf[2]==0xC6 && buf[3]==0x0A && buf[6]==发送行号低字节 && buf[5]==0x00:offset += LEN-2,upgrade_line_num++,err=0;offset >= file_len → EXIT_DSP_UPGRADE。否则 err++,>10 → UPGRADE_DSP_FINISH |
EXIT_DSP_UPGRADE(:830) | 延时 1 s;发 15 字节 0xC7 查询(取擦写状态);收 300 ms | buf[2]==0xC7 && buf[4]==0x03 → 发 15 字节 0xC5 退出指令,延时 200 ms,再收 300 ms;若 buf[2]==0xC5 && buf[4]==0x00 → UPGRADE_DSP_FINISH。随后 erase_failed_cnt++,>20 → UPGRADE_DSP_FINISH |
UPGRADE_DSP_FINISH(:869) | 复位状态与计数器、memset(&s_t_upgrade_tab,0,...)、usart2_init(9600)、usart2_clear_buffer()、OSTimeDlyHMSM(0, 0, 40, 0) 阻塞 40 s,最后 Set_Status_Is_Updated_From_Server(UPDT_INV1/FALSE) 与 (UPDT_INV2/FALSE) | 无条件回到 UPGRADE_DSP_IDLE |
DSP 状态机的三个独特设计:
WRITE_DSP_API_DATA整包直发:不经u8_upgrade_buf,直接把 SDRAM 中的镜像原样发送(ginlong_upgrade.c:716),这是"API/参数段"的专用上传方式,与后续逐包写固件格式完全不同。- 擦除失败的回退重传:
erase_failed_cnt == 10触发从WAIT_DSP_ERASE_RESPONSE退回WRITE_DSP_API_DATA(:758-761),把"擦除不成功"归因为"API 数据未正确落地",是一个闭环纠正机制。 - 变体查询指令补偿:第 0 次和第 10 次擦除查询用 27 字节版本(
s_u8_query_dsp_erase_flash),中间用 15 字节版本(:726-733),推测与 DSP BOOT 状态机对指令长度的识别差异有关。
4.6 发送流控(ginlong_send_upgrade_data,行 58-75)
if (abs(OSTimeGet() - s_u32_send_pack_tick) < DAT_PCK_UPGRADE_INV) // 300
OSTimeDlyHMSM(0, 0, 0, abs(DAT_PCK_UPGRADE_INV - abs(OSTimeGet() - s_u32_send_pack_tick)));
s_u32_send_pack_tick = OSTimeGet();
usart2_clear_buffer();
usart2_send_data(buf, len, 5000);
- 保证两次发送之间至少间隔 300 ms(
DAT_PCK_UPGRADE_INV),且延时量是精确差额(与ginlong_comm_can.c的固定延时不同)。 - 每次发送前
usart2_clear_buffer()清空接收缓冲,避免上一次的残留应答被误当作本次应答。 - 发送超时 5000 ms。
4.7 调度时序与 uCOS-II 配合
- 两个
*_handle()都是单步推进的状态机,内部通过OSTimeDlyHMSM阻塞等待(1 s / 5 s / 40 s / 200 ms),因此必须在可长时间阻塞的任务上下文中调用。 - 实际调用点:
midware/inverter/ginlong/ginlong_comm_usart.c:4875(ginlong_upgrade_dsp_handle)与:4885(ginlong_upgrade_arm_handle),由ginlong_inverter_comm()在is_inverter_firm_download_finished()或Get_Status_Is_Updated_From_Server(UPDT_INV3)成立时进入。 - 期间
g_t_ginlong_info.t_ginlong_comm_info.u8_ems_inv_comm_flag被置FALSE(ginlong_comm_usart.c:4876、4886),这会连带关闭 CAN 实时控制通道(ginlong_comm_can.c:302判断该标志)。 - 本文件不使用任何信号量或消息邮箱,状态与节拍全部靠
static变量和OSTimeGet()自持,属于典型的"任务内私有状态机"实现。
4.8 关键全局变量的读写
| 变量 | 读 | 写 |
|---|---|---|
s_t_upgrade_tab | 全部状态、百分比查询接口、组帧函数 | IDLE 状态初始化对象与长度;组帧函数写 u8_upgrade_buf;WRITE 状态更新 u32_upgrade_file_offset 与 u16_upgrade_line_num;FINISH 状态整体 memset |
g_u8_receive_upgrade_buf | 各状态的应答判断(buf[1]/[2]/[3]/[4]/[5]/[6]/[9]) | inv_recv_upgrade_data() 内部逐字节写入 |
g_u8_fw_sdram1 / g_u8_fw_sdram2 | 组帧时按 (line-1)*1024 偏移读取固件字节(fill_*_accl_code、WRITE_DSP_API_DATA) | 不由本文件写,由 net/upd_download.c 下载填充 |
g_t_inv_info.t_run_info.u8_run_mode | 不经本文件 | 由 ginlong_comm_usart.c:4871/4873/4882 依据 ginlong_read_upgrade_flag_*() 设为 cUpdateMaster(8)/cUpdateSlave(9)/cUpdateArm(10) |
Set_Status_Is_Updated_From_Server(...) | 各 IDLE 状态读 Get_Status_... | 两个 FINISH 状态置 FALSE,清除升级任务标志 |
5. 对外接口
本文件导出的函数与变量:
| 符号 | 声明位置 | 调用方 |
|---|---|---|
ginlong_upgrade_arm_handle | ginlong_upgrade.h:88 | ginlong_comm_usart.c:4885 |
ginlong_upgrade_dsp_handle | ginlong_upgrade.h:89 | ginlong_comm_usart.c:4875 |
ginlong_send_upgrade_data | ginlong_upgrade.h:91 | 本文件内部;头文件已导出但未见他文件调用 |
ginlong_read_upgrade_flag_master | ginlong_upgrade.h:92 | inverter.c:3816、ginlong_comm_usart.c:4870 |
ginlong_read_upgrade_percent_master | ginlong_upgrade.h:93 | inverter.c:3851 |
ginlong_read_upgrade_flag_slave | ginlong_upgrade.h:94 | inverter.c:3886、ginlong_comm_usart.c:4872 |
ginlong_read_upgrade_percent_slave | ginlong_upgrade.h:95 | inverter.c:3921 |
ginlong_read_upgrade_flag_arm | ginlong_upgrade.h:96 | inverter.c:3972 |
ginlong_read_upgrade_percent_arm | ginlong_upgrade.h:97 | inverter.c:3999 |
g_u8_receive_upgrade_buf | 无声明 | 仅本文件使用 |
inv_recv_upgrade_data | 无声明 | 仅本文件使用 |
fill_arm_upgrade_data_accl_code / fill_dsp_upgrade_data_accl_code | 无声明 | 仅本文件使用 |
本文件调用的外部函数/变量:
| 名称 | 来源模块 |
|---|---|
OSTimeGet、OSTimeDlyHMSM | uCOS-II |
usart2_init、usart2_clear_buffer、usart2_send_data、usart2_get_data_len、usart2_read_data_async | hardware/usart2/usart2.h |
Get_Status_Is_Updated_From_Server、Set_Status_Is_Updated_From_Server、get_length_file_updating | net/upd_download.h |
UPDT_INV1、UPDT_INV2、UPDT_INV3、g_u8_fw_sdram1、g_u8_fw_sdram2 | net/server.h |
abs、memset、printf | C 标准库 |
hex_file_parse.h、inv_download.h、sdram.h | 已 include 但本文件中无直接引用 |
6. 依赖关系
依赖(include,ginlong_upgrade.c:13-19): ginlong_upgrade.h、upd_download.h、inv_download.h、hex_file_parse.h、server.h、sdram.h、usart2.h。
被依赖:
midware/inverter/ginlong/ginlong_comm_usart.c:在ginlong_inverter_comm()中按升级类型调用两个*_handle()(:4870-4886)。midware/inverter/inverter.c:inverter_read_upgrade_flag_*()/inverter_read_upgrade_percent_*()系列在GINLONG_TYPE分支下转发到本文件的查询接口(:3816、:3851、:3886、:3921、:3972、:3999),最终供 HMI/云端显示升级进度。
7. 备注与风险点
严重
inv_recv_upgrade_data()中缓冲区越界写(:156-165):l_u16_recv_len完全来自报文内容(buf[3]与buf[4]),未做任何上界检查,就直接用于*(buf + l_u16_index + l_tick)与*(buf + l_u16_recv_len + l_tick)写入。缓冲区仅 200 字节(g_u8_receive_upgrade_buf[200],ginlong_upgrade.c:25)。若 UART 受干扰或逆变器返回异常长度(最大可达0xFFFF),会直接写穿全局区,可能破坏相邻全局变量甚至栈。建议在:146前对l_u16_recv_len做> INV_RECV_UPGRADE_BUFFER_SIZE - 8的钳位并返回 0。
buf[l_u16_len-1]在l_u16_len == 0时下标为 -1(:182):该分支进入条件正是l_u16_len == 0(第 109 行if(l_u16_len)为假),因此buf[-1]是必然发生的越界读(只不过读到的值不影响结果,因为两个分支都写*len = 0)。这是一段逻辑上完全无效且不安全的代码,建议直接删除整个else if,改为*len = 0。
- 长度高字节拼接写错(
:149):l_u16_recv_len = (data << 16) | l_u16_recv_len;注释为"协议数据长度高8位",但data << 16对 16 位目标变量而言低 16 位全为 0,赋值时被截断,高字节被彻底丢弃(应为data << 8)。当前之所以没出问题,是因为所有0xC4应答的长度高字节都是0x00(如7E 01 C4 0A 00 ...),一旦出现长度 > 255 的应答就会解析错误,并可能触发上述第 1 条的越界写。
中等
SEND_ARM_OFF_GRID_COMMAND是死状态:ginlong_upgrade.h:49定义了该状态,:394有完整实现,但UPGRADE_ARM_IDLE直接跳到ENTER_ARM_UPGRADE_BOOT(:386),全代码中没有任何向SEND_ARM_OFF_GRID_COMMAND的迁移。也就是说 ARM 升级从不发送离网指令,逆变器在上位机侧保持并网状态下被直接跳进 BOOT。是否是设计意图需要与逆变器协议方确认。
- 两处错误处理与注释相反:
:418-422与:658-662,注释写"升级失败,退出升级,上传错误信息",实际却迁移到ENTER_ARM_UPGRADE_BOOT/ENTER_DSP_UPGRADE_BOOT(继续尝试),并非退出。属于注释与实现不一致,容易误导维护者。
UPGRADE_DSP_FINISH中阻塞 40 s(:877):OSTimeDlyHMSM(0, 0, 40, 0)在任务上下文里睡 40 秒,期间该任务完全不可响应。若该任务还负责其他通信/看门狗喂狗,存在看门狗复位或通信失联的隐患。
err_cnt > 10判定与内层循环叠加(:396-422):SEND_ARM_OFF_GRID_COMMAND的内层for(i=0;i<3;i++)每次都会err_cnt++,所以外层一次调用最多 +3,>10实际需要约 4 次调用。但ENTER_ARM_UPGRADE_BOOT的if(10 == err_cnt)依赖精确等于 10,而该计数器在 SEND 状态里可能从 9 直接跳到 11,导致usart2_init(57600)被跳过——这是一处真实的时序脆弱点(虽然该状态目前不可达)。
ginlong_upgrade_percent_*可能除零(:293、:325、:355):(u32_upgrade_file_offset*100) / u32_upgrade_file_len,当get_length_file_updating()返回 0(例如服务器下发的文件长度域异常)时会整数除零,在 Cortex-M 上默认触发 HardFault。
u16_upgrade_file_offset*100溢出:u32_upgrade_file_offset最大可接近固件长度(数 MB),乘以 100 后仍在u32范围内(约 4.29e9),但若文件大于 42 MB 则溢出。当前锦浪固件远小于该值,暂不构成实际问题。
轻微
ginlong_upgrade_handle只有声明没有定义:ginlong_upgrade.h:90声明,全工程无定义(grep -rn "ginlong_upgrade_handle"仅命中头文件一行),是遗留死声明。
u8_upgrade_buf[1035]与SEND_UPGRADE_DATA_SIZE无编译期约束:ARM 帧最大占用 1032 字节、DSP 帧 1030 字节,1035 仅有 3 字节余量。若将来把SEND_UPGRADE_DATA_SIZE调大(如 2048),会静默栈/全局溢出。
- 校验和是简单累加和(
:208-220、:252-263):sum & 0xFF无进位旋转,抗随机误码能力很弱。同时s_u16_accl_temp类型为u16,累加 1028 个字节不会溢出(最大 261, 实际 <65536),但如果未来包长超过 257 字节则需注意u16溢出后& 0xFF的结果仍然正确(模 256 语义下累加不敏感),实际无影响。
s_u8_query_dsp_erase_flash[27]结构可疑(:46-48):前 15 字节与g_u8_query_dsp_erase_flash_instruction完全相同,第 15 字节是0xA7(既非0x7E起始码,也不是前一帧的校验0xD2),后接 11 个零再以0xD2结尾。这看起来像是"两帧拼接后人为插入的填充",缺乏任何注释说明其必要性。
inv_recv_upgrade_data的轮询策略低效(:98-107):仅当接收长度为 15 或 16 时才提前退出,否则一律空等到timeout。这意味着所有非 15/16 字节的应答(如 25 字节的0xC3应答)都会引入完整的超时延迟(300~2500 ms),是升级过程总耗时偏长的主要原因之一。
g_u8_receive_upgrade_buf声明为全局但头文件无extern(:25):与ginlong_comm_can.c的g_t_ginlong_comm_can_info同类问题——本意是全局可访问,实际退化为文件私有,属于接口不一致。
- 冗余 include:
hex_file_parse.h、inv_download.h、sdram.h在本文件中未见直接引用(SDRAM相关地址是经server.h的g_u8_fw_sdram1/2间接使用的)。
- 函数注释模板残留:
:268、:280、:300、:312、:330、:342的注释标题全部写为 "kelong_read_upgrade_flag_*" / "kelong_read_upgrade_percent_*",而实现是 ginlong 版本,是从kelong升级模块复制后未清理的痕迹。