锂电池改用 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
+124 -22
View File
@@ -14,41 +14,143 @@ inline int16_t rdS16BE(const uint8_t* p) {
return static_cast<int16_t>(rdU16BE(p));
}
// 有符号 8 位(温度)
inline int8_t rdS8(uint8_t b) { return static_cast<int8_t>(b); }
// 设置功能码已收位
inline void markRx(BcuNodeStatus& n, uint8_t func) {
n.rxMask |= (1u << (func & 31));
}
} // namespace
bool decodeCanBmsFrame(uint32_t canId, const uint8_t* data, int dlc, BmsStatusValue& v) {
//============================================================================
// 附录1:0x10XX0010 告警位定义(04KT38电池BCU-MBMS通信(CAN)定义.docx)
//============================================================================
const BcuAlarmDesc kBcuAlarmTable[] = {
// byte0:2 级告警
{0, 0, "BCU绝缘2级"},
{0, 1, "BCU最高温度过高2级"},
{0, 2, "BCU最低温度过低2级"},
{0, 3, "BCU单体电压不均衡2级"},
{0, 4, "BCU单体温度不均衡2级"},
{0, 5, "BCU放电过流2级"},
{0, 6, "BCU单体电压过高2级"},
{0, 7, "BCU单体电压过低2级"},
// byte1
{1, 0, "BCU总电压过高2级"},
{1, 1, "BCU总电压过低2级"},
{1, 2, "BCU烟雾报警1级"},
{1, 3, "烟雾报警器不在线"},
{1, 4, "烟雾报警器上报自身故障"},
{1, 5, "BCU单包绝缘3级"},
{1, 6, "BCU最高温度过高3级"},
{1, 7, "BCU最低温度过低3级"},
// byte2
{2, 0, "单体电压不均衡3级"},
{2, 1, "单体温度不均衡3级"},
{2, 2, "单包放电过流3级"},
{2, 3, "BCU单体电压过高3级"},
{2, 4, "BCU单体电压过低3级"},
{2, 5, "BCU总电压过高3级"},
{2, 6, "BCU总电压过低3级"},
{2, 7, "BCU单体温度传感器检测故障"},
// byte3
{3, 0, "BCU正极继电器失效"},
{3, 1, "BCU负极继电器失效"},
{3, 3, "单包BMS内部通讯故障"},
{3, 4, "BCU电流传感器故障"},
{3, 5, "BCU电压传感器故障"},
{3, 7, "BCU高压回路连接异常"},
// byte4
{4, 1, "烟雾报警2级"},
{4, 4, "烟雾报警3级"},
{4, 6, "烟雾报警4级"},
{4, 7, "回馈电流过大2级"},
// byte5
{5, 0, "回馈电流过大3级"},
{5, 1, "充电电流过大3级"},
{0xFF, 0, nullptr}, // 表尾
};
int bcuAlarmTexts(const BcuNodeStatus& node, const char* texts[], int cap) {
if (!texts || cap <= 0) return 0;
int count = 0;
for (int i = 0; kBcuAlarmTable[i].text; ++i) {
const BcuAlarmDesc& d = kBcuAlarmTable[i];
if (d.byteIdx < sizeof(node.alarmBits) &&
(node.alarmBits[d.byteIdx] >> d.bitIdx) & 0x01) {
if (count >= cap) break;
texts[count++] = d.text;
}
}
return count;
}
bool decodeCanBcuFrame(uint32_t canId, const uint8_t* data, int dlc,
uint8_t& nodeAddr, BcuNodeStatus& node) {
if (!data || dlc <= 0) return false;
uint8_t addr = bcuAddrOf(canId);
if (!addr || !isCanBcuId(canId)) return false;
nodeAddr = addr;
// 缩放用除法(而非乘 0.1/0.001),保证结果与十进制字面量严格一致。
switch (canId) {
case CAN_BMS_ID_STATUS: // 0x10010000:总电压 / SOC / 故障码 / 状态位
switch (bcuFuncOf(canId)) {
case BCU_FUNC_BASE: // 0x10XX0000:电压 / 电流 / SOC / 告警码 / 自检
if (dlc >= 8) {
v.totalVoltage = rdU16BE(data + 0) / 10.0; // 0.1V
v.soc = rdU16BE(data + 4) / 10.0; // 0.1%
v.faultCode = data[6];
v.statusBits = data[7];
node.totalVoltage = rdU16BE(data + 0) / 10.0; // 0.1V
node.current = rdS16BE(data + 2) / 10.0 - 600.0; // 0.1A,偏移-600A
node.soc = rdU16BE(data + 4) / 10.0; // 0.1%
node.alarmCode = data[6];
node.selfCheck = data[7];
markRx(node, BCU_FUNC_BASE);
}
break;
case CAN_BMS_ID_CURRENT:
// 0x10010005:最大电流限制 / 总电流 / 最高单体电压
// 注:xlsx 表中"总电流"标注为 3 字节(s24),但与其自身示例
// (总电流 00 00 => 0.0A、最高单体电压 0x0EA8 => 3.752V,
// 对应实车帧 07 d0 | 00 00 | 0e a8 | 00 00)矛盾——s24@2 会与
// 单体电压@4 重叠。按示例与实测采用 2 字节 s16(负电流示例
// 0xFF06 => -25.0A 亦为 2 字节补码)。
if (dlc >= 2) v.maxCurrentLimit = rdU16BE(data + 0) / 10.0; // 0.1A
if (dlc >= 4) v.totalCurrent = rdS16BE(data + 2) / 10.0; // 0.1A,放电为正
if (dlc >= 6) v.maxCellVoltage = rdU16BE(data + 4) / 1000.0; // 0.001V
case BCU_FUNC_CELL_V: // 0x10XX0001:最高/最低/平均单体电压
if (dlc >= 2) node.maxCellVoltage = rdU16BE(data + 0) / 1000.0; // 1mV
if (dlc >= 3) node.maxCellVoltageNo = data[2];
if (dlc >= 5) node.minCellVoltage = rdU16BE(data + 3) / 1000.0; // 1mV
if (dlc >= 6) node.minCellVoltageNo = data[5];
if (dlc >= 8) node.avgCellVoltage = rdU16BE(data + 6) / 1000.0; // 1mV
markRx(node, BCU_FUNC_CELL_V);
break;
case CAN_BMS_ID_VOLT_CHK: // 0x10010006:总电压(校验)
if (dlc >= 6) {
v.totalVoltageChk = rdU16BE(data + 4) / 10.0; // 0.1V
}
case BCU_FUNC_CELL_T: // 0x10XX0002:最高/最低/平均单体温度
if (dlc >= 1) node.maxCellTemp = rdS8(data[0]) - 40.0; // 偏移-40℃
if (dlc >= 2) node.maxCellTempNo = data[1];
if (dlc >= 3) node.minCellTemp = rdS8(data[2]) - 40.0; // 偏移-40℃
if (dlc >= 4) node.minCellTempNo = data[3];
if (dlc >= 5) node.avgCellTemp = rdS8(data[4]) - 40.0; // 偏移-40℃(文档写偏移0,
// 但实测固件与最高/最低一致均带-40偏移,见0x10020002:
// 原始68/69减40后为28/29℃,落在最低28~最高30区间内)
markRx(node, BCU_FUNC_CELL_T);
break;
default:
case BCU_FUNC_RELAY: // 0x10XX0003:正/负极继电器状态
if (dlc >= 1) node.posRelay = data[0];
if (dlc >= 2) node.negRelay = data[1];
markRx(node, BCU_FUNC_RELAY);
break;
case BCU_FUNC_ISU: // 0x10XX0006:绝缘阻值 / 端口电压
if (dlc >= 2) node.posInsulationKohm = rdU16BE(data + 0); // 1kΩ
if (dlc >= 4) node.negInsulationKohm = rdU16BE(data + 2); // 1kΩ
if (dlc >= 6) node.portVoltage = rdU16BE(data + 4) / 10.0; // 0.1V
if (dlc >= 8) node.posRelayOuterVoltage = rdU16BE(data + 6) / 10.0; // 0.1V
markRx(node, BCU_FUNC_ISU);
break;
case BCU_FUNC_ALARM: // 0x10XX0010:告警位(附录1)
for (int i = 0; i < 6 && i < dlc; ++i) node.alarmBits[i] = data[i];
if (dlc >= 7) node.alarmExtraByte6 = data[6];
if (dlc >= 8) node.alarmExtraByte7 = data[7];
markRx(node, BCU_FUNC_ALARM);
break;
default: // Reserve(0x10XX0004/0005 等)不解析
return false;
}
return true;