VTRUX 与 Coda 的 CAN 解码笔记:信号、电芯索引与验证方法

作者:

在

结论:这份笔记覆盖九份 DBC、合计 238 条报文与 1,066 条信号,分属 Coda 的 B/C/D 三总线、VTRUX 的 OBD/PT 两总线、Bel 逆变器的 J1939 扩展帧,以及一辆 1975 Porsche 914 改装车的自定义 VCU 遥测。它的价值不在清单本身,而在把「反编译出来的标签」和「验证过的信号」分成五档,并如实记录了一次被纠正三次的电芯索引映射与八条被推翻的假设。

九份 DBC 的量级与定义来源

仓库统计明确说明:下面的报文 / 信号数来自拷进来的 DBC 文件本身,而不是上游 README 的表格——后者已经过期。Coda 的 B、D 两总线在上游 README 里写的是 35 和 22,实际文件是 34 和 23;914 的 README 还在描述一份 75 报文 / 496 信号的 schema 3.0,而文件已经是 schema 4.2、hash 0x09EB175A。

DBC 文件 报文 / 信号 总线 定义来源 证据边界
CODA-B-CAN.dbc 34 / 156 B CAN 原厂 CODACAN LabVIEW 诊断应用 + 实车抓包 上游 README 的 35 为过期值,以文件为准
CODA-C-CAN-cells.dbc 26 / 104 C CAN 26 帧 0x000–0x019,每帧 4 个 16 位 Motorola 电芯,1 mV/bit 电芯编号自 2026-07-22 起改为 1-based
CODA-D-CAN.dbc 23 / 44 D CAN LabVIEW 反编译 + 日志 上游 README 的 22 为过期值
epri-obd-bus.dbc 12 / 18 OBD EPRI Logger iPhone 应用的 Ghidra ARMv7 反编译,再用车辆日志比对修正 字节位置与刻度并非一律正确
epri-pt-bus.dbc 26 / 101 PT 同上 同上
vtrux-obd-experimental.dbc 3 / 5 OBD 作者由日志推导的实验性定义 含未解析报文桩
vtrux-powertrain-experimental.dbc 19 / 26 PT 同上 同上
BelInverter-v2.dbc 8 / 73 J1939 扩展 ID EPRI 结论 + 一份更早的 DBC 合并 原始作者与部分字节布局未确认
914-telemetry.dbc 87 / 539 自定义 VCU 遥测 改装 914 的 VCU 映射,非原厂 Porsche 协议 标准 11 位 ID 为真实车辆帧;29 位扩展 ID 为 VCU 串行遥测里的内部合成值

确认档位:怎么区分「反编译出来的」和「验证过的」

这份仓库最值得抄的一点是它不给信号打勾,而是打档。反编译只能告诉你应用把哪个字节当什么读,不能告诉你它读对了;日志相关只能告诉你两件事一起动,不能告诉你它们是不是一件事。于是每条信号都落在下面五档之一,档位直接写在验证表里。

档位 含义
CONFIRMED 编码与角色都被独立验证过:物理测试、行为日志证据、与已知参考相关、或跨 DBC 比对一致
CONFIRMED-SYMM 靠与某个 CONFIRMED 信号的结构对称性确认,没有独立测试
CONFIRMED-DECOMPILE 编码由 Ghidra 确认,角色只是应用自己的标签,没有独立物理或行为检查
PARTIAL 编码确认,角色或刻度还有一条具体问题挂着
REMOVED 按 DBC 信号策略删掉 SG_ 条目,原因记在 CM_ 注释里

档位不是形式主义

PT 总线上 0x0054 的 GENE_EmergcyStp_flg 是 CONFIRMED-DECOMPILE:Ghidra 里写着 (pbVar15[6] 与 8) 不等于 0,但所有日志里它一次都没触发过,没有行为测试。同理 0x0580 的 tcm_CAN_ERR。这类条目可以读,但不能当成「已经证明这个位是这个意思」来用。

0x381 与 C CAN 电芯索引:一条被纠正了三次的映射

C CAN 只在少数几次采集里被同时录下(需要第三个适配器),所以能回答这个问题的只有三份日志。总线识别靠内容而不是总线号:0x000–0x019 的电芯帧是 C CAN,0x381/0x382/0x383/0x390/0x439 是 B CAN,0x308/0x312/0x325/0x330/0x55C 是 D CAN——两次会话给同一条物理总线编的总线号并不一致。

会话 日志 配对样本 偏移 +1 严格命中率
2026-07-21 Android 三适配器 coda_20260721_192435_T0..T2、_201109_T0/T1、_202031_T0 660 82–87%
2026-02 Bonnydoon 行驶 33 min projects/coda/logs/frombonnydoon.log 1,972 80–84%
2026-07-22 充至 100% coda_20260722_173203_T0..T21 979 100%

合计 43 万帧以上的 C CAN 电芯帧、8,300 帧以上 0x381、3,600 对以上时间配对样本,且配对时刻 C CAN 数组完整、没有帧旧于 2 秒。充电会话最干净:负荷低而稳,电芯在 B 与 C 两轮扫掠之间几乎不重排,严格测试在 VmaxId(591 样本)与 VminId(706 样本)上都命中 100.0%。

会话 信号 严格样本数 命中偏移 +1 不同 ID 数 ID 范围
Android VmaxId 135 82.2% 11 6–104
Android VminId 146 87.0% 26 1–101
Bonnydoon VmaxId 225 79.6% 44 6–104
Bonnydoon VminId 376 84.3% 33 1–104
充电 07-22 VmaxId 591 100.0% 2 82–95
充电 07-22 VminId 706 100.0% 1 96

「严格」的口径是:只保留 C CAN 数组自身的极值恰好等于所报极值、该极值由唯一一只电芯持有、且领先第二名至少 3 mV 的样本。行驶会话里没有任何竞争偏移超过 4%;剩下约 15% 也不是反例——两条总线不是同时采样的,C CAN 扫完 26 帧要约 800 ms,而 0x381 只有约 1.1 Hz,行驶中负荷波动会在两个时刻之间把几乎并列的电芯重排。签名也说明这是偏斜而不是置换:未命中的配对集中在少数几个本来就常年处于极值的 C CAN 索引上(max 侧 81/83/94/102,min 侧 76/80/95),真置换会把它们撒满整个数组。

三次纠正各纠正了什么

第一次是编号口径:映射被发现时 C CAN DBC 还是 0-based,关系是「上报 id 等于旧索引加 1」;2026-07-22 改为 1-based 后加 1 消失,两套编号重合。这也推翻了更早那句「id 用的是与 C CAN 位置无关的 BMS 内部编号」——BMS 编号就是 C CAN 的发送顺序(1-based)。

第二次是字节序。2026-07-15 之前 0x381 的归属是 B0-1=MinCellVoltage、B2-3=MaxCellVoltage、B4-5=AvgCellVoltage、B6=VminId、B7=VmaxId;修正后是 B0-1=Avg、B2-3=Max、B4-5=Min、B6=VmaxId、B7=VminId。当年的错误来自照着 LabVIEW cluster 的名字「VminVmax」排,而不是按 LabVIEW 字段顺序 Vavg、Vmax、Vmin、VmaxId、VminId。重跑严格测试可以量化这两种读法的差距。

归属方式 Android 样本 / 命中 +1 Bonnydoon 样本 / 命中 +1
修正后 max — 值 B2-3,id B6 135 / 111(82.2%) 225 / 179(79.6%)
修正后 min — 值 B4-5,id B7 146 / 127(87.0%) 376 / 317(84.3%)
旧 max — 值 B2-3,id B7 135 / 0(0.0%) 225 / 1(0.4%)
旧 min — 值 B0-1,id B6 0 / 0 0 / 0

两端的失效并不对称。min 侧当时根本没有信号可用:旧的 MinCellVoltage 读 B0-B1,而那其实是包平均值;平均值几乎不可能唯一且带余量地等于某一只电芯,所以两次会话里都是零样本存活,没有东西可相关。min 侧还有第二个错:id 也取自错误的字节(B6 其实是真正的 VmaxId)。max 侧则是「值对、id 错」:B2-B3 在两种读法下都真是最大值,但索引来自 B7(真正的 VminId),于是正确的最大电压配上了最小电芯的编号,噪声里最好的偏移只有 21%——读起来很像「另一套编号约定」,非常容易被误判。

第三次是对比方式。frombonnydoon.log 同时带 C CAN 与 0x381,它自己的 0x381 报 VminId 95 / VmaxId 84,正是那次笔记已经点名的两只电芯。也就是说这条映射在单份日志内部就可验,而当时的笔记却拿它们去和另一次(充电)会话的 96/76 比,把差距读成了编码差异。结论成了方法:只要两条总线同时在场,就在同一会话内部比。

还有一条适用范围限制:本节的「+1」只适用于经 DBC 从原始字节解出来的值。从 CODACAN 界面截图上读到的数字(如充电分析里的 VminId=96 / VmaxId=76)来自 LabVIEW cluster,它一直是对的,不受 DBC 错误影响。哨兵值 255 也要单独处理:104 只电芯的包里 255 不可能是索引,它是无效 / 无数据哨兵,不要当成「第 254 只电芯」。

证据边界:部分解码、未解与已推翻

「已确认且完整」的信号有其编码摘要,例如 0x275 BMSDriveLimits 的 B0 = 放电限值(uint8 乘 2 得 A)、B1 = 充电限值(乘 2 得 A)、B2-B3 = 包电流(BE uint16 乘 0.1 A,零点 0x8000)、B4-B5 = 包电压(BE uint16,1 V/bit)、B6 = HVAC 限值(乘 2 得 A)、B7 未知(基线 0xDD)。下面列的是还没收口的。

ID 名称 已知 还缺什么
0x050 BMSChargerCmd B0-B1 = HVCurrentCmd(BE uint16 乘 0.1 A,R2 = 0.992;基线 0x00DC = 22 A);B2-B3 = MaxVoltage(乘 0.1 V,R2 = 0.605;基线 0x0FF7 = 408.7 V);中位帧 00 dc 0f f7 00 01 3e 01 B4-B5 取非基线值会把 Main Current 压到约 6 A,疑似帧校验或滚动计数;B6-B7 未知;需注入测试
0x270 BMS_FaultStatus B0 为故障字节(0x00 无故障,0x12 = 故障码 18,bit 1 = 0x02 与 bit 4 = 0x10 置位);比 0x271 早一帧;drop 后 CODACAN 的 BMS Fault Severity 掉下去 B0 内部故障码与严重度的分界未扫;B6-B7 未知
0x2C1 BMSDrivingRange B0 状态字节(raw = 0x70 时翻转);B1 / B3 / B5 分别为 Exp / Max / Min,R2 约 0.96–0.97;比例暂定约 1.1 乘 raw 减 19 刻度 / 偏移未精确确认;B2、B4、B6-B7 未扫;单位可能是英里
0x330 Long_Accel 被抑制时会掉,但与噪声边界模糊;VICD 刻度为有符号字节乘 0.08 m/s² 未确认为模拟量;需要一次含急刹的行驶事件做时序提取
0x383 BMSBattStatus SoC 在 B0-B1(BE uint16 除以 10 得 %)已确认;同时带 HVBattCharging、ChrgPortOpen、ChargeCableConnected 三个布尔位的确切位号未记录
0x410 AC_Compressor_Command B2 = compressor_on_off / CASS_on_off / warmup_cycle_cntr_incr;B6 = 目标转速(乘 60 rpm/bit);B7 = 最大功耗(乘 0.1 kW/bit) B0 的三个布尔位(HVAC FaultRed、FaultYellow、KeepWakeNetworkHVAC)位号未解;也可能是扫描引起的 ECU 响应假象
0x241 MCM_Thermal 角色已确认:drop-ID 后 Rotor / Stator / IGBT 三个字段归零;DLC = 8 已由日志确认 逐字节归属未确认;偏移假定为减 40 摄氏度;B4 恒为 0x00,疑似哨兵或非温度字段
0x491 / 0x273 IPK 帧 两者都带 Primary ODO(km)、Interior Illum、Side Light Active、Low Beam Active;0x491 还会点亮 Overview 的 IPK 指示,0x273 不会 ODO 的字节位置与编码(可能是 BE uint24 或 uint32);灯光的位号

八条被推翻的早期假设

这些条目都曾写进笔记或报告,后来被数据推翻。留着不是自曝,而是让下一个人不用再踩一遍。

早期假设 更正为
0x212 = EPS_SAM_1(转向角,0.25 deg/count) 0x212 = DLCM Torque Request;转向角在 0x080
0x210 = Vehicle_Mode 含 VBatt(乘 0.4 V/count) 0x210 B2 = ThrottlePedalPos(乘 0.4 %/count)
0x271 = Death Counter 的一部分(整数字节计数) 0x271 = GWM 心跳 + VehFaults 的源
0x55C = VehFaults(3 字节:灯 / 严重度 / 故障字) 0x55C = TCM 心跳
0x381 = BMSDriveLimits 候选(哨兵 1000.0 A) 0x381 = BMSVminVmax;BMSDriveLimits 在 0x275
0x382 B0 = 充电阶段计数器(0x8B 到 0x8E 步进) B0 = Cell_T_avg;充电过程中包升温时为 11–14 摄氏度
0x275 B4 = HV Active 布尔标志 B4-B5 = PackVoltage(BE uint16,1 V/bit);104 串 LFP 包始终使 uint16 的最高位置位
BattSentDischargeLimit / ChargeLimit 刻度 = 1 刻度 = 2(uint8 乘 2 得 A)

常见问题

反编译出来的 DBC 能直接拿来解日志吗?

不能直接。反编译只证明应用把某个字节按某种方式读,不证明它读对了。所以这里把信号分成 CONFIRMED、CONFIRMED-SYMM、CONFIRMED-DECOMPILE、PARTIAL、REMOVED 五档,只有前两档带独立验证,第三档只是「应用自己这么命名」。

电芯编号到底是 0 起还是 1 起?

C CAN DBC 自 2026-07-22 起改为 1-based(Cell1 到 Cell104),与 BMS 自己的 VmaxId / VminId 直接对应,VmaxId 82 就是 Cell82。此前是 0-based,老 CSV、老图、老导出都要加 1 才能对上,例如旧 Cell94 等于现 Cell95。MinTempId / MaxTempId 是另一套索引,不受影响。

为什么 0x381 的映射要改字节序?

因为最初的归属照着 LabVIEW cluster 的名字「VminVmax」排,把 Min 与 Avg、VminId 与 VmaxId 都放反了;按 LabVIEW 字段顺序应是 Vavg、Vmax、Vmin、VmaxId、VminId。重跑严格测试后,旧的 max 读法命中 0.0%,旧的 min 读法零样本存活,修正后分别是 82.2% 与 87.0%。

偏航率刻度为什么判不出来?

两种说法分别是 0.0045(转向角 / 轴距运动学模型)与 0.003924(后轮差速几何法),但它们不是两条独立标定:同一几何法随速度窗口与轮距取值会给出 0.00347 到 0.00488。系统误差预算合计 43.4%(左右不对称 34.1%、速度窗口 21.5%、旅程间 16.0%),远大于两者相差的 14.7%,因此只能记为「判不出」。

这份素材的许可证状况如何?

被检查的上游快照里没有许可证文件,本文所依据的是一份离线私有研究副本。公开仓库可访问并不等于取得再分发或制作衍生作品的权利,对那些从第三方应用推断出来的定义尤其如此。共享或商用之前应与相关权利人确认。

延伸阅读

参考资料

  • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/SOURCE.md(九份 DBC 的报文 / 信号数与权利边界)
  • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/projects/coda/notes/codacan-signal-decode-notes.md(CODACAN 台架方法、分阶段完成表、模块在位映射与已推翻假设)
  • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/projects/coda/notes/cell-index-mapping.md(0x381 VmaxId/VminId 与 C CAN 电芯数组的三次纠正与严格测试口径)
  • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/projects/vtrux/notes/epri-verification.md(PT 与 OBD 总线的逐信号确认档位与 Gap)

免责声明

本文所述信号定义全部来自社区逆向工程,不代表车辆制造商的官方数据。Coda 与 VTRUX 两家整车企业均已退出市场,无原厂文档可核对;VTRUX 的 EPRI 定义最初由一款 iPhone 日志应用的二进制反编译重建,再用车辆日志比对修正,字节位置与比例并非一律正确。上游快照未包含许可证文件,公开仓库可访问不构成再分发或衍生权利。涉及安全相关用途时请仅作参考,并以实际测量为准。

更新记录

  • 2026-10-15 初稿

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注