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

好,咱们今天聊聊CAN总线最核心的部分——数据链路层。说白了,这一层决定了数据怎么打包、怎么发出去、怎么避免冲突。我在MTK8678项目里调试CAN通信时,有八成的问题都出在这一层。搞懂了它,你基本就掌握了CAN通信的精髓。

4.1 四种帧类型:各司其职

CAN总线上一共就四种帧:数据帧、远程帧、错误帧、过载帧。嗯,别被吓到,其实每种帧的职责非常清晰。

4.1.1 数据帧:最常用的“快递员”

数据帧就是用来传输实际数据的。我习惯把它分成七段:

  • 帧起始(SOF):1个显性位,告诉总线“我要开始发了”。
  • 仲裁段:包含ID和RTR位。标准帧11位ID,扩展帧29位ID。
  • 控制段:6位,包含IDE、保留位和DLC(数据长度码)。
  • 数据段:0~8字节,真正要传的数据。
  • CRC段:15位CRC校验 + 1位CRC界定符。
  • ACK段:2位,接收节点确认收到。
  • 帧结束(EOF):7个隐性位。

重点提醒:标准帧和扩展帧的区别就在仲裁段。扩展帧的ID更长,但优先级更低。我在MTK8678项目中,所有高优先级信号(比如刹车、油门)都用标准帧,低优先级信息(比如空调状态)才用扩展帧。

4.1.2 远程帧:只问不拿

远程帧很有意思。它没有数据段,RTR位是隐性(1)。说白了就是“你发个数据给我看看”。

举个例子:ECU-A发一个远程帧,ID=0x123。ECU-B收到后,如果它负责这个ID的数据,就会发一个数据帧回来。

我的经验:远程帧在实际项目中用得不多。我曾经在一个项目中看到有人用远程帧做轮询,结果总线负载率直接飙到80%。后来我改成事件触发,负载率降到20%。能不轮询就别轮询。

4.1.3 错误帧:总线的“警察”

错误帧由6个连续的显性位(错误标志)和8个隐性位(错误界定符)组成。任何节点检测到错误,都会立刻发错误帧。

错误帧分两种:

  • 主动错误帧:6个显性位,由主动错误状态的节点发出。
  • 被动错误帧:6个隐性位,由被动错误状态的节点发出。

注意:如果一个节点频繁发错误帧,它可能会进入“总线关闭”状态。我在调试时遇到过,一个节点因为硬件问题疯狂发错误帧,整条总线都瘫痪了。排查了半天才发现是CAN收发器的共模电压出了问题。

4.1.4 过载帧:说“等等”

过载帧和错误帧结构很像,但用途不同。它表示“我太忙了,先别发”。

过载帧由6个显性位(过载标志)和8个隐性位(过载界定符)组成。节点在两种情况下会发过载帧:

  1. 接收缓冲区满了,处理不过来。
  2. 帧间隔期间检测到显性位(这属于异常情况)。

4.2 仲裁机制:谁先说话?

CAN总线的仲裁机制,说白了就是“谁ID小谁先发”。

具体怎么工作的?

  • 所有节点同时发送仲裁段。
  • 每个节点逐位比较:自己发的是显性(0)还是隐性(1)。
  • 如果某个节点发了隐性位,但总线上是显性位,它就输了,立刻转为接收状态。

举个例子:节点A发ID=0x100(二进制...00100000000),节点B发ID=0x200(二进制...01000000000)。在ID的第10位,A发0(显性),B发1(隐性)。B检测到总线是显性,知道自己输了,乖乖闭嘴。

关键点:仲裁不丢数据。输了的节点会在总线空闲后自动重发。这就是CSMA/CA(载波监听多点接入/冲突避免)的精髓。

你想想看,如果两个节点ID相同怎么办?那就看RTR位。数据帧的RTR是显性(0),远程帧是隐性(1)。所以数据帧优先级更高。

4.3 位填充规则:保证同步

位填充规则,我刚开始学的时候觉得它很烦,后来才发现它有多重要。

规则很简单:连续发送5个相同电平后,必须插入一个相反电平。

比如你连续发了5个1,第6位必须插一个0。接收端收到5个相同位后,会自动忽略第6位(填充位),恢复原始数据。

为什么要这么做?

  • 保证同步:CAN节点靠电平跳变来同步时钟。如果长时间没有跳变,时钟漂移会导致采样错误。
  • 区分帧类型:错误帧和过载帧的6个连续显性位,就是故意违反填充规则,让所有节点都能识别。

避坑指南:我曾经在一个项目中,DLC设置成8,但数据段只填了3个字节。结果后面5个字节是随机值,恰好出现了连续6个相同位,触发了错误帧。从那以后,我每次都会把未使用的数据字节清零。

4.4 帧格式对比表

帧类型 帧起始 仲裁段 控制段 数据段 CRC段 ACK段 帧结束
数据帧(标准) 1位显性 12位 6位 0~8字节 16位 2位 7位隐性
数据帧(扩展) 1位显性 32位 6位 0~8字节 16位 2位 7位隐性
远程帧 1位显性 12/32位 6位 16位 2位 7位隐性
错误帧 14位
过载帧 14位

4.5 实际项目中的注意事项

最后,我分享几个在MTK8678项目中踩过的坑:

  1. ID分配要留余量:别把ID排得太满。我习惯每10个ID留一个空位,方便后期加功能。
  2. DLC要精确:DLC设成8但只发3个字节,不仅浪费带宽,还可能触发位填充错误。
  3. 远程帧慎用:能不用就不用。用事件触发或周期发送代替轮询。
  4. 错误帧监控:在诊断工具里加一个错误帧计数器。如果某个节点频繁发错误帧,赶紧查硬件。

最后提醒:CAN总线设计,细节决定成败。一个位填充错误,可能让整条总线瘫痪。我在MTK8678项目初期,就因为没注意位填充规则,导致总线负载率一高就出问题。后来加上填充位检查,问题就解决了。

好了,这一章的内容就到这里。下一章咱们聊聊CAN的物理层,看看那些电平信号到底是怎么工作的。