CAN数据链路层:帧格式详解

各位同学,今天我们来啃CAN协议里最基础、也最绕不开的一块——数据链路层的帧格式。说白了,就是CAN总线上到底在传什么东西,每种报文长什么样,干什么用的。

我记得刚入行那会儿,拿着逻辑分析仪抓了一堆CAN波形,对着手册看帧格式,看得眼花缭乱。后来自己动手写CAN驱动,才真正把这些帧结构刻进脑子里。今天我把这些经验掰开揉碎讲给你听。

CAN帧的五大类型

CAN总线上一共定义了五种帧,每种都有明确的用途。我习惯把它们分成两类:一类是“干活”的,一类是“管秩序”的。

帧类型 英文名 用途 谁发
数据帧 Data Frame 携带数据从发送器到接收器 发送节点
远程帧 Remote Frame 请求其他节点发送数据 接收节点
错误帧 Error Frame 检测到总线错误时通知所有节点 任何节点
过载帧 Overload Frame 接收器来不及处理时请求延迟 接收节点
帧间隔 Interframe Space 分隔前后两帧,让总线休息 总线空闲

你想想看,这五种帧配合起来,CAN总线才能既高效又可靠地工作。下面我一个一个讲。

数据帧:最常用的帧

数据帧是CAN总线上的主角,90%以上的总线流量都是它。它负责把发送节点的数据传给一个或多个接收节点。

数据帧有两种格式:标准帧和扩展帧。区别就在ID的长度上——标准帧11位ID,扩展帧29位ID。

核心区别速览:

  • 标准帧:11位标识符,帧头更短,适合节点不多的系统
  • 扩展帧:29位标识符,帧头更长,适合复杂网络

标准帧结构

标准帧的完整结构,我拆成几个段来讲:

  1. 帧起始(SOF):1个显性位,告诉所有节点“我要发帧了”
  2. 仲裁场:11位ID + RTR位 + IDE位 + r0保留位
  3. 控制场:DLC(4位数据长度码)+ 2个保留位
  4. 数据场:0~8字节数据
  5. CRC场:15位CRC校验 + 1位CRC界定符
  6. ACK场:1位ACK槽 + 1位ACK界定符
  7. 帧结束(EOF):7个隐性位

嗯,这里要注意:标准帧的IDE位是显性(0),扩展帧的IDE位是隐性(1)。这是接收节点区分两种帧的关键。

扩展帧结构

扩展帧比标准帧多了18位ID,结构上稍微复杂一点:

  1. 帧起始(SOF):1个显性位
  2. 仲裁场:11位基础ID + SRR位 + IDE位(隐性)+ 18位扩展ID + RTR位
  3. 控制场:r1 + r0 + DLC
  4. 数据场:0~8字节数据
  5. CRC场:15位CRC + 1位CRC界定符
  6. ACK场:1位ACK槽 + 1位ACK界定符
  7. 帧结束(EOF):7个隐性位

我的经验: 实际项目中,如果网络节点少于20个,用标准帧就够了。扩展帧虽然ID空间大,但帧头长,总线利用率会低一些。我曾经在一个项目里硬要用扩展帧,结果总线负载率飙到70%以上,后来改回标准帧才压到40%。

远程帧:请求数据的“请帖”

远程帧的作用很简单——一个节点想获取另一个节点的数据,就发一个远程帧过去。远程帧的结构和数据帧几乎一样,唯一的区别是RTR位为隐性(1)。

为什么会这样?因为RTR位在仲裁场里,远程帧的RTR=1,数据帧的RTR=0。如果同一时刻总线上同时出现相同ID的数据帧和远程帧,数据帧会赢得仲裁,因为显性位优先。

远程帧没有数据场,DLC表示请求的数据长度。比如你发一个DLC=8的远程帧,就是告诉对方“请给我发8字节数据”。

避坑指南: 我曾经在一个项目中,远程帧的DLC设错了,请求4字节但对方发8字节,结果接收缓冲区溢出,整个节点直接进入总线关闭状态。所以远程帧的DLC一定要和对方数据帧的DLC一致。

错误帧:总线的“警报器”

错误帧是CAN总线自我纠错的关键机制。任何节点检测到错误,都会立即发送错误帧,通知所有节点“刚才那帧有问题,别用了”。

错误帧由两部分组成:

  • 错误标志:6个连续相同位(显性或隐性,取决于错误类型)
  • 错误界定符:8个隐性位

错误帧有两种:

  • 主动错误帧:6个显性位,会破坏总线上的位流
  • 被动错误帧:6个隐性位,不会破坏总线

节点处于主动错误状态时发主动错误帧,处于被动错误状态时发被动错误帧。如果被动错误节点发的错误帧和其他节点发的主动错误帧冲突,被动错误帧会被覆盖。

关键点: 错误帧的发送时机很讲究。节点必须在检测到错误的位之后立即发送错误标志,不能等整帧结束。这就是CAN实时性的体现——发现问题立刻处理,不拖泥带水。

过载帧:接收器的“慢一点”

过载帧用得不多,但关键时刻能救命。当接收节点来不及处理数据时,就发过载帧,让发送节点等一等。

过载帧的结构和错误帧很像:

  • 过载标志:6个显性位
  • 过载界定符:8个隐性位

过载帧的触发条件有两个:

  1. 接收器内部条件没准备好(比如缓冲区满)
  2. 帧间隔期间检测到显性位(总线异常)

我个人很少在项目里主动用过载帧。因为如果接收器频繁发过载帧,说明系统设计有问题——要么缓冲区太小,要么CPU处理不过来。与其靠过载帧硬撑,不如优化软件或加大缓冲区。

帧间隔:总线的“呼吸”

帧间隔是两帧之间的空闲时间,让总线从上一帧的传输中恢复过来。帧间隔由三部分组成:

  • 间歇场:3个隐性位
  • 总线空闲:任意长度的隐性位(直到有节点开始发送)
  • 暂停发送场:8个隐性位(仅被动错误节点需要)

你想想看,如果没有帧间隔,总线上的位流连续不断,接收节点怎么判断一帧结束、下一帧开始?帧间隔就是给接收器一个“喘口气”的机会。

小技巧: 调试CAN总线时,用示波器看帧间隔的长度,可以判断总线负载率。帧间隔越短,说明总线越忙。我曾经靠这个办法定位过一个总线拥堵问题——帧间隔几乎为零,一看就是某个节点在疯狂重发错误帧。

标准帧 vs 扩展帧:怎么选?

很多初学者纠结这个问题。我直接给你一个决策表:

场景 推荐格式 原因
节点数 < 20,ID需求少 标准帧 帧头短,总线利用率高
节点数 > 20,ID需求多 扩展帧 29位ID空间大,避免ID冲突
需要兼容J1939协议 扩展帧 J1939强制使用29位ID
对实时性要求极高 标准帧 帧头短,传输时间少
混合网络(标准+扩展共存) 视情况 标准帧优先级高于扩展帧

嗯,这里要注意:标准帧和扩展帧可以在同一条总线上共存,但标准帧的优先级永远高于扩展帧。因为标准帧的IDE位是显性(0),扩展帧的IDE位是隐性(1),仲裁时显性位赢。

SVG:CAN帧格式结构图

下面这张图把标准帧和扩展帧的结构画在一起,方便对比。红色是标准帧独有部分,蓝色是扩展帧独有部分,灰色是两者共有的。

CAN帧格式结构对比(标准帧 vs 扩展帧) 标准帧(11位ID) SOF 11位ID RTR IDE r0 DLC 数据场(0-8B) CRC ACK EOF 扩展帧(29位ID) SOF 11位ID SRR IDE 18位扩展ID RTR r1 r0 DLC 数据场(0-8B) CRC ACK EOF 图例说明 帧起始/CRC/ACK/EOF(公共字段) 标识符(标准帧11位,扩展帧11+18位) RTR位(数据帧=0,远程帧=1) IDE位(标准帧=0,扩展帧=1) 保留位/控制位 DLC(数据长度码) 数据场(0~8字节,远程帧无数据场)

总结一下

CAN数据链路层的帧格式,说白了就是一套“交通规则”。数据帧负责运货,远程帧负责叫货,错误帧负责报警,过载帧负责喊停,帧间隔负责让行。每种帧各司其职,CAN总线才能跑得稳、跑得快。

我个人建议,初学者先把标准帧和扩展帧的结构背熟,尤其是仲裁场和控制场的位定义。然后去抓几组实际波形,对着手册一比特一比特地看。等你亲手解析过几十帧数据,这些概念就刻在脑子里了。