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

battery_comm.c

源文件路径: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_packstatic标准帧发送包装:与上次发送不足 1 tick 时延时 1ms 再发,记录 s_u32_send_tick
can_send_exten_packstatic扩展帧发送包装,节流逻辑同上
Battery_Ackstatic用 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 定义了本文件私有状态:

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_Ack0x0CF200A0FF 05 02 00 01 03 01 04(固定识别码)
send_battery_restart_cmd0x101 或 0x10a[0..3]=0,[4..7]=53 4C 4A 54("SLJT") 或 41 4C 45 58("ALEX")
send_battery_shutdown_cmd0x0CF200D0[0..6]=0,[7]=0x01
send_battery_cmd调用方传入[1]=action1,[6]=action2,其余 0
send_battery_set_code_1_cmd0x18FFA2F1[0]=绝缘故障标志,[3]=清能量标志,[4]=SOC 显示上限,[5]=SOC 显示下限,[6:7]=最大电池电压(大端)
send_battery_set_code_2_cmd0x18FFA2F3[1:2]=最小电池电压(大端),[3]=PV charger 使能(主机模式 2,否则 1),[4]=电池加热指令
send_battery_activate_time_cmd0x18FFA2F6[0]=电池 ID+1,[1:2]=年(大端),[3]=月,[4]=日
send_battery_set_brand_cmd0x400(标准帧)[0]=电池 ID,[1]=0x3A,[2]=品牌码,[3]=产地
send_battery_set_code_c_cmd0x18FFA2F4[0]=1(源SN)/2(目标SN),[1]=包序号+1,[2..7]=SN 的 6 字节分片
send_battery_set_code_d_cmd0x18FFA2F5[0]:配置命令 = (value&1)<<7;清除命令 = 0xC0
send_battery_energy_cmd0x18FFA2F2[0..3]=充电电量(大端 u32),[4..7]=放电电量(大端 u32)
send_battery_set_real_time_cmd0x18FFA2F7[0..5] = 年、月、日、时、分、秒

4.4 alpha_bms_package_parser() 主解析流程

函数入口 battery_comm.c:1088,结构为"三个超时看门狗 + 两类前置处理 + 一个巨型 switch(id)"。

超时看门狗(每帧都会执行)

  1. 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)。
  2. 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)

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_IDdata[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_1SOC 显示最大/最小值(各 1 字节)、当前可用电量、系统最大储能容量
GET_REQUEST_CODE_IDdata[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_IDdata[0]=电池号、data[1]=包号(1..5);包号 1 清完成标志、包号 5 置完成标志;memcpy 到 s_single_sn_splice[bat-1][(pkg-1)*6] 并补 '\0'
GET_TYPE_SPLICE_IDdata[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_IDmemcpy 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
defaultreturn FALSE,表示非本协议帧

4.5 单台电池最大充放电功率的型号修正算法

ALPHA_BATTERY_GET_OPERATION_STATE_1_ID 分支中,当 u8_num == 1(单台)时按 bms_get_store_type() 分档修正(1201-1260):

多台(u8_num > 1)时不做修正,直接使用 BMS 上报值。

4.6 单体温度/电压明细的分包组装

get_single_cell_temper_info(id, number, data)(940)

get_single_cell_volt_info(id, number, info, data)(995)

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 的配合

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清 FALSEsend_battery_restart_cmd() 发送成功
g_t_bat_info.t_run_info.u8_set_bms_shutdown清 FALSEsend_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置 TRUE60s 无欧盟电池法帧
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.ccan0_send_standard_pack、can0_send_exten_pack、can0_clear_standard_pack、can0_clear_exten_pack
uCOS-IIOSTimeGet、OSTimeDlyHMSM
midware/bms/bms.cbms_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/pvchargerpvcharger_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.hg_t_ems_info(经包含链)
libcmemcpy、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. 备注与风险点

  1. 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。

  1. 单体电压为 0 时跳过写入(1024)。if(u16_cell_volt != 0) 使得"单体电压确实为 0 mV"与"该点未上报"无法区分,异常电池可能长期保留上一次的旧值。
  1. get_single_cell_volt_info 的 number 校验方向不严:判断为 number > 2,number == 0 会通过,虽然当前 id & 0x0F 只会取到 1/2,但缺少下界检查。
  1. ALPHA_BATTERY_GET_SINGLE_CELL_VOLT_x_ID 宏疑似漏写一位十六进制。battery_comm.h:80-91 定义为 0x0CF1012、0x0CF1022…,而同族温度帧是 0x0CF1013、0x0CF1023…(7 位十六进制也没问题),但电压帧缺少与温度帧对应的第 8 位。由于解析用的是"与基址做差再右移"的相对算法(1618),所以目前自洽可运行,一旦有人按温度帧的写法"修正"成 0x0CF10120,与 BMS 实际 ID 不匹配即会整族解析失效。
  1. 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,属"调用者约定"型隐患。
  1. send_battery_set_brand_cmd() 调试打印引用了不存在的标识符(493、499):printf("... Brand %d ...", bat_id, brand_code) 中的 brand_code 并不存在于函数作用域(形参名是 brand)。目前因 DEBUG_ALPHA_BAT == 0 未参与编译,一旦打开调试开关会直接编译失败。
  1. 多处把联合体按整数格式化输出:如 854 printf("%x", g_t_bat_info...u_bms_protection_code[id]) 把一个 u64 联合体直接传给 %x,属未定义行为;同样只在调试开关打开时暴露。
  1. get_battery_alarm_code() 只填 32 位:u_bms_protection_code[id].u64_all = (u32)(...),高 32 位恒为 0,而 BMS_FAULT_U/BMS_PROTECTION_U 是 64 位联合体,若后续 BMS 协议扩展出 bit32+ 的保护位,此处需要同步修改。
  1. 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 校验,风险较低)。
  1. CAN 发送节流用 abs(OSTimeGet() - s_u32_send_tick) < 1(54、72):两个 tick 相同时延时 1ms。这是"同一次任务迭代内不连发两帧"的写法,但 s_u32_send_tick 由标准帧与扩展帧两个函数共享,可能出现标准帧刚发完、扩展帧被强制等 1ms 的情况,对时序影响可接受但含意不直观。
  1. 大量代码被 #if 0 关闭但仍保留在文件中(电池日志 FSM 2287-2469、电量同步 1753-1813、send_battery_self_info_clear_cmd 1043-1079、日志查询指令 693-779),以及 battery_comm.h:412-416 把头文件的三个日志函数声明也注释掉了。这是明确的版本遗留代码:对应的 BAT_LOG_T/BAT_LOG_PACK_T 结构体和 ALPHA_BATTERY_QUERY_LOG_* 宏仍在头文件中有效,容易被误当"可用功能"。
  1. g_t_bms_log_buff / g_t_bms_log_pack_buff / g_u8_bat_log_send_flag 的定义也被注释(38-40),与 BAT_LOG_FSM_E 状态机一起构成一组"半删除"的死代码。
  1. 无并发保护: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 上虽是单指令访问,但"一组相关字段同时更新"与"读者读到一半"之间仍存在逻辑撕裂窗口。
  1. 魔法数字密集:温度偏移 -40、SOC 系数 *4、电流零点 32000(及压缩帧的 127 * 50)、容量除数 6000、0.5C/0.9C 钳位的 500/900、*5/10、*9/10、*8/10 等散落在代码中,没有宏或注释说明来源,修改协议时需要逐处确认。