2、CAN总线协议基础:CAN帧结构、数据帧/远程帧/错误帧/过载帧、位时序与仲裁机制

好,咱们正式开始聊CAN协议的基础。说实话,很多工程师用了好几年CANalyzer,但对CAN帧的底层结构还是一知半解。我个人觉得,搞懂帧结构就像学开车前先搞懂油门刹车——不一定要背下每个螺丝,但基本原理必须清楚。

2.1 CAN帧的“骨架”:帧结构

CAN总线上的数据,是以“帧”为单位传输的。每一帧就像一封信,有信封(帧头)、正文(数据段)、还有签名(CRC校验)。

咱们先看标准帧(CAN 2.0A)的结构。我习惯把它分成三段来记:

  • 帧起始(SOF):1个显性位,告诉总线上所有节点“我要发数据了”。
  • 仲裁场:包含11位标识符(ID)和1位RTR位。ID决定了这帧数据的优先级,数字越小优先级越高。
  • 控制场:6位,包含IDE位、保留位和DLC(数据长度码)。DLC告诉你后面跟着几个字节的数据。
  • 数据场:0~8字节,这就是你要传的实际数据。
  • CRC场:15位CRC校验码+1位CRC界定符。用来检查数据有没有传错。
  • ACK场:2位。发送节点发完数据后,接收节点如果正确收到,就会在ACK槽位拉低电平(显性),告诉发送方“我收到了”。
  • 帧结束(EOF):7个隐性位,表示这帧结束了。

重要提醒:标准帧的ID是11位,扩展帧(CAN 2.0B)的ID是29位。扩展帧的仲裁场更长,但优先级计算方式一样——ID越小优先级越高。

我在项目里遇到过一个问题:有个同事用CANalyzer抓到的报文,DLC明明是8,但数据场只有3个字节有值。后来发现是发送节点的DLC写死了8,但实际只填充了3个字节。嗯,这种问题用CANalyzer的“触发条件”一抓一个准。

2.2 四种帧类型:各司其职

CAN总线上一共有四种帧类型。你想想看,如果只有数据帧,那总线怎么知道谁该发、谁该收?所以就有了远程帧、错误帧和过载帧。

帧类型 作用 关键特征
数据帧 发送节点向总线广播数据 包含0~8字节数据,RTR位=0
远程帧 请求其他节点发送特定ID的数据 无数据场,RTR位=1
错误帧 检测到总线错误时主动通知 6个连续显性位(错误标志)
过载帧 接收节点表示自己“忙不过来” 6个连续显性位(过载标志)

数据帧是最常见的。你平时用CANalyzer抓到的报文,99%都是数据帧。它的结构我刚才已经讲过了。

远程帧有点意思。它没有数据场,但DLC字段会指明它想要多少字节的数据。比如节点A发一个远程帧,ID=0x123,DLC=4,那节点B收到后,如果它负责这个ID,就会发一个ID=0x123、数据长度为4的数据帧回去。

实战技巧:在CANalyzer里,你可以用“CAN Remote Frame Generator”来模拟远程帧请求。我调试ECU的休眠唤醒逻辑时,经常用这招来验证节点会不会在收到远程帧后自动唤醒。

错误帧是总线上的“警报器”。当任何节点检测到错误(比如位错误、填充错误、CRC错误),就会立刻发送错误帧。错误帧由6个连续显性位组成,这会把总线上的其他帧都破坏掉——说白了就是“我不管你们了,先报警再说”。

过载帧比较少见。它表示接收节点太忙了,处理不过来。比如ECU正在刷写程序,CAN控制器缓存满了,就会发过载帧让发送方等一等。

2.3 位时序:CAN总线的“心跳”

CAN总线是异步通信,没有单独的时钟线。那节点怎么知道什么时候采样数据?靠的就是位时序。

一个CAN位被分成四个段:

  • 同步段(SS):1个Tq(时间份额)。用于同步总线上的所有节点。
  • 传播段(PTS):1~8个Tq。补偿信号在总线上的传播延迟。
  • 相位缓冲段1(PBS1):1~8个Tq。用于补偿采样点的位置。
  • 相位缓冲段2(PBS2):1~8个Tq。用于补偿采样点的位置。

采样点通常位于PBS1和PBS2之间。我建议采样点设置在75%~85%的位置,这样对总线延迟和晶振误差的容忍度最好。

避坑指南:我曾经在一个项目里,ECU和传感器之间的CAN总线长度超过40米。默认的位时序参数导致采样点太靠前,总线老是报CRC错误。后来我把传播段从2个Tq改成4个Tq,问题就解决了。所以长距离总线一定要重新计算位时序。

位时序的计算公式很简单:

位时间 = 1 / 波特率
Tq = 位时间 / (SS + PTS + PBS1 + PBS2)
采样点 = (SS + PTS + PBS1) / 位时间 × 100%

举个例子,假设波特率500kbps,位时间就是2μs。如果SS=1 Tq,PTS=3 Tq,PBS1=3 Tq,PBS2=3 Tq,那总Tq数=10,每个Tq=200ns,采样点=(1+3+3)/10=70%。

2.4 仲裁机制:谁先说话?

CAN总线是“多主”架构,任何节点都可以随时发送。但如果两个节点同时发,怎么办?靠仲裁。

仲裁的原理很简单:显性位(逻辑0)覆盖隐性位(逻辑1)。发送节点在发送ID的同时,也在监听总线。如果它发送的是隐性位(1),但总线上检测到显性位(0),说明有更高优先级的节点在发数据,它就自动退出发送。

举个例子:

  • 节点A发ID=0x100(二进制:001 0000 0000)
  • 节点B发ID=0x200(二进制:010 0000 0000)

从最高位开始比较:

  • 第1位:A发0(显性),B发0(显性)→ 总线为0,继续
  • 第2位:A发0(显性),B发1(隐性)→ 总线为0(显性),B检测到“我发1但总线是0”,B退出仲裁

所以节点A获胜,继续发送剩余的数据。

关键点:仲裁只发生在ID段和RTR位。一旦仲裁结束,胜出的节点独占总线,其他节点只能接收。这也是为什么CAN总线没有“碰撞”的概念——仲裁机制保证了总线上永远只有一帧数据在传输。

我在测试中遇到过一种情况:两个ECU的ID设置得太接近,导致高优先级节点频繁抢占总线,低优先级节点几乎发不出数据。后来我们调整了ID分配策略,把实时性要求高的信号(如刹车、转向)分配小ID,把诊断、配置类信号分配大ID。嗯,这就是仲裁机制在实际项目中的应用。

2.5 小结

这一章我们聊了CAN帧的骨架、四种帧类型、位时序和仲裁机制。说白了,CAN协议的设计哲学就是“简单、可靠、实时”。帧结构保证了数据完整性,仲裁机制保证了总线秩序,位时序保证了同步精度。

下一章我们会深入CANalyzer的配置和基本操作。到时候我会教你怎么用CANalyzer抓取这些帧,怎么看位时序参数,怎么分析仲裁过程。准备好了吗?