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抓取这些帧,怎么看位时序参数,怎么分析仲裁过程。准备好了吗?