锂电池改用 BCU-MBMS CAN 协议按节点解析 + bcu_node 解析数据落库 + 板卡对时脚本

- pCCU/CanBms:按《04KT38电池BCU-MBMS通信(CAN)定义》重写解码,
  BmsStatus 单状态模型改为 BcuNodeStatus 多节点模型(0x10XX00YY,
  节点地址 01~36h),支持 0x0000 电压/电流/SOC/告警码、0x0001 单体
  电压、0x0002 单体温度、0x0003 继电器、0x0006 绝缘/端口电压、
  0x0010 告警位(附录1 中文码表)六类报文
- 平均单体温度偏移修正:协议文档 BYTE5 写"偏移0"有误,实测固件与
  最高/最低一致均带 -40℃ 偏移(实车 0x10020002 原始 68/69 减 40 后
  为 28/29℃,落在最低28~最高30区间内,按文档直读则超出物理范围)
- pCCU/DbStore:新增 bcu_node 解析数据表,锂电池 BMS 报文每帧落一行
  节点合成状态(原 comm_log 仅原始帧,BMS/CAN 帧此前不落 pCCU 库),
  buildReport 增加 BMS 记录数
- 快照/网页:BCU 按节点分组展示(告警位解析中文含义、数据 age),
  PM 状态报文锂电池/应急电池卡片
- pPowerManger:新增 iport 本地输入端口配置(UDP bind 延迟到
  OnStartUp 读取配置后执行,保证 iport/ccuhost/ccuport 生效),
  各 mission 文件补充注释
- test:CAN BCU 解码用例按新协议/新偏移更新(实车抓包 + 文档示例值),
  133 项全部通过
- docs:删除旧 BMS 协议(xlsx/20230324docx),归档 04KT38 BCU-MBMS
  CAN 定义、配电控制器通信协议 20260824、配电系统 CAN 通讯协议
- scripts:新增 sync-board-time.sh,223 板卡(RK3588)时间校正
  (本机为基准 + RTT 折半补偿对时,尽力写 RTC,支持 status/--time)
This commit is contained in:
zjk
2026-09-01 12:58:33 +08:00
parent 88bed850ee
commit 5b8f0b1421
27 changed files with 894 additions and 170 deletions
+90 -35
View File
@@ -289,50 +289,105 @@ static void testDbStore() {
}
//--------------------------------------------------------------------------
// 9. CAN BMS 报文解码(docs/BMS_协议字段定义.xlsx,测试向量为实车抓包数据)
// 9. CAN BCU 报文解码(docs/04KT38电池BCU-MBMS通信(CAN)定义.docx,
// 测试向量为实车抓包数据与协议文档示例值)
static void testCanBmsDecode() {
BmsStatusValue v;
uint8_t addr = 0;
// 0x10010000:14 da 17 70 00 c9 31 01(实车抓包)
// 总电压 0x14DA=5338 -> 533.8V;SOC 0x00C9=201 -> 20.1%
// 故障码 0x31=49;状态位 0x01
// 0x10010000(节点01,实车抓包):14 da 17 70 00 c9 31 01
// 累加电压 0x14DA=5338 -> 533.8V;回路电流 0x1770=6000 -> 600.0-600=0.0A
// SOC 0x00C9=201 -> 20.1%;告警码 0x31;自检状态 0x01
BcuNodeStatus n1;
const uint8_t d0[8] = {0x14,0xDA,0x17,0x70,0x00,0xC9,0x31,0x01};
CHECK(decodeCanBmsFrame(0x10010000, d0, 8, v));
CHECK(v.totalVoltage == 533.8);
CHECK(v.soc == 20.1);
CHECK(v.faultCode == 0x31);
CHECK(v.statusBits == 0x01);
CHECK(decodeCanBcuFrame(0x10010000, d0, 8, addr, n1));
CHECK(addr == 1);
CHECK(n1.totalVoltage == 533.8);
CHECK(n1.current == 0.0);
CHECK(n1.soc == 20.1);
CHECK(n1.alarmCode == 0x31);
CHECK(n1.selfCheck == 0x01);
// 文档示例帧:14 d9 => 533.7V
const uint8_t d0doc[8] = {0x14,0xD9,0x17,0x70,0x00,0xC9,0x31,0x01};
CHECK(decodeCanBmsFrame(0x10010000, d0doc, 8, v));
CHECK(v.totalVoltage == 533.7);
// 回路电流:充电 -25A -> raw 0x1676=5750 -> 575.0-600=-25.0A
const uint8_t d0n[8] = {0x14,0xDA,0x16,0x76,0x00,0xC9,0x31,0x00};
decodeCanBcuFrame(0x10010000, d0n, 8, addr, n1);
CHECK(n1.current == -25.0);
// 0x10010005:07 d0 00 00 0e a8 00 00
// 最大电流限制 0x07D0=2000 -> 200.0A;总电流 00 00 00 -> 0.0A
// 最高单体电压 0x0EA8=3752 -> 3.752V
const uint8_t d5[8] = {0x07,0xD0,0x00,0x00,0x0E,0xA8,0x00,0x00};
CHECK(decodeCanBmsFrame(0x10010005, d5, 8, v));
CHECK(v.maxCurrentLimit == 200.0);
CHECK(v.totalCurrent == 0.0);
CHECK(v.maxCellVoltage == 3.752);
// 0x10010001(节点01)单体电压:0e a8 05 0e a0 03 0e a4
// 最高 0x0EA8=3752 -> 3.752V (#5);最低 0x0EA0=3744 -> 3.744V (#3)
// 平均 0x0EA4=3748 -> 3.748V;部分更新,概要字段保留
const uint8_t dv[8] = {0x0E,0xA8,0x05,0x0E,0xA0,0x03,0x0E,0xA4};
CHECK(decodeCanBcuFrame(0x10010001, dv, 8, addr, n1));
CHECK(n1.maxCellVoltage == 3.752);
CHECK(n1.maxCellVoltageNo == 5);
CHECK(n1.minCellVoltage == 3.744);
CHECK(n1.minCellVoltageNo == 3);
CHECK(n1.avgCellVoltage == 3.748);
CHECK(n1.totalVoltage == 533.8); // 同节点部分更新,前一帧字段不被清除
CHECK(n1.rxMask == ((1u << BCU_FUNC_BASE) | (1u << BCU_FUNC_CELL_V)));
// 总电流负数:s16 补码 FF 06 = -250 -> -25.0A(xlsx 负数示例)
const uint8_t d5n[8] = {0x07,0xD0,0xFF,0x06,0x0E,0xA8,0x00,0x00};
CHECK(decodeCanBmsFrame(0x10010005, d5n, 8, v));
CHECK(v.totalCurrent == -25.0);
// 0x10010002(节点01)单体温度:3c 01 2d 02 33
// 最高 0x3C=60-40=20℃ (#1);最低 0x2D=45-40=5℃ (#2);平均 0x33=51-40=11℃
// (实测固件平均温度与最高/最低一致均带 -40℃ 偏移,协议文档"偏移0"有误:
// 实车 0x10020002 原始 68/69 减 40 后为 28/29℃,落在最低28~最高30区间内)
const uint8_t dt[8] = {0x3C,0x01,0x2D,0x02,0x33,0x00,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010002, dt, 8, addr, n1));
CHECK(n1.maxCellTemp == 20.0);
CHECK(n1.maxCellTempNo == 1);
CHECK(n1.minCellTemp == 5.0);
CHECK(n1.minCellTempNo == 2);
CHECK(n1.avgCellTemp == 11.0);
// 0x10010006:07 d0 07 d0 14 da 00 00 -> 总电压校验 0x14DA=533.8V
const uint8_t d6[8] = {0x07,0xD0,0x07,0xD0,0x14,0xDA,0x00,0x00};
CHECK(decodeCanBmsFrame(0x10010006, d6, 8, v));
CHECK(v.totalVoltageChk == 533.8);
// 0x10010003(节点01)继电器:01 01 -> 正/负极均闭合
const uint8_t dr[8] = {0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010003, dr, 8, addr, n1));
CHECK(n1.posRelay == 1 && n1.negRelay == 1);
// 非 BMS 报文 ID 不命中
// 0x10010006(节点01,实车抓包):07 d0 07 d0 14 da 00 00
// 正/负极绝缘 2000kΩ;端口电压 0x14DA -> 533.8V;外侧电压 0.0V
const uint8_t di[8] = {0x07,0xD0,0x07,0xD0,0x14,0xDA,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010006, di, 8, addr, n1));
CHECK(n1.posInsulationKohm == 2000.0);
CHECK(n1.negInsulationKohm == 2000.0);
CHECK(n1.portVoltage == 533.8);
CHECK(n1.posRelayOuterVoltage == 0.0);
// 0x10010010(节点01)告警位:byte0=0x01 -> BCU绝缘2级;byte1=0x04 -> 烟雾报警1级
const uint8_t da[8] = {0x01,0x04,0x00,0x00,0x00,0x00,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10010010, da, 8, addr, n1));
CHECK(n1.alarmBits[0] == 0x01 && n1.alarmBits[1] == 0x04);
const char* texts[8] = {nullptr};
int cnt = bcuAlarmTexts(n1, texts, 8);
CHECK(cnt == 2);
CHECK(cnt > 0 && std::string(texts[0]) == "BCU绝缘2级");
CHECK(cnt > 1 && std::string(texts[1]) == "BCU烟雾报警1级");
// 无告警
const uint8_t dz[8] = {0,0,0,0,0,0,0,0};
decodeCanBcuFrame(0x10010010, dz, 8, addr, n1);
CHECK(bcuAlarmTexts(n1, texts, 8) == 0);
// 节点分离:0x10020000(节点02)解析到独立状态
BcuNodeStatus n2;
const uint8_t d02[8] = {0x0D,0x40,0x17,0x70,0x01,0x2C,0x00,0x00};
CHECK(decodeCanBcuFrame(0x10020000, d02, 8, addr, n2));
CHECK(addr == 2);
CHECK(n2.totalVoltage == 339.2);
CHECK(n2.soc == 30.0);
CHECK(n1.alarmCode == 0x31); // 节点01 快照不受节点02 帧影响
// 非 BCU 报文 / 预留功能码 / 地址越界 / 下发指令 均不命中
const uint8_t dx[8] = {0,0,0,0,0,0,0,0};
CHECK(!decodeCanBmsFrame(0x10010035, dx, 8, v));
CHECK(!decodeCanBmsFrame(0x10010000, nullptr, 0, v));
CHECK(isCanBmsId(0x10010000) && isCanBmsId(0x10010005) && isCanBmsId(0x10010006));
CHECK(!isCanBmsId(0x10010001));
CHECK(!decodeCanBcuFrame(0x10010035, dx, 8, addr, n1)); // 未定义功能码
CHECK(!decodeCanBcuFrame(0x10010004, dx, 8, addr, n1)); // Reserve
CHECK(!decodeCanBcuFrame(0x10010005, dx, 8, addr, n1)); // Reserve
CHECK(!decodeCanBcuFrame(0x10370000, dx, 8, addr, n1)); // 地址 0x37 越界
CHECK(!decodeCanBcuFrame(0x10000000, dx, 8, addr, n1)); // 地址 0 非法
CHECK(!decodeCanBcuFrame(0x100181FF, dx, 8, addr, n1)); // MBMS 下发指令
CHECK(!decodeCanBcuFrame(0x10010000, nullptr, 0, addr, n1));
CHECK(isCanBcuId(0x10010000) && isCanBcuId(0x10010001) &&
isCanBcuId(0x10010002) && isCanBcuId(0x10010003) &&
isCanBcuId(0x10010006) && isCanBcuId(0x10010010));
CHECK(!isCanBcuId(0x10010004) && !isCanBcuId(0x10010005) &&
!isCanBcuId(0x100181FF) && !isCanBcuId(0x10370000));
}
//--------------------------------------------------------------------------