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位标识符,帧头更长,适合复杂网络
标准帧结构
标准帧的完整结构,我拆成几个段来讲:
- 帧起始(SOF):1个显性位,告诉所有节点“我要发帧了”
- 仲裁场:11位ID + RTR位 + IDE位 + r0保留位
- 控制场:DLC(4位数据长度码)+ 2个保留位
- 数据场:0~8字节数据
- CRC场:15位CRC校验 + 1位CRC界定符
- ACK场:1位ACK槽 + 1位ACK界定符
- 帧结束(EOF):7个隐性位
嗯,这里要注意:标准帧的IDE位是显性(0),扩展帧的IDE位是隐性(1)。这是接收节点区分两种帧的关键。
扩展帧结构
扩展帧比标准帧多了18位ID,结构上稍微复杂一点:
- 帧起始(SOF):1个显性位
- 仲裁场:11位基础ID + SRR位 + IDE位(隐性)+ 18位扩展ID + RTR位
- 控制场:r1 + r0 + DLC
- 数据场:0~8字节数据
- CRC场:15位CRC + 1位CRC界定符
- ACK场:1位ACK槽 + 1位ACK界定符
- 帧结束(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个隐性位
过载帧的触发条件有两个:
- 接收器内部条件没准备好(比如缓冲区满)
- 帧间隔期间检测到显性位(总线异常)
我个人很少在项目里主动用过载帧。因为如果接收器频繁发过载帧,说明系统设计有问题——要么缓冲区太小,要么CPU处理不过来。与其靠过载帧硬撑,不如优化软件或加大缓冲区。
帧间隔:总线的“呼吸”
帧间隔是两帧之间的空闲时间,让总线从上一帧的传输中恢复过来。帧间隔由三部分组成:
- 间歇场:3个隐性位
- 总线空闲:任意长度的隐性位(直到有节点开始发送)
- 暂停发送场:8个隐性位(仅被动错误节点需要)
你想想看,如果没有帧间隔,总线上的位流连续不断,接收节点怎么判断一帧结束、下一帧开始?帧间隔就是给接收器一个“喘口气”的机会。
小技巧: 调试CAN总线时,用示波器看帧间隔的长度,可以判断总线负载率。帧间隔越短,说明总线越忙。我曾经靠这个办法定位过一个总线拥堵问题——帧间隔几乎为零,一看就是某个节点在疯狂重发错误帧。
标准帧 vs 扩展帧:怎么选?
很多初学者纠结这个问题。我直接给你一个决策表:
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 节点数 < 20,ID需求少 | 标准帧 | 帧头短,总线利用率高 |
| 节点数 > 20,ID需求多 | 扩展帧 | 29位ID空间大,避免ID冲突 |
| 需要兼容J1939协议 | 扩展帧 | J1939强制使用29位ID |
| 对实时性要求极高 | 标准帧 | 帧头短,传输时间少 |
| 混合网络(标准+扩展共存) | 视情况 | 标准帧优先级高于扩展帧 |
嗯,这里要注意:标准帧和扩展帧可以在同一条总线上共存,但标准帧的优先级永远高于扩展帧。因为标准帧的IDE位是显性(0),扩展帧的IDE位是隐性(1),仲裁时显性位赢。
SVG:CAN帧格式结构图
下面这张图把标准帧和扩展帧的结构画在一起,方便对比。红色是标准帧独有部分,蓝色是扩展帧独有部分,灰色是两者共有的。
总结一下
CAN数据链路层的帧格式,说白了就是一套“交通规则”。数据帧负责运货,远程帧负责叫货,错误帧负责报警,过载帧负责喊停,帧间隔负责让行。每种帧各司其职,CAN总线才能跑得稳、跑得快。
我个人建议,初学者先把标准帧和扩展帧的结构背熟,尤其是仲裁场和控制场的位定义。然后去抓几组实际波形,对着手册一比特一比特地看。等你亲手解析过几十帧数据,这些概念就刻在脑子里了。