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

bms.c

源文件路径:midware/bms/bms.c 所属模块:midware / bms(BMS 电池管理中间层) 代码行数:4438

1. 文件职责

本文件是 EMS 主控固件中「电池管理」的应用层主体,承担四件事:

  1. 数据汇聚:以 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_* 只读访问器取数。
  1. 参数映射:把电池产品号(如 ALPHA_M4850_BATTERY_ID)翻译成型号字符串、容量、DoD、充放电截止电压、48V/高压平台等派生参数,并做变化检测后 save_job_post() 落 Flash。
  1. 电池加热控制:实现两套加热状态机——「逆变器给电池加热」(bat_inv_heat_deal)和「电池自加热」(bat_self_heat_deal),由 bat_heat_handle 按电池能力/SOC/工况选择,含进入、执行、正常退出、故障退出、故障恢复全流程。
  1. 辅助业务:电池并机 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_infoBATTERY_TABLE_Tbms.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_Tbms.c:40,并机 SN 改写任务的 6 个槽位,由 net/server.c 填状态、本文件发送
g_t_can_send_queueCAN_SEND_QUEUE_Tbms.c:4236,自定义 CAN 帧队列(最多 6 帧),由 net/server.c 入队、net/ems.c 周期驱动 can_send_task()

2.2 关键外部全局变量(本文件读写)

名称类型说明
g_t_logic_infoLOGIC_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_infoEMS_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_infoINVERTER_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_infoINOVANCE_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_datetimeRTC 时间读: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_flagu8(net/server.c:77)写:自定义 CAN 帧全部发完后置 TRUE,供 net/ems.c:4915 触发云平台响应

2.3 静态文件作用域状态变量(各函数内 static)

这些变量跨调用周期保持,是本文件各类状态机的实际载体。

所在函数变量说明
bat_mos_control_hhe_oemu8_bms_mos_open_flagHHE OEM 干接点曾断开 MOS 的标记
bat_mos_controls_u8BatMOSFlag(初始 1)、s_u32BatMOSTick、s_u32BatCurrTickMOS 闭合重试状态机(1 待闭 / 2 已发闭合 / 3 重试)与两个时间戳
force_bat_shutdown_handles_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_handles_u8_current_index(0~5 轮转)、s_u8_wait_tick、s_u32_delay_tickSN 改写任务轮询槽位与 200ms 节流
bat_self_lock_handles_e_self_lock_status、s_u32_time_delay自锁状态机当前位置与等待清除的 5s 计时
BMS_time_cmd_handles_u8_send_flag(初始 1)防止 1 秒内重复发校时
bat_heat_handles_u8_inti_flag首次运行做一次 bat_heat_data_inti()
bat_self_heat_deals_bat_self_heat_fsm、s_heat_self_fsm_sub[BMS_INV_HEAT_MAX](初始 {BMS_ENTER_SUB})自加热主/子状态机
bat_low_soc_detectl_u8_low_soc_flag低 SOC 事件边沿标记,防止重复上报
task_bmss_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_tickCAN 帧缓冲、主机丢失边沿、升级边沿、通信/收帧/参数下发时间戳

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_detectstatic开机 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_handlestatic电池通信丢失时整体清零 t_run_info(仅保留 u8_type)
bms_master_lost_handlestatic主机电池丢失时逐字段清零关键实时量并清 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_codestatic遍历 13 个扩展保护 1 结构,任一 u8_heat_film_abnormal 置位则返回 1
bms_read_over_temperature_codestatic遍历 12 台电池的告警/保护码,任一过温则返回 1
BMS_time_cmd_handlestatic每天 1:00:00 发一次 BMS 校时;也响应 u8_timing_flag 请求
bat_bat_connet_checkstatic电池在线且未通信丢失返回 0,否则 1
bat_heat_low_vol_state_checkstatic欠压告警/保护且有加热请求时返回 0(强充加热场景)
bat_heat_execute_checkstatic系统未升级 + 电池在线 + (欠压强充 或 用户开加热且电池请求加热)时返回 0
bat_heat_inv_ongrid_checkstatic逆变器正常模式返回 1;inverter_read_grid_freq() < 4000(离网)返回 0
bat_heat_exit_checkstatic加热正常退出的复合条件:用户关使能、请求清除、离网、系统升级任一成立则返回 0
bat_heat_data_intistatic初始化 t_static_data 全部加热字段(SOC 阈值 20,升温阈值 2)
bat_heat_fault_checkstatic逆变器实测加热电流/电压与 BMS 上报值比对,误差超限返回 1(INVERTER_TYPE != SET_TYPE 时编译)
get_bat_heat_forbid_power_flag全局读 u8_bat_forbid_chg_dischg_flag(加热期间是否停电池功率)
bat_heat_fault_drop_outstatic加热异常退出的三步退出(停逆变器加热 → 发 0x55 → 等继电器断开),并记录加热故障
bat_heat_inv_state_checkstatic逆变器侧可进入加热的状态检查(贝瓦强充加热允许 bypass/check/wait/normal,其他仅 normal)
bat_heat_into_1_stepstatic进入加热第 1 步:停电池功率,3s 后向逆变器发加热指令并置 u8_bat_heat_data_flag
bat_heat_into_2_stepstatic进入加热第 2 步:等待 u32_wait_time 后向 BMS 发 0xAA 加热指令
bat_heat_into_sta_checkstatic进入加热第 3 步:检测电池是否上报加热中;2 分钟未进入则判加热膜故障并跳故障状态机
bat_heat_vol_current_checkstatic加热期间电流/电压异常持续 1s 判为加热膜故障(INVERTER_TYPE != SET_TYPE 时编译)
bat_heat_hardware_fault_checkstatic从 BMS 读加热膜故障/过温,并判断加热 15 分钟未升温超过阈值(混装不判断)
bat_heat_into_fault_dealstatic统一跳转到加热故障状态机并打时间戳
bat_heat_drop_outstatic加热正常退出三步(停逆变器 → 发 0x55 → 等继电器断开复位),退出后按需闭合电池继电器
bat_heat_fault_fsm_dealstatic加热故障态处理:故障退出 + 10 分钟后最多 3 次恢复机会
bat_heat_no_request_dealstatic无加热请求时清零所有加热指令、清故障、放开电池功率
bat_charge_demand_checkstatic充电需求检测:峰谷/调度/时段强充/一键强充/欠压强充/5:00~18:00 任一成立返回 0
bat_new_heat_execute_checkstatic新加热方案执行条件:支持自加热的电池需有充电需求,否则直接允许
bat_inv_heat_deal全局逆变器加热主状态机(NORMAL / INV_HEAT / INV_HEAT_FAULT 三态)
bat_self_heat_data_intistatic自加热数据初始化(发 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_stringstatic平台号转字符串("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_framestatic组帧并按帧类型调 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 == TRUEsend_battery_cmd(0x0cf200c0, 1, 0xf2/0xf3, 0x00) 断开/闭合主 MOS= (发送返回值 == FALSE),即发送成功才清标志
u8_bms_mos_set_flag == TRUEsend_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 == TRUEsend_battery_restart_cmd()由 BMS 回帧链路清
u8_set_bms_shutdown == TRUEsend_battery_shutdown_cmd()同上
u8_bms_clear_energy 或 u8_bms_soc_display_set_flag 或距上次下发 >1000mssend_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):

函数开头对 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_Ememset 清空该槽位,交还给 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_SYNCsend_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_CLEARset_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:

  1. 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。
  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)。
  3. 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 判定:

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] 的三步子流程):

  1. BMS_ENTER_SUB:bat_self_heat_data_inti(),转 EXECUTE_SUB;
  2. 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。
  3. BMS_EXIT_SUB:子状态回 ENTER_SUB,清零 u8_into_heat_step。

BMS_INV_HEAT_FSM(自加热运行中):

BMS_INV_HEAT_FAULT_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() 额外做两件事:

  1. 清尾:对 i = u8_num 到 BATTERY_MAX_NUM - 1 的槽位,把 u16_single_soft_ver 归 0、u8_single_platform_type 归 MAX_TYEP、SN 清零——即电池被拆掉后,扩展配置区不能残留旧数据;
  2. 老电池降级: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_handleget_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_volt133u16_chg_cut_rate_volt各 ALPHA_*_BATTERY_CHG_CUT_VOLT/10
handle_bat_dischg_cut_rate_volt194u16_dischg_cut_rate_volt各 ALPHA_*_BATTERY_DISCHG_CUT_VOLT/10
handle_battery_cell_capacity255u16_cell_rate_capacity2500/3000/4800/5400/5700/2867/10300/10100/5800/13340,以及 154*32、256*32、48*105、96*105、96*40 等算式
handle_battery_dod404u8_dod80(M4850)/90(M4860~SMILE5_JP)/95(SEEDS、SMILE-BAT 系列、OEM)/96(SMILE5、M4856-P)/100(SMILE13.3)
handle_battery_model488s_type_splice[31]各 TYPE_BAT_* / TYPE_SMILE_* / OEM 专用字符串

四个函数的 default 分支都是自赋值(x = x),含义是「未知产品号则保持原值不动」。另有几个特例需要注意:

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)

业务流程跨三个文件:

  1. net/server.c:7174 解析云平台指令,多次调用 can_send_queue_add(can_id, data, frame_type)(上限 6 帧,满则打印丢弃);
  2. net/server.c:7193 调 can_send_queue_start() 置 u8_status = 1、u8_send_index = 0、记时间戳;
  3. 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_queuenet/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.cbms_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.cbms_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.cbms_get_connect_state、bms_get_store_num、bms_get_ready_enable、大量 bms_read_*
midware/bms/alpha/battery_comm.cbms_get_store_type、get_bms_hardware_platform、get_bms_cell_data_support、bms_read_work_status、直接写 g_t_bat_info
data/data.cbms_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.cbms_set_shutdown
midware/interface/dry_node/dry_node.cbms_read_total_soc、bms_read_fault_code
midware/meter/backupbox.cbms_read_total_soc
midware/auxiliary/system_aux.cbms_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/halpha_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.chouseholds_batter_upgrade_handle
hardware/can/can0.cget_can0_exten_pack、can0_send_standard_pack、can0_send_exten_pack
hardware/sdram/sdram.hSDRAM_BAT_INFO_ADDR(链接定位)
data/data.c/hems_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/hg_t_logic_info、get_dispatch_enable、logic_read_time_force_chg_flag、logic_comparing_time
net/ems.c/hIsSystemDownload、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.cpvcharger_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.cg_u8_can_send_done_flag
uCOS-IIOSTimeGet、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。

被依赖(会用到本文件导出内容)

7. 备注与风险点

  1. 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 而永远不跳故障态。正确写法应是先比较、后刷新,或使用独立的计时戳。

  1. 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,对比即可确认是漏写。

  1. 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 判断不改变行为,看起来是条件写错或漏写了应执行的动作。
  1. CAN_SEND_QUEUE_T.t_frames 上限 6 与相关边界。can_send_queue_add() 用 u8_count >= 6 挡越界(正确),但队列函数全是常开 printf 且无 DEBUG_* 宏保护,在 100ms 级任务里高频打印会拖慢串口。
  1. 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 而非崩溃——但这是依赖未定义行为。
  1. 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,展开式必须逐个修改,容易漏。
  1. 部分单台访问器无边界检查。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 检查。两种风格并存,调用方不能假设统一行为。
  1. 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,暂未触发;但这是潜在隐患。
  1. abs(s_u32BatCurrTick - OSTimeGet()) 用于「持续 3s」判定不严谨(bms.c:1102)。u32 相减得到的是无符号差值,abs() 对它无作用(部分编译器会给 -Wabsolute-value 警告)。同时该差值在两个后续赋值为同值的调用里可能异常放大,语义上不如直接比较并检查溢出。
  1. 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 侧的禁充保护,属于刻意的业务设计,但风险等级高,值得在评审中确认。
  1. 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() 链路中按无符号区间使用。命名与类型不一致,虽不致错但易误读。
  1. 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 是三处共同写入的关键量,改任何一处都需同步核对。
  1. bat_heat_handle() 中 s_u8_inti_flag 只保证「本文件首次运行」初始化,bat_heat_data_inti() 会把 SOC 阈值恢复成 20、升温阈值恢复成 2。若上层修改过这两个阈值(目前只在初始化里被写),不会持久化。
  1. #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;「默认故障」的写法在初读时容易误解。
  1. bms_comm_detect() 的时序窗口(bms.c:58):只有在 OSTimeGet() > 60000 之后才检测 BMS 丢失,因此开机前 60 秒内不会报 BMS 丢失故障。这是刻意的上电宽限,但若电池真正未接,故障要 60s 后才上报。
  1. task_bms 收帧循环中那批 handle_* 映射函数在每帧都重复执行(bms.c:2870-2880)。虽然各函数内部都有「值相同则不写」的判断,但 fresh_and_save_bat_info() / fresh_and_save_bat_extend_info() 内含 memcmp 与多字段比较,一秒钟内若收到几十帧就会重复几十次。功能正确但存在不必要的 CPU 开销,在 100ms 周期内叠加 50ms 收帧上限时值得关注。
  1. u8_heat_mode 被三处写入(bms.c:2314 置 2、2349 置 0、2501 置 1、1759 初始化置 0),是 bat_heat_handle() 路由判断的输入,同时又由被路由到的状态机写入,形成「读-判-写」耦合。修改任一处都需重跑两条路径。
  1. #if 0 注释掉的电池日志功能(bms.c:2825-2840、2922-2925)。bms_self_info_clear_flag / bms_self_info_clear_id 字段仍在 BMS_RUN_TABLE_T 中保留,但已无代码消费;注释写明「此功能改为在 bms 上位机实现」。属明确的版本遗留,保留字段会持续占用 SDRAM。
  1. 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 时本文件直接丢弃不重试。
  1. 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 做了保护,但本函数没有。

  1. BATTERY_EXTEND_CONFIG_TAB_T.battery_basic_info[18] 与运行区 [12] 的长度差(bms.h:1108 vs bms.h:18)。bms_get_store_sn() / bms_get_platform() 没有基于 12 或 18 的检查,fresh_and_save_bat_extend_info() 的清尾循环用的是 BATTERY_MAX_NUM(12),因此索引 12~17 的槽位只在初始化时被清零,后续从不维护。

暂无其它可疑写法。