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)。
仲裁过程是这样的:
- 所有节点同时发送ID位
- 每个节点发送一位后,监听总线电平
- 如果自己发送的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发送
- 这个节点立即停止发送,转为接收模式
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能成为汽车行业标准的原因。