battery_comm.c
alpha_bms_package_parser() 主解析流程4.5 单台电池最大充放电功率的型号修正算法4.6 单体温度/电压明细的分包组装4.7 激活时间解析的合法性校验4.8 定时/调度与 uCOS-II 的配合4.9 关键全局变量的读写关系5. 对外接口5.1 本文件导出(供外部调用)5.2 本文件调用的外部函数/变量6. 依赖关系7. 备注与风险点源文件路径:
midware/bms/alpha/battery_comm.c所属模块:midware / bms / alpha(ALPHAESS 自有 48V/高压电池 CAN 通信协议实现) 代码行数:2530
1. 文件职责
本文件是 ALPHAESS 自有电池(BMS)与 EMS 主控之间 CAN 总线协议的应用层实现,包含下行控制指令组帧、上行 BMS 状态帧解析、以及电池数据的落地存储三部分。
下行方向:EMS 通过 send_battery_*_cmd() 系列函数,把开关机、重启、MOS 控制、并机 SN 配置、自锁配置、激活时间、SOC 显示范围、能量同步、校时等指令打成 8 字节标准帧/扩展帧发到 CAN0。
上行方向:task_bms(midware/bms/bms.c:2731)在收到 CAN0 扩展帧后调用 alpha_bms_package_parser(),用一个巨大的 switch(id) 把不同 CAN ID 的 8 字节原始数据解码写入全局 g_t_bat_info.t_run_info,供逻辑层(充放电功率限制、故障判断)、HMI 和云平台读取。
数据流位置:CAN0 中断/驱动(hardware/can/can0.c)→ task_bms 轮询 get_can0_exten_pack() → 本文件解析 → g_t_bat_info → logic/、hmi/、net/。同时本文件也反向驱动 CAN0 发送,阻塞式等待应答。
2. 关键数据结构与宏
2.1 本文件定义的数据结构
| 名称 | 类型 | 说明 |
|---|---|---|
BAT_LOG_FSM_E | 枚举 | 电池日志状态机:BAT_LOG_QUERY_FSM/BAT_LOG_REV_DATA_FSM/BAT_LOG_SEND_DATA_FSM/BAT_LOG_END_FSM。定义在 battery_comm.h:365,本文件中代码全部被 #if 0 关闭 |
BAT_LOG_REV_STA_E | 枚举 | 日志接收结果:BAT_LOG_REV_SUCCESS/BAT_LOG_REV_ERROR/BAT_LOG_REV_REJECT。battery_comm.h:372 |
BAT_LOG_T | 结构体 | 单条电池日志的接收缓存:u8_bat_id、u8_frame_number、u8_log_len、u8_frame_index、u8_rev_flag、u8_rev_all_flag、u8_rev_err_flag、u8_log_buff[256]、u16_log_number。battery_comm.h:378 |
BAT_LOG_PACK_T | 结构体 | 向云平台打包的日志缓存:u8_bat_count、u8_bat_id、u16_log_all_count、u8_log_send_number、u8_log_residue_number、log_rev_state、u8_log_buff[19][256]。battery_comm.h:390 |
BATTERY_SET_SN_TABLE_T | 结构体 | 并机 SN 配置表(定义在 midware/bms/bms.h:1117-1123),本文件 send_battery_set_code_c_cmd() 逐包发送其内容:u8_status、u8_send_sequence_number、s_src_single_sn_splice[32]、s_dest_single_sn_splice[32] |
BMS_SINGLE_SN_U | 联合体/结构体 | 8 字节 SN 的逐字节访问视图 byte.byte1..byte8 与 u64_sn(midware/bms/bms.h:672-687),本文件用它把 BMS 上报的 5 字节 SN 拼成十进制字符串 |
CAN_MSG_TYPEDEF_T | 结构体 | CAN 帧描述(hardware/can/can0.h:21-28):frame_id、frame_type、frame_len、frame_data[8]、reading_flag。本文件所有收发的载体 |
2.2 本文件使用的宏常量
| 名称 | 类型 | 说明 |
|---|---|---|
DEBUG_ALPHA_BAT | 宏 | 调试打印总开关,battery_comm.h:17 定义为 0(全部 #if 关闭) |
ALPHA_M4850/M4860/M48100/M48112/SMILE5/M4856_P/SMILE10_3/SMILE10_1/SMILE5_8/SMILE5_JP/SMILE13_3/M48XXX_BATTERY_CHG_CUT_VOLT / ..._DISCHG_CUT_VOLT | 宏 | 各电池型号的充电截止电压/放电截止电压(0.01V),battery_comm.h:22-56。本文件未直接引用,由 handle_bat_chg_cut_rate_volt()(bms.c)使用 |
ALPHA_BMS_LOG_BUFF_LEN | 宏 | 单条电池日志缓存长度 256。battery_comm.h:58 |
ALPHA_BMS_LOG_SEND_BUFF_NUMBER | 宏 | 单次上传云平台的日志最大条数 19。battery_comm.h:59 |
ALPHA_BATTERY_GET_CONF_ID | 宏 | 0x0CF200A0,电池握手/识别帧(多 EMS 干扰排除) |
ALPHA_BATTERY_GET_OPERATION_STATE_1..17_ID | 宏 | 0x0CF100C0、0x185717F3、0x18F225F3、0x18F212F3、0x181E17F3、0x181F17F3、0x18F216F3、0x18F30001、0x18F30002、0x0CF100D0、0x18F30004、0x18F30005、0x18F30006、0x18F217F3、0x18F218F3、0x18F219F3、0x181F18F3。系统运行状态/汇总数据帧 |
ALPHA_BATTERY_GET_SINGLE_CELL_VOLT_1..12_ID | 宏 | 0x0CF1012~0x0CF10C2,逐台电池的单体电压极值帧 |
ALPHA_BATTERY_GET_SINGLE_CELL_TEMPER_1..12_ID | 宏 | 0x0CF1013~0x0CF10C3,逐台电池的单体温度极值帧 |
ALPHA_BATTERY_GET_MIX_CODE_1..12_ID | 宏 | 0x18F211F6~0x18F21CF6,混装电池单台额定参数帧 |
ALPHA_BATTERY_GET_ALARM_CODE_1..12_ID | 宏 | 0x18F311F4~0x18F31CF4,逐台电池保护/故障码 |
ALPHA_BATTERY_GET_WARNING_CODE_1..12_ID | 宏 | 0x18F311F5~0x18F31CF5,逐台电池告警码 |
ALPHA_TW_BATTERY_GET_PROTECT_CODE_1..12_ID | 宏 | 0x18F311F8~0x18F31CF8,台湾版扩展保护码 |
ALPHA_BATTERY_GET_ERROR_CODE_1..3_ID | 宏 | 0x18F214F3、0x18F215F4、0x18F216F4,主机级保护/故障位域映射 |
ALPHA_BATTERY_GET_ENERGY_CODE_1_ID / ALPHA_BATTERY_READ_SOCDISPLAY_CODE_1_ID | 宏 | 0x18FFA1F1 / 0x18FFA1F2,能量读取与 SOC 显示范围读取 |
ALPHA_BATTERY_GET_REQUEST_CODE_ID | 宏 | 0x18FFA1F4,BMS 主动请求(加热、校时) |
ALPHA_BATTERY_SET_STEP_PROTECT_CODE_ID | 宏 | 0x18FFA1F5,阶梯保护电流/额定电压 |
ALPHA_BATTERY_BATTERY_SHUTDOWN_ID | 宏 | 0x0CF200D0,电池关机指令(下行) |
ALPHA_BATTERY_SEND_SET_CODE_1_ID / _2_ID | 宏 | 0x18FFA2F1 / 0x18FFA2F3,周期下发配置 1/配置 2 |
ALPHA_BATTERY_SET_ENERGY_CODE_1_ID | 宏 | 0x18FFA2F2,向 BMS 写入 EMS 侧充/放电电量 |
ALPHA_BATTERY_SET_SN_SPLICE_ID / ALPHA_BATTERY_GET_SN_SPLICE_ID / ALPHA_BATTERY_GET_TYPE_SPLICE_ID | 宏 | 0x18FFA2F4 / 0x18F410F3 / 0x18F510F3,并机 SN、电池型号读写 |
ALPHA_BATTERY_SET_SELF_LOCK_ID / _GET_SELF_LOCK_STATUS_ID | 宏 | 0x18FFA2F5 / 0x18F610F3,自锁配置与状态回读 |
ALPHA_BATTERY_GET_ACTIVATE_TIME_1..12_ID / ALPHA_BATTERY_SET_ACTIVATE_TIME_ID | 宏 | 0x18FF11F5~0x18FF1CF5 / 0x18FFA2F6,电池激活时间的读/写 |
ALPHA_BATTERY_SET_REAL_TIME_ID | 宏 | 0x18FFA2F7,给 BMS 校时 |
ALPHA_BATTERY_QUERY_LOG_TIME_ID / _LOG_INDEXES_ID / _QUERY_LOG_NUM_ID / _LOG_DATA_ID | 宏 | 0x18FE00F0/0x18FE0000/0x18FE00F1/0x18FE0001,电池日志查询(对应代码已全部 #if 0) |
ALPHA_BATTERY_GET_MAX_CAPACITY_1..12_ID | 宏 | 0x18F23013~0x18F230C3,SOC 算法最大容量(Ah)上报 |
ALPHA_BATTERY_GET_CLUSTER_A_INFO / _B_INFO | 宏 | 0x18F00AF3 / 0x18F00BF3,串并联 A/B 簇信息 |
ALPHA_BATTERY_1..12ID_TEMP_INFO_1/2_AFE | 宏 | 0x18231X01/0x18231X02,云 BMS 分钟级单体温度明细 |
ALPHA_BATTERY_1..12ID_VOLT_1..4_INFO_1/2_AFE | 宏 | 0x18241X01~0x18271X02,云 BMS 分钟级单体电压明细 |
BMS_INFO_GROUP_ID / BMS_INFO_ID(id,func) / BMS_ID_F1..F7(x) | 宏 | 0x18FF1000 基址 + (id<<8) + func,欧盟电池法电池自信息帧族 0x18FF1X F1..F7(battery_comm.h:350-361) |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
can_send_standard_pack | static | 标准帧发送包装:与上次发送不足 1 tick 时延时 1ms 再发,记录 s_u32_send_tick |
can_send_exten_pack | static | 扩展帧发送包装,节流逻辑同上 |
Battery_Ack | static | 用 s_u8_bms_identification_code 对 0x0CF200A0 握手帧做应答 |
send_battery_restart_cmd | 全局 | 发电池重启指令:48V+支持低压重启时用 0x10a+"ALEX",否则 0x101+"SLJT" |
send_battery_shutdown_cmd | 全局 | 发电池关机指令 0x0CF200D0,data[7]=0x01 |
send_battery_cmd | 全局 | 通用命令帧构造:data[1]=action1、data[6]=action2;action2==0x1e 时连发 3 次、间隔 20ms |
send_battery_set_code_1_cmd | 全局 | 周期配置 1:绝缘故障码、清能量标志、SOC 显示上下限、最大电池电压 |
send_battery_activate_time_cmd | 全局 | 下发激活时间:type==1 用本地 SYSTEM_DATETIME,type==2 用云端写入的时间 |
send_battery_set_code_2_cmd | 全局 | 周期配置 2:最小电池电压、PV charger 使能、电池加热指令 |
send_battery_set_brand_cmd | 全局 | 标准帧 0x400 下发电池品牌/产地(云平台下发,net/server.c:2581 调用) |
send_battery_set_code_c_cmd | 全局 | 并机 SN 分包下发:每次 6 字节,0x18FFA2F4 |
send_battery_set_code_d_cmd | 全局 | 自锁配置/清除指令 0x18FFA2F5 |
send_battery_energy_cmd | 全局 | 把 EMS 统计的充/放电电量(各 4 字节大端)写入 BMS 0x18FFA2F2 |
send_battery_set_real_time_cmd | 全局 | 用 SYSTEM_DATETIME 给 BMS 校时 0x18FFA2F7 |
send_battery_query_log_cmd | 全局(#if 0) | 查询指定时间段电池日志,当前不参与编译 |
send_battery_get_log_cmd | 全局(#if 0) | 取第 X 条电池日志,当前不参与编译 |
get_single_cell_volt | 全局 | 解析单台电池单体电压极值(min/max 值 + 单体号) |
get_single_cell_temper | 全局 | 解析单台电池单体温度极值(raw-40,含采样点号) |
get_battery_alarm_code | 全局 | 解析单台电池保护码(低 32bit)与故障码(高 32bit) |
get_battery_protection_extend1_code | 全局 | 解析台湾版扩展保护码低/高 32bit |
get_battery_warning_code | 全局 | 解析单台电池告警码低 32bit |
get_battery_activate_time | 全局 | 解析并校验电池激活年月日,非法值回退为 2001-01-01 语义(年=1/月=1/日=1) |
get_single_cell_temper_info | 全局 | 云 BMS 单体温明细组装:按 6 点/帧或半段(ceil(count/2))写入采样数组 |
get_single_cell_volt_info | 全局 | 云 BMS 单体压明细组装:按帧号 info(0~3) 与分包号 number(1/2) 计算偏移写入 |
alpha_bms_package_parser | 全局 | 本文件核心:按 CAN ID 分派并解析全部上行 BMS 帧,返回 TRUE/FALSE 表示是否识别 |
get_bms_self_info | 全局 | 解析欧盟电池法自信息帧 BMS_ID_F1..F7(容量吞吐、效率、循环、日期等 7 类) |
get_master_battery_lost | 全局 | 遍历 0..u8_num 判断是否存在主机通信丢失 |
bat_log_handle | 全局(#if 0) | 电池日志状态机,当前不参与编译 |
get_bat_log_cmd_id_code | 全局(#if 0) | 返回日志命令 ID 缓存指针,不参与编译 |
set_bat_log_cmd_id_code | 全局(#if 0) | 写入日志命令 ID 缓存,不参与编译 |
get_battery_cell_low_vol_protection | 全局 | 按位或汇总 12 台电池的单体欠压保护位 |
get_battery_cell_low_vol_warning | 全局 | 按位或汇总 12 台电池的单体欠压告警位 |
get_battery_comm_lost | 全局 | 按位或汇总 12 台电池的主/从通信丢失位 |
4. 核心逻辑解析
4.1 文件内静态全局
battery_comm.c:32-35 定义了本文件私有状态:
s_u8_bms_identification_code[8] = {0xFF,0x05,0x02,0x00,0x01,0x03,0x01,0x04}:握手识别码,Battery_Ack()原样回发,用于排除同一 CAN 总线上多台 EMS 互相干扰。s_u32_charge_energy[3]/s_u32_discharge_energy[3]:中值滤波用三点缓冲(历史版本遗留,当前只有send_battery_set_code_1_cmd()里的memset清零和#if 0段的读写)。s_u32_send_tick:发送节流时间戳,被can_send_standard_pack()/can_send_exten_pack()共享。
alpha_bms_package_parser() 内部另有三个函数级静态变量(battery_comm.c:1106-1109):s_u32_master_lost_tick、s_u32_bat_activate_time_tick、u_single_sn,它们跨调用保持,是本文件实现"超时降级"和"SN 拼装"的关键。
4.2 CAN 帧格式与收发机制
帧格式:本项目不使用带起始码/长度/校验的串口式协议,而是直接用 CAN 帧承载:29 位扩展 ID 或 11 位标准 ID 作为"命令字",恒为 8 字节数据域(frame_len),校验由 CAN 控制器硬件 CRC 完成,应用层无额外校验和。
发送路径:所有下行都走两个 static 包装函数(battery_comm.c:52-80):
can_send_standard_pack()/can_send_exten_pack()
→ if (abs(OSTimeGet() - s_u32_send_tick) < 1) OSTimeDlyHMSM(0,0,0,1); // 1 tick 内不重复发
→ s_u32_send_tick = OSTimeGet();
→ return can0_send_standard_pack(pack) / can0_send_exten_pack(pack);
响应/超时重试:本文件不做应答等待,全部是"发出即返回"。唯一的特例是 send_battery_cmd() 当 action2 == 0x1e(MOS 通断命令)时连发 3 帧、帧间 OSTimeDlyHMSM(0,0,0,20)(约 20ms),属于冗余发送而非应答重试。调用方通过 (send_xxx() == FALSE) 的返回值判断发送队列是否满,并置位相应的 *_set_flag 以便下一轮重发(见 bms.c:2787-2816)。
主动清缓冲:send_battery_restart_cmd()(162-163)、send_battery_energy_cmd()(630)、send_battery_set_code_1_cmd()(332)在发送前后调用 can0_clear_standard_pack() / can0_clear_exten_pack() 丢弃接收队列中可能堆积的旧帧,避免解析到过期数据。
4.3 下行指令帧字段映射
| 函数 | CAN ID | 数据域布局 |
|---|---|---|
Battery_Ack | 0x0CF200A0 | FF 05 02 00 01 03 01 04(固定识别码) |
send_battery_restart_cmd | 0x101 或 0x10a | [0..3]=0,[4..7]=53 4C 4A 54("SLJT") 或 41 4C 45 58("ALEX") |
send_battery_shutdown_cmd | 0x0CF200D0 | [0..6]=0,[7]=0x01 |
send_battery_cmd | 调用方传入 | [1]=action1,[6]=action2,其余 0 |
send_battery_set_code_1_cmd | 0x18FFA2F1 | [0]=绝缘故障标志,[3]=清能量标志,[4]=SOC 显示上限,[5]=SOC 显示下限,[6:7]=最大电池电压(大端) |
send_battery_set_code_2_cmd | 0x18FFA2F3 | [1:2]=最小电池电压(大端),[3]=PV charger 使能(主机模式 2,否则 1),[4]=电池加热指令 |
send_battery_activate_time_cmd | 0x18FFA2F6 | [0]=电池 ID+1,[1:2]=年(大端),[3]=月,[4]=日 |
send_battery_set_brand_cmd | 0x400(标准帧) | [0]=电池 ID,[1]=0x3A,[2]=品牌码,[3]=产地 |
send_battery_set_code_c_cmd | 0x18FFA2F4 | [0]=1(源SN)/2(目标SN),[1]=包序号+1,[2..7]=SN 的 6 字节分片 |
send_battery_set_code_d_cmd | 0x18FFA2F5 | [0]:配置命令 = (value&1)<<7;清除命令 = 0xC0 |
send_battery_energy_cmd | 0x18FFA2F2 | [0..3]=充电电量(大端 u32),[4..7]=放电电量(大端 u32) |
send_battery_set_real_time_cmd | 0x18FFA2F7 | [0..5] = 年、月、日、时、分、秒 |
4.4 alpha_bms_package_parser() 主解析流程
函数入口 battery_comm.c:1088,结构为"三个超时看门狗 + 两类前置处理 + 一个巨型 switch(id)"。
超时看门狗(每帧都会执行)
abs(OSTimeGet() - s_u32_master_lost_tick) > 3000→ 清u_bms_fault_code[0].bit.u8_master_battery_communication_lost。即主机丢失故障位由ALPHA_BATTERY_GET_OPERATION_STATE_3_ID帧置位,3 秒内无该帧则自动清除(1112-1115)。abs(OSTimeGet() - s_u32_bat_activate_time_tick) > 3000→ 清g_system_function_info.t_dev_fun.bms_fun_u.bit.u8_bat_activate_time,表示电池在连续 3 秒内未上报激活时间帧则判定该电池型号不支持此功能(1118-1121)。
欧盟电池法前置处理(1138-1171)
- 若
(id & 0xFFFFF000) == BMS_INFO_GROUP_ID(0x18FF1000),调用get_bms_self_info();首次成功置位u8_is_get_bms_self_info并调用Set_Setup_Config_Extend1_Status(FALSE)触发云平台配置刷新。 - 一旦
u8_is_get_bms_self_info == TRUE,对i = 0..BATTERY_MAX_NUM-1巡检:若当前 tick 距u_bms_self_info[i].u32BmsCommTick超过SENCOND_60S(60 s)且该电池 SN 非空,则清空t_extend_config_data.battery_basic_info[i]、u_bms_self_info[i]、s_single_sn_splice[i],并置bms_self_info_clear_flag = TRUE。这是"电池被拔除后清除其档案"的实现。
switch(id) 主要分支
| case | 关键解析公式 / 动作 | |||
|---|---|---|---|---|
ALPHA_BATTERY_GET_CONF_ID | 与 s_u8_bms_identification_code 比较;不一致 → Battery_Ack() 应答;一致 → 直接 return FALSE(说明是本 EMS 自己发出的帧回环) | |||
..._OPERATION_STATE_1_ID | 前置条件 0 < data[6] <= BATTERY_MAX_NUM;data[0] 位域拆出混装标志(bit0)、型号混装(bit1)、充放允许(&0xF0)、加热状态(bit3);u32_max_dischg_power = data[1:2]、u32_max_charge_power = data[3:4];u8_num = data[6];u8_working_status = data[5]、u8_shut_down = data[7]。单台电池时按型号做限流修正(见 4.5)。随后把 u8_num 之后的多余电池 SOC/电流/SOH/循环/温度清零 | |||
..._OPERATION_STATE_10_ID | 充电截止电压、放电截止电压、加热电压、加热电流;加热电流单位从 0.1A 换算为 0.01A:u16_heat_current *= 10 | |||
..._OPERATION_STATE_2_ID | 继电器状态、硬件版本;把 data[7],[6],[5],[4],[1] 逆序拼成 u_master_sn 的 byte1..byte5,SN 变化时清 u8_energy_enable / u8_energy_sync_flag | |||
..._OPERATION_STATE_3_ID | data[0] 即为主机通信丢失位;同时刷新 s_u32_master_lost_tick | |||
..._OPERATION_STATE_4_ID | 总 SOC = data[0]*4;总压 `= ((data[1]<<8)\ | data[2])*2;总电流 = ((data[3]<<8)\ | data[4]) - 32000) * 10(零点 32000,0.01A 单位);软版本 = ((data[5]<<8)\ | data[6]);电池类型 = data[7]` |
..._OPERATION_STATE_5_ID | 单体最低压模块号/电压/单体号 与 单体最高压模块号/电压/单体号 | |||
..._OPERATION_STATE_6_ID | 最低/最高温度:(s8)(data[1] - 40),含模块号与采样点号 | |||
..._OPERATION_STATE_17_ID | 动力线(功率线)最低/最高温度,格式同上 | |||
..._OPERATION_STATE_7_ID | 前置条件 0 < data[1] <= u8_num;用 u_single_sn 以 byte1=data[5], byte2=data[4], byte3=data[3], byte4=data[2], byte5=data[0] 逆序拼 5 字节 SN;memset(...,0,31) 后 snprintf("%llu", u64_sn);置 u8_signle_sn_splice_done_flag | |||
..._OPERATION_STATE_8_ID | 逐台 SOC:u16_single_soc[i] = data[i]*4(i < u8_num) | |||
..._OPERATION_STATE_9_ID | 逐台电流:s16_single_cur[i] = (data[i] - 127) * 50(压缩为 1 字节、偏移 127、0.5A) | |||
..._OPERATION_STATE_11_ID | 逐台软件版本 = data[6:7] | |||
..._OPERATION_STATE_12_ID | 第 0..3 台电流,(u16 - 32000)*10;原始值为 0 时直接写 0(区分"未上报"与"零电流") | |||
..._OPERATION_STATE_13_ID | 第 4..5 台电流,公式同上 | |||
..._OPERATION_STATE_14_ID | 继电器状态、DOD、额定电压 u16_cell_rate_volt、额定 AH;额定容量 = cell_AH * cell_rate_volt / 1000;串并联状态 | |||
..._OPERATION_STATE_15_ID | 按 data[0] 逐台刷新 SOC(*4)、电流、单体继电器状态、平台类型、软件版本 | |||
..._OPERATION_STATE_16_ID | 逐台 SOH、循环次数、电芯品牌 data[4]、产地 data[5] | |||
..._SINGLE_CELL_VOLT_1..12_ID | 电池号 = (id - base) >> 4,调 get_single_cell_volt() | |||
..._SINGLE_CELL_TEMPER_1..12_ID | 电池号 = (id - base) >> 4,调 get_single_cell_temper() | |||
GET_ERROR_CODE_1..3_ID | 主机级保护/故障位域映射,大量 (data[n] & 0x18) >> 3 提取 2bit 状态(0=无/1=告警/2=保护/3=故障);GET_ERROR_CODE_3 只取 data[3]&0x01 动力线过温 | |||
ALARM_CODE_1..12 | 电池号 = ((id-base)>>8) + 1,get_battery_alarm_code() | |||
WARNING_CODE_1..12 | 同上,get_battery_warning_code() | |||
TW_BATTERY_GET_PROTECT_CODE_1..12 | 同上,get_battery_protection_extend1_code() | |||
READ_SOCDISPLAY_CODE_1 | SOC 显示最大/最小值(各 1 字节)、当前可用电量、系统最大储能容量 | |||
GET_REQUEST_CODE_ID | data[2] 位域:bit1 加热请求、bit2 具备加热功能、bit3 加热继电器、bit4 自加热;data[3] bit0 校时请求;data[4] 需加热电池数量;data[5] bit0 必须外部加热 | |||
SET_STEP_PROTECT_CODE_ID | 充电/放电阶梯保护电流、瞬时额定电压 | |||
GET_ENERGY_CODE_1_ID | 空实现(原电量同步逻辑在 #if 0 中,1753-1813) | |||
GET_SN_SPLICE_ID | data[0]=电池号、data[1]=包号(1..5);包号 1 清完成标志、包号 5 置完成标志;memcpy 到 s_single_sn_splice[bat-1][(pkg-1)*6] 并补 '\0' | |||
GET_TYPE_SPLICE_ID | data[0]==1 时写整机型 s_type_splice;同时写混装表 s_type_splice_MIX[data[0]-1] | |||
GET_ACTIVATE_TIME_1..12_ID | 刷新 s_u32_bat_activate_time_tick;电池号 = (id-base)>>8;调 get_battery_activate_time() | |||
GET_MAX_CAPACITY_1..12 | 电池号 = (id-base)>>4;容量 = (data[4..7] 大端 u32) / 6000(Ah) | |||
GET_CLUSTER_A/B_INFO | 对 i=0..5:id = byte & 0x0F,旁路状态 = (byte>>4)&0x03;运行模式 = data[6]&0x03;置 *_cluster_valid = 1 | |||
GET_SELF_LOCK_STATUS_ID | memcpy 8 字节到 u_self_lock_status.u64_all,再覆写 word.u16_word4 | |||
*_TEMP_INFO_*_AFE | 仅当 get_bms_cell_data_support() 返回 TRUE(即型号为 ALPHA_SMILE_G3_BAT_3_8S_ID)才解析;电池号 = (id-base)>>8,调 get_single_cell_temper_info(bat+1, id&0x0F, data) | |||
*_VOLT_*_INFO_*_AFE | 同上;info = (id-base)>>16,get_single_cell_volt_info(bat+1, id&0x0F, info, data) | |||
GET_MIX_CODE_1..12 | 电池号 = ((id-base)>>8)&0x0F;型号 = data[1:0](小端)、DOD、额定电压、额定 AH;容量 = 额定电压 * 额定AH / 1000 | |||
default | return FALSE,表示非本协议帧 |
4.5 单台电池最大充放电功率的型号修正算法
ALPHA_BATTERY_GET_OPERATION_STATE_1_ID 分支中,当 u8_num == 1(单台)时按 bms_get_store_type() 分档修正(1201-1260):
ALPHA_M48100_BATTERY_ID/ALPHA_M48112_BATTERY_ID:功率 = 上报值 * 8 / 10(打 8 折)。ALPHA_SMILE5 / M4856_P / SMILE5_8 / SMILE5_JP:先算等效电流上报功率 * 1000 / u16_total_volt;若 > 500(即 0.5C)则钳位为u16_total_volt * 5 / 10(0.5C),否则用上报值。ALPHA_SMILE10_3 / SMILE10_1 / SMILE13_3:同样算法但阈值与钳位系数为 900 /* 9 / 10(0.9C)。- 其它型号直接用上报值。
多台(u8_num > 1)时不做修正,直接使用 BMS 上报值。
4.6 单体温度/电压明细的分包组装
get_single_cell_temper_info(id, number, data)(940)
- 边界:
id > 12 || number > 2直接返回;u8_cell_temper_number[id-1] > 12也返回。 - 采样点数
≤ 6:单帧直接写入s16_single_cell_temper_sample[id-1][0..count-1]。 - 采样点数
> 6:j = (count%2==0) ? count/2 : count/2+1,把每帧的j个点写到[i + (number-1)*j],即number=1写前半段、number=2写后半段。 - 每点温度
= (s16)(data[i] - 40)。
get_single_cell_volt_info(id, number, info, data)(995)
- 边界:
id > 12 || info > 4 || number > 2直接返回。 - 每帧 4 组大端 16bit 电压;仅当
u16_cell_volt != 0才写入(0 视为无效点,保留旧值)。 - 偏移计算:
info == 0 → offset = 0;否则number == 2 → offset = 15;否则offset = 0。 - 写入位置
u16_single_cell_volt_sample[id-1][info*4 + j + offset]。 - 文件内
982-994行注释给出了 3.8S 电池 30 串的协议排布表,代码按该表硬编码(注释明确写"每个电池协议都不一样,暂时先按照 3.8s 的写死")。该偏移分支与注释表存在不一致,见第 7 节。
4.7 激活时间解析的合法性校验
get_battery_activate_time()(908)先把 u8_bat_get_time_info[id] = TRUE、把功能位 u8_bat_activate_time = 1 置上,然后校验 data[4..7]:
年 = (data[4]<<8)|data[5] 月 = data[6] 日 = data[7]
非法条件:年 == 0 || 年 > 2099 || 年 <= 2000 || 月 == 0 || 月 > 12 || 日 == 0 || 日 > 31
非法时写 年=1 / 月=1 / 日=1(占位值,表示"未激活"),否则写实际值
注意年月日的判断用的是 data[4] 起,即跳过 data[0..3](其中 data[1] 等字段在本文件中未被使用)。
4.8 定时/调度与 uCOS-II 的配合
- 驱动者:
task_bms(user/main.c:200用OSTaskCreateExt静态创建,优先级TASK_BMS_PRIO)。该任务的主循环一次迭代顺序为:bms_get_ready_enable()判电池是否接入 →bms_comm_detect()判通信丢失 → 升级判断(优先) → 各种send_battery_*下行 →while(get_can0_exten_pack(...))批量取扩展帧并逐帧调用alpha_bms_package_parser()(bms.c:2858-2884)→ 各类后处理。 - 批量收帧的 50ms 上限:
while内每次成功解析后检查abs(l_u32_bms_recv_tick - OSTimeGet()) > 50即break(bms.c:2882-2885),防止 BMS 持续压帧时本任务饿死其它任务。 - 本文件不使用信号量、消息邮箱或互斥量:所有数据交换都通过全局
g_t_bat_info直接读写,靠"单一写者(task_bms)+ 多读者"的约定保证一致性,没有加锁。 - 阻塞点:
send_battery_cmd()在action2 == 0x1e时连续OSTimeDlyHMSM(0,0,0,20)两次;send_battery_set_code_1_cmd()在清能量时OSTimeDlyHMSM(0,0,1,100)(1.1 秒);can_send_*_pack()内部最多延时 1ms。这些都会阻塞task_bms。 - 10 秒通信超时:由
bms.c用l_u32_bms_comm_tick自己维护,而不是本文件。 - 本文件的周期性:
send_battery_set_code_1_cmd()/send_battery_set_code_2_cmd()由bms.c:2847-2854的abs(OSTimeGet()-l_u32_set_bat_tick) > 1000条件驱动,即每秒一次(另外清能量标志或 SOC 显示设置标志置位时立即触发)。
4.9 关键全局变量的读写关系
| 全局变量 | 本文件的读/写 | 触发条件 |
|---|---|---|
g_t_bat_info.t_run_info.* | 大量写(几乎所有解析分支);少量读(u8_num 做边界、u8_bms_clear_energy 等做状态判断) | 每收到一帧 BMS 扩展帧 |
g_t_bat_info.t_run_info.u8_connect | 不写(由 bms.c 写) | — |
g_t_bat_info.t_activate_time_info[id] | 写(get_battery_activate_time) | 收到 0x18FF1XF5 |
g_t_bat_info.t_extend_config_data.battery_basic_info[i] | 写(清零) | 60s 未收欧盟电池法帧 |
g_t_bat_info.t_run_info.u_bms_self_info[i] | 写(get_bms_self_info 与超时清零) | 收到 0x18FF1XF1..F7 |
g_system_function_info.t_dev_fun.bms_fun_u.bit.u8_bat_activate_time | 置 1 / 置 0 | 收到激活时间帧 / 3s 超时 |
g_system_function_info.t_dev_fun.bms_fun_u.bit.u8_is_get_bms_self_info | 置 1 | 首次成功解析欧盟电池法帧 |
g_t_bat_info.t_run_info.u8_bms_restart | 清 FALSE | send_battery_restart_cmd() 发送成功 |
g_t_bat_info.t_run_info.u8_set_bms_shutdown | 清 FALSE | send_battery_shutdown_cmd() 发送成功 |
g_t_bat_info.t_run_info.u8_bms_clear_energy、u8_bms_soc_display_set_flag | 清 FALSE;同时把 u32_energy_charge / u32_energy_discharge 清零 | send_battery_set_code_1_cmd() 发送成功 |
g_t_bat_info.t_run_info.u8_energy_enable、u8_energy_sync_flag | 清 FALSE | 总 SOC 帧 u8_working_status == 0(电池重启)或主机 SN 变化 |
g_t_bat_info.t_run_info.bms_self_info_clear_flag | 置 TRUE | 60s 无欧盟电池法帧 |
SYSTEM_DATETIME | 读 | send_battery_activate_time_cmd(type=1)、send_battery_set_real_time_cmd() |
5. 对外接口
5.1 本文件导出(供外部调用)
| 导出符号 | 调用方 |
|---|---|
u8 alpha_bms_package_parser(uint32_t id, uint8_t *data) | midware/bms/bms.c:2860(task_bms 主循环) |
u8 send_battery_restart_cmd(void) | midware/bms/bms.c:2822 |
u8 send_battery_shutdown_cmd(void) | midware/bms/bms.c:2844 |
u8 send_battery_cmd(u32 id, u8 flag, u8 action1, u8 action2) | midware/bms/bms.c:2787,2791,2799,2803(MOS 开关控制) |
u8 send_battery_set_code_1_cmd(void) / _2_cmd(void) | midware/bms/bms.c:2852,2853 |
u8 send_battery_activate_time_cmd(u8 type,u8 id) | midware/bms/bms.c:2811,2816 |
u8 send_battery_set_brand_cmd(u8 bat_id,u8 brand,u8 origin) | net/server.c:2581(云平台下发品牌设置) |
u8 send_battery_set_code_c_cmd(BATTERY_SET_SN_TABLE_T *data) | midware/bms/bms.c:1342,1370(并机 SN 配置) |
u8 send_battery_set_code_d_cmd(u8 u8_type,u8 u8_value) | midware/bms/bms.c:1454,1491(自锁) |
u8 send_battery_energy_cmd(void) | 声明为 extern(battery_comm.h:417),当前仅在 #if 0 段被调用 |
u8 send_battery_set_real_time_cmd(void) | midware/bms/bms.c:1589,1600(校时) |
u8 get_master_battery_lost(void) | midware/bms/bms.c:2909,2911 |
uint8_t get_battery_cell_low_vol_protection(void) | midware/bms/bms.c:1638,1717 |
uint8_t get_battery_cell_low_vol_warning(void) | midware/bms/bms.c:1638,1717 |
uint8_t get_battery_comm_lost(void) | midware/bms/bms.c:1617,2674 |
u8 get_bms_self_info(uint32_t id, uint8_t *data) | 仅本文件内部(battery_comm.c:1141),非 static 但未对外声明 |
void get_single_cell_volt / _temper / get_battery_alarm_code / get_battery_protection_extend1_code / get_battery_warning_code / get_battery_activate_time / get_single_cell_temper_info / get_single_cell_volt_info | 仅本文件内部调用(均非 static,存在被外部误调用的隐患) |
send_battery_self_info_clear_cmd | 只在头文件 battery_comm.h:411 声明,实现已被注释(1043-1079),任何外部调用都会链接失败 |
| 本文件全局变量 | 无(s_u8_bms_identification_code 等皆为 static) |
5.2 本文件调用的外部函数/变量
| 来源模块 | 被调用符号 |
|---|---|
hardware/can/can0.c | can0_send_standard_pack、can0_send_exten_pack、can0_clear_standard_pack、can0_clear_exten_pack |
| uCOS-II | OSTimeGet、OSTimeDlyHMSM |
midware/bms/bms.c | bms_get_store_type、get_bms_hardware_platform、bms_read_work_status、get_bms_cell_data_support、全局 g_t_bat_info |
midware/inverter/ | inverter_support_low_volt_bms_restart、inverter_read_insulation_fault_code、inverter_get_max_bat_volt、inverter_get_min_bat_volt |
midware/pvcharger | pvcharger_read_machine_mode |
data/resume_data/ | energy_read_bat_chgrge、energy_read_bat_dischg |
data/system_config/ | Set_Setup_Config_Extend1_Status、SYSTEM_DATETIME、g_system_function_info |
data/data.h | g_t_ems_info(经包含链) |
| libc | memcpy、memset、memcmp、snprintf、abs |
6. 依赖关系
依赖(battery_comm.c:13-24 的 include 列表)
battery_comm.h → includes.h + bms.h
includes.h
bms.h
meter.h
system_aux.h
inverter.h
can1.h
gd32f4xx_libopt.h
system_config.h
crc.h
sdram.h
ems.h
实际使用到的核心外部资源:CAN_MSG_TYPEDEF_T 与 can0_* 系列(can0.h,经 includes.h 间接引入)、g_t_bat_info / BATTERY_MAX_NUM / BATTERY_SET_SN_TABLE_T / BMS_SINGLE_SN_U(bms.h)、SYSTEM_DATETIME / g_system_function_info(system_config.h)。
被依赖
| 文件 | 引用内容 |
|---|---|
midware/bms/bms.c:14 | #include "battery_comm.h",调用 alpha_bms_package_parser 及全部下行指令 |
net/ems.c:54 | #include "battery_comm.h" |
net/server.c | 调用 send_battery_set_brand_cmd(未直接 include,经其它头文件间接可见) |
battery_upgrade.c | 同目录协作(都依赖 bms.h 的 BMS_48V 等枚举),无直接调用关系 |
7. 备注与风险点
get_single_cell_volt_info()的偏移分支与注释表不一致(潜在真实缺陷)。代码1006-1017:
if (info == 0) offset = 0;
else if (number == 2) offset = 15;
else offset = 0;
info 由 (id - 0x18241101) >> 16 得出,0x18241102(ALPHA_BATTERY_1ID_VOLT_1_INFO_2_AFE)的 info 计算结果为 0,因此走第一分支 offset = 0,把第 2 分包的 4 个单体电压写到 [0..3],覆盖了第 1 分包已写的数据;按 982-994 行注释表,它应当写到 [15..18](即第 16~19 串)。其它型号/帧号的组合(info != 0)反而正确。若 3.8S 电池确实按该表逐帧上报,则第 16~19 串电压会读到旧值、第 1~4 串电压被反复覆写。建议改为先判 number 再判 info。
- 单体电压为 0 时跳过写入(
1024)。if(u16_cell_volt != 0)使得"单体电压确实为 0 mV"与"该点未上报"无法区分,异常电池可能长期保留上一次的旧值。
get_single_cell_volt_info的number校验方向不严:判断为number > 2,number == 0会通过,虽然当前id & 0x0F只会取到 1/2,但缺少下界检查。
ALPHA_BATTERY_GET_SINGLE_CELL_VOLT_x_ID宏疑似漏写一位十六进制。battery_comm.h:80-91定义为0x0CF1012、0x0CF1022…,而同族温度帧是0x0CF1013、0x0CF1023…(7 位十六进制也没问题),但电压帧缺少与温度帧对应的第 8 位。由于解析用的是"与基址做差再右移"的相对算法(1618),所以目前自洽可运行,一旦有人按温度帧的写法"修正"成0x0CF10120,与 BMS 实际 ID 不匹配即会整族解析失效。
send_battery_set_code_c_cmd()越界风险:533/538处s_src_single_sn_splice[(u8_send_sequence_number)*6 + i],u8_send_sequence_number无任何范围校验,若为 5 及以上则读到s_src_single_sn_splice[32]之外(索引 30、31 尚可,≥32 即越界)。调用方bms.c目前保证包号在 0..4,属"调用者约定"型隐患。
send_battery_set_brand_cmd()调试打印引用了不存在的标识符(493、499):printf("... Brand %d ...", bat_id, brand_code)中的brand_code并不存在于函数作用域(形参名是brand)。目前因DEBUG_ALPHA_BAT == 0未参与编译,一旦打开调试开关会直接编译失败。
- 多处把联合体按整数格式化输出:如
854printf("%x", g_t_bat_info...u_bms_protection_code[id])把一个u64联合体直接传给%x,属未定义行为;同样只在调试开关打开时暴露。
get_battery_alarm_code()只填 32 位:u_bms_protection_code[id].u64_all = (u32)(...),高 32 位恒为 0,而BMS_FAULT_U/BMS_PROTECTION_U是 64 位联合体,若后续 BMS 协议扩展出 bit32+ 的保护位,此处需要同步修改。
get_master_battery_lost()的循环上界是i <= u8_num(2265),当u8_num == BATTERY_MAX_NUM(12)时访问u_bms_fault_code[12];该数组声明为[BATTERY_MAX_NUM + 1](bms.h:894)恰好够用,但这是"靠数组多留一个元素"兜住的边界,若u8_num被污染成 >12 就会越界(u8_num在 STATE_1 分支有<= BATTERY_MAX_NUM校验,风险较低)。
- CAN 发送节流用
abs(OSTimeGet() - s_u32_send_tick) < 1(54、72):两个 tick 相同时延时 1ms。这是"同一次任务迭代内不连发两帧"的写法,但s_u32_send_tick由标准帧与扩展帧两个函数共享,可能出现标准帧刚发完、扩展帧被强制等 1ms 的情况,对时序影响可接受但含意不直观。
- 大量代码被
#if 0关闭但仍保留在文件中(电池日志 FSM2287-2469、电量同步1753-1813、send_battery_self_info_clear_cmd1043-1079、日志查询指令693-779),以及battery_comm.h:412-416把头文件的三个日志函数声明也注释掉了。这是明确的版本遗留代码:对应的BAT_LOG_T/BAT_LOG_PACK_T结构体和ALPHA_BATTERY_QUERY_LOG_*宏仍在头文件中有效,容易被误当"可用功能"。
g_t_bms_log_buff/g_t_bms_log_pack_buff/g_u8_bat_log_send_flag的定义也被注释(38-40),与BAT_LOG_FSM_E状态机一起构成一组"半删除"的死代码。
- 无并发保护:
g_t_bat_info.t_run_info由task_bms写、被task_system_logic_control/task_hmi_parse/task_net_app读,本文件没有任何临界区保护。多字节字段(如u32_max_charge_power、u16_total_volt)在 32 位 MCU 上虽是单指令访问,但"一组相关字段同时更新"与"读者读到一半"之间仍存在逻辑撕裂窗口。
- 魔法数字密集:温度偏移
-40、SOC 系数*4、电流零点32000(及压缩帧的127 * 50)、容量除数6000、0.5C/0.9C 钳位的500/900、*5/10、*9/10、*8/10等散落在代码中,没有宏或注释说明来源,修改协议时需要逐处确认。