2. CAN报文结构:标准帧与扩展帧、数据帧与远程帧、ID仲裁机制、位填充机制

好,咱们今天来啃一块硬骨头——CAN报文的内部结构。说实话,我刚入行那会儿,看着CAN协议文档里那一堆场、位、域,头都大了。但后来我发现,只要你搞懂了几个核心概念,CAN报文其实挺有逻辑的。

我个人习惯把CAN报文想象成一个快递包裹。包裹外面有地址(ID),里面装着货(数据),还有各种标签说明这个包裹是干嘛的。嗯,咱们就从这个角度切入。

2.1 标准帧 vs 扩展帧:地址位宽之争

CAN总线最早用的是11位ID,这就是标准帧。后来发现11位不够用了,就搞了个29位ID的扩展帧。说白了,就是地址空间从2^11=2048个,扩展到了2^29=5亿多个。

标准帧(CAN 2.0A)

  • ID长度:11位
  • 仲裁场:12位(11位ID + 1位RTR)
  • 控制场:6位(IDE + r0 + DLC)
  • 总帧头开销:47位

扩展帧(CAN 2.0B)

  • ID长度:29位(11位基本ID + 18位扩展ID)
  • 仲裁场:32位(11位ID + SRR + IDE + 18位扩展ID + RTR)
  • 控制场:6位(r1 + r0 + DLC)
  • 总帧头开销:67位

关键区别点:

标准帧和扩展帧可以共存于同一总线。IDE位(Identifier Extension)就是用来区分两者的——IDE=0是标准帧,IDE=1是扩展帧。

我在项目中遇到过一个问题:某ECU只支持标准帧,但新来的传感器非要发扩展帧。结果呢?那个ECU直接把扩展帧当成了错误帧,总线上一堆错误计数器飙升。后来我加了个CAN网关做帧格式转换,才搞定。

2.2 数据帧 vs 远程帧:谁在请求数据?

数据帧就是「我这里有数据,你收一下」。远程帧则是「你那边有数据吗?发给我看看」。两者结构几乎一样,唯一的区别在于RTR位(Remote Transmission Request)。

数据帧: RTR = 0,携带数据场

远程帧: RTR = 1,无数据场,DLC表示请求的数据长度

我的经验:远程帧在实际项目中用得不多。我见过一些工程师用远程帧做轮询,但说实话,我更推荐用周期性发送数据帧的方式。为什么?远程帧有个坑——如果多个节点同时发远程帧请求同一个ID,仲裁机制会导致数据帧发送者收到多个请求,容易乱套。

你想想看,一个传感器节点收到三个远程帧请求,它该回哪个?嗯,它只能回一个。其他两个请求就超时了。所以,远程帧适合点对点通信,不适合多节点场景。

2.3 ID仲裁机制:谁优先级高谁先走

CAN总线的仲裁机制,是我觉得最巧妙的设计之一。它用的是「线与」逻辑——显性位(0)会覆盖隐性位(1)。

仲裁过程是这样的:

  1. 所有节点同时发送ID位
  2. 每个节点发送一位后,监听总线电平
  3. 如果自己发送的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发送
  4. 这个节点立即停止发送,转为接收模式

ID越小,优先级越高。 0x000是最高优先级,0x7FF(标准帧)或0x1FFFFFFF(扩展帧)是最低优先级。

避坑指南:我曾经把两个ECU的ID设成了相同的值,结果总线上一片混乱。CAN协议要求每个报文ID在总线上唯一,否则仲裁会失败,导致错误帧。所以,设计阶段一定要做好ID分配表。

还有个细节:扩展帧的SRR位(Substitute Remote Request)在标准帧的RTR位位置。扩展帧的SRR必须为隐性位(1),这样在仲裁时,如果标准帧和扩展帧的11位基本ID相同,标准帧会赢。这是有意设计的,保证标准帧优先级高于扩展帧。

2.4 位填充机制:保证时钟同步

位填充,说白了就是防止总线上出现连续5个相同电平。为什么?因为CAN节点靠电平跳变来同步时钟。如果连续6个相同电平,节点可能失步。

规则很简单:

  • 发送方在连续发送5个相同位后,自动插入一个相反位
  • 接收方检测到5个相同位后,自动丢弃后面的第6位
  • 填充位不参与CRC计算

举个例子:

原始数据:11111 00000 11111
填充后:  111110 000001 111110
(下划线是填充位)

注意:位填充覆盖的范围是SOF到CRC场之前。ACK场、EOF、IFS不进行位填充。另外,如果总线上出现连续6个相同位,接收方会认为这是填充错误,发送错误帧。

我记得有一次调试,发现总线老是莫名其妙报错。查了半天,原来是某个节点在发送数据时,数据场里恰好有连续6个0。嗯,这其实是位填充机制在起作用——它把连续6个0当成了错误。后来我检查了那个节点的发送逻辑,发现它的位填充实现有bug,修复后就正常了。

2.5 完整报文结构一览

咱们把标准数据帧的完整结构过一遍:

位数 说明
SOF 1 帧起始,显性位
仲裁场 12 11位ID + 1位RTR
控制场 6 IDE + r0 + 4位DLC
数据场 0-64 0-8字节数据
CRC场 16 15位CRC + 1位CRC分隔符
ACK场 2 1位ACK槽 + 1位ACK分隔符
EOF 7 帧结束,7个隐性位
IFS 3 帧间空间,3个隐性位

扩展帧的仲裁场是32位,控制场没有IDE位(IDE移到了仲裁场),多了r1位。其他都一样。

实用建议:设计CAN通信协议时,我一般遵循几个原则:

  • 高优先级报文(如刹车、转向)用小的ID
  • 周期性报文用数据帧,不要用远程帧
  • 尽量用标准帧,除非地址不够用
  • DLC要精确,不要多传无用字节

好了,CAN报文结构这块就聊到这儿。你想想看,一个看似简单的报文,背后藏着这么多设计智慧。仲裁机制保证了多节点无冲突通信,位填充保证了时钟同步,标准帧和扩展帧兼顾了效率和扩展性。嗯,这就是CAN能成为汽车行业标准的原因。