2. CAN协议帧结构:数据帧、远程帧、错误帧、过载帧、帧间隔详解

CAN总线上跑的数据,说白了就是五种帧。我刚开始接触CAN时,总觉得帧结构很复杂,各种场、位、CRC算来算去。后来调了几个项目才发现,真正需要死磕的其实就两种:数据帧和远程帧。剩下的错误帧、过载帧、帧间隔,更多是总线自己维护用的,你理解它们的触发条件就够了。

咱们一个一个来拆。

2.1 数据帧 —— 最常用的那个

数据帧就是“谁谁谁,我给你带了点东西”。它负责把数据从发送节点送到接收节点。CAN 2.0规范里,数据帧分两种格式:标准帧(11位ID)和扩展帧(29位ID)。

一个完整的数据帧长这样:

帧起始(SOF) → 仲裁场 → 控制场 → 数据场 → CRC场 → ACK场 → 帧结束(EOF)

我画个图,你一眼就能看明白:

CAN 数据帧结构(标准帧) SOF 仲裁场 11位ID + RTR + IDE 控制场 r0 + DLC 数据场 0~8字节 CRC场 15位CRC + 界定符 ACK场 ACK + 界定符 EOF 关键说明: • SOF:1个显性位,同步所有节点 • 仲裁场:ID越小优先级越高,RTR=0表示数据帧 • 数据场:最多8字节,CAN FD可以到64字节 • CRC:校验数据完整性,发送节点自己算 • ACK:接收节点拉低应答,发送节点检测

每个场都有它的作用。我挑几个容易踩坑的说说。

仲裁场 —— 谁优先级高谁先说话

CAN总线没有主从,所有节点都能发。那冲突了怎么办?靠仲裁。说白了就是:ID越小,优先级越高。发送时每个节点逐位比较,遇到显性位(0)就赢,隐性位(1)就退。

我的经验:设计ID分配时,一定要把实时性要求高的报文放在小ID。比如刹车信号给0x001,车窗控制给0x100。我曾经见过一个项目,把诊断报文ID设得比控制报文还小,结果一跑诊断,车身控制就卡顿。嗯,后来改ID才解决。

控制场 —— DLC别搞错

控制场里的DLC(Data Length Code)告诉别人你带了多少数据。注意:DLC可以设0~8,但如果你设了5,数据场就必须发5个字节,接收方按5个字节收。少了会丢数据,多了会报错。

CRC场 —— 校验不对直接扔

CRC是15位,加上1位界定符。发送节点根据数据场和控制场算出一个CRC值,接收节点自己也算一遍。对不上?直接丢弃,不ACK。发送节点收不到ACK就会重发。

2.2 远程帧 —— 只要数据,不给数据

远程帧很有意思。它跟数据帧长得几乎一样,唯一的区别是:RTR位=1(隐性)。

远程帧的作用是:“喂,谁有ID=0x123的数据?给我发一份。” 它自己不携带数据场,但DLC要填对,告诉别人你想要几个字节。

对比项 数据帧 远程帧
RTR位 0(显性) 1(隐性)
数据场 0~8字节 无
用途 主动发送数据 请求数据
仲裁优先级 高于同ID远程帧 低于同ID数据帧

注意:远程帧的ID如果跟某个数据帧的ID相同,数据帧的优先级更高(因为RTR=0是显性)。所以远程帧不会“抢”数据帧的发送机会。但如果你设计不当,远程帧发得太频繁,会占用总线带宽。

2.3 错误帧 —— 总线自己纠错

错误帧不是人发的,是硬件检测到错误后自动发的。它由两部分组成:错误标志(6个连续显性位) + 错误界定符(8个隐性位)。

CAN总线有5种错误检测机制:

  • 位错误:发送节点监控总线,发现发出去的跟总线上的不一样。
  • 填充错误:连续5个相同电平后,第6位必须是相反电平,否则报错。
  • CRC错误:接收节点算出来的CRC跟发送的不一致。
  • 形式错误:固定格式的位(如EOF、界定符)出现错误电平。
  • ACK错误:发送节点没检测到ACK应答。

一旦某个节点检测到错误,它会立刻拉出6个显性位,把当前帧破坏掉。其他节点收到这个错误标志后,也会跟着发错误标志。这就是所谓的错误扩散。

我曾经踩过的坑:有次调试电机控制器,CAN总线时不时丢帧。用示波器一抓,发现总线上频繁出现错误帧。查了半天,原来是电机驱动器的高频噪声耦合到了CAN差分线上,导致位错误。后来加了共模扼流圈和终端电阻匹配,问题才解决。所以,错误帧出现时,先查物理层。

2.4 过载帧 —— 我忙,等会儿

过载帧是接收节点发的。它告诉发送节点:“我处理不过来了,你慢点发。” 过载帧的结构跟错误帧很像,也是6个显性位 + 8个隐性位。

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

  1. 接收节点内部缓冲区满了,来不及处理。
  2. 接收节点在帧间隔期间检测到显性位(异常情况)。

实际项目中,过载帧出现得不多。如果频繁出现,说明你的接收节点处理能力不够,或者总线负载太高了。

2.5 帧间隔 —— 喘口气再发

帧间隔不是帧,是帧与帧之间的空闲时间。它让总线上的节点有时间切换状态。

帧间隔分三部分:

  • 间歇场:3个隐性位。所有节点在这段时间内不能发数据。
  • 总线空闲:隐性电平,节点可以开始发送。
  • 暂停发送场:只对被动错误节点有效,8个隐性位,让它们多等一会儿。

你想想看,如果没有帧间隔,前一个帧刚结束,下一个帧立刻开始,接收节点可能还没准备好。这3个隐性位就是给总线一个缓冲。

2.6 五种帧的关系 —— 一张图看懂

我把五种帧的交互关系画了个流程图,方便你理解:

CAN 五种帧交互关系 数据帧 主动发送数据 远程帧 请求数据 错误帧 检测到错误时触发 过载帧 接收节点忙时触发 帧间隔 帧与帧之间的空闲 交互逻辑: • 数据帧和远程帧是主动发起的,错误帧和过载帧是被动触发的 • 帧间隔插在每一帧之后,保证总线有时间切换 • 错误帧优先级最高,一旦出现,所有节点停止发送

2.7 避坑总结

最后,我把自己这些年调CAN总线踩过的坑,浓缩成几条:

  • ID分配要留余量:别把ID排得太满,后期加功能时你会感谢自己。
  • 远程帧慎用:能不用就不用。远程帧多了,总线负载会飙升,而且容易造成“请求风暴”。
  • 错误帧出现先查物理层:终端电阻、线缆长度、共模干扰,这三个是头号嫌疑犯。
  • 过载帧频繁说明设计有问题:要么接收节点处理太慢,要么发送频率太高,要么缓冲区太小。
  • 帧间隔别忽略:有些低端CAN控制器对帧间隔时间敏感,太短了会丢帧。

嗯,帧结构这块就这些。你把这些搞清楚了,后面调协议栈、写驱动,心里就有底了。