DBC 到固件的版本校验:T26 用 64 位 SHA-256 心跳

作者:

在

结论:T26 的 DBC 工作流把版本一致性做成了一条 CAN 心跳:cantools 生成 C 代码的同时,脚本把每个 DBC 的 SHA-256 截断为前 8 字节(64 bit)写成宏,各 ECU 用专用 ID 0x055、DLC 8 周期广播并互相比对。截断到 64 bit 不是为了省事,而是为了塞进一帧经典 CAN 的 8 字节载荷——代价是碰撞概率从 2⁻²⁵⁶ 升到约 2⁻⁶⁴,对这个用途完全够用。

DBC 字段与固件解码的一一对应

DBC 的价值在于把十六进制报文翻译成带名字和单位的信号。T26 的流程从 Kvaser Database Editor 建库开始:先建节点(如 ECU_Principal),再建报文(Name、ID、DLC、Transmitter),最后逐条加信号。每个字段的填写错误都会直接变成固件里的读数错误。

DBC 字段 要填什么 填错后的固件表现
ID 与 DLC CAN 标识符与字节长度(如 0x101、8) 收不到该帧,或按错误长度解包直接越界
Start bit 与 Length 起始位与位长度(如 0、16) 取到相邻信号的位,数值整体偏移
Byte order Intel(小端)或 Motorola(大端) 高低字节互换,数值随量级剧烈跳变
Value type Signed / Unsigned 负值变成极大正数,温度、转矩类信号尤其明显
Factor 与 Offset 缩放因子与偏移(如 factor = 0.1,则 100 表示 10.0 km/h) 量程整体缩放错误,趋势对但绝对值全错
Unit 单位(如 km/h) 不影响解码,但跨团队协作时是主要误读源

这张表也是版本校验为何必要的直接解释:上述任何一格被改动,固件都不会报错,只会安静地输出错误数据。

自动生成:DBC 到 C 代码与 Excel

仓库配了一个 GitHub Actions 工作流,每当 DBC 变更被推送,就用 python3 -m cantools generate_c_source Autonomous.dbc --output autonomous_c_output 重新生成 C 代码,产物落在 generated/。同一个工作流还会导出 xls,每个 DBC 一个页面、每个 ID 一张表,方便非固件成员审阅。

把生成环节放进 CI 的意义在于消除「本地改了 DBC、忘了重新生成」这类偏差。手工维护的解码头文件几乎必然与 DBC 漂移,而漂移本身不可见。

版本校验怎么做

工作流同时执行 embed_dbc_sha256.py,计算每个 .dbc 的 SHA-256,并把结果作为宏写进生成的头文件:

#define DATA_T26_DBC_SHA256 0x3ee5339f1dc658c6ULL

为塞进经典 CAN 单帧最大 8 字节的限制,哈希被截断为 SHA-256 的前 8 字节(64 bit)。文档给出的取舍依据是:两个不同版本碰撞的概率约 2⁻⁶⁴;要累积到约 43 亿个版本才有 50% 的碰撞概率;而加密哈希的前 64 位仍保留完整的雪崩效应,改一个字节就会改变该值。

方案 承载报文 载荷长度 碰撞概率 适用网络
截断 64 bit 经典 CAN 2.0,DLC 8 8 字节 约 2⁻⁶⁴,约 43 亿版本才有 50% 概率 CAN 2.0 网络
完整 256 bit CAN FD 单帧 32 字节 2⁻²⁵⁶ 需 CAN FD(单帧至多 64 字节)

文档给的实现示例是 STM32 HAL + CAN 2.0:每个 ECU 用专用 ID 0x055(文中示例为每 1 秒一次)发送 8 字节版本帧,其余 ECU 收到后与自身宏比对,不一致即判定为版本落后并告警或拒绝该节点。拷贝时所有 ECU 必须统一使用 Intel 小端字节序,否则比对本身会失效。

不做校验会发生什么

文档把风险写得很直白:运行旧版 DBC 的 ECU 会按错误的 bit order、factor、offset 或信号位置去解释报文,可能导致传感器读数错误、发动机与制动等系统误动作、以及难以诊断的安全故障——而这些症状在运行时没有任何可见错误。加上版本常量比对后,版本分歧在第一帧收到时就能被发现。

在 Teensy 4.1 上的落地形态

仓库给出的 MCU 示例用 FlexCAN_T4,CAN.setBaudRate(500000),接收队列 RX_SIZE_256、发送队列 TX_SIZE_16。解码时直接包含生成的头文件,例如 autonomous_dv_driving_dynamics_1.h,调用 autonomous_dv_driving_dynamics_1_unpack 读入 speed_actual、speed_target、steering_angle_actual;回包时用 _pack 打包,示例把 speed_target 改为 150 后写回同一 ID。

这条链路把「DBC 是唯一事实源」落到了实处:固件里不应再出现手写的位运算,所有位定义都来自生成产物,而生成产物与 DBC 哈希绑定。

常见问题

为什么 SHA-256 要截断到 64 位?

为了塞进一帧经典 CAN。CAN 2.0 单帧最多 8 字节,因此只取 SHA-256 的前 8 字节(64 bit)作为宏。文档给出的依据是碰撞概率约 2⁻⁶⁴、需约 43 亿个版本才有 50% 碰撞概率,且前 64 位保留完整雪崩效应,改一个字节即变化。

DBC 版本不一致在实车上会表现出什么症状?

不会报错,只会输出错误数据。运行旧版 DBC 的 ECU 会按错误的 bit order、factor、offset 或信号位置解包,导致传感器读数错误、执行系统误动作以及难以诊断的安全故障,运行期没有可见错误提示。因此需要版本心跳在第一帧就比对出来。

版本心跳必须用 0x055 吗?

不必。0x055 只是文档示例里为版本心跳专门分配的 ID,任何未被占用的标准 ID 都可以,关键是全网络约定一致、DLC 固定为 8,且所有 ECU 拷贝常量时使用相同的 Intel 小端字节序。

没有 CAN FD 的网络还能做版本校验吗?

能。经典 CAN 用截断到 64 bit 的方案即可,把 8 字节常量放进 DLC 8 的帧里周期广播。若网络支持 CAN FD(单帧至多 64 字节),则可以一帧发送完整的 32 字节 SHA-256,无需截断。

延伸阅读

参考资料

  • 项目 01 —— public-can-dbc-datasets/additional/fslart-t26-dbc/README.md(DBC 字段定义、cantools 自动生成、SHA-256 截断与 0x055 心跳示例、Teensy 4.1 用例)

免责声明

本文内容整理自公开开源仓库的自述文档,其中的宏值、ID、波特率与概率估算均为该仓库给出的示例或说明,未经独立实测验证。不同工具链、编译器与网络拓扑下的实现细节可能不同,工程落地请以实际测试为准。

更新记录

  • 2026-10-14 初稿

评论

发表回复

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