第3章:CAN协议数据链路层——帧格式、仲裁、位填充与CRC校验

数据链路层,说白了就是CAN协议最核心的“交通规则”。它决定了消息怎么打包、怎么抢总线、怎么保证不出错。我当年刚接触CAN时,觉得帧格式就是一堆位,背下来就行。后来在实车上被坑过几次才明白——每一个位域的设计都有它的道理。

这一章我们就把数据链路层的四个关键点掰开揉碎:帧格式(标准帧 vs 扩展帧)、仲裁机制、位填充、CRC校验。嗯,内容不少,但都是硬货。

3.1 CAN帧格式:标准帧与扩展帧

CAN总线上一共跑四种帧:数据帧、远程帧、错误帧、过载帧。日常工作中打交道最多的就是数据帧。数据帧又分两种格式——标准帧(11位ID)和扩展帧(29位ID)。

我个人习惯把帧结构画成一张图记在脑子里。下面这张SVG图,就是标准帧和扩展帧的对比,你看一眼就能明白差异在哪。

标准帧(11位ID)与扩展帧(29位ID)对比 标准帧: SOF 11位ID RTR IDE r0 DLC 数据域(0~8字节) CRC(15位) ACK EOF 扩展帧: SOF 11位ID(基ID) SRR IDE 18位ID(扩展ID) RTR r1 r0 DLC 数据域(0~8字节) CRC(15位) ACK EOF 关键差异说明 • 标准帧:11位ID + RTR + IDE(显性) + r0 + DLC + 数据 + CRC + ACK + EOF • 扩展帧:11位基ID + SRR(隐性) + IDE(隐性) + 18位扩展ID + RTR + r1 + r0 + DLC + 数据 + CRC + ACK + EOF • IDE位:标准帧为显性(0),扩展帧为隐性(1)——接收器靠这个位区分两种帧 • SRR位:扩展帧特有,替代标准帧的RTR位置,发送时置隐性(1) • 数据域:两种帧都是0~8字节,长度由DLC(4位)指定

3.2 仲裁机制——谁先说话?

CAN总线是多主总线,所有节点都能发消息。那问题来了:如果两个节点同时发,谁先发?

答案是:ID小的赢。这就是CAN的无损逐位仲裁机制。

你想想看,CAN总线电平是“显性(0)覆盖隐性(1)”。两个节点同时往总线上送位流,只要有一个节点送0,总线就是0。每个节点在发送每个位的同时,会回读总线电平。如果自己送的是1但读到0,就知道自己输了,立刻停止发送,转为接收。

我在项目中遇到过一个问题:某次台架测试,两个ECU同时发消息,其中一个总是发不出去。查了半天,发现它们的ID分别是0x100和0x101——就差一个位。0x100每次都能赢,0x101永远抢不到。后来我们把0x101的优先级调高了才解决。

设计建议:分配CAN ID时,把高优先级消息(如制动、安全相关)分配小ID,低优先级消息(如车窗、空调)分配大ID。别把关键消息和普通消息的ID挤在一起,否则关键消息可能被阻塞。

3.3 位填充——保证同步的“小把戏”

CAN协议里有个很有意思的规则:连续发送5个相同电平后,必须插入一个相反电平。这就是位填充。

为什么要这么做?

说白了,是为了保证接收器的时钟能跟发送器保持同步。CAN用的是NRZ编码,电平不变就没有跳变沿,没有跳变沿接收器就不知道什么时候采样。如果连续发一堆0或一堆1,接收器的时钟漂移就会累积,最后采样错位。

位填充的规则很简单:

  • 发送器在发送过程中,每发出5个相同位,就自动插入一个相反位
  • 接收器在接收过程中,检测到5个相同位后,自动丢弃第6位(填充位)
  • 填充位不参与CRC计算

举个例子:

原始数据:  11111 00000 11111
填充后:    111110 000001 111110
            ↑     ↑     ↑
            填充位  填充位  填充位
注意:错误帧和过载帧的格式固定为6个显性位+8个隐性位,这6个显性位故意违反了位填充规则,用来向总线广播错误状态。我曾经在调试时看到总线上一串6个显性位,第一反应是“位填充违规了”,然后才意识到是某个节点在发错误帧。

3.4 CRC校验——数据完整性的最后防线

CAN帧里有一个15位的CRC字段(标准帧和扩展帧都是15位)。这个CRC覆盖SOF、仲裁场、控制场和数据场。

生成多项式是:

CRC-15-CAN: x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1

换算成二进制就是:0x4599(左对齐,15位有效)。

接收器收到一帧后,会用同样的多项式对收到的数据(包括CRC本身)做除法。如果余数为0,说明数据没出错;否则就报CRC错误。

我个人的经验是:CRC能检测出所有单比特错误、双比特错误、奇数个错误,以及大部分突发错误。但要注意,CRC不是万能的——如果错误模式恰好是多项式的整数倍,CRC就检测不出来。不过这种概率极低,工程上完全够用。

关键指标:CAN的CRC-15能检测出:
  • 所有≤5位的突发错误
  • 所有奇数个错误
  • 99.997%的更长突发错误

3.5 避坑指南——我踩过的几个坑

做CAN开发这些年,我在数据链路层上栽过几次跟头。分享出来,你们少走弯路。

  • ID分配太随意:我曾经在一个项目里把制动和车窗的ID分得很近,结果制动消息偶尔被车窗消息阻塞。后来强制规定:安全相关ID必须小于0x100,舒适相关ID大于0x200。
  • 位填充导致的数据长度变化:位填充会让实际发送的位数比原始数据多。如果你在计算总线负载率时忘了考虑位填充,算出来的结果会偏小。我一般按原始数据长度×1.2来估算。
  • CRC计算范围搞错:标准帧的CRC不包括填充位,但包括SOF。扩展帧的CRC包括SOF、基ID、SRR、IDE、扩展ID、RTR、r1、r0、DLC和数据域。别把填充位算进去。

3.6 本章小结

数据链路层是CAN协议的“硬核”部分。标准帧和扩展帧的区别,说白了就是ID长度不同;仲裁机制靠的是“显性覆盖隐性”的物理特性;位填充是为了时钟同步;CRC校验是数据完整性的保障。

这四个知识点,你在实际开发中几乎每天都会用到。尤其是仲裁和位填充——很多奇怪的通信问题,追根溯源都能回到这两个机制上。

嗯,这一章就到这。内容不少,但都是干货。你把这些吃透了,CAN协议的数据链路层就算入门了。