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

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_Msgstatic通用发送包装:先写入 TxMsg->frame_len = len,再调 can0_send_standard_pack() 发标准帧
send_enter_boot_codestatic发送"进入 Bootloader"帧并阻塞等待 BMS 应答(2 秒超时);48V+GD32 用 0x10a+"ALEX",否则 0x101+"SLJT"
send_addr_codestatic发送单行 S19 的"烧写地址+数据长度"帧(0x106 或 0x102);48V 阻塞等应答(2 秒),其它平台累加 CRC 后延 1ms
send_data_codestatic发送 1~8 字节固件数据(0x108 或 0x104);48V 阻塞等应答(6 秒),其它平台补 0xFF 到 8 字节并累加 CRC 后延 1ms
send_over_updata_codestatic发送"升级结束"帧(0x101 + "NEHC",前 2 字节带 CRC,48V 额外补发 0x10a),阻塞等待应答最长 60 秒
check_bms_upgrade_filestatic扫描 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)
                     ◄──────────────────────────────────────────────┘

关键点:

  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,从而立即中止升级。

4.3 CAN 帧格式与应答/超时机制

升级全程使用标准 CAN 帧(11 位 ID),8 字节数据域,无应用层校验和(除结束帧的 CRC 外)。ID 分两套:高压/默认路径与 48V+GD32 路径。

阶段默认路径帧 ID48V+GD32 路径帧 ID数据域
进入 Boot0x1010x10a[0..3]=00,[4..7]= 53 4C 4A 54("SLJT") / 41 4C 45 58("ALEX")
烧写地址0x1020x106[0..1]= Burn_len (小端),[2]=包序号,[3]=0,[4..7]= Burn_address (小端 32bit)
烧写数据0x1040x108默认:补 0xFF 到 8 字节后整帧发送;48V:只填实际数据、按实际长度发送
升级结束0x1010x101 + 再发一帧 0x10a[0..1]=CRC16(小端,仅非48V),[2..3]=0,[4..7]= 4E 45 48 43("NEHC")

应答匹配规则(send_enter_boot_code,:157-186):

各阶段超时与轮询间隔:

阶段超时无数据时的等待
send_enter_boot_code2000 msOSTimeDlyHMSM(0,0,0,50)
send_addr_code(仅 48V 等待)2000 msOSTimeDlyHMSM(0,0,0,5)
send_data_code(仅 48V 等待)6000 msOSTimeDlyHMSM(0,0,0,5)
send_over_updata_code60000 msOSTimeDlyHMSM(0,0,0,500)

send_over_updata_code 的回帧 ID 校验:48V 接受 0x101 或 0x10a;其它平台要求 0x107。

无应答路径(非 48V)的完整性保障:不等待回帧,改为在本地累加 CRC16:

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):

本文件用指针差 (u8_end_addr - g_u8_fw_sdram1) + 1 作为下一次解析的偏移,因此游标始终指向下一行的行首,逐行推进。u32_file_address_offset 同时被当作"已升级字节数"上报给云平台/LCD/Modbus。

4.5 与 uCOS-II 任务/调度的配合

  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 每轮刷新)。

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:16midware/bms/bms.c:2762(task_bms 升级分支)
u8 GetPercentUpgradeBMS(void)battery_upgrade.h:17hmi/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:18net/ems.c:6697(上报云平台升级进度)
全局变量无(s_bat_upgrade_tab、s_t_batter_can_msg 均为 static)—

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

来源模块被调用符号
hardware/can/can0.ccan0_send_standard_pack、can0_clear_standard_pack、get_can0_standard_pack
common/s19.cparsing_upgrade_file
common/crc.cget_data_crc_check
net/server.hg_u8_fw_sdram1、UPGRADE_NXP/UPGRADE_GD32/MAX_PLATFORM
net/upd_download.cSet_Status_Is_Updated_From_Server、get_length_file_updating、UPDT_BMS
midware/inverter/get_inverter_control_bat_disable_flag
midware/bms/bms.hBMS_48V / BMS_HV / BMS_MAX_TYEP
uCOS-IIOSTimeGet、OSTimeDlyHMSM
libcmemset、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. 备注与风险点

  1. u8_send_addr_cnt 是未使用字段(:43):结构体里声明并随 memset 复位,但全文件没有任何读写,属遗留字段。
  1. u8_erase_flash_flash 命名重复 "flash"(:45):语义是"擦除等待已执行",变量名有笔误,且只在 48V+GD32 组合下被使用,48V+NXP 或高压平台下该延时(2 秒)永远不会执行——如果 NXP 平台同样需要擦除等待,这是一个缺失。
  1. 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 协议确认。

  1. 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),实际安全,但函数本身是"长度由调用者保证"的脆弱接口。
  1. 非 48V 路径完全没有应答确认,也无逐包重试:send_addr_code/send_data_code 只延时 1ms 就返回 TRUE。若 CAN 发送队列满或帧丢失,代码不感知,最终只能靠结束帧的 CRC 由 BMS 判失败——但那时本文件的代码不会重传整个固件(失败即 UPGRADE_INFO_INIT 退出),需要云平台重新触发。
  1. 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 字节。
  1. check_bms_upgrade_file() 的判定过于宽松(:514-538):
  1. GetPercentUpgradeBMS() 存在除零风险(:784):offset * 100 / get_length_file_updating(UPDT_BMS),若服务器尚未登记固件长度(返回 0),将触发异常。该函数被 LCD 与 Modbus 在主循环/协议解析中无条件调用,而不仅是在升级期间。
  1. 进度语义偏差:u32_file_address_offset 是"S19 文本文件中的字节位置",而 get_length_file_updating() 返回的应是服务端认定的固件长度。两者口径不一致(文本 vs 二进制)时百分比会失真;这一点在 net/ems.c:6697 也只是被原样透传。
  1. u8_pck_indx 为 u8 会回绕(:50、:709):注释明确写"包流水号 0~255 变化",大固件(>255 条 S19 记录)必然回绕,依赖 BMS 端按模处理。
  1. 阻塞时长可达 60 秒(send_over_updata_code,:449):abs(OSTimeGet() - l_u32_tick_start_temp) >= 1000*60 期间一直循环,无数据时每次 OSTimeDlyHMSM(0,0,0,500)。这期间 task_bms 无法刷新电池状态,云平台上报的电池数据会停更;若同时有其它模块依赖电池数据做保护判断,存在风险窗口。
  1. UPGRADE_INFO_INIT 同时承担"中止态"与"复位态":memset 后 u8_upgrade_status 变回 UPGRADE_IDLE,因此不存在"卡在 INIT"的情况;但也意味着任何一次失败都会静默丢失进度,下次若服务器再次置位 UPDT_BMS,升级会从头开始重新写入,而不是断点续传。u32_file_address_offset 虽然被上报给云平台(ems.c:6697),但代码里没有任何地方用它做断点续传的初始化(ENTER_BOOT 成功后无条件置 0,:606)。
  1. s_t_batter_can_msg 共用缓冲区导致 frame_len 残留:frame_len 只在 CAN_Send_Msg() 中赋值,因此每次调用都会被覆盖,但 frame_data 之外的字段(如 frame_type)在 send_over_updata_code 的 48V 分支中被沿用,属于隐式耦合;若后续有人插入新的发送函数而忘记设置 frame_type,会复用上一次的值。
  1. 文件为单任务设计,无并发保护:Get_Idx_Updt_BMS_Loacl() 被 LCD / Modbus / 云平台任务读取 u32_file_address_offset,而该字段在 task_bms 中被非原子地"读-改-写"(:644、:699),32 位对齐访问本身是单指令,但从属于状态机上下文,读到的是"某一行的边界值"而非绝对实时值——功能上可接受,属需知晓的行为特征而非缺陷。
  1. 函数头注释块被复制粘贴错位::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 等)。
  1. 状态机超时计时量的作用域易误读: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 执行"这一隐式前件。