battery_upgrade.c
源文件路径:
midware/bms/alpha/battery_upgrade.c所属模块:midware / bms / alpha(ALPHAESS 自有电池固件 CAN 升级) 代码行数:786
1. 文件职责
本文件实现 ALPHAESS 自有 48V / 高压电池的 CAN 总线固件升级(Bootloader 交互),把服务器下载到 SDRAM 的 S19 固件文件逐包通过标准 CAN 帧烧写进 BMS。
升级由云平台发起:服务器把固件落到 g_u8_fw_sdram1(SDRAM 3MB 区)后置位 UPDT_BMS 更新标志;task_bms 检测到该标志后进入升级分支,每个任务周期调用一次 households_batter_upgrade_handle()(midware/bms/bms.c:2762),由本文件的六态状态机推进。
本文件同时对外提供进度查询:Get_Idx_Updt_BMS_Loacl() 返回已解析的文件字节偏移,GetPercentUpgradeBMS() 返回百分比,被云平台上报(net/ems.c:6697)、LCD(hmi/ui/ui_main.c:859)和 Modbus(midware/interface/modbus/modbus.c:9385/9429/9462)使用。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
BATTER_UPGRADE_STATUS_E | 枚举 | 升级状态机(battery_upgrade.c:25-33):UPGRADE_IDLE=0、SYSTEM_STATUS_CHECK、ENTER_BOOT、SEND_UPGRADE_DATA、UPGRADE_END、UPGRADE_INFO_INIT。注意 UPGRADE_INFO_INIT 既是"复位"也是"中止"状态 |
BATTER_UPGRADE_TAB_T | 结构体 | 升级上下文(:36-52)。字段:u8_bms_platform(BMS 硬件平台,取值 BMS_48V/BMS_HV)、u8_upgrade_type(UPGRADE_NXP/UPGRADE_GD32/MAX_PLATFORM)、u8_upgrade_status(状态机当前态)、u8_send_boot_cnt(进 Boot 失败计数,同时被地址/数据发送失败复用)、u8_file_parse_cnt(S19 解析失败计数)、u8_send_addr_cnt(声明后从未被使用)、u8_send_over_cnt(结束帧失败计数)、u8_erase_flash_flash(擦除等待已执行标志,变量名重复 "flash")、u8_burn_data[256](本行 S19 的数据缓存)、u8_Burn_len(本行数据长度)、u32_Burn_address(本行烧写地址)、u32_file_address_offset(在 g_u8_fw_sdram1 中的解析游标)、u8_pck_indx(包流水号 0~255 循环)、u16_crc_check(累加 CRC16,初值 0xFFFF) |
BMS_UPGRADE_DEBUG | 宏 | 调试打印开关,battery_upgrade.h:14 定义为 0(全部 #if 关闭) |
CAN_FT_DATA | 宏 | CAN 帧类型:数据帧(hardware/can/can0.h 提供),本文件所有帧都设为该值 |
BMS_48V / BMS_HV / BMS_MAX_TYEP | 枚举值 | BMS 硬件平台分类,定义于 midware/bms/bms.h:168-175;BMS_MAX_TYEP 同时用作"平台未知"的非法标记 |
UPGRADE_NXP / UPGRADE_GD32 / MAX_PLATFORM | 枚举值 | 升级平台分类,定义于 net/server.h:149-156;由 check_bms_upgrade_file() 依据 S19 文件首行的 S1/S3 判定 |
S19_FILE_PASRE_PCK | 结构体 | S19 解析输出包(common/s19.h:32-39):u8_s_addr、u8_end_addr、u8_burn_data、u8_burn_len、u32_burn_addr |
HEAD_OK / PARSE_OK / PARSE_END / BUF_ADDR_ERR / S_ADDR_ERR / END_ADDR_ERR / LEN_ERR / DATA_ERR / CHECK_SUM_ERR | 枚举值 | S19 解析返回码(common/s19.h:20-30),本文件只区分 HEAD_OK / PARSE_OK / PARSE_END 与"其它(失败)" |
UPDT_BMS | 枚举值 | 更新对象类型,net/upd_download.h 提供,用于 Set_Status_Is_Updated_From_Server / get_length_file_updating |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
CAN_Send_Msg | static | 通用发送包装:先写入 TxMsg->frame_len = len,再调 can0_send_standard_pack() 发标准帧 |
send_enter_boot_code | static | 发送"进入 Bootloader"帧并阻塞等待 BMS 应答(2 秒超时);48V+GD32 用 0x10a+"ALEX",否则 0x101+"SLJT" |
send_addr_code | static | 发送单行 S19 的"烧写地址+数据长度"帧(0x106 或 0x102);48V 阻塞等应答(2 秒),其它平台累加 CRC 后延 1ms |
send_data_code | static | 发送 1~8 字节固件数据(0x108 或 0x104);48V 阻塞等应答(6 秒),其它平台补 0xFF 到 8 字节并累加 CRC 后延 1ms |
send_over_updata_code | static | 发送"升级结束"帧(0x101 + "NEHC",前 2 字节带 CRC,48V 额外补发 0x10a),阻塞等待应答最长 60 秒 |
check_bms_upgrade_file | static | 扫描 g_u8_fw_sdram1 前 512 字节,依据首条 S19 记录的 S1/S3 判定 u8_upgrade_type |
households_batter_upgrade_handle | 全局 | 状态机主入口,每次由 task_bms 调用一次,推进升级流程 |
Get_Idx_Updt_BMS_Loacl | 全局 | 返回 u32_file_address_offset(已解析的文件字节偏移),供进度上报 |
GetPercentUpgradeBMS | 全局 | 计算升级百分比:offset * 100 / get_length_file_updating(UPDT_BMS) |
4. 核心逻辑解析
4.1 文件级静态状态
static BATTER_UPGRADE_TAB_T s_bat_upgrade_tab = {0}; // battery_upgrade.c:57
static CAN_MSG_TYPEDEF_T s_t_batter_can_msg = {0}; // battery_upgrade.c:58
两者都是文件级静态,因此整个升级过程中所有组帧函数共用同一个 s_t_batter_can_msg 缓冲区——frame_id 在每次调用时被覆盖,frame_data 由各函数自行灌满/清零。整个升级流程是单任务串行调用,所以不存在重入问题,但也意味着这些函数不可被其它任务并发使用。
4.2 状态机与迁移条件
状态机在 households_batter_upgrade_handle()(:551)内,每次调用执行一个 switch 分支后返回。
硬件平台非法(>= BMS_MAX_TYEP)
┌────────────────────────────────────────────┐
│ ▼
UPGRADE_IDLE ──┬─ 48V 且文件校验成功 ────────► SYSTEM_STATUS_CHECK
└─ 非 48V / 校验失败 ─────────► UPGRADE_INFO_INIT ──┐
│
SYSTEM_STATUS_CHECK ── 逆变器下发"禁止电池"标志 ──► ENTER_BOOT │
└── 60 秒超时 ────────────────────► UPGRADE_INFO_INIT ─┤
│
ENTER_BOOT ── 应答成功 ──► SEND_UPGRADE_DATA(并延时 1s/10s) │
└── 失败累计 3 次 ───────────────────► UPGRADE_INFO_INIT ─┤
│
SEND_UPGRADE_DATA ── HEAD_OK ──► 跳过 S0 头,停留在本状态 │
── PARSE_OK ──► 烧写一行,停留在本状态 │
── PARSE_END ─► UPGRADE_END │
── 其它(解析失败) 累计 3 次 ──► UPGRADE_INFO_INIT ─┤
── 地址/数据帧发送失败 ──────► UPGRADE_INFO_INIT ─┤
│
UPGRADE_END ── 结束帧应答成功 ──────────────────► UPGRADE_INFO_INIT ─┤
└── 失败累计 > 3 次 ────────────────► UPGRADE_INFO_INIT ─┤
│
UPGRADE_INFO_INIT ── memset 整个上下文(=UPGRADE_IDLE) + 清 CAN 队列 │
+ Set_Status_Is_Updated_From_Server(UPDT_BMS,FALSE)
◄──────────────────────────────────────────────┘
关键点:
- 入口的强制中止(
:558-561):
if (hardware_platform >= BMS_MAX_TYEP) {
s_bat_upgrade_tab.u8_upgrade_status = UPGRADE_INFO_INIT;
}
s_bat_upgrade_tab.u8_bms_platform = hardware_platform;
get_bms_hardware_platform()(bms.c:677)在电池型号不被识别时返回 BMS_MAX_TYEP,此时每次调用都被强制打成 UPGRADE_INFO_INIT,从而立即中止升级。
- UPGRADE_IDLE(
:567-585):刷新s_u32_timeout_tick;仅当平台为BMS_48V时才做check_bms_upgrade_file()(S19 魔数判定),非 48V 平台直接进SYSTEM_STATUS_CHECK(即高压 BMS 不做文件类型判定,u8_upgrade_type保持 0)。
- SYSTEM_STATUS_CHECK(
:587-600):等待逆变器给出"可以停止电池"的条件——get_inverter_control_bat_disable_flag() == TRUE(midware/inverter/inverter.h:1951)。超过 60 秒仍未满足则放弃本次升级。
- ENTER_BOOT(
:602-630):send_enter_boot_code()成功则把u8_send_boot_cnt、u32_file_address_offset归零,CRC 初值置0xFFFF,进入SEND_UPGRADE_DATA,并延时 1 秒(48V)或 10 秒(高压)等待 Bootloader 就绪;失败则计数,满 3 次放弃。
- SEND_UPGRADE_DATA(
:632-729):这是升级的工作循环,每次任务周期只解析并烧写一行 S19:parsing_upgrade_file(&g_u8_fw_sdram1[u32_file_address_offset], &l_pasre_pck),解析结果决定动作。HEAD_OK(S0 记录):把u32_file_address_offset = (u8_end_addr - g_u8_fw_sdram1) + 1,即跳过整行 S0 头。PARSE_OK(S1/S2/S3 数据记录):取出u32_Burn_address与u8_Burn_len→ 先发地址帧send_addr_code(u8_pck_indx)(失败计入u8_send_boot_cnt,满 3 次放弃)→ 把 256 字节数据按 8 字节一组循环调用send_data_code()(任何一组失败立即放弃)→ 处理不足 8 字节的尾包 → 更新u32_file_address_offset→ 首次进入且为 48V+GD32 时置u8_erase_flash_flash并OSTimeDlyHMSM(0,0,2,0)(等 BMS 擦除 Flash)→u8_pck_indx++。PARSE_END(S7/S8/S9 结束记录):进入UPGRADE_END。- 其它:
u8_file_parse_cnt++,满 3 次放弃。
- UPGRADE_END(
:731-746):先延时 1 秒,再发结束帧;成功即进UPGRADE_INFO_INIT,失败计数超过 3 次(即第 4 次才放弃)也进UPGRADE_INFO_INIT。
- UPGRADE_INFO_INIT(
:748-752):memset(&s_bat_upgrade_tab, 0, sizeof(...))把状态复位成UPGRADE_IDLE=0,清 CAN 接收队列,并Set_Status_Is_Updated_From_Server(UPDT_BMS, FALSE)通知框架"退出升级"。下一次task_bms迭代就会走正常电池通信分支。
4.3 CAN 帧格式与应答/超时机制
升级全程使用标准 CAN 帧(11 位 ID),8 字节数据域,无应用层校验和(除结束帧的 CRC 外)。ID 分两套:高压/默认路径与 48V+GD32 路径。
| 阶段 | 默认路径帧 ID | 48V+GD32 路径帧 ID | 数据域 |
|---|---|---|---|
| 进入 Boot | 0x101 | 0x10a | [0..3]=00,[4..7]= 53 4C 4A 54("SLJT") / 41 4C 45 58("ALEX") |
| 烧写地址 | 0x102 | 0x106 | [0..1]= Burn_len (小端),[2]=包序号,[3]=0,[4..7]= Burn_address (小端 32bit) |
| 烧写数据 | 0x104 | 0x108 | 默认:补 0xFF 到 8 字节后整帧发送;48V:只填实际数据、按实际长度发送 |
| 升级结束 | 0x101 | 0x101 + 再发一帧 0x10a | [0..1]=CRC16(小端,仅非48V),[2..3]=0,[4..7]= 4E 45 48 43("NEHC") |
应答匹配规则(send_enter_boot_code,:157-186):
- 帧 ID 必须等于发送帧 ID(48V 与非 48V 相同)。
- 非 48V:回帧
[4..7]必须逐字节等于发送值。 - 48V:回帧
[4..7]必须逐字节等于 发送值 + 1(BMS 以 +1 表示确认)。 - 不匹配则
continue重新收,直到超时。
各阶段超时与轮询间隔:
| 阶段 | 超时 | 无数据时的等待 |
|---|---|---|
send_enter_boot_code | 2000 ms | OSTimeDlyHMSM(0,0,0,50) |
send_addr_code(仅 48V 等待) | 2000 ms | OSTimeDlyHMSM(0,0,0,5) |
send_data_code(仅 48V 等待) | 6000 ms | OSTimeDlyHMSM(0,0,0,5) |
send_over_updata_code | 60000 ms | OSTimeDlyHMSM(0,0,0,500) |
send_over_updata_code 的回帧 ID 校验:48V 接受 0x101 或 0x10a;其它平台要求 0x107。
无应答路径(非 48V)的完整性保障:不等待回帧,改为在本地累加 CRC16:
send_addr_code的 else 分支把frame_data[4..7](地址 4 字节)逐个喂给get_data_crc_check()(common/crc.c:190,表驱动 CRC-16,crc ^= data; crc >>= 8; crc ^= wCRCTable[crc & 0xFF]形式),然后延时 1ms。send_data_code的 else 分支把 8 字节全部喂给同一个 CRC,然后延时 1ms。- 最终在结束帧的
[0..1]带上该 CRC,由 BMS 自行校验固件完整性。
48V 路径不使用该 CRC(u16_crc_check 虽被初始化为 0xFFFF 但在 48V 分支里从不更新),而是靠逐包应答保证。
4.4 S19 文件解析与游标推进
解析委托给 common/s19.c:35 的 parsing_upgrade_file(const u8* file_addr, S19_FILE_PASRE_PCK* file_pack):
- 从
file_addr起用strstr(..., "S")找下一行记录的起始,用strstr(..., "\n")找行尾。 S0→ 校验长度与校验和,返回HEAD_OK,输出u8_s_addr/u8_end_addr。S1/S2/S3→ 解析 16/24/32 位烧写地址u32_burn_addr与数据长度u8_burn_len = 长度字段 - 地址字节数 - 1,返回PARSE_OK。S7/S8/S9→ 返回PARSE_END。- 任何格式/长度/校验和错误返回对应的错误码。
本文件用指针差 (u8_end_addr - g_u8_fw_sdram1) + 1 作为下一次解析的偏移,因此游标始终指向下一行的行首,逐行推进。u32_file_address_offset 同时被当作"已升级字节数"上报给云平台/LCD/Modbus。
4.5 与 uCOS-II 任务/调度的配合
- 驱动者:
task_bms(user/main.c:200静态创建)。在bms.c:2758-2779:
if (Get_Status_Is_Updated_From_Server(UPDT_BMS) == TRUE) {
ems_set_fault_bit(6, FALSE);
l_u32_bms_comm_tick = OSTimeGet();
households_batter_upgrade_handle(get_bms_hardware_platform());
if (l_u8_bms_updated_flag == FALSE) { /* 首次进入:清 BMS 运行数据 */ }
OSTimeDlyHMSM(0, 0, 0, 10);
continue; // 跳过正常电池通信
}
即升级期间完全接管 task_bms,不解析 BMS 状态帧、不给 BMS 发周期配置、不检测通信丢失(l_u32_bms_comm_tick 每轮刷新)。
- 本文件不使用信号量、消息邮箱或互斥量;与 uCOS-II 的交互只有
OSTimeGet()(计时/超时)与OSTimeDlyHMSM()(延时与让出 CPU)。
- 长时间阻塞:
send_enter_boot_code最长 2s、send_addr_code(48V)最长 2s、send_data_code(48V)最长 6s、send_over_updata_code最长 60s,全部在task_bms上下文同步阻塞。由于task_bms是该任务唯一职责且升级期间已被"接管",功能上可接受,但会让 EMS 对电池状态的刷新完全停滞(云平台会看到电池数据冻结)。
- 每轮只推进一步:
SEND_UPGRADE_DATA每次调用只解析并烧写一行 S19(32 字节数据量级),加上每包 1ms(非 48V)延时,实际吞吐由 CAN 帧率决定;task_bms在升级分支末尾固定OSTimeDlyHMSM(0,0,0,10)。
4.6 关键全局变量的读写
| 变量 | 读/写 | 说明 |
|---|---|---|
s_bat_upgrade_tab(static) | 读写 | 状态机全部上下文;UPGRADE_INFO_INIT 时整体 memset 清零 |
s_t_batter_can_msg(static) | 读写 | 共用发送缓冲区 |
g_u8_fw_sdram1[SDRAM_3M](net/server.h:258,位于 SDRAM) | 只读 | 固件源数据;check_bms_upgrade_file 只读前 512 字节,解析器从 u32_file_address_offset 起读 |
Get_Status_Is_Updated_From_Server(UPDT_BMS)(读) | 读 | 由调用方 bms.c 判定;本文件不读 |
Set_Status_Is_Updated_From_Server(UPDT_BMS, FALSE) | 写 | 仅在 UPGRADE_INFO_INIT 中调用一次,用于退出升级模式 |
get_length_file_updating(UPDT_BMS) | 读 | GetPercentUpgradeBMS() 中作为分母 |
get_inverter_control_bat_disable_flag() | 读 | SYSTEM_STATUS_CHECK 的唯一推进条件 |
can0_clear_standard_pack() | 调用 | send_enter_boot_code(发前)、send_addr_code(发前)、send_data_code(发前)、send_over_updata_code(发前)、UPGRADE_INFO_INIT(复位时)共 5 处清接收队列 |
5. 对外接口
5.1 本文件导出
| 导出符号 | 声明位置 | 调用方 |
|---|---|---|
void households_batter_upgrade_handle(u8 hardware_platform) | battery_upgrade.h:16 | midware/bms/bms.c:2762(task_bms 升级分支) |
u8 GetPercentUpgradeBMS(void) | battery_upgrade.h:17 | hmi/ui/ui_main.c:859(LCD 显示);midware/interface/modbus/modbus.c:9385,9429,9462(Modbus 寄存器) |
u32 Get_Idx_Updt_BMS_Loacl(void) | battery_upgrade.h:18 | net/ems.c:6697(上报云平台升级进度) |
| 全局变量 | 无(s_bat_upgrade_tab、s_t_batter_can_msg 均为 static) | — |
5.2 本文件调用的外部函数/变量
| 来源模块 | 被调用符号 |
|---|---|
hardware/can/can0.c | can0_send_standard_pack、can0_clear_standard_pack、get_can0_standard_pack |
common/s19.c | parsing_upgrade_file |
common/crc.c | get_data_crc_check |
net/server.h | g_u8_fw_sdram1、UPGRADE_NXP/UPGRADE_GD32/MAX_PLATFORM |
net/upd_download.c | Set_Status_Is_Updated_From_Server、get_length_file_updating、UPDT_BMS |
midware/inverter/ | get_inverter_control_bat_disable_flag |
midware/bms/bms.h | BMS_48V / BMS_HV / BMS_MAX_TYEP |
| uCOS-II | OSTimeGet、OSTimeDlyHMSM |
| libc | memset、memcpy |
注:
battery_upgrade.c:15-20还 include 了upd_download.h、bms_download.h、sdram.h、ems.h、bms.h、inverter.h,其中若干在当前实现中已无直接符号引用(历史遗留)。
6. 依赖关系
依赖(battery_upgrade.c:13-23)
#include "battery_upgrade.h" // → includes.h
#include "can0.h"
#include "upd_download.h"
#include "bms_download.h"
#include "server.h"
#include "sdram.h"
#include "ems.h"
#include "bms.h"
#include "s19.h"
#include "crc.h"
#include "inverter.h"
被依赖
| 文件 | 说明 |
|---|---|
midware/bms/bms.c:15 | #include "battery_upgrade.h",在 task_bms 中驱动状态机 |
net/ems.c:53 | #include "battery_upgrade.h",读取升级进度 |
midware/interface/modbus/modbus.c:36 | #include "battery_upgrade.h",读取升级百分比 |
hmi/ui/ui_main.c:26 | #include "battery_upgrade.h",LCD 显示升级百分比 |
7. 备注与风险点
u8_send_addr_cnt是未使用字段(:43):结构体里声明并随memset复位,但全文件没有任何读写,属遗留字段。
u8_erase_flash_flash命名重复 "flash"(:45):语义是"擦除等待已执行",变量名有笔误,且只在 48V+GD32 组合下被使用,48V+NXP 或高压平台下该延时(2 秒)永远不会执行——如果 NXP 平台同样需要擦除等待,这是一个缺失。
send_over_updata_code在 48V 下会向0x10a重发同一帧内容(:439-440):
s_t_batter_can_msg.frame_id = 0x10a;
l_u8_res = CAN_Send_Msg(&s_t_batter_can_msg, 8);
此时 frame_data[4..7] 仍是 "NEHC"(0x4E/0x45/0x48/0x43),并非该 ID 在其它函数中使用的 "ALEX"。这是有意为之还是笔误无法从代码判断,建议对照 BMS Bootloader 协议确认。
send_data_code缺少长度上界校验(:327/:333):memcpy(s_t_batter_can_msg.frame_data, buf, len)未检查len <= 8(frame_data只有 8 字节)。当前两个调用点分别传 8 和u8_Burn_len % 8(0~7),实际安全,但函数本身是"长度由调用者保证"的脆弱接口。
- 非 48V 路径完全没有应答确认,也无逐包重试:
send_addr_code/send_data_code只延时 1ms 就返回 TRUE。若 CAN 发送队列满或帧丢失,代码不感知,最终只能靠结束帧的 CRC 由 BMS 判失败——但那时本文件的代码不会重传整个固件(失败即UPGRADE_INFO_INIT退出),需要云平台重新触发。
send_addr_code的frame_data[1] = (Burn_len & 0xFF00) >> 8恒为 0(:220):u8_Burn_len本身是u8(上游s19.c计算l_u32_s19_len - 3,最大 252),高位永远取不到,是无效代码。同时也说明单行数据长度上限被隐含限制在 255 字节。
check_bms_upgrade_file()的判定过于宽松(:514-538):
- 只在
g_u8_fw_sdram1前 512 字节内找'S'+'1'/'3',若固件首行 S0 的注释区含有此类组合可能误判。- 找到
'S'但后一字符既不是'1'也不是'3'时,把u8_upgrade_type置为MAX_PLATFORM但不退出循环,后续若命中'S1'/'S3'又会覆盖;若始终未命中则返回 FALSE → 直接UPGRADE_INFO_INIT,u8_upgrade_type留在MAX_PLATFORM。 - 该函数只在
BMS_48V平台被调用(:569-579),高压平台的u8_upgrade_type恒为 0(UPGRADE_NXP的值为 1,0 是枚举外的非法值),代码里所有u8_upgrade_type == UPGRADE_GD32的比较对高压平台恒为假——当前逻辑能够工作,但依赖了"未初始化值不等于任何合法枚举"这一巧合。
- 找到
GetPercentUpgradeBMS()存在除零风险(:784):offset * 100 / get_length_file_updating(UPDT_BMS),若服务器尚未登记固件长度(返回 0),将触发异常。该函数被 LCD 与 Modbus 在主循环/协议解析中无条件调用,而不仅是在升级期间。
- 进度语义偏差:
u32_file_address_offset是"S19 文本文件中的字节位置",而get_length_file_updating()返回的应是服务端认定的固件长度。两者口径不一致(文本 vs 二进制)时百分比会失真;这一点在net/ems.c:6697也只是被原样透传。
u8_pck_indx为u8会回绕(:50、:709):注释明确写"包流水号 0~255 变化",大固件(>255 条 S19 记录)必然回绕,依赖 BMS 端按模处理。
- 阻塞时长可达 60 秒(
send_over_updata_code,:449):abs(OSTimeGet() - l_u32_tick_start_temp) >= 1000*60期间一直循环,无数据时每次OSTimeDlyHMSM(0,0,0,500)。这期间task_bms无法刷新电池状态,云平台上报的电池数据会停更;若同时有其它模块依赖电池数据做保护判断,存在风险窗口。
UPGRADE_INFO_INIT同时承担"中止态"与"复位态":memset后u8_upgrade_status变回UPGRADE_IDLE,因此不存在"卡在 INIT"的情况;但也意味着任何一次失败都会静默丢失进度,下次若服务器再次置位UPDT_BMS,升级会从头开始重新写入,而不是断点续传。u32_file_address_offset虽然被上报给云平台(ems.c:6697),但代码里没有任何地方用它做断点续传的初始化(ENTER_BOOT成功后无条件置 0,:606)。
s_t_batter_can_msg共用缓冲区导致frame_len残留:frame_len只在CAN_Send_Msg()中赋值,因此每次调用都会被覆盖,但frame_data之外的字段(如frame_type)在send_over_updata_code的 48V 分支中被沿用,属于隐式耦合;若后续有人插入新的发送函数而忘记设置frame_type,会复用上一次的值。
- 文件为单任务设计,无并发保护:
Get_Idx_Updt_BMS_Loacl()被 LCD / Modbus / 云平台任务读取u32_file_address_offset,而该字段在task_bms中被非原子地"读-改-写"(:644、:699),32 位对齐访问本身是单指令,但从属于状态机上下文,读到的是"某一行的边界值"而非绝对实时值——功能上可接受,属需知晓的行为特征而非缺陷。
- 函数头注释块被复制粘贴错位:
:64-75的注释块标题写的是@Name :static u8 send_enter_boot_code(void),但它标注的却是紧邻其后的CAN_Send_Msg()(:76);紧接着:82-93又出现一份完全相同的send_enter_boot_code注释块,真正对应send_enter_boot_code()(:94)。前一份属误植,且@Outpu/t : none被折行破坏。全文件大量函数缺少@Name头注释(send_addr_code、send_data_code、send_over_updata_code、Get_Idx_Updt_BMS_Loacl等)。
- 状态机超时计时量的作用域易误读:
s_u32_timeout_tick声明在households_batter_upgrade_handle()内部(:556,static局部变量),只在UPGRADE_IDLE分支被刷新(:568)。SYSTEM_STATUS_CHECK里abs(OSTimeGet() - s_u32_timeout_tick) > 60*1000实际量的是"进入本状态后经过的时间"(因为 IDLE 只执行一次),语义正确但依赖"IDLE 一定先于 CHECK 执行"这一隐式前件。