bms.c
static)2.4 本文件重点使用的宏与枚举3. 函数清单4. 核心逻辑解析4.1 task_bms 主循环与调度时序(bms.c:2731-2935)4.2 bat_mos_control MOS 闭合重试状态机(bms.c:1019-1125)4.3 bat_set_sn_handle 并机 SN 改写状态机(bms.c:1279-1419)4.4 bat_self_lock_handle 电池自锁状态机(bms.c:1429-1527)4.5 逆变器加热状态机 bat_inv_heat_deal(bms.c:2297-2436)4.6 电池自加热状态机 bat_self_heat_deal(bms.c:2467-2681)4.7 加热方式选择 bat_heat_handle(bms.c:2692-2723)4.8 配置持久化:fresh_and_save_bat_info / fresh_and_save_bat_extend_info(bms.c:735-900)4.9 通信丢失处理的两个层次(bms.c:1133-1190)4.10 产品号 → 派生参数的映射族4.11 打包型只读访问器(bms.c:3563-3674)4.12 自定义 CAN 帧下发队列(bms.c:4233-4353)4.13 其他周期逻辑5. 对外接口5.1 本文件导出的函数与全局变量5.2 本文件调用的外部函数/变量6. 依赖关系7. 备注与风险点源文件路径:
midware/bms/bms.c所属模块:midware / bms(BMS 电池管理中间层) 代码行数:4438
1. 文件职责
本文件是 EMS 主控固件中「电池管理」的应用层主体,承担四件事:
- 数据汇聚:以
task_bms(bms.c:2731,uCOS-II 任务,100ms 周期)为主循环,从 CAN0 收 BMS 上报帧,交给midware/bms/alpha/battery_comm.c的alpha_bms_package_parser()解析,结果全部落在全局g_t_bat_info(bms.c:39,绝对定位在外部 SDRAM)里。所有上层模块(logic/、net/、hmi/、modbus/、inverter/)都通过本文件的bms_read_*/bms_get_*只读访问器取数。
- 参数映射:把电池产品号(如
ALPHA_M4850_BATTERY_ID)翻译成型号字符串、容量、DoD、充放电截止电压、48V/高压平台等派生参数,并做变化检测后save_job_post()落 Flash。
- 电池加热控制:实现两套加热状态机——「逆变器给电池加热」(
bat_inv_heat_deal)和「电池自加热」(bat_self_heat_deal),由bat_heat_handle按电池能力/SOC/工况选择,含进入、执行、正常退出、故障退出、故障恢复全流程。
- 辅助业务:电池并机 SN 改写(
bat_set_sn_handle)、电池自锁配置与清除(bat_self_lock_handle)、每天 1 点校时(BMS_time_cmd_handle)、MOS 干接点控制(bat_mos_control)、逆变器失联强制电池关机(force_bat_shutdown_handle)、低 SOC 事件上报(bat_low_soc_detect)、自定义 CAN 帧下发队列(can_send_*)。
数据流位置:CAN0 驱动(hardware/can/can0.c)→ task_bms 轮询 get_can0_exten_pack() → battery_comm.c 解析 → g_t_bat_info → 本文件派生出 u16_status_flag、u32_max_charge/dischg_power、加热指令 u8_inv_heat_cmd/u8_bms_heat_cmd 等控制量 → 被 logic/(功率限制)、inverter/(下发 DSP)、net/(云平台上报)消费。
2. 关键数据结构与宏
2.1 本文件定义/持有的全局变量
| 名称 | 类型 | 说明 |
|---|---|---|
g_t_bat_info | BATTERY_TABLE_T | bms.c:39,全工程电池数据的唯一宿主。用 __attribute__((at(SDRAM_BAT_INFO_ADDR))) 钉在外部 SDRAM(hardware/sdram/sdram.h:65,注释标注 7KB)。含 t_config_data(落盘配置)、t_extend_config_data(落盘扩展配置)、t_run_info(实时数据)、t_activate_time_info[12]、t_static_data(断通信保留的加热中间态) |
g_t_bat_set_sn_info[6] | BATTERY_SET_SN_TABLE_T | bms.c:40,并机 SN 改写任务的 6 个槽位,由 net/server.c 填状态、本文件发送 |
g_t_can_send_queue | CAN_SEND_QUEUE_T | bms.c:4236,自定义 CAN 帧队列(最多 6 帧),由 net/server.c 入队、net/ems.c 周期驱动 can_send_task() |
2.2 关键外部全局变量(本文件读写)
| 名称 | 类型 | 说明 |
|---|---|---|
g_t_logic_info | LOGIC_INFO_T(logic/logic.h) | 读:Force_Charge_Local/Force_Charge_user(强充)、u8_time_peak_clip_flag/u8_time_valley_fill_flag(峰谷)、u8_bat_adapt_bat_flag(写)、t_config_data.u8_ongrid_low_soc/u8_offgrid_low_soc |
g_t_ems_info | EMS_INFO_T(data/data.h) | 读:u_ems_fault.bit.u8_inv_lost_bat_shutdown、u8_bat_mixed_installation_num_error;写:经 ems_set_fault_bit() |
g_t_inv_info | INVERTER_INFO_T(midware/inverter/inverter.h) | 读:t_run_info.u8_run_mode(cNormalMode/cUPS/cByPassMode/cCheckMode/cWaitMode)、u8_inverter_comm_lost_flag;写:t_control_parameter.u8_bat_heat_data_flag |
g_t_inovance_comm_info | INOVANCE_COMM_INFO_T | 写:u64_sci_flag.bit.u8_inovance_bat_heater_sta_flag(告知汇川逆变器加热继电器状态) |
g_system_function_info | 系统功能开关(data/system_config) | 读写:t_dev_fun.bms_fun_u.bit.u8_bat_activate_time、t_dev_fun.ems_fun_3_u.bit.u8_bat_heat |
g_t_sys_datetime | RTC 时间 | 读:RTC_Hours/RTC_Minutes/RTC_Seconds,用于每天 1 点校时 |
g_t_system_base_info | 系统基础信息 | 读:u8_factory_flag(工厂模式)、u16_oem_flag(OEM_HHE 判定) |
g_u8_can_send_done_flag | u8(net/server.c:77) | 写:自定义 CAN 帧全部发完后置 TRUE,供 net/ems.c:4915 触发云平台响应 |
2.3 静态文件作用域状态变量(各函数内 static)
这些变量跨调用周期保持,是本文件各类状态机的实际载体。
| 所在函数 | 变量 | 说明 |
|---|---|---|
bat_mos_control_hhe_oem | u8_bms_mos_open_flag | HHE OEM 干接点曾断开 MOS 的标记 |
bat_mos_control | s_u8BatMOSFlag(初始 1)、s_u32BatMOSTick、s_u32BatCurrTick | MOS 闭合重试状态机(1 待闭 / 2 已发闭合 / 3 重试)与两个时间戳 |
force_bat_shutdown_handle | s_u8_inv_lost_flag、s_u32_inverter_comm_lost_tick、s_u32_battery_shut_down_tick、u8_start_tick_flag | 强制关机 2s 防抖与逆变器失联 12h 计时 |
bat_set_sn_handle | s_u8_current_index(0~5 轮转)、s_u8_wait_tick、s_u32_delay_tick | SN 改写任务轮询槽位与 200ms 节流 |
bat_self_lock_handle | s_e_self_lock_status、s_u32_time_delay | 自锁状态机当前位置与等待清除的 5s 计时 |
BMS_time_cmd_handle | s_u8_send_flag(初始 1) | 防止 1 秒内重复发校时 |
bat_heat_handle | s_u8_inti_flag | 首次运行做一次 bat_heat_data_inti() |
bat_self_heat_deal | s_bat_self_heat_fsm、s_heat_self_fsm_sub[BMS_INV_HEAT_MAX](初始 {BMS_ENTER_SUB}) | 自加热主/子状态机 |
bat_low_soc_detect | l_u8_low_soc_flag | 低 SOC 事件边沿标记,防止重复上报 |
task_bms | s_t_bms_info、l_u8_bms_master_lost_flag、l_u8_bms_updated_flag、l_u32_bms_comm_tick、l_u32_bms_recv_tick、l_u32_set_bat_tick | CAN 帧缓冲、主机丢失边沿、升级边沿、通信/收帧/参数下发时间戳 |
2.4 本文件重点使用的宏与枚举
| 名称 | 类型 | 说明 |
|---|---|---|
BATTERY_MAX_NUM | 宏 | 12,电池台数上限(bms.h:18) |
ALPHA_*_BATTERY_ID / OEM_*_BATTERY_ID | 宏 | 约 35 个电池产品号(bms.h:20-59),本文件全部映射 switch 的 case 标签 |
ALPHA_BAT_HEAT_MAX_CURRENT_ERR_RANGE | 宏 | 200,加热电流最大误差(bms.c:1782 使用) |
ALPHA_BAT_HEAT_MAX_VOLTAGE_ERR_RANGE | 宏 | 100,加热电压最大误差(bms.c:1783 使用时乘电池台数) |
DEBUG_BMS | 宏 | 0(bms.h:16),为 1 时 bat_inv_heat_deal/bat_self_heat_deal 打印约 50 行状态 |
VERSION_BMS_HARD / VERSION_BMS_HARD_2 | 宏 | "V1.00" / "V1.02",bms_read_hard_version() 返回值 |
BMS_HEAT_STATE | 枚举 | 加热主状态机:BMS_NORMAL_FSM / BMS_INV_HEAT_FSM / BMS_INV_HEAT_FAULT_FSM / BMS_INV_HEAT_MAX |
BMS_FSM_SUBSTATE | 枚举 | 子步骤:BMS_ENTER_SUB / BMS_EXECUTE_SUB / BMS_EXIT_SUB |
BMS_SET_SN_STATUS_E | 枚举 | 并机 SN 状态机:BMS_SET_SN_NONE/IDLE/SRC_SEND_E/DEST_SEND_E/WATTING_E/FINISH_E |
BMS_SELF_LOCK_STATUS_E | 枚举 | 自锁状态机:BMS_SELF_LOCK_NONE/CONFIG_SYNC/IDLE/WAIT_SEND_CLEAR/WAIT_CLEAR/UPLOAD_FAULT_CLEAR;子命令 BMS_SELF_LOCK_SEND_CONFIG_COMMAND(0)/SEND_CLEAR_COMMAND(1) |
BMS_PLATFORM | 枚举 | TYPE_NXP=1 / TYPE_GD32=2 / MAX_TYEP=3 |
BMS_HARDWARE_PLATFORM | 枚举 | BMS_48V=0 / BMS_HV=1 / BMS_MAX_TYEP |
BATTERY_STATUS_E | 枚举 | 充放电权限字:FORBID_CHG_DISCHG_E(0)/FORBID_CHG_ALLOW_DISCHG_E(1)/ALLOW_CHG_FORBID_DISCHG_E(256)/ALLOW_CHG_ALLOW_DISCHG_E(257)/FORCE_CHG_FORBID_DISCHG_E(512)/FORCE_CHG_ALLOW_DISCHG_E(513) |
SOC_LOW_STATUS | 枚举 | 0x2000(net/ems.h:262),低 SOC 事件位 |
BAT_SELFLOCK_STATUS | 枚举 | 0x20000(net/ems.h:266),自锁状态上报事件位 |
BATTERY_CONFIG_PAGE / BATTERY_EXTEND_CONFIG_PAGE | 宏 | 4074 / 4087(data/system_config/system_config.h:26,39),Flash 保存任务页号 |
UPDT_BMS | 枚举 | 云平台 BMS 固件升级状态标记,用 Get/Set_Status_Is_Updated_From_Server() 读写 |
ALPHA_BATTERY_GET_REQUEST_CODE_ID / ..._OPERATION_STATE_1_ID / _8_ID / _15_ID | 宏 | 0x18FFA1F4 / 0x0CF100C0 / 0x18F30001 / 0x18F218F3(battery_comm.h:163,62,69,76),handle_bat_heat_prestore_info() 据此识别加热相关帧 |
DI_1_ACCESS_POINT | 宏 | di0_input_bit_get()(dry_node.h:24),干接点 1 输入,HHE OEM MOS 控制用 |
INVERTER_TYPE / SET_TYPE / ALPHAESS_TYPE / INVERTER_MAX | 宏 | 编译期逆变器型号选择,控制 bat_heat_fault_check/bat_heat_vol_current_check 是否编译,以及盛能杰 30s 起压等待 |
3. 函数清单
本文件共 144 个函数(含 29 个 static)。下表按源码顺序完整列出。
| 函数名 | 作用域 | 说明 | ||
|---|---|---|---|---|
bms_comm_detect | static | 开机 60s 后检测 BMS 通信丢失,置/清 EMS 故障位 6(u8_bms_lost) | ||
handle_bat_status_flag | 全局 | 把 BMS 上报的 u8_allow_charg_discharg_flag 翻译成 u16_status_flag(强充优先),并刷新 u8_bat_adapt_bat_flag | ||
handle_bat_chg_cut_rate_volt | 全局 | 按电池产品号把充电截止电压(*_CHG_CUT_VOLT/10)写入 t_run_info | ||
handle_bat_dischg_cut_rate_volt | 全局 | 按电池产品号写入放电截止电压 | ||
handle_battery_cell_capacity | 全局 | 按电池产品号写入 u16_cell_rate_capacity(0.1Ah) | ||
bms_get_energy_rating | 全局 | 读系统额定能量 u16_energy_rating | ||
bms_get_real_time_energy_rating | 全局 | 读实时电池能量总数 u16_real_time_energy_rating | ||
handle_battery_dod | 全局 | 按电池产品号写入 u8_dod(80/90/95/96/100) | ||
strncpy_0 | 全局 | 定长字符串拷贝并补零(自带 NULL/maxlen==0 检查) | ||
handle_battery_model | 全局 | 按产品号写入型号字符串 s_type_splice[31](OEM 号映射到客户专用字符串) | ||
handle_battery_cell_temper_number | 全局 | 按产品号设定单体温度采样点数量(仅 G3-3.8S 为 8,其余 0) | ||
handle_battery_cell_volt_number | 全局 | 按产品号设定单体电压采样点数量(仅 G3-3.8S 为 30,其余 0) | ||
get_bms_hardware_platform | 全局 | 返回 48V / 高压平台;default 分支用 u16_cell_rate_volt > 6000 反推 | ||
fresh_and_save_bat_info | 全局 | 逐字段比对 t_run_info 与 t_config_data,变化则更新并 save_job_post(BATTERY_CONFIG_PAGE) | ||
fresh_and_save_bat_extend_info | 全局 | 比对/保存单台电池软件版本、平台类型、SN;清掉超出当前台数及老电池不适配的字段,必要时保存扩展页 | ||
handle_bat_activate_time_info | 全局 | 汇总各台激活时间,生成 EMS 侧配置标志与云平台配置标志 | ||
bat_mos_control_hhe_oem | 全局 | HHE OEM 专用:干接点 1 状态联动断开/闭合电池 MOS | ||
bat_mos_control | 全局 | MOS 闭合重试状态机(30 分钟周期重试、5/10 分钟无充电电流则清除) | ||
bms_lost_handle | static | 电池通信丢失时整体清零 t_run_info(仅保留 u8_type) | ||
bms_master_lost_handle | static | 主机电池丢失时逐字段清零关键实时量并清 u8_energy_sync_flag | ||
force_bat_shutdown_handle | 全局 | 并网/离网异常工况下 2s 防抖后强制电池关机;逆变器失联 12h 置故障位 17 | ||
bat_set_sn_handle | 全局 | 并机 SN 改写状态机,6 槽位轮转、每槽 200ms 节流、5 次重发 | ||
bat_self_lock_handle | 全局 | 电池自锁配置同步/清除状态机(含 5s 等待与状态上报) | ||
bms_read_heat_fault_code | static | 遍历 13 个扩展保护 1 结构,任一 u8_heat_film_abnormal 置位则返回 1 | ||
bms_read_over_temperature_code | static | 遍历 12 台电池的告警/保护码,任一过温则返回 1 | ||
BMS_time_cmd_handle | static | 每天 1:00:00 发一次 BMS 校时;也响应 u8_timing_flag 请求 | ||
bat_bat_connet_check | static | 电池在线且未通信丢失返回 0,否则 1 | ||
bat_heat_low_vol_state_check | static | 欠压告警/保护且有加热请求时返回 0(强充加热场景) | ||
bat_heat_execute_check | static | 系统未升级 + 电池在线 + (欠压强充 或 用户开加热且电池请求加热)时返回 0 | ||
bat_heat_inv_ongrid_check | static | 逆变器正常模式返回 1;inverter_read_grid_freq() < 4000(离网)返回 0 | ||
bat_heat_exit_check | static | 加热正常退出的复合条件:用户关使能、请求清除、离网、系统升级任一成立则返回 0 | ||
bat_heat_data_inti | static | 初始化 t_static_data 全部加热字段(SOC 阈值 20,升温阈值 2) | ||
bat_heat_fault_check | static | 逆变器实测加热电流/电压与 BMS 上报值比对,误差超限返回 1(INVERTER_TYPE != SET_TYPE 时编译) | ||
get_bat_heat_forbid_power_flag | 全局 | 读 u8_bat_forbid_chg_dischg_flag(加热期间是否停电池功率) | ||
bat_heat_fault_drop_out | static | 加热异常退出的三步退出(停逆变器加热 → 发 0x55 → 等继电器断开),并记录加热故障 | ||
bat_heat_inv_state_check | static | 逆变器侧可进入加热的状态检查(贝瓦强充加热允许 bypass/check/wait/normal,其他仅 normal) | ||
bat_heat_into_1_step | static | 进入加热第 1 步:停电池功率,3s 后向逆变器发加热指令并置 u8_bat_heat_data_flag | ||
bat_heat_into_2_step | static | 进入加热第 2 步:等待 u32_wait_time 后向 BMS 发 0xAA 加热指令 | ||
bat_heat_into_sta_check | static | 进入加热第 3 步:检测电池是否上报加热中;2 分钟未进入则判加热膜故障并跳故障状态机 | ||
bat_heat_vol_current_check | static | 加热期间电流/电压异常持续 1s 判为加热膜故障(INVERTER_TYPE != SET_TYPE 时编译) | ||
bat_heat_hardware_fault_check | static | 从 BMS 读加热膜故障/过温,并判断加热 15 分钟未升温超过阈值(混装不判断) | ||
bat_heat_into_fault_deal | static | 统一跳转到加热故障状态机并打时间戳 | ||
bat_heat_drop_out | static | 加热正常退出三步(停逆变器 → 发 0x55 → 等继电器断开复位),退出后按需闭合电池继电器 | ||
bat_heat_fault_fsm_deal | static | 加热故障态处理:故障退出 + 10 分钟后最多 3 次恢复机会 | ||
bat_heat_no_request_deal | static | 无加热请求时清零所有加热指令、清故障、放开电池功率 | ||
bat_charge_demand_check | static | 充电需求检测:峰谷/调度/时段强充/一键强充/欠压强充/5:00~18:00 任一成立返回 0 | ||
bat_new_heat_execute_check | static | 新加热方案执行条件:支持自加热的电池需有充电需求,否则直接允许 | ||
bat_inv_heat_deal | 全局 | 逆变器加热主状态机(NORMAL / INV_HEAT / INV_HEAT_FAULT 三态) | ||
bat_self_heat_data_inti | static | 自加热数据初始化(发 0x55 退出、清步骤与展示状态、清加热故障) | ||
bat_self_heat_deal | 全局 | 电池自加热状态机(自带独立的主/子状态机,发 0xBB 进加热) | ||
bat_heat_handle | 全局 | 加热总入口:首帧初始化、按电池能力刷新 u8_bat_heat 功能位,择路调用自加热或逆变器加热 | ||
task_bms | 全局 | BMS 主任务:100ms 周期,处理使能/升级/指令下发/CAN 收帧/丢包检测/校时/加热 | ||
bms_get_connect_state | 全局 | 读电池在线标志 | ||
bms_reset | 全局 | 置 u8_bms_restart,请求重启电池 | ||
bms_clear_energy | 全局 | 清零充放电电量并置 u8_bms_clear_energy | ||
bms_set_shutdown | 全局 | 写 u8_set_bms_shutdown | ||
bms_set_mos_enable | 全局 | 设置充放电 MOS 开合并置 u8_bms_mos_set_flag | ||
bms_set_mos_open_enable | 全局 | 设置主 MOS 断开/闭合并置 u8_bms_mos_open_set_flag | ||
bms_set_ready_enable | 全局 | 写电池使能配置位 | ||
bms_get_ready_enable | 全局 | 读电池使能配置位 | ||
bms_get_store_type | 全局 | 读电池产品号 t_config_data.u8_type | ||
bms_get_store_MIX_type | 全局 | 读混装第 mun 台产品号 | ||
bms_get_store_num | 全局 | 读电池台数配置值 | ||
bms_get_store_ver | 全局 | 返回版本字符串(id=0 整机 Vx.yz,1~num 单台),静态缓冲 | ||
get_platform_string | static | 平台号转字符串("NXP" / "GD " / " "),静态缓冲 | ||
bms_get_platform | 全局 | 返回指定电池的平台字符串,静态缓冲 | ||
bms_get_store_sn | 全局 | 返回指定电池 SN 指针(无边界检查) | ||
bms_set_self_lock_enable | 全局 | 写自锁使能配置 | ||
bms_get_self_lock_enable | 全局 | 读自锁使能配置 | ||
bms_set_self_lock_clear | 全局 | 置自锁清除请求 | ||
bms_read_chg_cut_rate_volt | 全局 | 读充电截止电压(配置值) | ||
bms_read_chg_max_rate_cur | 全局 | 计算最大充电倍率电流 = 最大充电功率×1000/总压 | ||
bms_read_dischg_cut_rate_volt | 全局 | 读放电截止电压(配置值) | ||
bms_read_dischg_max_rate_cur | 全局 | 计算最大放电倍率电流 | ||
bms_read_max_charge_power | 全局 | 读最大充电功率;BMS 升级进度 >50% 或混装数量异常时返回 0 | ||
bms_read_max_dischg_power | 全局 | 读最大放电功率;UPS + ATE 允许放电时兜底 1000W;升级/混装异常返回 0 | ||
bms_read_work_status | 全局 | 读电池工作状态 | ||
bms_read_relay_status | 全局 | 读电池继电器状态 | ||
bms_read_num | 全局 | 读电池台数(运行值) | ||
bms_read_mixed_installation_flag | 全局 | 读混装标志(0 老电池/新老混装,1 新电池) | ||
get_bat_mixed_installation_num_error_flag | 全局 | M-BAT-13.9P 场景下按混装标志与台数判定混装数量异常,置/清 EMS 故障位 28 | ||
bms_read_shut_down | 全局 | 读电池关机标志 | ||
bms_read_temp | 全局 | 读电池温度 | ||
bms_read_total_soc | 全局 | 读电池组总 SOC | ||
bms_read_total_volt | 全局 | 读电池组总压 | ||
bms_read_total_cur | 全局 | 读电池组总电流 | ||
bms_read_status_flag | 全局 | 读充放电使能状态字 | ||
bms_read_total_power | 全局 | 计算总功率 = 总压 × 总流 / 10000 | ||
bms_read_single_soc | 全局 | 读单台 SOC(无边界检查) | ||
bms_read_single_cur | 全局 | 读单台电流(无边界检查) | ||
bms_read_protection_code | 全局 | 合并 13 个电池保护码(按位或) | ||
bms_read_protection_extend2_code | 全局 | 合并 13 个扩展保护 2 码(按位或) | ||
bms_read_fault_code | 全局 | 合并 13 个电池故障码(按位或) | ||
bms_read_cell_min_volt_info | 全局 | 打包最低单体电压信息(模块号<<24 \ | 串号<<16 \ | 电压) |
bms_read_cell_max_volt_info | 全局 | 打包最高单体电压信息 | ||
bms_read_low_temper_info | 全局 | 打包最低温度信息 | ||
bms_read_high_temper_info | 全局 | 打包最高温度信息 | ||
bms_read_pwrline_low_temper_info | 全局 | 打包功率线最低温度信息 | ||
bms_read_pwrline_high_temper_info | 全局 | 打包功率线最高温度信息 | ||
bms_read_single_cell_min_volt_info | 全局 | 单台最低单体电压打包值(无边界检查) | ||
bms_read_single_cell_max_volt_info | 全局 | 单台最高单体电压打包值(无边界检查) | ||
bms_read_single_low_temper_info | 全局 | 单台最低温度打包值(无边界检查) | ||
bms_read_single_high_temper_info | 全局 | 单台最高温度打包值(无边界检查) | ||
bms_read_single_soh | 全局 | 读单台 SOH(无边界检查) | ||
bms_read_single_single_cycle | 全局 | 读单台循环次数(无边界检查) | ||
bms_read_single_single_runhrs | 全局 | 读单台运行小时(无边界检查) | ||
bms_read_hard_version | 全局 | 返回 BMS 硬件版本字符串 | ||
bms_read_battery_model | 全局 | 返回电池型号字符串指针 | ||
bms_read_battery_cell_model | 全局 | 返回混装下第 num 台型号(无边界检查) | ||
bms_read_battery_cell_dod | 全局 | 返回混装下第 num 台 DoD(无边界检查) | ||
bms_read_battery_MIX_capacity | 全局 | 返回混装下第 num 台容量(无边界检查) | ||
bms_MIX_battery | 全局 | 返回混搭信息有效标志 | ||
bms_read_battery_capacity | 全局 | 系统总容量:混装逐台累加,同型号单台容量×台数 | ||
bms_read_battery_available_capacity | 全局 | 返回可用容量(即配置 DoD) | ||
bms_read_battery_series_para_state | 全局 | 返回串并联状态 | ||
bms_cell_rated_vot | 全局 | 返回额定单体电压(0.01V → V) | ||
bms_read_rated_vot_instant | 全局 | 返回额定电压瞬时值 | ||
bms_read_charge_pro_cur_step_1 | 全局 | 返回充电倍率电流 step1 | ||
bms_read_discharge_pro_cur_step_1 | 全局 | 返回放电倍率电流 step1 | ||
bat_low_soc_detect | 全局 | 并网/UPS 下 SOC 低于阈值时上报 SOC_LOW_STATUS,边沿触发 | ||
handle_bat_set_activation_time | 全局 | 云平台下发的电池激活时间写入(逐台比对后写 u_bat_activate_time) | ||
get_bms_cell_data_support | 全局 | 仅 G3-3.8S 返回 TRUE(支持云 BMS 分钟级数据) | ||
bms_read_current_actual_remaining_available_electricity | 全局 | 返回当前实际剩余可用电量 | ||
bms_read_max_system_storage_capacity | 全局 | 返回系统最大存储能力 | ||
handle_bat_heat_prestore_info | 全局 | 按 CAN 帧 ID 预存加热请求/自加热能力/加热台数,并触发 SOC 判定 | ||
handle_bat_heat_soc_info | 全局 | 逐台判断 SOC 是否 ≤ 阈值(默认 20%),刷新 u8_bat_soc_low_flag | ||
bms_read_single_max_capacity | 全局 | 需求 1:单台 SOC 算法最大容量(Ah),带边界检查 | ||
bms_read_fault_code_by_id | 全局 | 需求 2:指定电池 ID 的原始故障码,带边界检查 | ||
bms_read_warning_code_by_id | 全局 | 需求 2:指定电池 ID 的原始告警码,带边界检查 | ||
bms_is_fault_code_valid | 全局 | 需求 2:指定电池故障码有效标志,带边界检查 | ||
bms_is_warning_code_valid | 全局 | 需求 2:指定电池告警码有效标志,带边界检查 | ||
can_send_queue_clear | 全局 | 清空自定义 CAN 帧队列 | ||
can_send_queue_add | 全局 | 入队一帧(上限 6 帧,满则打印丢弃) | ||
can_send_queue_start | 全局 | 启动发送(索引归零、置发送中、记时间戳) | ||
can_send_custom_frame | static | 组帧并按帧类型调 can0_send_standard_pack() 或 can0_send_exten_pack() | ||
can_send_task | 全局 | 队列发送推进器(每帧间隔 10ms,失败也推进),发完置 g_u8_can_send_done_flag | ||
bms_read_a_cluster_mode | 全局 | 需求 5:A 簇运行模式(0 正常/1 维护/2 单簇) | ||
bms_read_a_cluster_id | 全局 | 需求 5:A 簇第 index(0~5) 位电池 ID,越界返回 0 | ||
bms_read_a_cluster_status | 全局 | 需求 5:A 簇第 index 位旁路状态 | ||
bms_read_b_cluster_mode | 全局 | 需求 5:B 簇运行模式 | ||
bms_read_b_cluster_id | 全局 | 需求 5:B 簇第 index 位电池 ID | ||
bms_read_b_cluster_status | 全局 | 需求 5:B 簇第 index 位旁路状态 | ||
bms_is_a_cluster_valid | 全局 | 需求 5:A 簇数据有效标志 | ||
bms_is_b_cluster_valid | 全局 | 需求 5:B 簇数据有效标志 |
4. 核心逻辑解析
4.1 task_bms 主循环与调度时序(bms.c:2731-2935)
任务启动时先 memset(&g_t_bat_info, 0, sizeof(g_t_bat_info))(bms.c:2741)整体清零,然后进入 for(;;)。每轮周期由末尾 OSTimeDlyHMSM(0, 0, 0, 100) 决定,即 100ms;OS_TICKS_PER_SEC = 1000(UCOSII/uCOS-CONFIG/os_cfg.h:51),所以代码里所有 OSTimeGet() 差值都是毫秒。
一轮的判定顺序如下(顺序即优先级):
① 电池未使能短路分支(bms.c:2744-2754)
if(bms_get_ready_enable() == TRUE) // 注释写"系统中未接入电池"
{
l_u32_bms_comm_tick = OSTimeGet();
g_t_bat_info.t_run_info.u8_connect = TRUE; // 强制置在线
g_t_bat_info.t_run_info.u8_bms_restart = FALSE;
g_t_bat_info.t_run_info.u8_bms_clear_energy = FALSE;
ems_set_fault_bit(6, FALSE);
Set_Status_Is_Updated_From_Server(UPDT_BMS, FALSE);
OSTimeDlyHMSM(0, 0, 0, 100);
continue;
}
注意这里的语义反直觉:u8_ready 为 TRUE 时跳过整个采集流程并假装电池在线。这是给「系统不接电池」配置用的旁路,u8_connect = TRUE 是为了让上层判断不至于报 BMS 丢失。
② BMS 通信检测(bms.c:2756) —— bms_comm_detect(),开机 60s 后开始生效,条件是 u8_connect == FALSE 且 pvcharger_read_machine_mode() != PVCHARGR_FROM_E(非纯光伏充电机型)时置 EMS 故障位 6(EMS_FAULT_BMS_LOST)。
③ BMS 固件升级分支(bms.c:2758-2779) —— 若 Get_Status_Is_Updated_From_Server(UPDT_BMS) == TRUE,调用 households_batter_upgrade_handle(get_bms_hardware_platform()) 做整包升级,并在首次进入时清零 t_run_info(保留 u8_type);每轮只 OSTimeDlyHMSM(0,0,0,10)(10ms)后 continue——即升级期间不执行任何采集与加热逻辑。
④ 指令下发段(bms.c:2783-2854)
| 触发条件 | 动作 | 完成后清标志方式 |
|---|---|---|
u8_bms_mos_open_set_flag == TRUE | send_battery_cmd(0x0cf200c0, 1, 0xf2/0xf3, 0x00) 断开/闭合主 MOS | = (发送返回值 == FALSE),即发送成功才清标志 |
u8_bms_mos_set_flag == TRUE | send_battery_cmd(0x0cf200c0, 1, 0xaa/0x55, 0x1e) 开/关充放电 MOS(0x1e=30s 超时参数) | 同上 |
u8_bat_activation_ems_set_flag[i] / u8_bat_activation_web_set_flag[i] | send_battery_activate_time_cmd(1/2, i) 逐台写激活时间 | 同上 |
u8_bms_restart == TRUE | send_battery_restart_cmd() | 由 BMS 回帧链路清 |
u8_set_bms_shutdown == TRUE | send_battery_shutdown_cmd() | 同上 |
u8_bms_clear_energy 或 u8_bms_soc_display_set_flag 或距上次下发 >1000ms | send_battery_set_code_1_cmd() + send_battery_set_code_2_cmd() | l_u32_set_bat_tick = OSTimeGet() |
注意第 3 列「发成功才清标志」的写法 flag = (send_xxx() == FALSE):这是本文件的通用重试模式,函数返回 FALSE 表示本次未发出(例如 CAN 忙),标志保留以便下一轮重试。
SOC 显示范围下发是 1 秒周期无条件刷新的(abs(OSTimeGet()-l_u32_set_bat_tick) > 1000),这是为了防止 BMS 侧参数被意外复位。
⑤ CAN0 收帧解析段(bms.c:2856-2886)
l_u32_bms_recv_tick = OSTimeGet();
while(get_can0_exten_pack(&s_t_bms_info) == TRUE)
{
if(alpha_bms_package_parser(s_t_bms_info.frame_id, s_t_bms_info.frame_data) == TRUE)
{
l_u32_bms_comm_tick = OSTimeGet(); // 刷新通信时间戳
g_t_bat_info.t_run_info.u8_connect = TRUE;
bat_mos_control();
if(g_t_system_base_info.u16_oem_flag == OEM_HHE) bat_mos_control_hhe_oem();
handle_bat_chg_cut_rate_volt(); handle_bat_dischg_cut_rate_volt();
handle_battery_cell_capacity(); handle_battery_dod();
handle_battery_model();
handle_battery_cell_temper_number(); handle_battery_cell_volt_number();
fresh_and_save_bat_info(); fresh_and_save_bat_extend_info();
handle_bat_activate_time_info();
handle_bat_heat_prestore_info(s_t_bms_info.frame_id);
}
if (abs(l_u32_bms_recv_tick - OSTimeGet()) > 50) break; // 单轮最多耗 50ms 收帧
}
这里有两个关键设计:一是收帧循环带 50ms 时间上限,避免 CAN 帧洪泛时占满任务周期(abs() 是为了兼容 OSTimeGet() 在 32 位溢出后的环绕);二是那一长串 handle_* 在每收到一帧有效数据时都重复执行,而不是每轮一次。这些函数都带「值未变则不写」的内层判断,所以功能上等价于「按需刷新」,但代价是同一轮内可能重复计算多次。
⑥ 周期性业务段(bms.c:2888-2927),每轮固定执行:
| 调用 | 作用 |
|---|---|
handle_bat_status_flag() | 刷新充放电权限字 |
force_bat_shutdown_handle() | 逆变器异常时的强制电池关机 |
bat_set_sn_handle() | 并机 SN 改写任务推进 |
bat_low_soc_detect() | 低 SOC 事件上报 |
bat_self_lock_handle() | 电池自锁配置/清除 |
BMS_time_cmd_handle() | 每天 1 点校时 |
bat_heat_handle() | 加热总入口(内含两个状态机) |
get_bat_mixed_installation_num_error_flag() | 混装数量异常检测 |
(bat_log_handle() 被 #if 0 关闭,注释说明电池日志功能改由 BMS 上位机实现。)
⑦ 通信丢失检测(bms.c:2899-2917)
if (abs(l_u32_bms_comm_tick - OSTimeGet()) > 10*1000) // 10s 无有效帧
{
if (g_t_bat_info.t_run_info.u8_connect == TRUE)
{
g_t_bat_info.t_run_info.u8_connect = FALSE;
bms_lost_handle(); // memset 整个 t_run_info
}
}
因为 l_u32_bms_comm_tick 只在「解析成功」时刷新,10 秒阈值针对的是有意义的 BMS 数据而非任意 CAN 流量。丢失时 bms_lost_handle() 用 memset(&g_t_bat_info.t_run_info, 0, sizeof(BMS_RUN_TABLE_T)) 一键清零,再单独恢复 u8_type(bms.c:1137-1140)——u8_type 是产品号,清掉会导致后续所有映射函数失效,所以必须保留。
主机丢失走另一条路径:get_master_battery_lost() 变化时才动作,置 TRUE 时调用 bms_master_lost_handle()。该函数不清整个结构体,而是逐字段清 SOC/总压/总流/各类极值/单台数组等实时量(bms.c:1149-1190),保留连接状态与产品号类信息。
4.2 bat_mos_control MOS 闭合重试状态机(bms.c:1019-1125)
状态变量:s_u8BatMOSFlag(初始 1)、s_u32BatMOSTick、s_u32BatCurrTick。
条件:u8_relay_status == 0x02(MOS 断开)
且 (inverter_read_grid_freq() >= 4500 或 (并网丢失且 pv 启动状态为真))
| 状态 | 含义 | 迁移条件 |
|---|---|---|
| 1 | 待闭合 | 首次进入立即 bms_set_mos_enable(TRUE),转 2,记时间戳 |
| 2 | 已发闭合指令 | ① 5 分钟后仍未闭合 → bms_set_mos_enable(FALSE),转 3;② 一旦 u8_relay_status == 0x03(闭合成功)进入下面的充电电流判定分支 |
| 3 | 重试等待 | 距上次动作 >30 分钟 → 重新 bms_set_mos_enable(TRUE),转 2 |
状态 2 且继电器已闭合时的子逻辑(bms.c:1088-1124):
- 闭合时间阈值
l_u8_BatMOSCloseTime:ALPHA_SMILE_BAT_8_2PHA_ID且u16_soft_ver >= 115时为 10 分钟,其余 5 分钟(bms.c:1090-1097); - 若充电电流
s16_total_cur <= -150(即 ≥1.5A 充电)持续 3s(abs(s_u32BatCurrTick - OSTimeGet()) > 3000),说明电池确实在充,标志复位为 1; - 否则若超过
l_u8_BatMOSCloseTime * 60 * 1000仍无充电电流,则bms_set_mos_enable(FALSE)转 3,并把时间戳推后。
函数开头对 bms_read_work_status() == FALSE(电池非工作态)做了短路复位:状态归 1、两个时间戳刷新后直接 return。
另有一条 ATE 联动(bms.c:1034-1042):状态为 1 且 u8_bms_ate_allow_discharge_enable 为 TRUE 时置 u8_bms_ate_allow_discharge_flag,否则两者都清——而 bms_read_max_dischg_power() 会在 cUPS 模式下用这个标志兜底 1000W 放电功率。
所有 s_u32BatMOSTick > OSTimeGet() 的分支(bms.c:1066, 1080, 1119)都是 32 位毫秒计数溢出的兜底:溢出后差值变负,直接把时间戳重置为当前值。
4.3 bat_set_sn_handle 并机 SN 改写状态机(bms.c:1279-1419)
6 个槽位 g_t_bat_set_sn_info[0..5] 轮询,s_u8_current_index 循环递增;u8_status == BMS_SET_SN_NONE 的槽位直接跳过。整函数入口有 200ms 节流(OSTimeGet() - s_u32_delay_tick < 200 则返回)。
| 状态 | 迁移条件与动作 |
|---|---|
BMS_SET_SN_IDLE | 在已连电池 SN 列表中查源 SN:命中 → SRC_SEND_E;再查目标 SN:命中 → FINISH_E(无需改写);若既未命中源 SN 又不设 SRC_SEND_E → FINISH_E |
BMS_SET_SN_SRC_SEND_E | 调 send_battery_set_code_c_cmd() 发送。等待计数 s_u8_wait_tick > 3 → 放弃转 FINISH_E;发送成功则序号 u8_send_sequence_number++,累计到 5 次后转 DEST_SEND_E |
BMS_SET_SN_DEST_SEND_E | 同上重发 5 次,之后转 WATTING_E |
BMS_SET_SN_WATTING_E | 轮询目标 SN 是否出现在已连电池列表中,出现即 FINISH_E 并 return;否则 s_u8_wait_tick++,累计 200 次超时转 FINISH_E |
BMS_SET_SN_FINISH_E | memset 清空该槽位,交还给 net/server.c 重新分配 |
default | 强制置 FINISH_E |
u8_send_sequence_number 在两次 SRC/DEST 之间不清零(bms.c:1345-1349、1373-1376),因此 5 次是累积的;由于 FINISH_E 会 memset 整个槽位,下一轮任务从 0 重新计。
4.4 bat_self_lock_handle 电池自锁状态机(bms.c:1429-1527)
前置条件:bms_read_work_status() == FALSE 时整个函数直接 return(电池非工作态不处理自锁)。
状态机比对的核心量是 u_self_lock_status.bit.self_lock_enable_flag(BMS 侧回传)与 bms_get_self_lock_enable()(EMS 配置侧),不一致即需要同步。
| 状态 | 迁移条件与动作 |
|---|---|
BMS_SELF_LOCK_NONE | 配置一致 → IDLE;不一致 → CONFIG_SYNC |
BMS_SELF_LOCK_CONFIG_SYNC | send_battery_set_code_d_cmd(BMS_SELF_LOCK_SEND_CONFIG_COMMAND, 使能值) 成功后 → IDLE |
BMS_SELF_LOCK_IDLE | 配置又变了 → CONFIG_SYNC;配置一致且使能为 TRUE → WAIT_SEND_CLEAR |
BMS_SELF_LOCK_WAIT_SEND_CLEAR | ① 若 word.u16_word4(自锁故障类型)变化,写回 t_config_data.u16_self_lock_fault_type 并 set_info_status(BAT_SELFLOCK_STATUS) 上报;② 故障码为 0 且使能又与配置不一致 → CONFIG_SYNC;③ 故障码非 0 且 EMS 配置为使能 FALSE → 强制 bms_set_self_lock_enable(TRUE);④ 云平台下发了清除(u8_bms_self_lock_clear_flag == TRUE)→ 发 SEND_CLEAR_COMMAND,成功后清标志、记时间戳、转 WAIT_CLEAR |
BMS_SELF_LOCK_WAIT_CLEAR | 距发清除指令不足 5s 则 return;到点后故障码归零 → UPLOAD_FAULT_CLEAR,否则回 WAIT_SEND_CLEAR 并再次上报自锁状态 |
BMS_SELF_LOCK_UPLOAD_FAULT_CLEAR | set_info_status(BAT_SELFLOCK_STATUS) 上报清除,转 IDLE |
default | 强制回 CONFIG_SYNC |
这里的 5s 等待(bms.c:1503)是给 BMS 侧留出执行清除的时间,注释写「电池发送完等 5S 后上报清除状态」。
4.5 逆变器加热状态机 bat_inv_heat_deal(bms.c:2297-2436)
由 g_t_bat_info.t_static_data.e_bms_heat_FSM 驱动,三个状态 + default。
BMS_NORMAL_FSM(空闲/待进入)
g_t_bat_info.t_static_data.u8_heat_state_display = 0;
u8_drop_out_heat_step = 0; u8_fault_drop_out_step = 0;
set_bat_heat_fault_flag(0); // 清加热故障标志
if (bat_heat_execute_check() == 0 // 总条件:未升级+在线+(欠压强充 或 开使能且电池请求)
&& bat_heat_inv_state_check() == 0 // 逆变器运行模式允许
&& bat_bat_connet_check() == 0 // 电池通信正常
&& bat_new_heat_execute_check() == 0) // 新方案:自加热电池需有充电需求
{
if (u8_heat_hardware_fault == FALSE) // 加热膜正常
{
u8_heat_mode = 2; // 标记:逆变器加热
bat_heat_into_1_step(); // 第 1 步
...
bat_heat_into_sta_check(); // 第 3 步
}
else { u32_heat_fault_tick = OSTimeGet(); e_bms_heat_FSM = BMS_INV_HEAT_FAULT_FSM; }
}
else { u8_heat_mode = 0; bat_heat_no_request_deal(); }
进入过程分三步,跨多个调用周期完成,步骤号存 u8_into_heat_step:
bat_heat_into_1_step()(bms.c:1895):先置u8_bat_forbid_chg_dischg_flag = TRUE(停电池功率,注释强调「防止带电切继电器」);步骤 0→1 时记u32_heat_tick;步骤 1 且超过 3000ms 后置u8_inv_heat_cmd = 1(向逆变器下发加热)、g_t_inv_info.t_control_parameter.u8_bat_heat_data_flag = TRUE,步骤转 2。bat_heat_into_2_step(u32_wait_time)(bms.c:1924):步骤 2 且超过u32_wait_time后置u8_bms_heat_cmd = 0xAA(向 BMS 下发进入加热),步骤转 3。等待时间按逆变器型号分档:- 贝瓦(
ALPHAESS_TYPE)强充加热且已回cNormalMode→bat_heat_into_2_step(0),并网后立即发; - 盛能杰(
SET_TYPE)→bat_heat_into_2_step(30000),注释「加热模式需要 30S 才能起电压」; - 其余 →
bat_heat_into_2_step(2000)。
- 贝瓦(
bat_heat_into_sta_check()(bms.c:1944):步骤 3 期间,若u8_heat_relay_sta == TRUE则置u8_heat_relay_cmd = 1并告知汇川加热继电器状态;若u8_heat_state == FALSE(电池未进入加热),重发 0xAA 并刷新u32_heat_tick——注意这里的 120 分钟判故障逻辑实际不可达:u32_heat_tick刚在上一行被赋值为OSTimeGet(),紧接着(OSTimeGet() - u32_heat_tick) > 120000必然为假。这是代码缺陷(见第 7 节)。若u8_heat_state == TRUE,等 2000ms 后记录s16_heat_init_temperature = s16_low_temper、置展示状态 1、清硬件故障、重置步骤与时间戳,并跳入BMS_INV_HEAT_FSM。
BMS_INV_HEAT_FSM(加热中)
| 条件 | 动作 |
|---|---|
u8_heat_state == TRUE 且 bat_heat_execute_check() == 0 且 bat_heat_inv_ongrid_check() != 0(并网) | 正常加热:刷新 u32_inv_heat_tick;非盛能杰时执行 bat_heat_vol_current_check()(电流/电压异常 1s 判故障);执行 bat_heat_hardware_fault_check()(加热膜 + 15 分钟未升温判故障) |
bat_heat_exit_check() == 0 且 bat_bat_connet_check() == 0 | 正常退出:bat_heat_drop_out() |
| 其他 | 异常退出:距 u32_inv_heat_tick 超过 5s 才 bat_heat_into_fault_deal(),之后立刻刷新 u32_inv_heat_tick。注释说明 5s 是防抖:「逆变器电压在电池退出加热时会被拉低导致 u8_heat_state 为 FALSE」 |
正常退出 bat_heat_drop_out()(bms.c:2082)同样三步:步骤 0→1 置 u8_inv_heat_cmd = 0;步骤 1 超过 3100ms 发 u8_bms_heat_cmd = 0x55 转步骤 2;步骤 2 中若电池仍报加热中且继电器仍闭合且超过 9100ms 则判故障进 bat_heat_into_fault_deal(),否则视为已退出:置继电器指令 0、清展示状态、放开电池功率、回 BMS_NORMAL_FSM、重置故障恢复次数,并按需 bms_set_mos_enable(TRUE) 闭合电池继电器(注释解释:欠压断开充电继电器后,加热完成需立刻闭合才能充电)。
BMS_INV_HEAT_FAULT_FSM(加热故障) —— bat_heat_fault_fsm_deal()(bms.c:2137):清步骤号 → 按需清继电器指令 → bat_heat_fault_drop_out() → 若距 u32_heat_fault_tick 超过 360000ms(6 分钟) 且电池通信正常,则按 u8_heat_fault_recovery_count 判定:
- < 3 次:计数加一,清硬件故障标志与退出步骤,回
BMS_NORMAL_FSM(允许重试); - ≥ 3 次:若电池支持自加热(
u8_bat_self_heat_function == TRUE)且(加热请求清除或用户关使能)→ 恢复正常态;否则(不支持自加热)无条件恢复正常态并重置计数。
bat_heat_fault_drop_out()(bms.c:1814)也是三步:步骤 0→1 置 u8_inv_heat_cmd = 0;步骤 1 超过 3100ms 发 0x55、放开功率、set_bat_heat_fault_flag(1) 记录故障、转步骤 2;步骤 2 等继电器断开后告知汇川并转步骤 3。
注意
bat_heat_fault_fsm_deal()中有一处可疑写法:if(g_t_bat_info.t_static_data.u8_bat_self_heat_function == TRUE)分支内的判断与else分支的动作完全相同(都是清故障、回正常态),差异仅在于else分支还重置了u8_heat_fault_recovery_count。逻辑上 TRUE 分支多了一层if但只做同一件事,属冗余/疑似漏写条件。
4.6 电池自加热状态机 bat_self_heat_deal(bms.c:2467-2681)
与逆变器加热完全独立的另一套状态机,用函数内 static 变量而非 t_static_data 保存位置:
static BMS_HEAT_STATE s_bat_self_heat_fsm = BMS_NORMAL_FSM;
static BMS_FSM_SUBSTATE s_heat_self_fsm_sub[BMS_INV_HEAT_MAX] = {BMS_ENTER_SUB};
入口先判使能:u8_heat_en == 0 则 bat_self_heat_data_inti() 后直接 return。随后有一条全局故障跳转(bms.c:2477):加热膜故障,或(正在加热且报过温)→ 强制 s_bat_self_heat_fsm = BMS_INV_HEAT_FAULT_FSM。这条跳转在每个周期开头执行,即故障会立即抢占当前状态。
BMS_NORMAL_FSM(使用 s_heat_self_fsm_sub[BMS_NORMAL_FSM] 的三步子流程):
BMS_ENTER_SUB:bat_self_heat_data_inti(),转EXECUTE_SUB;BMS_EXECUTE_SUB:要求系统未升级,且u8_heat_en != 0 && u8_request_heat == TRUE:u8_into_heat_step == 0且bat_charge_demand_check() == 0→ 置u8_heat_mode = 1(自加热)、发u8_bms_heat_cmd = 0xBB、记时间戳、步骤置 1;- 步骤非 0 时:15000ms 内若
u8_heat_state == TRUE && u8_heat_relay_sta != FALSE(电池确认进入自加热)→ 展示状态 1、子状态转EXIT_SUB、主状态跳BMS_INV_HEAT_FSM且其子状态置ENTER_SUB;超过 15000ms 仍未进入 → 子状态EXIT_SUB、主状态跳BMS_INV_HEAT_FAULT_FSM; - 不满足加热请求时子状态回
ENTER_SUB(清零步骤); - 系统升级中 → 子状态
EXIT_SUB。
BMS_EXIT_SUB:子状态回ENTER_SUB,清零u8_into_heat_step。
BMS_INV_HEAT_FSM(自加热运行中):
ENTER_SUB→ 清u8_drop_out_heat_step→EXECUTE_SUB;EXECUTE_SUB:- 使能开 && 加热请求 &&
u8_heat_state == TRUE&&u8_heat_relay_sta == TRUE→ 刷新u32_heat_tick(保持加热); - 或
u8_heat_state == FALSE或使能关 → 子状态EXIT_SUB、回BMS_NORMAL_FSM; - 否则(请求在、状态在、但继电器未闭合)若距
u32_heat_tick超过 9000ms → 判故障跳BMS_INV_HEAT_FAULT_FSM; - 系统进入升级 → 清展示状态、回
BMS_NORMAL_FSM; bms_read_over_temperature_code()/bms_read_heat_fault_code()非 0,或电池通信异常 → 跳BMS_INV_HEAT_FAULT_FSM;
- 使能开 && 加热请求 &&
EXIT_SUB:发u8_bms_heat_cmd = 0x55退出自加热,子状态回ENTER_SUB。
BMS_INV_HEAT_FAULT_FSM(自加热故障):
- 无条件先发
0x55; ENTER_SUB:展示状态 2、set_bat_heat_fault_flag(1)、记u32_heat_fault_tick、转EXECUTE_SUB;EXECUTE_SUB:距故障时间超过 360000ms 且通信正常后,u8_heat_fault_recovery_count < 3则加一并回BMS_NORMAL_FSM(重置条件同逆变器加热);否则仅在「加热请求清除或使能关」时才回正常态——这与逆变器加热分支不同,自加热在无请求时不会主动退出故障态。
4.7 加热方式选择 bat_heat_handle(bms.c:2692-2723)
if(s_u8_inti_flag == 0) { bat_heat_data_inti(); s_u8_inti_flag = 1; } // 首次初始化
if(u8_heat_function == TRUE) g_system_function_info.t_dev_fun.ems_fun_3_u.bit.u8_bat_heat = 1;
else g_system_function_info.t_dev_fun.ems_fun_3_u.bit.u8_bat_heat = 0;
if( ((u8_bat_self_heat_function == TRUE) && (u8_heat_mode == 0)
&& (u8_bat_soc_low_flag == 0) && (u8_must_inv_heat_flag == 0))
|| (u8_heat_mode == 1 && (u8_must_inv_heat_flag == 0)) )
bat_self_heat_deal(); // 电池自加热
else
bat_inv_heat_deal(); // 逆变器给电池加热
判据含义:电池具备自加热能力、当前未在加热(u8_heat_mode == 0)、各单体 SOC 都高于阈值(u8_bat_soc_low_flag == 0)、且没有「必须外部加热」标志 → 走自加热;已经在自加热(u8_heat_mode == 1)且无必须外部加热要求 → 继续自加热。其余情况(SOC 偏低、有必须外部加热标志、已在逆变器加热)一律走逆变器加热。注释「新增异常工况进入外部加热」正是指 u8_must_inv_heat_flag。
SOC 阈值判定在 handle_bat_heat_soc_info()(bms.c:4134):逐台(0 ~ u8_bat_heat_num)比较 bms_read_single_soc(i) <= u8_set_bat_heat_soc * 10(默认 20 × 10 = 200,即 20.0%),任一台低于阈值即置 u8_bat_soc_low_flag = 1。它的触发时机在 handle_bat_heat_prestore_info()(bms.c:4094):只有收到特定的 CAN 帧 ID 才刷新——
switch(id)
{
case ALPHA_BATTERY_GET_REQUEST_CODE_ID: // 0x18FFA1F4:加热请求帧
// 同步 u8_bat_heat_request / u8_bat_self_heat_function 到 t_static_data
break;
case ALPHA_BATTERY_GET_OPERATION_STATE_1_ID: // 0x0CF100C0:汇总帧
// 同步电池台数 u8_bat_heat_num
/* 故意没有 break —— 落入下面三个 case 共用 handle_bat_heat_soc_info() */
case ALPHA_BATTERY_GET_OPERATION_STATE_8_ID: // 0x18F30001
case ALPHA_BATTERY_GET_OPERATION_STATE_15_ID: // 0x18F218F3
handle_bat_heat_soc_info();
break;
default: break;
}
OPERATION_STATE_1_ID 分支故意缺 break(bms.c:4112-4119),利用 case 落穿让汇总帧也执行 SOC 判定——这是有意为之的隐式技巧,修改时需要特别注意。
4.8 配置持久化:fresh_and_save_bat_info / fresh_and_save_bat_extend_info(bms.c:735-900)
这两个函数把「运行值」固化到「配置区」,统一模式是:
if((config != run) && (run != 0)) // 运行值非零 且 与配置不同
{
config = run;
l_u8_save_flag = TRUE;
}
...
if(l_u8_save_flag == TRUE) save_job_post(BATTERY_CONFIG_PAGE);
run != 0 的附加条件是关键:运行区在通信丢失时会被 bms_lost_handle() 清零,若无条件回写,一次通信中断就会把 Flash 里的配置抹成 0。因此所有比对都要求运行值有效。
比对字段(fresh_and_save_bat_info):u8_type、u8_num、u16_soft_ver、u16_chg_cut_rate_volt(注意此项漏写 l_u8_save_flag = TRUE,见第 7 节)、u16_dischg_cut_rate_volt、u8_dod、u16_cell_rate_volt、u16_bat_rate_volt_instant、u16_charge_pro_cur_step_1、u16_discharge_pro_cur_step_1、u16_cell_rate_capacity、u8_series_para_state、s_type_splice(memcmp 31 字节)。
fresh_and_save_bat_extend_info() 额外做两件事:
- 清尾:对
i = u8_num到BATTERY_MAX_NUM - 1的槽位,把u16_single_soft_ver归 0、u8_single_platform_type归MAX_TYEP、SN 清零——即电池被拆掉后,扩展配置区不能残留旧数据; - 老电池降级:M4850/M4860/M48100/M48112-P 四款「未完全适配 48V 协议」的老电池,软件版本与平台类型被强制清零(
bms.c:859-873)。
SN 的保存多一个前置条件:u8_signle_sn_splice_done_flag[i] == TRUE(SN 拼接完成标志),避免半截 SN 被写进 Flash。
4.9 通信丢失处理的两个层次(bms.c:1133-1190)
| 函数 | 触发 | 动作 |
|---|---|---|
bms_lost_handle | 距上次有效帧 >10s 且原本在线 | 先存 u8_type,memset 整个 t_run_info,再恢复 u8_type |
bms_master_lost_handle | get_master_battery_lost() 由 FALSE 变 TRUE | 逐字段清零:允许充放电标志、最大充/放电功率、关机状态、继电器状态、总 SOC/总压/总流、各极值(最低/最高单体和温度)、数组(单台 SOC/电流/SOH/循环/运行小时/温度)、s8_temp、u8_energy_sync_flag |
bms_lost_handle 用整体清零是激进但简洁的做法;bms_master_lost_handle 因为只丢主机而保留从机数据,所以逐字段清。两个函数都不清 t_static_data,因此加热状态在通信恢复后能延续(与 BMS_STATIC_TABLE_T 的设计意图一致)。
4.10 产品号 → 派生参数的映射族
以下 5 个函数结构相同,都是对 bms_get_store_type() 结果做 switch:
| 函数 | 行号 | 输出字段 | 关键取值 |
|---|---|---|---|
handle_bat_chg_cut_rate_volt | 133 | u16_chg_cut_rate_volt | 各 ALPHA_*_BATTERY_CHG_CUT_VOLT/10 |
handle_bat_dischg_cut_rate_volt | 194 | u16_dischg_cut_rate_volt | 各 ALPHA_*_BATTERY_DISCHG_CUT_VOLT/10 |
handle_battery_cell_capacity | 255 | u16_cell_rate_capacity | 2500/3000/4800/5400/5700/2867/10300/10100/5800/13340,以及 154*32、256*32、48*105、96*105、96*40 等算式 |
handle_battery_dod | 404 | u8_dod | 80(M4850)/90(M4860~SMILE5_JP)/95(SEEDS、SMILE-BAT 系列、OEM)/96(SMILE5、M4856-P)/100(SMILE13.3) |
handle_battery_model | 488 | s_type_splice[31] | 各 TYPE_BAT_* / TYPE_SMILE_* / OEM 专用字符串 |
四个函数的 default 分支都是自赋值(x = x),含义是「未知产品号则保持原值不动」。另有几个特例需要注意:
handle_battery_model的ALPHA_SMILE_G3_BAT_3_8S_ID与default分支带s_type_splice[0] == 0保护,即只在型号字符串为空时才写,防止覆盖 BMS 上报的真实型号;handle_battery_cell_temper_number/handle_battery_cell_volt_number(bms.c:623/650)目前只对ALPHA_SMILE_G3_BAT_3_8S_ID赋值 8 / 30,其余全是 0——即除该型号外,单体温度/电压采样点数量恒为 0,s16_single_cell_temper_sample/u16_single_cell_volt_sample数组不会被填充;get_bms_hardware_platform(bms.c:677)的default用u16_cell_rate_volt > 6000(>60V)反推为高压平台。
4.11 打包型只读访问器(bms.c:3563-3674)
一批访问器把多个字段压进一个 u32,方便云平台一帧上报:
最低/最高单体电压(整机):(u8_cell_min_volt_module_id << 24) | (u8_cell_min_volt_cell_id << 16) | u16_cell_min_volt
最低/最高温度(整机): (u8_low_temper_module_id << 24) | (u8_low_temper_sample_point_id << 16) | (u16)s16_low_temper
单台最低/最高单体电压: (串号 << 16) | 电压
单台最低/最高温度: (样本点号 << 16) | 温度
温度字段做了 (u16) 强制转换:s16_low_temper 可为负,转成 u16 后以补码形式进低 16 位,调用方需按有符号解读。
三类按位或合并的访问器写法更值得注意:bms_read_protection_code()(bms.c:3511)、bms_read_protection_extend2_code()(3528)、bms_read_fault_code()(3545)都是把索引 0~12 共 13 个元素手工展开逐个 |。这依赖 u_bms_fault_code 等数组长度为 BATTERY_MAX_NUM + 1 = 13(bms.h:894-898)。新增/减少电池台数上限时这三处必须同步改。
4.12 自定义 CAN 帧下发队列(bms.c:4233-4353)
业务流程跨三个文件:
net/server.c:7174解析云平台指令,多次调用can_send_queue_add(can_id, data, frame_type)(上限 6 帧,满则打印丢弃);net/server.c:7193调can_send_queue_start()置u8_status = 1、u8_send_index = 0、记时间戳;net/ems.c:7276在主循环里周期调can_send_task()推进。
can_send_task() 的节奏控制:
if (u8_status == 0) return; // 空闲
if (u8_send_index >= u8_count) { // 全部发完
u8_status = 0;
g_u8_can_send_done_flag = TRUE; // 通知上层
return;
}
if (OSTimeGet() - u32_last_send_tick < 10) return; // 帧间隔 10ms
即每帧间隔 10ms,6 帧最多 60ms 发完。can_send_custom_frame() 按 u8_frame_type 分派到 can0_send_standard_pack()(0,标准帧)或 can0_send_exten_pack()(1,扩展帧),组帧时固定 frame_len = 8。发送失败时照样推进索引(bms.c:4349-4351),不会卡住队列,但也意味着失败帧直接丢弃、不做重试。发完后 g_u8_can_send_done_flag 被 net/ems.c:4915 消费以触发云平台响应。
队列相关函数里有大量 printf(can_send_queue_clear/add/start/can_send_custom_frame/can_send_task),没有 DEBUG_* 宏包裹,属于调试期遗留的常开日志。
4.13 其他周期逻辑
force_bat_shutdown_handle(bms.c:1199-1270) —— 逆变器报 inverter_get_bat_shutdown_flag() 才生效。满足「断路器状态 2 且母线电压 <230 且无光伏唤醒 且并网丢失(grid_freq < 4500)且继电器 bit0 置位」时启动 2s 计时,到点置 EMS 故障位 19(EMS_FAULT_INV_SATUSE_ABNORMAL_BAT_SHUTDOWN)并 bms_set_shutdown(TRUE);同时若 u_ems_fault.bit.u8_inv_lost_bat_shutdown 为真,延迟 100ms 后也关机。另一条独立逻辑:逆变器通信丢失且处于工厂模式(u8_factory_flag == TRUE)持续超过 12 小时,置故障位 17(EMS_FAULT_INV_LOST_BAT_SHUTDOWN)。
BMS_time_cmd_handle(bms.c:1579-1603) —— RTC 恰好为 01:00:00 时发一次校时,用 s_u8_send_flag 保证 1 秒内不重复;另外 u8_timing_flag == 1(电池主动请求校时)时立即发一次并清标志。
bat_low_soc_detect(bms.c:3956-3986) —— 仅在 cNormalMode 或 cUPS 下工作(否则直接 return)。阈值取 u8_ongrid_low_soc * 10 或 u8_offgrid_low_soc * 10(0.1% 单位)。SOC < 阈值 && 标志为 0 && SOC != 0 时置标志并 set_info_status(SOC_LOW_STATUS);SOC > 阈值 时清标志。SOC != 0 的条件避免通信初值 0 误触发。
get_bat_mixed_installation_num_error_flag(bms.c:3375-3395) —— 电池型号为 SMILE_M_BAT_13.9P(新旧两种字符串之一)且 get_risk_of_system_installation_plan() == TRUE 时:老电池/新老混装(u8_bat_mixed_installation_flag == 0)台数 >2,或新电池(== 1)台数 >3,则置 EMS 故障位 28(EMS_FAULT_BAT_MIXED_INSTALLATION_NUM_ERROR),否则清。该标志会进一步让 bms_read_max_charge_power() / bms_read_max_dischg_power() 返回 0(禁止充放电功率)。
5. 对外接口
5.1 本文件导出的函数与全局变量
全局变量(extern 声明在 bms.h:1151-1154)
| 名称 | 使用者 |
|---|---|
g_t_bat_info | 全工程直接读写:data/、logic/、net/、hmi/、midware/interface/、midware/inverter/ |
g_t_bat_set_sn_info[6] | net/server.c:1632-1639 |
g_t_can_send_queue | net/server.c:7193-7196 |
对外函数(在 bms.h:1156-1252 声明)
按消费方归类(基于全工程 grep 实测的调用点):
| 消费方 | 调用的函数 |
|---|---|
net/ems.c(云平台上报) | 几乎全部 bms_read_* 数值接口、bms_get_store_ver、bms_get_platform、bms_get_store_sn、bms_get_self_lock_enable、bms_MIX_battery、bms_read_single_max_capacity、bms_read_fault/warning_code_by_id、bms_is_*_valid、bms_read_a/b_cluster_*、bms_is_a/b_cluster_valid、can_send_task |
net/server.c(云平台下行) | bms_reset、bms_set_mos_enable、bms_set_ready_enable、bms_set_self_lock_enable、bms_set_self_lock_clear、handle_bat_set_activation_time、bms_read_battery_model、can_send_queue_add、can_send_queue_start |
logic/logic.c | bms_read_max_charge/dischg_power、bms_read_work_status、bms_read_relay_status、bms_read_total_soc、bms_read_total_power、bms_read_fault_code、bms_read_battery_capacity、get_bat_heat_forbid_power_flag、bms_get_ready_enable |
midware/interface/modbus/ | 大部分 bms_read_* 数值与打包接口、bms_set_mos_enable/bms_set_mos_open_enable/bms_set_ready_enable、bms_get_store_type/bms_get_store_MIX_type |
midware/inverter/pvcharger.c | bms_get_ready_enable、bms_get_store_type、bms_get_store_num、bms_read_chg/dischg_cut_rate_volt、bms_read_chg/dischg_max_rate_cur、bms_read_*_pro_cur_step_1、bms_read_rated_vot_instant、bms_cell_rated_vot、bms_read_battery_series_para_state、bms_read_battery_available_capacity、bms_read_battery_capacity、bms_get_connect_state |
midware/inverter/parallel.c | bms_get_connect_state、bms_get_store_num、bms_get_ready_enable、大量 bms_read_* |
midware/bms/alpha/battery_comm.c | bms_get_store_type、get_bms_hardware_platform、get_bms_cell_data_support、bms_read_work_status、直接写 g_t_bat_info |
data/data.c | bms_clear_energy |
data/resume_data/flash/flash_data_store.c | 大批 bms_read_*(断电续传存储) |
hmi/ | bms_get_connect_state、bms_read_work_status、bms_read_total_soc/volt/cur/power、bms_read_status_flag、bms_read_num、bms_read_shut_down、bms_read_max_charge/dischg_power、bms_read_relay_status、bms_read_fault_code、bms_read_high_temper_info、bms_read_battery_model、bms_get_ready_enable、bms_set_mos_enable(key.c)、bms_set_ready_enable(key.c) |
midware/interface/interface.c | bms_set_shutdown |
midware/interface/dry_node/dry_node.c | bms_read_total_soc、bms_read_fault_code |
midware/meter/backupbox.c | bms_read_total_soc |
midware/auxiliary/system_aux.c | bms_read_total_power、bms_get_ready_enable |
未在 bms.h 声明、但也非 static 的全局函数(仅 bms.c 内部使用,无外部调用点):handle_bat_status_flag、handle_bat_chg_cut_rate_volt、handle_bat_dischg_cut_rate_volt、handle_battery_cell_capacity、handle_battery_dod、handle_battery_model、strncpy_0、fresh_and_save_bat_info、fresh_and_save_bat_extend_info、handle_bat_activate_time_info、bat_mos_control、bat_mos_control_hhe_oem、force_bat_shutdown_handle、bat_set_sn_handle、bat_self_lock_handle、bat_inv_heat_deal、bat_self_heat_deal、bat_heat_handle、handle_battery_cell_temper_number、handle_battery_cell_volt_number、get_bat_mixed_installation_num_error_flag、bms_get_energy_rating、bms_get_real_time_energy_rating、bms_read_mixed_installation_flag、handle_bat_heat_prestore_info、handle_bat_heat_soc_info、handle_bat_set_activation_time(后者其实在 bms.h:1222 有声明)。
5.2 本文件调用的外部函数/变量
| 来源模块 | 被调用的内容 |
|---|---|
midware/bms/alpha/battery_comm.c/h | alpha_bms_package_parser、send_battery_cmd、send_battery_set_code_1_cmd/_2_cmd、send_battery_set_code_c_cmd、send_battery_set_code_d_cmd、send_battery_activate_time_cmd、send_battery_restart_cmd、send_battery_shutdown_cmd、send_battery_set_real_time_cmd、get_master_battery_lost、get_battery_comm_lost |
midware/bms/alpha/battery_upgrade.c | households_batter_upgrade_handle |
hardware/can/can0.c | get_can0_exten_pack、can0_send_standard_pack、can0_send_exten_pack |
hardware/sdram/sdram.h | SDRAM_BAT_INFO_ADDR(链接定位) |
data/data.c/h | ems_set_fault_bit、g_t_ems_info |
data/system_config/ | g_system_function_info、g_t_system_base_info、g_t_sys_datetime、Set_Setup_Config_Status、Set_Setup_Config_Extend1_Status、get_risk_of_system_installation_plan |
data/resume_data/(保存任务) | save_job_post、Get_Status_Is_Updated_From_Server、Set_Status_Is_Updated_From_Server、UPDT_BMS |
data/fault_warn_log/ | set_info_status、set_bat_heat_fault_flag、SOC_LOW_STATUS、BAT_SELFLOCK_STATUS |
logic/logic.c/h | g_t_logic_info、get_dispatch_enable、logic_read_time_force_chg_flag、logic_comparing_time |
net/ems.c/h | IsSystemDownload、IsSystemUpgrade、get_updt_status_current、IsDownloadBMS、GetPercentDownloadBMS |
midware/inverter/ | g_t_inv_info、inverter_read_grid_freq、inverter_read_run_mode、inverter_read_bat_curr、inverter_read_bat_volt、inverter_read_bus_volt、inverter_get_battery_awaken_pv_start_status、inverter_get_bat_shutdown_flag、inverter_get_bat_breaker_statuse、get_inverter_type |
midware/inverter/pvcharger.c | pvcharger_read_machine_mode、pvcharger_read_bat_model、pvcharger_read_bat_rated_volt_instant、pvcharger_read_bat_charge_pro_cur_step_1、pvcharger_read_bat_discharge_pro_cur_step_1 |
midware/inverter/inovance/ | g_t_inovance_comm_info |
midware/interface/dry_node/ | DI_1_ACCESS_POINT |
net/server.c | g_u8_can_send_done_flag |
| uCOS-II | OSTimeGet、OSTimeDlyHMSM、CAN_MSG_TYPEDEF_T |
| C 库 | memset、memcpy、memcmp、strlen、sprintf、printf、abs |
6. 依赖关系
依赖(本文件 include,bms.c:13-34)
includes.h、battery_comm.h、battery_upgrade.h、app_cfg.h、data.h、ems.h、bms.h、upd_download.h、bms_download.h、inverter.h、logic.h、can0.h、system_config.h、dry_node.h(重复 include inverter.h)、meter.h、alphaess_comm_usart.h、inovance_comm_usart.h、parallel.h、fault_warn_log.h、ems_download.h、sdram.h。
被依赖(会用到本文件导出内容)
- 本模块:
midware/bms/alpha/battery_comm.c、midware/bms/alpha/battery_upgrade.c - 数据层:
data/data.c、data/fault_warn_log/fault_warn_log.c、data/system_config/system_config.c、data/resume_data/flash/flash_data_store.c - 逻辑层:
logic/logic.c、logic/dispatch.c、logic/dc_control.c、logic/self_test.c - 网络层:
net/ems.c、net/server.c - HMI:
hmi/key.c、hmi/led.c、hmi/led/app_led.c、hmi/ui/ui_main.c、hmi/ui/ui_status_bat.c、hmi/ui/ui_status_comm.c、hmi/ui/ui_Information.c、hmi/ui/ui_setting_user_func_battery.c、hmi/ui/ui_setting_admin_debug_battery.c、hmi/ui/ui_setting_admin_debug_power.c - 中间件:
midware/interface/interface.c、midware/interface/dry_node/dry_node.c、midware/interface/modbus/modbus.c、midware/interface/modbus/sunspec_business.c、midware/interface/modbus/sunspec_modbus.c、midware/inverter/inverter.c、midware/inverter/pvcharger.c、midware/inverter/parallel.c、midware/inverter/alphaess/alphaess_comm_can.c、midware/inverter/alphaess/alphaess_comm_usart.c、midware/inverter/goodwe/goodwe_comm.c、midware/inverter/ginlong/ginlong_comm_usart.c、midware/inverter/kelong/kelong_comm.c、midware/inverter/inovance/inovance_comm_usart.c、midware/inverter/set/set_comm_can.c、midware/inverter/set/set_comm_usart_s5.c、midware/meter/backupbox.c、midware/auxiliary/system_aux.c - 启动:
user/main.c:200(OSTaskCreateExt(task_bms, ...))
7. 备注与风险点
bat_heat_into_sta_check()的「2 分钟未进入加热」判据不可达(bms.c:1955-1968)。电池报未加热时执行:
g_t_bat_info.t_static_data.u32_heat_tick = OSTimeGet(); // 1959:先刷新
if((OSTimeGet() - g_t_bat_info.t_static_data.u32_heat_tick) > 120000) // 1960:必然 ≤ 0
u32_heat_tick 刚被赋为当前时刻,差值恒为 0 或极小值,120000ms 阈值永远不成立。结果是「电池迟迟不进入加热」这条本该报加热膜故障的路径完全失效,会一直重发 0xAA 而永远不跳故障态。正确写法应是先比较、后刷新,或使用独立的计时戳。
fresh_and_save_bat_info()中充电截止电压漏置保存标志(bms.c:757-760):
if((config.u16_chg_cut_rate_volt != run.u16_chg_cut_rate_volt) && (run.u16_chg_cut_rate_volt != 0))
{
config.u16_chg_cut_rate_volt = run.u16_chg_cut_rate_volt;
// 缺 l_u8_save_flag = TRUE;
}
配置值被更新到内存,但如果本函数调用的其它字段都没变化,save_job_post() 不会被触发,充电截止电压不会落 Flash,掉电后丢失。同函数中 u16_dischg_cut_rate_volt(bms.c:762-766)有 l_u8_save_flag = TRUE,对比即可确认是漏写。
bat_heat_fault_fsm_deal()中u8_bat_self_heat_function == TRUE分支与else分支动作重复(bms.c:2167-2183)。两个分支都执行「清硬件故障、清退出步骤、回BMS_NORMAL_FSM」,仅else分支多了u8_heat_fault_recovery_count = 0。TRUE 分支内的if判断不改变行为,看起来是条件写错或漏写了应执行的动作。
CAN_SEND_QUEUE_T.t_frames上限 6 与相关边界。can_send_queue_add()用u8_count >= 6挡越界(正确),但队列函数全是常开printf且无DEBUG_*宏保护,在 100ms 级任务里高频打印会拖慢串口。
bms_read_chg_max_rate_cur()/bms_read_dischg_max_rate_cur()存在除零风险(bms.c:3241、3266)。两处都是u32_max_charge_power * 1000 / bms_read_total_volt(),通信中断时u16_total_volt被bms_lost_handle()清零,此时相除是除以 0。ARM Cortex-M4 的整数除零默认返回 0 而不触发异常(除非使能 DIV_0_TRP),因此表现为静默返回 0 而非崩溃——但这是依赖未定义行为。
bms_read_protection_code()/bms_read_fault_code()等手工展开 13 个元素的按位或(bms.c:3511-3554)。数组长度依赖BATTERY_MAX_NUM + 1(bms.h:894-898),而bms_read_battery_capacity()(bms.c:3836)循环用的是t_config_data.u8_num(≤12)。两处边界不统一,后续若调整BATTERY_MAX_NUM,展开式必须逐个修改,容易漏。
- 部分单台访问器无边界检查。
bms_read_single_soc、bms_read_single_cur、bms_read_single_soh、bms_read_single_single_cycle、bms_read_single_single_runhrs、bms_read_single_cell_min/max_volt_info、bms_read_single_low/high_temper_info、bms_read_battery_cell_model、bms_read_battery_cell_dod、bms_read_battery_MIX_capacity、bms_get_store_sn、bms_get_platform、bms_get_store_MIX_type全部直接下标访问。而同文件的需求 1/2/5 新接口(bms_read_single_max_capacity、bms_read_fault_code_by_id、bms_read_a_cluster_id等)都做了index < BATTERY_MAX_NUM或index >= 6检查。两种风格并存,调用方不能假设统一行为。
strncpy_0()的maxlen语义是「精确长度」而非「含终止符」(bms.c:460-479)。当strlen(source) >= maxlen时它memcpy(destinin, source, maxlen)不补终止符;否则按strlen(source)拷贝并补零到maxlen。因此调用方传 31 且源串恰好 31 字节时,s_type_splice[31]会被填满且无终止符,后续任何printf("%s")/strlen()都会越界读。handle_battery_model()全部用 31 调用,TYPE_SMILE_BAT_8_2PHO = "SMILE-BAT-8.2PH/OUTDOOR"长度 24,暂未触发;但这是潜在隐患。
abs(s_u32BatCurrTick - OSTimeGet())用于「持续 3s」判定不严谨(bms.c:1102)。u32相减得到的是无符号差值,abs()对它无作用(部分编译器会给-Wabsolute-value警告)。同时该差值在两个后续赋值为同值的调用里可能异常放大,语义上不如直接比较并检查溢出。
handle_bat_status_flag()的强充优先级(bms.c:81-84):Force_Charge_Local或Force_Charge_user为真时无条件把u16_status_flag置为FORCE_CHG_FORBID_DISCHG_E(512),覆盖 BMS 上报的任何保护状态。这意味着云端或本地的强充指令会屏蔽 BMS 侧的禁充保护,属于刻意的业务设计,但风险等级高,值得在评审中确认。
u16_soc_display_minimum/u16_soc_display_maximum/u16_read_soc_display_minimum/u16_read_soc_display_maximum声明为s16(bms.h:932-935),本文件在send_battery_set_code_2_cmd()链路中按无符号区间使用。命名与类型不一致,虽不致错但易误读。
bat_self_heat_deal()与bat_inv_heat_deal()两套状态机并存,状态变量不共享。逆变器加热用g_t_bat_info.t_static_data.e_bms_heat_FSM(掉电不保存、断通信保留),自加热用函数内static s_bat_self_heat_fsm(仅复位时清零)。因此切换加热方式时,t_static_data里的步骤号可能与static状态机不同步;bat_heat_handle()的选择条件中u8_heat_mode是三处共同写入的关键量,改任何一处都需同步核对。
bat_heat_handle()中s_u8_inti_flag只保证「本文件首次运行」初始化,bat_heat_data_inti()会把 SOC 阈值恢复成 20、升温阈值恢复成 2。若上层修改过这两个阈值(目前只在初始化里被写),不会持久化。
#if INVERTER_TYPE != SET_TYPE条件的两个函数(bat_heat_fault_check、bat_heat_vol_current_check)在盛能杰机型下不编译,对应的加热电流/电压异常检测功能整体缺失。bat_heat_fault_check的返回 1 语义(有故障)与调用处if(0 != bat_heat_fault_check())一致,但函数开头u8 ret = 1;「默认故障」的写法在初读时容易误解。
bms_comm_detect()的时序窗口(bms.c:58):只有在OSTimeGet() > 60000之后才检测 BMS 丢失,因此开机前 60 秒内不会报 BMS 丢失故障。这是刻意的上电宽限,但若电池真正未接,故障要 60s 后才上报。
task_bms收帧循环中那批handle_*映射函数在每帧都重复执行(bms.c:2870-2880)。虽然各函数内部都有「值相同则不写」的判断,但fresh_and_save_bat_info()/fresh_and_save_bat_extend_info()内含memcmp与多字段比较,一秒钟内若收到几十帧就会重复几十次。功能正确但存在不必要的 CPU 开销,在 100ms 周期内叠加 50ms 收帧上限时值得关注。
u8_heat_mode被三处写入(bms.c:2314置 2、2349置 0、2501置 1、1759初始化置 0),是bat_heat_handle()路由判断的输入,同时又由被路由到的状态机写入,形成「读-判-写」耦合。修改任一处都需重跑两条路径。
#if 0注释掉的电池日志功能(bms.c:2825-2840、2922-2925)。bms_self_info_clear_flag/bms_self_info_clear_id字段仍在BMS_RUN_TABLE_T中保留,但已无代码消费;注释写明「此功能改为在 bms 上位机实现」。属明确的版本遗留,保留字段会持续占用 SDRAM。
can_send_task()由net/ems.c:7276驱动而非task_bms(bms.c:4318的注释「在 task_net_app 中循环调用」)。也就是说自定义 CAN 帧的发送节奏受网络任务调度影响,与task_bms的 BMS 通信共用 CAN0 但无互斥保护。若 BMS 正忙时下发自定义帧,可能出现总线仲裁掉帧——can0_send_*_pack()返回 FALSE 时本文件直接丢弃不重试。
handle_bat_set_activation_time()中memcmp被当作布尔使用(bms.c:4009、4011):
if(memcmp(... , l_u8_contrast_num, 12) == FALSE) // 相等
l_u8_res = memcmp(g_t_bat_info.t_run_info.u_bat_activate_time, bat_info, sizeof(...) * u8_num);
if(l_u8_res != FALSE) // 不相等
memcmp 返回的是 -1/0/1(实现相关,可能返回字节差值),用 != FALSE 判断「不等」虽然正确,但语义上依赖「非零即真」。同时 u8_num 为 0 时 memcmp 长度为 0 恒返回相等,会进入外层 if 之内——handle_bat_activate_time_info() 已用 l_u8_bat_num != 0 做了保护,但本函数没有。
BATTERY_EXTEND_CONFIG_TAB_T.battery_basic_info[18]与运行区[12]的长度差(bms.h:1108vsbms.h:18)。bms_get_store_sn()/bms_get_platform()没有基于 12 或 18 的检查,fresh_and_save_bat_extend_info()的清尾循环用的是BATTERY_MAX_NUM(12),因此索引 12~17 的槽位只在初始化时被清零,后续从不维护。
暂无其它可疑写法。