4、CAN FD数据场与DLC:长度、编码与CRC
各位好,我是老赵。在CAN FD的报文结构里,数据场和DLC这块我当年刚接触时也绕了不少弯子。说白了,DLC(Data Length Code)就是编码器跟你「商量」帧里到底装了多少字节数据。经典CAN里DLC只有0-8,到了CAN FD时代,你想想看,一个帧最多能塞64字节,DLC的编码方式可就完全不一样了。
4.1 DLC编码规则——从4位码到真实长度
DLC在CAN FD帧里仍然是4位,但它的映射表扩展了。经典CAN里DLC=0到8对应数据长度0到8字节,9到15也用但算做8字节。CAN FD则不一样,我见过不少新手直接拿DLC值当字节数读,结果解析出来的数据全乱套了。
下面这张表是我平时查得最勤的,建议你存下来。
| DLC值(4位二进制) | 经典CAN数据长度 | CAN FD数据长度 |
|---|---|---|
| 0 – 8 (0000 – 1000) | 0 – 8 字节 | 0 – 8 字节 |
| 9 (1001) | 8 字节(非标) | 12 字节 |
| 10 (1010) | 8 字节 | 16 字节 |
| 11 (1011) | 8 字节 | 20 字节 |
| 12 (1100) | 8 字节 | 24 字节 |
| 13 (1101) | 8 字节 | 32 字节 |
| 14 (1110) | 8 字节 | 48 字节 |
| 15 (1111) | 8 字节 | 64 字节 |
核心记忆口诀:DLC ≤ 8 时,长度 = DLC;DLC > 8 时,查表跳变:9→12,10→16,11→20,12→24,13→32,14→48,15→64。
4.2 可变数据场长度——为什么这么做?
嗯,这里要注意。CAN FD允许数据场长度在0到64字节之间以非2的幂次步进(12、20、48等)变化,设计上其实是为了平衡带宽和填充开销。我参与过一个车载诊断项目,ECU每次上报的DTC数据恰好22字节。经典CAN得拆成3帧发,CAN FD一帧搞定,DLC设成12(对应20字节)不够,只能选13(32字节)浪费10字节,但你想想看,省掉的帧头开销反而赚了。
可变长度的好处很明显:
- 减少协议开销:帧头、帧尾、CRC等固定开销被更大有效载荷分摊
- 匹配真实数据量:不用为了凑8字节而填充无意义数据
- 提高实时性:一次传输更多数据,总线访问次数减少
我曾经踩过的坑:在配置CAN FD控制器时,DLC写的是12(对应24字节),但发送缓冲区的实际数据只有22字节,剩下2字节是随机值。接收方解析时发现不对劲——后来规范里其实允许数据场比DLC指示的短吗?不行。CAN FD要求数据场长度必须严格等于DLC映射的长度,不足的部分你得主动填充(一般填0x00或0x55)。不填的话,控制器会报填充错误,帧根本发不出去。
4.3 CRC计算——变长的校验策略
数据场长了,CRC也得跟着变。经典CAN用一个15位CRC保护整个帧(包括数据场),CAN FD引入了两个CRC段:
- CRC-17:当数据场长度 ≤ 16字节时,使用17位CRC
- CRC-21:当数据场长度 > 16字节时,使用21位CRC
为什么这么分?我个人的理解是:CRC的纠错能力与多项式长度正相关,短帧用长CRC浪费带宽,长帧用短CRC风险高。这个切换是自动的,控制器硬件根据DLC的值决定用哪个多项式。你只需要在配置寄存器里开启CAN FD模式,硬件自己处理。
小技巧:调试时可以抓一段CAN FD总线波形,手动验证CRC。把数据场、位填充后的比特流全部提取出来,用多项式x¹⁷ + x¹⁶ + x¹⁴ + x¹¹ + x⁶ + x⁵ + x² + 1(CRC-17)或x²¹ + x¹⁹ + x¹⁷ + x¹⁵ + x¹¹ + x⁵ + x¹ + 1(CRC-21)做除法,余数应该等于帧里的CRC段。
4.3.1 一个CRC计算的简单示例
假设我们要发送一个CAN FD帧,数据场长度为12字节(DLC=9)。此时硬件采用CRC-17。我写了个伪代码帮你理解计算过程:
// 输入:数据场 payload(12字节),以及帧中其他固定位(SOF、ID、控制场等)
// 输出:17位 CRC 校验值
uint32_t crc17_calculate(const uint8_t* data, uint32_t len) {
uint32_t poly = 0x3685B; // x^17 + x^16 + x^14 + x^11 + x^6 + x^5 + x^2 + 1
uint32_t crc = 0; // 初始值为0
for (uint32_t i = 0; i < len; i++) {
crc ^= (data[i] << 9); // 左移17-8=9位
for (int bit = 0; bit < 8; bit++) {
if (crc & 0x10000) { // 检查最高位(17位)
crc = (crc << 1) ^ poly;
} else {
crc <<= 1;
}
crc &= 0x1FFFF; // 只保留17位
}
}
return crc & 0x1FFFF;
}
注意:实际CAN FD CRC计算的范围不仅包括数据场,还包括SOF、标识符、控制场(含DLC、ESI、BRS等)以及填充位后的比特流。不同CAN FD控制器在填充位处理上可能有细微差别,我曾经遇到过两个控制器厂商的硬件在CRC段含填充位的算法上不一致,导致跨平台通信失败。建议用官方提供的参考实现做交叉验证。
4.4 知识体系总览
下面这张图把DLC、可变数据场长度和CRC计算串在了一起,你可以把它当作本章的知识地图。
你看,整个流程其实很顺:先解析DLC得到实际数据长度,然后根据长度决定用CRC-17还是CRC-21,最后硬件自动完成校验。我当年在一款国产MCU上移植CAN FD协议栈时,发现它的CRC计算引擎在填充位的参与方式上跟标准不一样,折腾了两天才定位到问题。所以,拿到新控制器后,一定先用官方的参考帧做一致性测试。
4.5 配置参数小结
这一章的内容在实际解码配置时,你需要关心的寄存器或配置项如下:
| 配置项 | 典型值/范围 | 说明 |
|---|---|---|
| DLC | 0~15 | 4位码,映射到0~64字节 |
| 数据场长度 | 0,1,2,...,64 (限DLC映射值) | 发送时填充至精确长度 |
| CRC配置 | ISO 11898-1:2015 | 硬件自动选择17/21位 |
| 填充位策略 | 固定填充位(0或1) | 各个控制器需统一 |
我个人习惯:在工程中把所有DLC映射表写成一个常量数组,每次解析时直接查表,避免现场计算。同时写一个单元测试,用已知的CAN FD样本帧验证DLC->长度的映射是否正确。这样能提前发现MCU库函数的潜在bug。嗯,这招救过我两次了。