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_tab | static KELONG_UPGRADE_TABLE_T | 本文件唯一的全局状态实例(第 28 行) |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
send_inv_shutdown_command | static | 通过 CAN1 发一帧 0x184(数据 01 00 02 00 00 00 00 00)让逆变器先停机,为升级让路(第 40 行) |
kelong_confirm_upgrade_line_number | static | 统计固件文件里 0x3A(:)与 \r\n 的出现次数,二者相等则认为行计数可信,输出总行数(第 69 行) |
send_ems_shake_hands_cmd | static | 发 0x301 握手帧(携带升级对象与总行数),等 0x401 应答并核对对象与行号,超时 100 ms(第 111 行) |
send_ems_send_upgrade_file_cmd | static | 把一条 40 字节的二进制记录拆成 8 个 CAN 帧(每帧 5 字节数据)依次发出,从机每帧间隔 20 ms、主机 5 ms(第 181 行) |
send_ems_check_upgrade_file_cmd | static | 发 0x303 批次校验帧(携带校验行号),等 0x401 应答,超时 100 ms(第 226 行) |
send_ems_check_upgrade_state_cmd | static | 发 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 行)
| 字段 | 值 |
|---|---|
| ID | 0x184(裸写,无宏) |
| 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 行)用两个计数器交叉校验文件完整性:
l_u16_0x3A_num:遍历file_len字节,统计0x3A(:)出现次数;l_u16_0x0D0A_num:对每个:,用strstr()从该位置起找"\r\n",若找到的位置不超过&upgrade_file[file_len]才计数。
两者相等才认为"每条记录都以 CRLF 收尾",输出总行数。这一步实际上是在做"HEX 文件是否被完整下载"的粗校验。
紧接着的 send_ems_shake_hands_cmd() 用文件大小做第二层防错(第 117-131 行):
- 若对象是
KELONG_SLAVE:file_line > 2000就拒升级 —— 从机固件必须 ≤ 2000 行; - 否则(主机):
file_line < 2000或 文件头 36 字节不等于FOOL_PROOFING就拒升级 —— 主机固件必须 > 2000 行且以固定指纹开头。
这两个约束合起来是一道"防止把主从机固件互相刷错"的保护:从机小、主机大,且主机文件的头部有固定模板。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 控制:
- 从机:每帧 20 ms → 一批 3 行 × 8 帧 × 20 ms = 480 ms
- 主机:每帧 5 ms → 一批 3 行 × 8 帧 × 5 ms = 120 ms
随后一次 0x303 批次校验(最长 100 ms)。所以一批的最坏耗时:从机 ≈ 580 ms、主机 ≈ 220 ms。按 2000 行推算,从机整包约需 2000/3 × 0.58 ≈ 387 s(6.5 分钟);主机行数更多,按 20 ms 量级估算也在十几分钟以上。这是"不逐帧应答、只按批校验"换来的效率,代价是丢帧只能靠批次校验整批重发。
4.5 偏移与进度的推进
两个游标分工明确:
l_u8_recv_file_offset(kelong_upgrade_handle的局部变量,每次调用清零):本次调用内已消费的 ASCII 字节数,每解析成功一条加s_hex_file_data.u8_ascll_len;批次校验通过后整体累加进全局的u32_upgrade_file_offset。u32_upgrade_file_offset(全局,即进度分子):只在批次校验成功后推进,因此进度是"已确认送达"的字节数,而不是"已发出"的字节数。
进度百分比由 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 的配合
| 环节 | 超时 | 重试上限 | 失败动作 |
|---|---|---|---|
握手 0x301 | 100 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 |
批次校验 0x303 | 100 ms / 次 | 5 次(s_u8_upgrade_recv_cnt) | 清对应 flag → UPGRADE_END |
结果查询 0x304 | 100 ms / 次,整体 60 s | 无计数,靠 60 s 兜底 | 清对应 flag → UPGRADE_END |
| HEX 解析 | 无 | 3 次(l_u8_parse_err_cnt,局部变量,每次调用清零) | 清对应 flag,置 UPGRADE_END 后 return |
与 uCOS-II 的配合方式:
- 本文件不创建任务、不使用信号量或消息邮箱,是"被
task_inverter同步调用"的被动模块; - 唯一的主动让出是三个等待函数里的
OSTimeDlyHMSM(0, 0, 0, 5)(5 ms 轮询间隔)与数据帧之间的 5/20 ms,以及UPGRADE_INIT里的一次OSTimeDlyHMSM(0, 0, 3, 0)(3 秒硬延时); - 所有计时基于
OSTimeGet()(tick = ms),并用abs()处理回绕; - CAN 收发走
can1_*的全局缓冲 +can1_clear_standard_pack()机制,不使用邮箱。can1_clear_standard_pack()在三个等待函数里都在发送前调用,作用是丢弃上一条命令的迟到应答;UPGRADE_END里也调一次做收尾清理。
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_handle | kelong_upgrade.h:54 | midware/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_master | kelong_upgrade.h:55 | midware/inverter/inverter.c:3807;midware/inverter/kelong/kelong_comm.c:1377(置 u8_run_mode = cUpdateMaster) |
kelong_read_upgrade_percent_master | kelong_upgrade.h:56 | midware/inverter/inverter.c:3843 |
kelong_read_upgrade_flag_slave | kelong_upgrade.h:57 | midware/inverter/inverter.c:3878;kelong_comm.c:1379(置 u8_run_mode = cUpdateSlave) |
kelong_read_upgrade_percent_slave | kelong_upgrade.h:58 | midware/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_pack | hardware/can/can1.h:29/:25/:27(CAN1 收发与缓冲管理) |
CAN_MSG_TYPEDEF_T / CAN_FT_DATA | hardware/can/can0.h:28、:24 |
hex_parse / HEX_FILE_T / PARSE_OK | common/hex_file_parse.c(PARSE_OK 定义于 hex_file_parse.h:16) |
get_length_file_updating / Set_Status_Is_Updated_From_Server / UPDT_INV1 / UPDT_INV2 | net/upd_download.c / net/upd_download.h |
is_inverter_firm_download_finished | net/inv_download.h:34(由调用方 kelong_comm.c 使用,本文件不直接调用) |
g_u8_fw_sdram1 / g_u8_fw_sdram2 | hardware/sdram/sdram.c |
g_t_inv_info(t_config_data.u8_inv2_app_upgrade_disable) | midware/inverter/inverter.c / inverter.h:464 |
OSTimeGet / OSTimeDlyHMSM | uCOS-II(UCOSII/uCOS-II/Source/os_time.c) |
memcmp / memset / strstr / abs | C 标准库 |
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)
被依赖(谁调用本文件)
midware/inverter/kelong/kelong_comm.c— 唯一驱动者:kelong_inverter_comm()在"固件下载完成"分支里调kelong_upgrade_handle(),并用两个 flag 函数改写u8_run_modemidware/inverter/inverter.c— 通过kelong_read_upgrade_flag_master/slave、kelong_read_upgrade_percent_master/slave读取升级状态与进度(第 3807、3843、3878、3913 行,位于该文件的"逆变器信息上报"分发逻辑中)midware/inverter/kelong/kelong_upgrade.h的对应文档见docs/knowledge/代码解析/midware/inverter/kelong/kelong_upgrade.h.md;正常的通信协议实现见docs/knowledge/代码解析/midware/inverter/kelong/kelong_comm.c.md
与 kelong_comm.c 的资源互斥:两者共用 CAN1 的接收缓冲(can1_clear_standard_pack/get_can1_standard_pack)与发送接口。互斥靠 is_inverter_firm_download_finished() 这个条件分支实现——升级期间常规轮询整体跳过,因此不会出现"升级帧与常规帧同时进接收缓冲"的情形。
7. 备注与风险点
- 发送失败时整批重发,已成功的行会重复下发(
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 那种"累计到阈值"的语义不同。
s_u8_send_err_cnt > 3用的是大于而非大于等于(kelong_upgrade.c:500):实际允许 4 次失败(1、2、3 次只return),与其它计数器>= 3的语义不一致,容易误读。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有可能一直扫到很远。- 行号计数用
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传给逆变器的总行数就是错的,升级会在中途失配。当前科隆固件规模应远小于此,但这是一条隐式的规模上限。 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。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校验。- 文件尾多出一批"全零记录"与一次多余的批次校验(高危)(
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并跳过后续的发送与校验。 UPGRADE_INIT无条件用g_u8_fw_sdram1做指纹校验(kelong_upgrade.c:350):即使本轮只打算升级从机(文件在g_u8_fw_sdram2),入口仍要求g_u8_fw_sdram1里是合法的科隆主机固件。这意味着"只下载了从机固件"的场景会被直接判失败并跳到UPGRADE_END。当前的隐含假设是"主从机固件总是一起下载",这个耦合没有在代码里表达出来。OSTimeDlyHMSM(0, 0, 3, 0)阻塞 3 秒(kelong_upgrade.c:357):这一句在UPGRADE_INIT里同步阻塞 3 秒,整个task_inverter在此期间不执行任何其它工作(包括看门狗喂狗若在该任务中、以及其它逆变器状态刷新)。虽然升级期间本就不做常规通信,但 3 秒的硬延时对一个运行 RTOS 的实时任务来说是明显的设计瑕疵。send_inv_shutdown_command()有空if语句体(kelong_upgrade.c:56-59):if ((can1_send_standard_pack(...)) == TRUE) { }什么都不做,且不检查返回值。如果停机帧发送失败,代码照样延时 3 秒并继续升级。停机失败很可能导致逆变器在带载状态下被刷固件,是安全相关的薄弱点。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()清缓冲降低误判概率,但这不是协议级的保证。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 秒会先到,逻辑会判定失败并清掉升级标志——而设备其实正在烧写。缺少"烧写中"这一中间态的处理。abs()用于u32差值(kelong_upgrade.c:152、:251、:304、:560):OSTimeGet()返回INT32U,减法结果是无符号;传给abs(int)时发生"无符号→有符号"的隐式转换。差值小于 2^31 时行为正确(本次的差值都远小于),但这是一处依赖具体实现的定义方式,换成 64 位 tick 或更长的超时就会失效。FOOL_PROOFING的宏名与枚举名拼写:宏本意是 "foolproofing";KELONG_UPGRADE_SATE_E、EMS_INV_CHECK_UPGRADE_SATTE把STATE拼成了SATE/SATTE。不影响功能,但会让grep STATE之类的检索漏掉这些符号。HEX_FILE_T s_hex_file_data = {0};是局部变量(kelong_upgrade.c:337):约 70 字节,每次调用kelong_upgrade_handle()都要重新初始化并占用task_inverter的栈。同时注意它与evc_upgrade.c中"用 static 共享"的做法不同——本文件把它放在栈上是更安全的写法(无跨调用污染),但也意味着每次调用都付一次memset成本。- 文件头注释为空(
kelong_upgrade.c:1-8):@version、@date、@brief全部留空,与同目录kelong_comm.c一致的模板化残缺。考虑到本文件承载的是"刷固件"这类高风险动作,缺少版本与修订说明不利于后续维护与追溯。 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 上可能看到模式闪动——属可接受的显示抖动,此处记录以备排查。