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

evc_upgrade.c

源文件路径:midware/evc/evc_upgrade.c 所属模块:midware/evc(交流充电桩 EVC / EVC-II 固件远程升级) 代码行数:784

1. 文件职责

本文件负责把云端下发的充电桩固件(HEX 文件)通过 UART3(外部 485 总线)逐包烧写进充电桩,是"云端固件下载 → 充电桩烧写"这条链路的最后一环。云端先把固件存进 SDRAM(g_u8_fw_sdram1),本文件从 SDRAM 里读 ASCII 格式的 Intel HEX,用 hex_parse() 解析、再按 224 字节(EVC_MAX_PRG_LENGTH = 112*2)为单位重新拼包,最后按升级协议组帧、必要时做 AES-256 加密后下发。

它由 midware/interface/interface.c:341 驱动:当 is_upgrade_evc()(即 Get_Status_Is_Updated_From_Server(UPDT_EVC))为真时,接口任务会完全让出串口 3,改为每轮调用一次 evc_upgrad_handle() 并 OSTimeDlyHMSM(0,0,0,50),直到升级结束。升级标志由云端特殊命令经 net/server.c:2458 的 set_evc_upgrad_flag() 设置(参数是桩号,"0" 表示全部)。一代桩(g_t_evc_run_info)与二代桩(g_t_evc_ii_run_info[4])共用本文件,只是每代的运行时状态结构不同。

进度上报由 get_percent_upgrade_evc() / get_upgrade_file_offset_evc() 对外提供,前者被 HMI(hmi/ui/ui_main.c:895)画百分比,后者被云端上报(net/ems.c:6719)。

2. 关键数据结构与宏

名称类型说明
EVC_UPGRADE_T结构体(evc_upgrade.h:41-53,#pragma pack(1))本文件的全部升级状态:u8_upgrad_flag[4]/u8_upgrad_err_cnt[4](每桩的使能与重试计数)、u32_addr_flash_base(HEX 04 记录给出的基址高 16 位)、u32_addr_flash(当前包起始烧写地址)、u32_addr_flash_offset/u32_old_addr_offset(HEX 记录内偏移与上一包偏移)、u32_parse_length(已消费的 HEX 文件字节数,即进度)、u8_upgrad_file_buf(指向发送缓存第 18 字节的数据区指针)、u16_length(当前包长)、u8_evc_ii_encryption_flag[4](各桩是否启用加密)
HEX_FILE_T结构体(common/hex_file_parse.h:32-41)HEX 解析结果:u8_data_len/u16_addr/u8_code_type/u16_data_buf[32]/u8_sum_check/u8_ascll_len
EVC_UPGRADE_STATE_E枚举(evc_upgrade.h:18-28)升级主状态机:EVC_UPGRADE_INIT、EVC_PRAVITE_KEY、EVC_BOOT_ENTER、EVC_HANDSHAKE、EVC_ERASE_FLASH、EVC_WRITE_FLASH、EVC_SEND_KEY_WORD、EVC_UPGRADE_END
EVC_UPGRADE_CODE_E枚举(evc_upgrade.h:30-38)下发的命令码:EVC_APP_JUMP=0、EVC_HAND_SHAKE=1、EVC_ERASE_APP=2、EVC_WRITE_APP=3、EVC_CHECK_FILE=4、EVC_BOOT_JUMP=5
PARSE_STATE_E枚举(hex_file_parse.h:14-21)hex_parse() 与 parse_evc_hex_content() 的返回:PARSE_OK=0(已拼好一包)、PARSE_ADDR=1(收到地址/数据记录,需继续)、PARSE_END=2(收到 01 结束记录)、DATA_ERR/PARSE_ERR
HEX_CODE枚举(hex_file_parse.h:23-29)GET_DATA_CODE=0x00、GET_END_CODE=0x01、GET_BASE_ADDRESS_CODE=0x04、GET_LINEAR_ADDRESS_CODE=0x05
EVC_UPGRADE_TEMP_DATA_LEN宏(=80)hex_parse() 单次读取的 ASCII 字节窗口长度
EVC_MAX_PRG_LENGTH宏(=112\*2 = 224)单包固件数据最大字节数,同时决定 0xFF 填充长度
MAX_EVC_II_NUM宏(evc_ii.h:14 = 4)二代桩最大数量,也是 u8_upgrad_flag[] 的长度
EVC_BUF_LEN宏(evc.h:14 = 256)g_evc_send_buf/g_evc_recv_buf 长度
s_evc_upgrade_send_bufstatic u8*直接指向 g_evc_send_buf(一代桩通信缓存,与一代桩共用);有效组帧区仅前 256 字节
s_evc_upgrade_recv_bufstatic u8*指向 g_evc_recv_buf
s_evc_upgrade_send_aes_bufstatic u8[270]AES 加密后的发送缓存(16 字节对齐填充后最大 32+224+16 ≈ 272,实配 270,见第 7 节)
s_evc_upgrade_tabstatic EVC_UPGRADE_T全局升级状态实例
s_hex_file_datastatic HEX_FILE_T文件级 HEX 解析结果(注意 parse_evc_hex_content() 与主状态机共享它)
g_u8_fw_sdram1全局(sdram.c)固件缓冲区,本文件从 &g_u8_fw_sdram1[u32_parse_length] 处按 80 字节窗口解析
PUBLIC_KEY宏/常量(evc_ii.c)AES-256 公钥,get_evc_ii_pravite_key() 用它换回真实密钥

3. 函数清单

函数名作用域说明
evc_upgrade_send_cmd全局按升级协议组帧(0xFA/0x03 + 地址 + 0xAB + 长度 + 数据 + CRC16-IBM),若该桩启用加密则先 evc_ii_aes_encrypt() 再补 CRC,最后 uart3_send_data() 发出(第 45 行)
evc_upgrade_recv_data全局阻塞等待 UART3 异步接收数据,校验 CRC16-IBM,加密桩走 AESDecrypt() 解包后二次校验内层 CRC,超时返回 *len = 0(第 88 行)
evc_upgrade_send_code全局下发"纯命令"帧(数据域为 00×6 + code + 00×3),校验应答的地址/0x0B/命令码/结果字段,返回 TRUE/FALSE(第 178 行)
evc_upgrade_send_data全局下发"写 Flash"帧(EVC_WRITE_APP),前缀 10 字节携带烧写地址与长度(单位:16 位字),后跟 u16_length 字节固件数据(第 220 行)
evc_upgrade_send_key_word全局下发"跳转应用程序"帧(EVC_APP_JUMP),数据域含目标地址 0x0308FFFC 与魔术字 "OK"(第 262 行)
parse_evc_hex_contentstatic从 SDRAM 逐条解析 HEX,按地址连续性把多条记录拼成不超过 224 字节的一包;返回 PARSE_OK/PARSE_ADDR/PARSE_END/错误码(第 311 行)
evc_upgrad_handle全局升级主状态机,每轮由 interface.c 调用一次;负责按桩号重试下发、驱动 HEX 解析与分包发送、结束清理(第 433 行)
set_evc_upgrad_flag全局由云端特殊命令设置要升级哪几台桩:flag > MAX_EVC_II_NUM 时全部清零,flag == 0 时全体置位,否则只置 flag-1(第 694 行)
get_evc_upgrad_flagstatic遍历 u8_upgrad_flag[],只要还有一台桩待升级就返回 TRUE(第 735 行)
is_upgrade_evc全局转封 Get_Status_Is_Updated_From_Server(UPDT_EVC),供 interface.c 与 HMI 判断是否处于升级流程(第 755 行)
get_percent_upgrade_evc全局返回 u32_parse_length * 100 / get_length_file_updating(UPDT_EVC),即已解析字节数占比(第 767 行)
get_upgrade_file_offset_evc全局返回 u32_parse_length,供云端上报断点续传偏移(第 780 行)

共 13 个函数(3 个 static:parse_evc_hex_content、get_evc_upgrad_flag,加上文件头部第 38 行的前置声明 static u8 get_evc_upgrad_flag(void);,该声明本身不是定义)。

4. 核心逻辑解析

4.1 串口协议帧格式

发送帧(明文) 由 evc_upgrade_send_cmd() 组装(evc_upgrade.c:51-63),格式固定为 8 字节头 + 变长数据域 + 2 字节 CRC:

偏移长度内容
010xFA 起始码
110x03 固定字节(协议版本/类型)
21从机地址 addr = id + 1(id 为 0 基桩号)
310x00 保留
410xAB 升级通道标识
510x00 保留
6-72数据域长度 len,小端(先低字节)
8len数据域(由调用者填充)
8+len2CRC16-IBM,小端

CRC 计算范围为头 8 字节 + 数据域(不含 CRC 自身),函数 GetCrcCode_IBM() 来自 common/crc.c。

加密帧:当 s_evc_upgrade_tab.u8_evc_ii_encryption_flag[addr-1] == TRUE 时,明文整帧(含 CRC)经 evc_ii_aes_encrypt() 包装成

<addr> <len_hi> <len_lo> <remainder+2> <AES-256-ECB 密文...>

再在这段外层帧尾部追加一次 GetCrcCode_IBM() 的 2 字节 CRC,然后整体发出。注意外层帧与明文帧是两套不同的 CRC 域,接收侧也要校验两层(见 evc_upgrade_recv_data)。

接收帧(明文) 与发送帧同构([0]=0xFA、[2]=addr、[4]=0x0B 表示 ACK、[10]=被回显的命令码、[12..13]=结果码小端,0 表示成功)。接收帧(加密) 则是 <addr> <len_hi> <len_lo> <remainder+2> <密文>,解密后得到明文帧。

4.2 升级状态机

evc_upgrad_handle() 用一个 static u8 s_u8_upgrade_state(初值 EVC_UPGRADE_INIT)保存状态,每轮只执行一步,靠 interface.c 的 50 ms 轮询推进。迁移图:

EVC_UPGRADE_INIT ──(无条件)──► EVC_PRAVITE_KEY
                                    │ 对每个待升级桩最多重试 3 次 get_evc_ii_pravite_key(i)
                                    │ 成功 ⇒ u8_evc_ii_encryption_flag[i] = TRUE
                                    ▼
                               EVC_BOOT_ENTER
                                    │ 最多 5 次 evc_upgrade_send_code(i, EVC_BOOT_JUMP=5)
                                    │ 失败 5 次 ⇒ u8_upgrad_flag[i] = FALSE
                                    │ 全部桩都已放弃(get_evc_upgrad_flag()==FALSE) ⇒ EVC_UPGRADE_END
                                    ▼
                                EVC_HANDSHAKE
                                    │ 最多 3 次 evc_upgrade_send_code(i, EVC_HAND_SHAKE=1)
                                    ▼
                               EVC_ERASE_FLASH
                                    │ 最多 3 次 evc_upgrade_send_code(i, EVC_ERASE_APP=2)
                                    ▼
                               EVC_WRITE_FLASH ◄─────────────┐
                                    │ 内层 for(;;) 持续调 parse_evc_hex_content()│
                                    │  · PARSE_OK  ⇒ 跳出,向每桩最多 3 次 evc_upgrade_send_data(i)
                                    │                (失败间隔 OSTimeDlyHMSM 250 ms)
                                    │  · PARSE_END ⇒ 跳到 EVC_SEND_KEY_WORD
                                    │  · PARSE_ADDR⇒ 继续解析
                                    │  · 其它(错误) ⇒ 连续 30 次 ⇒ EVC_UPGRADE_END
                                    │ 全部桩放弃 ⇒ EVC_UPGRADE_END
                                    └────────────────────────────┘
                                        (下一轮回到 WRITE_FLASH 继续下一包)
                               EVC_SEND_KEY_WORD
                                    │ 每桩最多 3 次 evc_upgrade_send_key_word(i)(EVC_APP_JUMP=0)
                                    ▼
                                EVC_UPGRADE_END
                                    │ memset(&s_evc_upgrade_tab,0,...)
                                    │ g_t_evc_run_info.evc_flag_u.BIT.comm_flag = FALSE
                                    │ g_t_evc_ii_run_info[i].evc_ii_flag_u.BIT.comm_flag = FALSE
                                    │ Set_Status_Is_Updated_From_Server(UPDT_EVC, FALSE)
                                    └─► 回到 EVC_UPGRADE_INIT

default 分支把状态强制打到 EVC_UPGRADE_END,起到兜底复位作用。

关键点:状态机是"逐个桩串行、互不阻塞"的——每轮对所有 u8_upgrad_flag[i] == TRUE 的桩各下发一次;某台桩连续失败到计数上限就把它踢出(u8_upgrad_flag[i] = FALSE),但不影响其它桩。只有当一台都不剩时(get_evc_upgrad_flag() == FALSE)才整体进入 END。这意味着"部分成功"是可达状态,而 EVC_UPGRADE_END 无条件清 UPDT_EVC,云端无法从本文件区分成功与失败。

4.3 命令帧数据域布局

evc_upgrade_send_code(id, code)(第 178-212 行)——数据域 10 字节:

数据域偏移内容
0-50x00 × 6
6code(命令码)
7-90x00 × 3
10..若 u16_length != 0 再补 u16_length 字节(升级过程中此值在 EVC_WRITE_FLASH 之后会残留,见第 7 节)

发出后 evc_upgrade_recv_data(..., 5000) 等 5 s,判据为 recv_buf[2] == id+1 && recv_buf[4] == 0x0B && recv_buf[10] == code && (recv_buf[12] | recv_buf[13] << 8) == 0。

evc_upgrade_send_data(id)(第 220-254 行)——数据域 10 字节前缀 + u16_length 字节固件数据:

数据域偏移内容
0-10x00 × 2
2-3(u32_addr_flash >> 16) & 0xffff 的大端(高 16 位字)
4-5u32_addr_flash & 0xffff 的大端(低 16 位字)
6EVC_WRITE_APP(3)
70x00
8-9u16_length / 2 小端 —— 长度以 16 位字为单位(满包 224 字节 → 112 字)
10..10+u16_length-1固件数据(固件数据实际从 s_evc_upgrade_send_buf[18] 开始写,即头 8 字节 + 前缀 10 字节之后)

超时 2000 ms 发送、3000 ms 接收,判据同上但 recv_buf[10] 应为 EVC_WRITE_APP。

evc_upgrade_send_key_word(id)(第 262-298 行)——数据域 14 字节:

00 00 | 03 08 | FC FF | 00 | 00 | 02 00 | 'O' 'K' | 00 00
       └高字┘  └低字┘  cmd   长度=2字  魔术字

按与 send_data 相同的"高字先、低字后"约定,目标地址为 0x0308FFFC,写入 2 个字(4 字节:'O'、'K'、00、00),命令码 EVC_APP_JUMP(0)。这是告诉桩内 Bootloader"固件已写完,跳到 App"的握手。超时 200/1000 ms。

4.4 HEX 解析与分包(parse_evc_hex_content)

这是本文件最难的一段(第 311-424 行)。它的输入是 SDRAM 里的 ASCII HEX 文本,游标是 s_evc_upgrade_tab.u32_parse_length,输出是"是否已凑满一包可直接发送的数据"。

  1. l_u8_flash_data = &g_u8_fw_sdram1[u32_parse_length],调 hex_parse(&s_hex_file_data, l_u8_flash_data, 80) 解析一条记录。
  2. 解析失败时用 strstr() 在窗口里找 "\n"/"\r"/":",把游标推过该分隔符(+1);都找不到就整窗跳过 80 字节。这是对"记录跨窗口断裂"的容错。
  3. GET_BASE_ADDRESS_CODE(04 记录):把 u16_data_buf[0] 存进 u32_addr_flash_base;若正好处于"半包"状态(s_u8_data_len != 0)则丢弃半包并返回 PARSE_OK(先把手上的发出去),否则返回 PARSE_ADDR。
  4. GET_DATA_CODE(00 记录):核心拼包逻辑
    • u32_addr_flash_offset = s_hex_file_data.u16_addr;若与 u32_old_addr_offset 相同,l_u8_parse_len = 0(同一地址的重复记录,不计长度);
    • 若本次偏移比上次大 0x10 以上(说明中间有空洞),且已积累半包,则先把半包发出去(返回 PARSE_OK);
    • s_u8_data_len == 0 时判定为"新包开始":把 u8_upgrad_file_buf 指向 s_evc_upgrade_send_buf[18]、整块用 0xFF 填 224 字节、计算本包起始地址 u32_addr_flash = (u32_addr_flash_base << 16) | u32_addr_flash_offset;
    • s_u8_data_len = (当前绝对地址 - 本包起始地址) + l_u8_parse_len,即包内已填字节数;
    • 若未超 224:u16_length = s_u8_data_len,并把 u16_data_buf[] 里的 16 位字按 大端 逐字节写入 u8_upgrad_file_buf[s_u8_data_len - l_u8_parse_len + i*2](高字节在前)——即固件以"高字节在前的 16 位小端机数据"形式搬运(ARM 的 HEX 就是按 16 位字排放的);
    • 若超 224:s_u8_data_len = 0 并返回 PARSE_OK 把上一包发出去(本记录下次重解)。
  5. GET_LINEAR_ADDRESS_CODE(05 记录):直接 PARSE_ADDR 跳过,不做处理——即不支持线性地址记录,只支持 04 基址记录。
  6. GET_END_CODE(01 记录):若手上无半包则 PARSE_END(全部完成),否则清半包返回 PARSE_OK(先发最后一包,结束标志下一轮再处理)。

主状态机对返回值的处理(第 588-618 行):PARSE_OK → 跳出解析循环去发送;PARSE_END → 游标 += u8_ascll_len、转到 EVC_SEND_KEY_WORD;PARSE_ADDR → 游标 += u8_ascll_len、继续解析;其它 → s_u8_parse_cnt++,累计超过 30 次才放弃升级。

4.5 收发时序与超时重试

环节发送超时接收超时重试上限失败后动作
取 AES 私钥 get_evc_ii_pravite_key()500 ms(内部)500 ms(内部)3不置 encryption_flag(走明文)
Boot 跳转 EVC_BOOT_JUMP200 ms5000 ms5u8_upgrad_flag[i] = FALSE
握手 EVC_HAND_SHAKE200 ms5000 ms3u8_upgrad_flag[i] = FALSE
擦除 EVC_ERASE_APP200 ms5000 ms3u8_upgrad_flag[i] = FALSE
写数据 EVC_WRITE_APP2000 ms3000 ms3(失败间隔 250 ms)u8_upgrad_flag[i] = FALSE
结束跳转 EVC_APP_JUMP200 ms1000 ms3无(不置失败标志)

所有等待都基于 OSTimeGet()(OS_TICKS_PER_SEC = 1000,即 tick = ms),并显式处理 tick 回绕(else if (OSTimeGet() < l_u32_recv_tick) l_u32_recv_tick = OSTimeGet();)。

evc_upgrade_recv_data() 的循环(第 99-169 行)值得细看:它在每一轮都会重新写 *len——包括把 *len = 0 写进超时/校验失败/无数据的路径,只有两条路径写非零值并 return。这意味着调用者在函数返回后必须同时看 l_u16_recv_len > 0 与各字段,单看 > 0 不够(当前实现就是这么做的)。

4.6 与 uCOS-II 的配合

4.7 关键全局变量的读写

变量本文件的读/写触发条件
s_evc_upgrade_tab.u8_upgrad_flag[]读(决定对哪些桩操作)、写(置 FALSE 踢出失败桩)set_evc_upgrad_flag() 由 server.c:2458 写入;本文件在重试耗尽时清位
s_evc_upgrade_tab.u32_parse_length读写解析推进、PARSE_END 时补 u8_ascll_len、EVC_UPGRADE_END 时被 memset 清零
s_evc_upgrade_tab.u16_length写(解析时)、读(组帧时)每凑满一包更新一次
g_t_evc_run_info.evc_state_u.BIT.chgsts写 0x0Aget_evc_enable_flag() == TRUE 时,每轮都写,作为"升级中"状态给 HMI/云端
g_t_evc_ii_run_info[i].evc_ii_state_u.BIT.chgsts写 0x0A对每台 u8_upgrad_flag[i] == TRUE 的二代桩每轮都写
g_t_evc_run_info.evc_flag_u.BIT.comm_flag、g_t_evc_ii_run_info[i].evc_ii_flag_u.BIT.comm_flag写 FALSE仅在 EVC_UPGRADE_END,表示升级结束、可以恢复常规通信
g_u8_fw_sdram1[]只读每轮从 u32_parse_length 处读取 80 字节 HEX 窗口
UPDT_EVC 状态读(is_upgrade_evc)、写 FALSE(结束时)set_evc_upgrad_flag() 的调用方(server.c)负责置 TRUE
aesInfo[](common/aes.c:10)读(AESDecrypt)加密桩解包时按 buf[0]-1 索引
g_t_evc_ii_single_config[i].u8_enable读set_evc_upgrad_flag(0) 时决定哪些桩进升级队列

5. 对外接口

本文件导出的函数

函数声明位置调用方
evc_upgrad_handleevc_upgrade.h:56midware/interface/interface.c:343(接口任务,is_upgrade_evc() 为真时每轮调用,后跟 50 ms 延时)
set_evc_upgrad_flagevc_upgrade.h:57net/server.c:2458(收到 UPDT_EVC 特殊命令,取 FIELD_NAME_SPECIAL_CMD_PARA3 字段 atoi 作为桩号)
is_upgrade_evcevc_upgrade.h:58midware/interface/interface.c:341、hmi/ui/ui_main.c:889
get_percent_upgrade_evcevc_upgrade.h:59hmi/ui/ui_main.c:895(LCD 画 3 位百分比数字)
get_upgrade_file_offset_evcevc_upgrade.h:60net/ems.c:6719(云端上报断点偏移,为 0 时保留原值)
evc_upgrade_send_cmd / evc_upgrade_recv_data / evc_upgrade_send_code / evc_upgrade_send_data / evc_upgrade_send_key_word仅在 .c 内定义,.h 中没有声明本文件内部互调;因未声明为 static,理论上可被外部链接,但工程中无外部调用

本文件不导出任何全局变量(s_evc_upgrade_send_buf、s_evc_upgrade_recv_buf、s_evc_upgrade_send_aes_buf、s_evc_upgrade_tab、s_hex_file_data 全部为 static)。

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

外部符号来源模块
uart3_clear_buffer / uart3_send_data / uart3_is_recv_async_data / uart3_get_data_len / uart3_read_data_asynchardware/usart3/usart3.c(UART3 外部 485)
GetCrcCode_IBMcommon/crc.c(CRC16-IBM)
hex_parse / HEX_FILE_T / PARSE_*common/hex_file_parse.c
AESDecrypt / AESInit / aesInfo[]common/aes.c(AES-256 ECB)
evc_ii_aes_encrypt / get_evc_ii_pravite_keymidware/evc/evc_ii.c:2173 / :470
g_evc_send_buf / g_evc_recv_bufmidware/evc/evc.c:25-26
get_evc_enable_flag / g_t_evc_run_infomidware/evc/evc.c / evc.h
g_t_evc_ii_run_info[] / g_t_evc_ii_single_config[]midware/evc/evc_ii.c / evc_ii.h
g_u8_fw_sdram1hardware/sdram/sdram.c
Get_Status_Is_Updated_From_Server / Set_Status_Is_Updated_From_Server / get_length_file_updating / UPDT_EVCnet/upd_download.c / net/ems.h
OSTimeGet / OSTimeDlyHMSMuCOS-II

6. 依赖关系

依赖

evc_upgrade.c
  ├─ evc_upgrade.h ─► includes.h, evc.h(EVC_BUF_LEN、g_evc_send_buf/recv_buf、
  │                   g_t_evc_run_info、get_evc_enable_flag), evc_ii.h(MAX_EVC_II_NUM、
  │                   g_t_evc_ii_run_info/…_single_config、evc_ii_aes_encrypt、get_evc_ii_pravite_key)
  ├─ upd_download.h  (UPDT_EVC 状态、get_length_file_updating、Set/Get_Status_Is_Updated_From_Server)
  ├─ evc_download.h / evc.h / evc_ii.h
  ├─ ems.h / server.h(UPDT_* 枚举、ENUpdateType)
  ├─ usart3.h        (UART3 收发)
  ├─ sdram.h         (g_u8_fw_sdram1)
  ├─ hex_file_parse.h(hex_parse、HEX_FILE_T、PARSE_*)
  ├─ crc.h           (GetCrcCode_IBM)
  └─ aes.h           (AESDecrypt、aesInfo[])
        └─ UCOSII(OSTimeGet / OSTimeDlyHMSM)

被依赖(谁调用本文件)

对应头文件文档见 docs/knowledge/代码解析/midware/evc/evc_upgrade.h.md。

7. 备注与风险点

  1. 文件头注释写错(evc_upgrade.c:2):@file :evc.c,实际是 evc_upgrade.c;@date :2017/05/18 与 evc_upgrade.h 的 2017/09/26 也不一致,属复制模板未改。
  1. 加密接收时 buf[0] 的索引越界(高危)(evc_upgrade.c:137):s_evc_upgrade_tab.u8_evc_ii_encryption_flag[buf[0]-1] 假设 buf[0] 是从机地址。这个假设在 AES 外层帧下成立(外层帧首字节确实是 addr),但明文帧的首字节是 0xFA,0xFA - 1 = 249,远超 u8_evc_ii_encryption_flag[4] 的长度 → 越界读。越界读到的字节若非 0,代码会错误地走进 AES 解密分支,用错误的 buf[4] 作为密文起点、用 buf[1]<<8 | buf[2](对明文帧来说是 0x03 和 addr)当作长度,行为不可预测。这段代码是从 evc_ii.c:154 的同名逻辑复制过来的,但 evc_ii.c 的协议首字节恰好是地址,复制到本文件的 0xFA 协议后就失配了。建议:把判据改成"先看 buf[0] 是否为 0xFA,是则按明文处理;否则按 buf[0] 查加密表"。
  1. AES 解密输出缓冲区只有 40 字节(高危)(evc_upgrade.c:95、:140):static u8 l_u8_recv_data[40],但 AESDecrypt(&aesInfo[buf[0]-1], l_u8_recv_data, &buf[4], ((buf[1]<<8 | buf[2]) + buf[3])) 的第四参数是"密文长度",其值由接收到的字节决定(buf[1]<<8 | buf[2] 最大 65535)。收满 224 字节写 Flash 应答时,((len-2)&0xff) + 2 + remainder 可达 258,会直接冲垮这个 40 字节栈缓冲区。evc_ii.c:109 用的是 [90],本文件被缩小到 40 且未做长度校验,属实际的栈溢出风险。
  1. AES 发送缓冲区 270 字节可能不够(evc_upgrade.c:30):s_evc_upgrade_send_aes_buf[270]。加密帧 = 4 字节外层头 + ceil(明文长度/32)*32 密文 + 2 字节外层 CRC。最坏情况:明文 = 8 头 + 10 前缀 + 224 数据 + 2 CRC = 244 字节,补齐到 256,加 4 + 2 = 262,仍小于 270,安全余量仅 8 字节。一旦 EVC_MAX_PRG_LENGTH 或前缀被加大就会溢出,是典型的"靠常量对齐撑住"的脆弱设计。
  1. l_u16_len = buf[1]<<8 |buf[2] + 2; 的运算符优先级(evc_upgrade.c:139,evc_ii.c:153 同样):+ 优先于 |,实际语义是 (buf[1]<<8) | (buf[2]+2),与显然的意图 ((buf[1]<<8) | buf[2]) + 2 不同。凑巧的是发送侧 evc_ii_aes_encrypt() 把 len_hi 写成了 (u8)((len-2) & 0xff) >>8(先截断成 u8 再右移 8 位,恒为 0),所以 buf[1] 永远是 0,两种写法结果相同。这是"两个 bug 互相抵消"——一旦有人修正了 evc_ii_aes_encrypt 的高字节生成,这里立刻会算错长度。
  1. evc_ii_aes_encrypt() 的外层长度字段被写坏(evc_ii.c:2190):enc_data[1] = (u8)((len-2) & 0xff) >>8; 应为 (u8)(((len-2) >> 8) & 0xff)。本文件既然使用该函数,就在依赖这个错误行为,修正时必须两处同步。
  1. get_percent_upgrade_evc() 可能除零(evc_upgrade.c:769):分母 get_length_file_updating(UPDT_EVC) 在固件长度尚未写入时可能为 0。HMI 每帧都会调它(ui_main.c:895),若在"is_upgrade_evc() 为真但长度还没到"的窗口里刷新,会发生除以零。Cortex-M4 整数除零默认不触发异常(返回 0),所以表现为进度恒 0,隐患不大但意图不清。
  1. evc_upgrade_recv_data() 里 if(buf[0] == 0) return;(evc_upgrade.c:128-131):直接返回且不写 *len。当前所有调用方都在调用前把 l_u16_recv_len 初始化成 0,所以侥幸正确;如果将来有人复用这个函数而忘了初始化,会读到垃圾长度。evc_ii.c 的对应实现有同样的写法。
  1. evc_upgrade_send_code() 会带走上一包的 u16_length(evc_upgrade.c:195):l_u16_send_len += s_evc_upgrade_tab.u16_length; 在 send_code 里也会执行。u16_length 只在 parse_evc_hex_content() 中被写。首次升级时它是 0(结构体被 memset),但状态机走到 EVC_UPGRADE_END 后只清结构体、不清 s_u8_data_len(见第 11 条),下一轮若出现异常时序,u16_length 可能带着上一文件的残值被拼进 BOOT_JUMP/HAND_SHAKE/ERASE_APP 帧,导致帧长异常。设计上 send_code 不应该关心 u16_length。
  1. 解析游标的推进与 u8_ascll_len 强耦合(evc_upgrade.c:326-340、:599、:606):解析失败时用 strstr() 找分隔符推进,解析成功时用 s_hex_file_data.u8_ascll_len 推进。但 strstr() 是在可打印 ASCII 文本上找 \n/\r/:——若固件缓冲区里恰好有非文本字节,strstr 会一直扫到缓冲区内第一个 0 才停,可能跨过大量数据。另外 \n 的判断排在 \r 之前,而 HEX 文件用 CRLF,+1 会停在 \n 上,下一轮再按 \r 找一次,实际会多消耗一次循环;功能上不出错(下一轮会命中 strstr(...,":") 分支),但每次失败要消耗两个周期。
  1. s_u8_data_len 是"跨调用残留"的 static,且 EVC_UPGRADE_END 不清它(evc_upgrade.c:316、:671):evc_upgrad_handle() 结束时 memset(&s_evc_upgrade_tab, 0, sizeof(EVC_UPGRADE_T)) 只清结构体,parse_evc_hex_content() 内部那个 static u8 s_u8_data_len 不在其中。若一次升级中途被 u8_upgrad_flag[] 全部清零而结束(半包未发),残留的非零 s_u8_data_len 会让下一次升级的 parse_evc_hex_content() 误判"已在新包中",跳过 u32_addr_flash 的计算与 224 字节 0xFF 填充,直接往残留偏移继续写——跨文件的脏数据风险。建议在 EVC_UPGRADE_END 或 EVC_UPGRADE_INIT 里显式清零。
  1. 不支持线性地址记录(05 记录)(evc_upgrade.c:407-410):GET_LINEAR_ADDRESS_CODE 被无条件忽略。若充电桩固件是用"05 扩展线性地址"而非"04 扩展段地址"生成的 HEX(IAR/Keil 的可配置项不同),u32_addr_flash_base 永远保持 0 或上一次 04 记录的值,烧写地址会全错,且当前实现不会报错,只会静默地把固件写到错误地址。这是最容易导致"升级成功但设备变砖"的一点。
  1. 失败惩罚只针对单台桩、没有整体上限:EVC_WRITE_FLASH 中若某台桩反复"发送成功但应答校验失败",u8_upgrad_err_cnt[i] 会被清零(第 629 行),永远达不到"踢出"条件,循环会一直重发同一包。真正兜底的是解析错误计数 s_u8_parse_cnt > 30,它对"发送卡死"无效。也就是说,应答内容错误(而非超时)这一路径缺少退出条件,可能无限循环。
  1. EVC_CHECK_FILE(4) 命令码定义了但从未使用:EVC_UPGRADE_CODE_E 里有 EVC_CHECK_FILE,全文件没有任何地方下发它。看起来是预留的"文件校验"步骤未实现。
  1. #define SOCK_ANY_PORT_NUM 式的"变量命名遗留":s_evc_upgrade_tab.u8_upgrad_flag(拼写少了 e)、EVC_PRAVITE_KEY(应为 PRIVATE)、s_u8_upgrade_state vs s_u8_upgrade_sate(后者出现在 kelong_upgrade.c)等拼写瑕疵在升级模块内多处出现,不影响功能但增加了跨文件检索难度。
  1. evc_upgrad_handle() 的 EVC_PRAVITE_KEY 状态里 get_evc_ii_pravite_key() 对一代桩也调用(evc_upgrade.c:468):该函数内部按 id 索引 g_t_evc_ii_run_info[id] 与 aesInfo[id]。当 get_evc_enable_flag() == TRUE(一代桩)时 u8_upgrad_flag[0] 被置位,于是会对一代桩调用二代桩的取密钥流程,实际会往 g_t_evc_ii_run_info[0] 写数据、用二代桩的私有协议发文。一代/二代共用同一段状态机但协议不同,这里的混用是由 set_evc_upgrad_flag() 的"一代桩固定用索引 0"约定掩盖的,语义上并不干净。
  1. uart3_clear_buffer() 在每个发送前调用(evc_upgrade.c:72、:77):多桩并行升级时,这一句会丢掉其它桩刚回的应答。当前状态机对每台桩是"发一台、收一台"的严格顺序(for 循环里逐个 send_*() 并阻塞等待),所以恰好安全;若将来改成非阻塞并发,这里会成为丢应答的根源。