EMS 知识库
首页 / knowledge/代码解析/midware/inverter/kelong

kelong_upgrade.c

源文件路径:midware/inverter/kelong/kelong_upgrade.c 所属模块:midware/inverter/kelong(科隆逆变器 CAN 通信协议 — 固件升级部分) 代码行数:656

1. 文件职责

本文件负责把云端下发的科隆(Kelong)逆变器主/从机固件(HEX 文件)通过 CAN1(11 位标准帧,DLC 固定 8)逐批烧写进逆变器,是"云端固件下载 → EMS 转存 SDRAM → CAN 分帧下发 → 逆变器自烧写"链路的最后一环。它把 ASCII 的 Intel HEX 逐条解析成二进制的 长度+地址+类型+数据+校验和 记录,再把每条记录拆成 8 个 5 字节的 CAN 数据帧,每 3 条记录为一批下发后统一等一次确认。

它由 midware/inverter/kelong/kelong_comm.c:1382 驱动——只有当 is_inverter_firm_download_finished() 为真时,kelong_inverter_comm() 才把整条主循环让给 kelong_upgrade_handle(),常规的逆变器轮询/参数下发全部停摆。再往上,kelong_inverter_comm() 由 midware/inverter/inverter.c:1078 的 task_inverter 任务周期调用,所以升级期间是在逆变器任务上下文里同步阻塞执行的。

升级对象有两个:主机(master,固件在 g_u8_fw_sdram1,对应 UPDT_INV1)与从机(slave,固件在 g_u8_fw_sdram2,对应 UPDT_INV2)。顺序固定为先从机后主机,是否升级从机由 g_t_inv_info.t_config_data.u8_inv2_app_upgrade_disable 决定。对外通过 kelong_read_upgrade_flag_master/slave() 与 kelong_read_upgrade_percent_master/slave() 报告状态与进度(被 inverter.c:3807~inverter.c:3913 与 kelong_comm.c:1377 使用)。

2. 关键数据结构与宏

名称类型说明
KELONG_UPGRADE_TABLE_T结构体(kelong_upgrade.h:40-50,无 #pragma pack)本文件的升级上下文:u8_upgrade_addr(固件缓冲区首址,指向 SDRAM)、u8_upgrade_object(升级对象,取 KELONG_UPGRADE_OBJECT_E)、u8_upgrade_buf[3][40](一批 3 条记录的二进制化缓冲,每条 40 字节)、u16_send_line_num(当前批次第一条记录的 1 基行号)、u16_upgrade_line_num(文件总行数,向上取整到 3 的倍数)、u32_upgrade_file_len(文件 ASCII 总长度)、u32_upgrade_file_offset(已发送完的 ASCII 字节偏移,兼作进度分子)
KELONG_UPGRADE_OBJECT_E枚举(kelong_upgrade.h:24-29)KELONG_NONE=0、KELONG_MASTER=1、KELONG_SLAVE=2
KELONG_UPGRADE_SATE_E枚举(kelong_upgrade.h:31-38)升级主状态机:UPGRADE_INIT、QUERY_NUMBER_ROWS、SEND_UPGRADE_FILE、CHECK_UPGRADE_FILE、UPGRADE_END(注意枚举名拼作 SATE 而非 STATE)
HEX_FILE_T结构体(common/hex_file_parse.h:32-41,#pragma pack(1))HEX 单条记录解析结果:u8_data_len/u16_addr/u8_code_type/u16_data_buf[32]/u8_sum_check/u8_ascll_len。本文件在 kelong_upgrade_handle() 内以局部变量形式使用(第 337 行),约 70 字节栈占用
FOOL_PROOFING宏(字符串常量,第 23 行)主机器件的"防错指纹":固定 36 字节的 S 记录前缀 ":020000040008F2\r\n:040000000048088C20\r\n"。sizeof()-1 = 36 即比较长度
EMS_INV_SHAKE_HANDS宏(kelong_upgrade.h:16)CAN ID 0x301,升级握手(协商总行数)
EMS_INV_SEND_UPGRADE_FILE宏(kelong_upgrade.h:17)CAN ID 0x302,传输固件数据
EMS_INV_CHECK_UPGRADE_FILE宏(kelong_upgrade.h:18)CAN ID 0x303,批次校验
EMS_INV_CHECK_UPGRADE_SATTE宏(kelong_upgrade.h:19)CAN ID 0x304,查询升级结果(宏名同样拼作 SATTE)
INV_EMS_ACK宏(kelong_upgrade.h:22)CAN ID 0x401,逆变器统一应答帧
CAN_MSG_TYPEDEF_T结构体(hardware/can/can0.h:20-28,#pragma pack)CAN 报文:frame_id/frame_type/frame_len/frame_data[8]/reading_flag
KELONG_MASTER/KELONG_SLAVE枚举值作为 CAN 数据域里的"升级对象"编码下发(0x01/0x02)
cUpdateMaster/cUpdateSlave宏(midware/inverter/inverter.h:28-29 = 8/9)升级期间写入 g_t_inv_info.t_run_info.u8_run_mode 的运行模式码,由 kelong_comm.c:1377-1380 依据本文件的两个 flag 函数设置
s_t_upgrade_tabstatic KELONG_UPGRADE_TABLE_T本文件唯一的全局状态实例(第 28 行)

3. 函数清单

函数名作用域说明
send_inv_shutdown_commandstatic通过 CAN1 发一帧 0x184(数据 01 00 02 00 00 00 00 00)让逆变器先停机,为升级让路(第 40 行)
kelong_confirm_upgrade_line_numberstatic统计固件文件里 0x3A(:)与 \r\n 的出现次数,二者相等则认为行计数可信,输出总行数(第 69 行)
send_ems_shake_hands_cmdstatic发 0x301 握手帧(携带升级对象与总行数),等 0x401 应答并核对对象与行号,超时 100 ms(第 111 行)
send_ems_send_upgrade_file_cmdstatic把一条 40 字节的二进制记录拆成 8 个 CAN 帧(每帧 5 字节数据)依次发出,从机每帧间隔 20 ms、主机 5 ms(第 181 行)
send_ems_check_upgrade_file_cmdstatic发 0x303 批次校验帧(携带校验行号),等 0x401 应答,超时 100 ms(第 226 行)
send_ems_check_upgrade_state_cmdstatic发 0x304 查询升级结果帧,等 0x401 应答(frame_data[7] == 0 视为成功),超时 100 ms(第 279 行)
kelong_upgrade_handle全局升级主状态机,由逆变器任务周期调用;负责停机、行数协商、HEX 解析与分帧下发、结果确认、结束清理(第 332 行)
kelong_read_upgrade_flag_master全局返回"当前是否在升级主机"(u8_upgrade_object == KELONG_MASTER)(第 605 行)
kelong_read_upgrade_percent_master全局返回主机升级百分比 u32_upgrade_file_offset * 100 / u32_upgrade_file_len;非主机升级时返回 0(第 616 行)
kelong_read_upgrade_flag_slave全局返回"当前是否在升级从机"(第 634 行)
kelong_read_upgrade_percent_slave全局返回从机升级百分比;非从机升级时返回 0(第 645 行)

共 11 个函数:6 个 static(全部为 CAN 帧组装/等待辅助),5 个全局。kelong_upgrade_handle 与 4 个读取函数在 kelong_upgrade.h:54-58 中有声明,6 个 static 函数无声明。

4. 核心逻辑解析

4.1 CAN 帧格式一览

全部为 11 位标准数据帧、DLC = 8,通过 can1_send_standard_pack() / get_can1_standard_pack() 收发。方向约定:EMS → INV 用 0x301~0x304,INV → EMS 统一用 0x401。

① 停机帧(send_inv_shutdown_command,第 40-60 行)

字段值
ID0x184(裸写,无宏)
DATA[0..7]01 00 02 00 00 00 00 00

② 握手 / 行数协商(0x301,第 133-143 行)

偏移内容
DATA[0]0x01 子命令
DATA[1]upgrade_object:KELONG_MASTER(1) 或 KELONG_SLAVE(2)
DATA[2..5]A5 5A AB AB 固定魔术字
DATA[6..7]文件总行数,大端(高字节在前)

应答(0x401)必须满足:DATA[1] == 本方 DATA[1]、DATA[6] == 本方 DATA[6]、DATA[7] == 本方 DATA[7] —— 即对象与行号都要回显一致。

③ 固件数据(0x302,第 186-216 行)

偏移内容
DATA[0..1]本行行号,大端
DATA[2]帧序号 i+1,取值 1~8(一条记录 = 8 帧)
DATA[3..7]该行 40 字节缓冲的第 i*5 ~ i*5+4 字节

即"一行 40 字节 → 8 帧 × 5 字节"。该命令不等逐帧应答,属于盲发。

④ 批次校验(0x303,第 232-242 行)

偏移内容
DATA[0]0x03 子命令
DATA[1]upgrade_object
DATA[2..3]本批最后一行的行号(u16_send_line_num + 2),大端
DATA[4..5]AA BB 固定魔术字
DATA[6..7]00 00

应答判据:DATA[0] == 0x03 且 DATA[7] == 0x00。注意应答里没有核对行号,理论上一个来自其它批次的应答也能满足(实际由发送前的 can1_clear_standard_pack() 兜住)。

⑤ 结果查询(0x304,第 285-295 行)

偏移内容
DATA[0]0x04 子命令
DATA[1]upgrade_object
DATA[2..5]A5 5A AB AB
DATA[6..7]00 00

应答判据同 0x303:DATA[0] == 0x04 且 DATA[7] == 0x00。

三个等待函数(②④⑤)的结构完全一致:先 can1_clear_standard_pack() 清空接收缓冲 → 发帧 → OSTimeGet() 记时 → for(;;) 轮询 get_can1_standard_pack(),收到不匹配的帧就丢弃继续等,没收到就让出 OSTimeDlyHMSM(0,0,0,5);abs(OSTimeGet() - 起始) > 100 则返回 FALSE(100 tick = 100 ms,OS_TICKS_PER_SEC = 1000)。

4.2 升级主状态机

s_u8_upgrade_sate(static,初值 UPGRADE_INIT)是状态变量,配套四个 static 过程量:s_u8_upgrade_master_flag / s_u8_upgrade_slave_flag(是否还需要升级主/从机)、s_u8_send_err_cnt / s_u8_upgrade_recv_cnt / s_u8_upgrade_err_cnt(三级重试计数)、s_u32_upgrade_result_tick(进入结果等待的时刻)。

UPGRADE_INIT
  │ 若 memcmp(g_u8_fw_sdram1, FOOL_PROOFING, 36) != 0  ⇒ UPGRADE_END(文件不是科隆主机固件,直接放弃)
  │ 否则:send_inv_shutdown_command() → OSTimeDlyHMSM(0,0,3,0) 阻塞 3 秒让逆变器关机
  ▼
QUERY_NUMBER_ROWS
  │ 首次进入(两 flag 均为 FALSE):master_flag=TRUE;slave_flag = (u8_inv2_app_upgrade_disable == FALSE)
  │ 若 slave_flag ⇒ object=SLAVE、addr=g_u8_fw_sdram2、len=UPDT_INV2
  │ 否则若 master_flag ⇒ object=MASTER、addr=g_u8_fw_sdram1、len=UPDT_INV1
  │ 否则 ⇒ object=NONE、UPGRADE_END
  │ 调 kelong_confirm_upgrade_line_number() 统计行数
  │   ├ 行数可信 ⇒ 向上取整到 3 的倍数 ⇒ send_ems_shake_hands_cmd()
  │   │     ├ 成功 ⇒ send_line_num = 1 → SEND_UPGRADE_FILE
  │   │     └ 失败 ⇒ recv_cnt++,满 3 次 ⇒ 清对应 flag → UPGRADE_END
  │   └ 行数不可信 ⇒ err_cnt++,满 3 次 ⇒ 清对应 flag → UPGRADE_END
  ▼
SEND_UPGRADE_FILE  ─────────────────────────────┐
  │ memset(upgrade_buf, 0, 120)                  │
  │ for i in 0..2:                               │
  │   解析下一条 HEX 记录(窗口 80)              │
  │   写入 upgrade_buf[i][0]=数据长度             │
  │                    [1..2]=地址 大端           │
  │                    [3]=记录类型               │
  │                    [4..4+len-1]=数据(大端)    │
  │                    [4+len]=校验和             │
  │   · 解析失败 ⇒ parse_err_cnt++,满 3 ⇒ 清 flag 并置 UPGRADE_END → return │
  │   · 到达文件尾 ⇒ 记 tick、转 CHECK_UPGRADE_FILE 并 break(后续仍照发空批,见第 7 节第 7 条)│
  │ for i in 0..2:send_ems_send_upgrade_file_cmd(buf[i], send_line_num+i)
  │   · 任一帧发送失败 ⇒ send_err_cnt++;>3 则清 flag 并置 UPGRADE_END;随后一律 return │
  │ 调 send_ems_check_upgrade_file_cmd(object, send_line_num + 2)
  │   ├ 成功 ⇒ recv_cnt=0;send_line_num += 3;file_offset += recv_file_offset
  │   └ 失败 ⇒ recv_cnt++,满 5 ⇒ 清对应 flag → UPGRADE_END
  └──────────────────────────────────────────────┘(回到本状态继续下一批)
  ▼(文件解析完毕时)
CHECK_UPGRADE_FILE
  │ 反复 send_ems_check_upgrade_state_cmd(object)
  │   ├ 应答 DATA[7]==0 ⇒ 清对应 flag → UPGRADE_END
  │   └ 超时:自进入本状态起 > 1000*60 tick(60 秒)⇒ 清对应 flag → UPGRADE_END
  ▼
UPGRADE_END
  │ 状态先置 QUERY_NUMBER_ROWS,清 err_cnt/recv_cnt,memset(&s_t_upgrade_tab, 0, sizeof(...))
  │ 若主/从 flag 均为 FALSE ⇒ Set_Status_Is_Updated_From_Server(UPDT_INV1, FALSE)
  │                            Set_Status_Is_Updated_From_Server(UPDT_INV2, FALSE)
  │                            状态置 UPGRADE_INIT(升级真正结束)
  │ can1_clear_standard_pack()
  └─► 若还剩另一台待升级,则从 QUERY_NUMBER_ROWS 继续

default 分支把两个 flag 清掉并强制跳到 UPGRADE_END,是兜底复位。

先从机后主机的实现手法:状态变量不额外记录"当前轮到谁",而是用 if (slave_flag == TRUE) ... else if (master_flag == TRUE) ... 的优先级。从机升完(slave_flag = FALSE)后经 UPGRADE_END 回到 QUERY_NUMBER_ROWS,此时 master_flag 仍为 TRUE,于是自然切到主机。

4.3 行数协商与"防错指纹"

kelong_confirm_upgrade_line_number()(第 69-102 行)用两个计数器交叉校验文件完整性:

两者相等才认为"每条记录都以 CRLF 收尾",输出总行数。这一步实际上是在做"HEX 文件是否被完整下载"的粗校验。

紧接着的 send_ems_shake_hands_cmd() 用文件大小做第二层防错(第 117-131 行):

这两个约束合起来是一道"防止把主从机固件互相刷错"的保护:从机小、主机大,且主机文件的头部有固定模板。FOOL_PROOFING 展开是两条 S 记录::020000040008F2\r\n(04 扩展段地址记录,基址高 16 位 = 0x0008)+ :040000000048088C20\r\n(00 数据记录,在偏移 0 写 4 字节 00 48 08 8C,校验和 0x20)。也就是说主机固件的入口代码/向量表是固定的。

行数取整:u16_upgrade_line_num = u16_upgrade_line_num + (3 - u16_upgrade_line_num % 3)(第 393 行)。这是把总行数向上取整到 3 的倍数,好让 3 行一批的下发循环整除。副作用是无论原行数是否已是 3 的倍数都会再补 1~3 行(余数为 0 时补 3)。

4.4 HEX 记录 → 二进制记录 → CAN 分帧

第一层:HEX 记录二进制化(第 455-488 行)

hex_parse(&s_hex_file_data, &addr[file_offset + recv_file_offset], 80) 每次解析 1 条记录,结果按固定布局写进 u8_upgrade_buf[i](40 字节):

偏移内容来源
0数据字节数s_hex_file_data.u8_data_len
1地址高字节u16_addr >> 8
2地址低字节u16_addr & 0xFF
3记录类型u8_code_type(00/01/04/05)
4 .. 4+len-1数据,按 16 位字大端展开循环 u16_data_buf[j],高字节在前
4+len校验和u8_sum_check

这与 hex_file_parse 内部把 ASCII 转成 u16_data_buf[] 的字节序相反(那里是小端存放),本文件再显式翻成大端,相当于把 HEX 文件里的字节顺序原样还原。

第二层:CAN 分帧(send_ems_send_upgrade_file_cmd,第 181-217 行)

40 字节被切成 8 帧 × 5 字节,帧间隔用 OSTimeDlyHMSM 控制:

随后一次 0x303 批次校验(最长 100 ms)。所以一批的最坏耗时:从机 ≈ 580 ms、主机 ≈ 220 ms。按 2000 行推算,从机整包约需 2000/3 × 0.58 ≈ 387 s(6.5 分钟);主机行数更多,按 20 ms 量级估算也在十几分钟以上。这是"不逐帧应答、只按批校验"换来的效率,代价是丢帧只能靠批次校验整批重发。

4.5 偏移与进度的推进

两个游标分工明确:

进度百分比由 kelong_read_upgrade_percent_*() 用 u32_upgrade_file_offset * 100 / u32_upgrade_file_len 计算。注意 u32_upgrade_file_offset * 100 在 32 位平台上对大文件有溢出风险(超过约 42.9 MB 才溢出,实际固件远小于此)。

4.6 超时、重试与 uCOS-II 的配合

环节超时重试上限失败动作
握手 0x301100 ms / 次整体 3 次(s_u8_upgrade_recv_cnt)清对应 flag → UPGRADE_END
行数统计无(同步纯计算)3 次(s_u8_upgrade_err_cnt)清对应 flag → UPGRADE_END
数据帧 0x302 发送无(can1_send_standard_pack 同步返回)s_u8_send_err_cnt > 3(即第 4 次连续失败)<= 3 次时仅 return 下轮整批重发;第 4 次清对应 flag → UPGRADE_END
批次校验 0x303100 ms / 次5 次(s_u8_upgrade_recv_cnt)清对应 flag → UPGRADE_END
结果查询 0x304100 ms / 次,整体 60 s无计数,靠 60 s 兜底清对应 flag → UPGRADE_END
HEX 解析无3 次(l_u8_parse_err_cnt,局部变量,每次调用清零)清对应 flag,置 UPGRADE_END 后 return

与 uCOS-II 的配合方式:

4.7 关键全局变量的读写

变量本文件的读/写触发条件
s_t_upgrade_tab读写全程;UPGRADE_END 时 memset 清零
g_u8_fw_sdram1读UPGRADE_INIT 比对指纹;主机升级时按偏移解析
g_u8_fw_sdram2读从机升级时按偏移解析
g_t_inv_info.t_config_data.u8_inv2_app_upgrade_disable读QUERY_NUMBER_ROWS 首次进入时决定是否升级从机
UPDT_INV1 / UPDT_INV2 状态写 FALSE仅在 UPGRADE_END 且两个 flag 都已为 FALSE(即升级真正全部结束)时
g_t_inv_info.t_run_info.u8_run_mode间接写由 kelong_comm.c:1377-1380 依据本文件的 kelong_read_upgrade_flag_*() 写成 cUpdateMaster(8)/cUpdateSlave(9)
g_t_inv_info.t_run_info.u8_inverter_comm_lost_flag 及运行数据区间接清零由 kelong_comm.c:1372 在进入升级模式首轮做 memset,本文件不直接触碰

5. 对外接口

本文件导出的函数

函数声明位置调用方
kelong_upgrade_handlekelong_upgrade.h:54midware/inverter/kelong/kelong_comm.c:1382(在 kelong_inverter_comm() 内,仅当 is_inverter_firm_download_finished() 为真时调用;再上层是 midware/inverter/inverter.c:1078 的 task_inverter)
kelong_read_upgrade_flag_masterkelong_upgrade.h:55midware/inverter/inverter.c:3807;midware/inverter/kelong/kelong_comm.c:1377(置 u8_run_mode = cUpdateMaster)
kelong_read_upgrade_percent_masterkelong_upgrade.h:56midware/inverter/inverter.c:3843
kelong_read_upgrade_flag_slavekelong_upgrade.h:57midware/inverter/inverter.c:3878;kelong_comm.c:1379(置 u8_run_mode = cUpdateSlave)
kelong_read_upgrade_percent_slavekelong_upgrade.h:58midware/inverter/inverter.c:3913

本文件不导出任何全局变量(s_t_upgrade_tab 为 static)。6 个 send_ems_* / send_inv_* / kelong_confirm_* 辅助函数均为 static,不对外可见。

注:同目录/同族模块 midware/inverter/ginlong/ginlong_upgrade.c(第 268~342 行)与 midware/inverter/set/set_upgrade_s5.c(第 215~279 行)里有同名同注释的函数原型注释,是各厂商升级模块互相复制的痕迹,与本文档描述的科隆实现无调用关系。

本文件调用的外部函数/变量

外部符号来源模块
can1_send_standard_pack / get_can1_standard_pack / can1_clear_standard_packhardware/can/can1.h:29/:25/:27(CAN1 收发与缓冲管理)
CAN_MSG_TYPEDEF_T / CAN_FT_DATAhardware/can/can0.h:28、:24
hex_parse / HEX_FILE_T / PARSE_OKcommon/hex_file_parse.c(PARSE_OK 定义于 hex_file_parse.h:16)
get_length_file_updating / Set_Status_Is_Updated_From_Server / UPDT_INV1 / UPDT_INV2net/upd_download.c / net/upd_download.h
is_inverter_firm_download_finishednet/inv_download.h:34(由调用方 kelong_comm.c 使用,本文件不直接调用)
g_u8_fw_sdram1 / g_u8_fw_sdram2hardware/sdram/sdram.c
g_t_inv_info(t_config_data.u8_inv2_app_upgrade_disable)midware/inverter/inverter.c / inverter.h:464
OSTimeGet / OSTimeDlyHMSMuCOS-II(UCOSII/uCOS-II/Source/os_time.c)
memcmp / memset / strstr / absC 标准库

6. 依赖关系

依赖

kelong_upgrade.c
  ├─ kelong_upgrade.h ─► includes.h
  ├─ upd_download.h   (UPDT_INV1/UPDT_INV2、get_length_file_updating、
  │                     Set_Status_Is_Updated_From_Server)
  ├─ inv_download.h   (is_inverter_firm_download_finished 所在模块,被调用方使用)
  ├─ hex_file_parse.h (hex_parse、HEX_FILE_T、PARSE_OK)
  ├─ server.h         (UPDT_* 枚举、ENUpdateType)
  ├─ inverter.h       (g_t_inv_info、cUpdateMaster/cUpdateSlave)
  ├─ sdram.h          (g_u8_fw_sdram1 / g_u8_fw_sdram2)
  ├─ can0.h / can1.h  (CAN1 标准化收发接口与 CAN_MSG_TYPEDEF_T)
  └─ UCOSII           (OSTimeGet / OSTimeDlyHMSM)

被依赖(谁调用本文件)

与 kelong_comm.c 的资源互斥:两者共用 CAN1 的接收缓冲(can1_clear_standard_pack/get_can1_standard_pack)与发送接口。互斥靠 is_inverter_firm_download_finished() 这个条件分支实现——升级期间常规轮询整体跳过,因此不会出现"升级帧与常规帧同时进接收缓冲"的情形。

7. 备注与风险点

  1. 发送失败时整批重发,已成功的行会重复下发(kelong_upgrade.c:494-517):for (u8 i = 0; i < 3; i++) 循环里,一旦第 i 行的 8 帧发送有任意一帧返回 FALSE,就 s_u8_send_err_cnt++ 并立即 return(仅当计数 > 3 时才先置 UPGRADE_END)。由于 u32_upgrade_file_offset 只在批次校验成功后才推进,而 l_u8_recv_file_offset 是每次调用清零的局部变量,下一次调用会从同一偏移重新解析并重发这 3 行。也就是说:第 0 行已成功下发、第 1 行失败时,下一轮仍会把第 0 行再发一遍。协议层没有行号去重机制,是否可接受完全取决于逆变器侧对重复行的容忍度(同一地址重复写同一数据通常幂等,但前提是地址与数据完全一致)。对比之下,解析失败路径(第 456-471 行)是会先置 UPGRADE_END 再 return 的,两条失败路径的处理方式并不统一,维护时容易看错。

另外注意 s_u8_send_err_cnt 在每行发送成功后即被清零(第 516 行),因此"失败—成功—失败—成功"交替时计数永远涨不到 4,只有连续失败才会放弃;这一点本身是合理的重试策略,但与 s_u8_upgrade_err_cnt / s_u8_upgrade_recv_cnt 那种"累计到阈值"的语义不同。

  1. s_u8_send_err_cnt > 3 用的是大于而非大于等于(kelong_upgrade.c:500):实际允许 4 次失败(1、2、3 次只 return),与其它计数器 >= 3 的语义不一致,容易误读。
  2. kelong_confirm_upgrade_line_number() 用 strstr() 扫非 NUL 结尾的 SDRAM 缓冲(kelong_upgrade.c:85-86):strstr((char *)&upgrade_file[i], "\r\n") 会一直扫到内存中第一个 0 字节为止,可能远超 file_len。虽然随后用 <= (char *)&upgrade_file[file_len] 做了范围判断,但越界读本身已经发生(读进 SDRAM 其它区域)。若 g_u8_fw_sdram2 之后紧跟的不是已清零的内存,strstr 有可能一直扫到很远。
  3. 行号计数用 u16(kelong_upgrade.c:71-72、kelong_upgrade.h:45-46):l_u16_0x3A_num、l_u16_0x0D0A_num、u16_upgrade_line_num、u16_send_line_num 全是 16 位。主机固件要求行数 > 2000,若某个固件超过 65535 行,计数会回绕,kelong_confirm_upgrade_line_number() 的"两计数相等"判据可能仍然成立(两处同时回绕),但 u16_upgrade_line_num 传给逆变器的总行数就是错的,升级会在中途失配。当前科隆固件规模应远小于此,但这是一条隐式的规模上限。
  4. l_u8_recv_file_offset 是 u8(kelong_upgrade.c:336):一批 3 条记录的 ASCII 长度累加进这个 8 位变量。标准 16 字节数据记录约 45 字符,3 条约 135,安全;但若 HEX 采用 32 字节数据记录(约 75 字符/条),3 条 = 225,已接近 255 上限;再叠加 hex_parse 窗口是 80 字节而记录可能更长,溢出后 file_offset 推进会出错,表现为固件地址错位。建议改成 u16。
  5. u8_upgrade_buf[i][4 + u8_data_len] 可能越界(kelong_upgrade.c:487):缓冲每行仅 40 字节,写入位置是 4 + data_len,因此 data_len >= 36 就写出界(合法下标最大 39)。标准 HEX 记录的数据域最大 255 字节,实际用 16/32 居多,所以目前不会触发,但缺少显式的 data_len <= 35 校验。
  6. 文件尾多出一批"全零记录"与一次多余的批次校验(高危)(kelong_upgrade.c:442-541):当 file_offset + recv_file_offset >= file_len 时,内层 while 只做"记 s_u32_upgrade_result_tick、置状态为 CHECK_UPGRADE_FILE、break",外层 for i 却仍会跑完 3 次(i=1、i=2 的重进条件依然成立,只是重复赋值)。随后代码照旧下发 3 个刚刚被 memset 归零的行缓冲(行号为 u16_send_line_num、+1、+2)并再发一次 0x303 批次校验。危险之处在于这次校验的对象是"并不存在的数据行":若逆变器严格校验行号或数据,会连续失败 5 次,从而触发第 527-541 行的放弃逻辑,把 u8_upgrade_object 对应的 flag 清掉并直接跳到 UPGRADE_END——明明固件已经全部送达,却因为收尾这一批幽灵记录而被判定升级失败。这里有两条隐蔽前提同时成立才会出错:行数被向上取整到 3 的倍数、以及 send_ems_check_upgrade_file_cmd 的判据只比 DATA[0] 与 DATA[7] 而不比行号。建议在 EOF 分支里直接跳到 CHECK_UPGRADE_FILE 并跳过后续的发送与校验。
  7. UPGRADE_INIT 无条件用 g_u8_fw_sdram1 做指纹校验(kelong_upgrade.c:350):即使本轮只打算升级从机(文件在 g_u8_fw_sdram2),入口仍要求 g_u8_fw_sdram1 里是合法的科隆主机固件。这意味着"只下载了从机固件"的场景会被直接判失败并跳到 UPGRADE_END。当前的隐含假设是"主从机固件总是一起下载",这个耦合没有在代码里表达出来。
  8. OSTimeDlyHMSM(0, 0, 3, 0) 阻塞 3 秒(kelong_upgrade.c:357):这一句在 UPGRADE_INIT 里同步阻塞 3 秒,整个 task_inverter 在此期间不执行任何其它工作(包括看门狗喂狗若在该任务中、以及其它逆变器状态刷新)。虽然升级期间本就不做常规通信,但 3 秒的硬延时对一个运行 RTOS 的实时任务来说是明显的设计瑕疵。
  9. send_inv_shutdown_command() 有空 if 语句体(kelong_upgrade.c:56-59):if ((can1_send_standard_pack(...)) == TRUE) { } 什么都不做,且不检查返回值。如果停机帧发送失败,代码照样延时 3 秒并继续升级。停机失败很可能导致逆变器在带载状态下被刷固件,是安全相关的薄弱点。
  10. 0x303 / 0x304 的应答校验不核对行号(kelong_upgrade.c:258-261、:311-314):只比 frame_data[0] 与 frame_data[7] == 0,而 0x301 握手则核对了 frame_data[1]/[6]/[7]。批次校验尤其危险——它本该确认"第 N 行这一批收到了",但应答里没有行号可比,任何 0x401 且 DATA[7]==0 的帧都能让它通过。目前靠发送前 can1_clear_standard_pack() 清缓冲降低误判概率,但这不是协议级的保证。
  11. CHECK_UPGRADE_FILE 的 60 秒窗口从"文件解析完毕"起算(kelong_upgrade.c:448、:560):它在 EOF 时记 s_u32_upgrade_result_tick,而 send_ems_check_upgrade_state_cmd() 每次自身最长阻塞 100 ms。60 秒内约可轮询 600 次,一般足够;但如果逆变器把整个固件的烧写完成放在最终应答之后(即需要更长时间返回 DATA[7]==0),这里的 60 秒会先到,逻辑会判定失败并清掉升级标志——而设备其实正在烧写。缺少"烧写中"这一中间态的处理。
  12. abs() 用于 u32 差值(kelong_upgrade.c:152、:251、:304、:560):OSTimeGet() 返回 INT32U,减法结果是无符号;传给 abs(int) 时发生"无符号→有符号"的隐式转换。差值小于 2^31 时行为正确(本次的差值都远小于),但这是一处依赖具体实现的定义方式,换成 64 位 tick 或更长的超时就会失效。
  13. FOOL_PROOFING 的宏名与枚举名拼写:宏本意是 "foolproofing";KELONG_UPGRADE_SATE_E、EMS_INV_CHECK_UPGRADE_SATTE 把 STATE 拼成了 SATE/SATTE。不影响功能,但会让 grep STATE 之类的检索漏掉这些符号。
  14. HEX_FILE_T s_hex_file_data = {0}; 是局部变量(kelong_upgrade.c:337):约 70 字节,每次调用 kelong_upgrade_handle() 都要重新初始化并占用 task_inverter 的栈。同时注意它与 evc_upgrade.c 中"用 static 共享"的做法不同——本文件把它放在栈上是更安全的写法(无跨调用污染),但也意味着每次调用都付一次 memset 成本。
  15. 文件头注释为空(kelong_upgrade.c:1-8):@version、@date、@brief 全部留空,与同目录 kelong_comm.c 一致的模板化残缺。考虑到本文件承载的是"刷固件"这类高风险动作,缺少版本与修订说明不利于后续维护与追溯。
  16. u8_upgrade_object 在 UPGRADE_END 被 memset 归 0(KELONG_NONE):因此 kelong_read_upgrade_flag_master()/_slave() 在升级间隙会返回 FALSE,调用方(kelong_comm.c:1377)也会随之不再写 cUpdateMaster/cUpdateSlave。这是正确行为,但意味着 u8_run_mode 会在升级状态切换的那一瞬短暂回到常规值,HMI 上可能看到模式闪动——属可接受的显示抖动,此处记录以备排查。