2. CAN总线基础:CAN协议帧结构、仲裁机制与位时序

好,咱们今天聊聊CAN总线最核心的东西。说实话,我刚开始接触CAN的时候,觉得这东西不就是个通信协议嘛,能有多复杂?结果第一次调试就栽了跟头——总线莫名其妙挂掉,报文发不出去。后来才发现,问题出在帧结构上。嗯,咱们一步步来。

2.1 CAN协议帧结构

CAN总线上一共就四种帧:数据帧远程帧错误帧过载帧。平时打交道最多的就是数据帧和远程帧,错误帧和过载帧嘛,说白了就是总线出问题时的“求救信号”。

2.1.1 数据帧

数据帧是干活的主力。它负责把数据从一个节点送到另一个节点。结构上分两部分:仲裁场数据场

我个人习惯把数据帧拆成七个段来看:

  • 帧起始(SOF):一个显性位,告诉总线“我要开始说话了”。
  • 仲裁场:包含11位标识符(标准帧)或29位标识符(扩展帧),外加RTR位。这里决定了谁能抢到总线。
  • 控制场:6位,包含IDE位、保留位和DLC(数据长度码)。DLC告诉你后面跟了多少字节数据。
  • 数据场:0-8字节,真正要传的数据。嗯,最多8个字节,别嫌少,够用就行。
  • CRC场:15位CRC校验码+1位CRC界定符。用来检查数据有没有传错。
  • ACK场:2位。发送节点发完数据后,接收节点如果收到了,就在这里回一个显性位。
  • 帧结束(EOF):7个隐性位,表示帧结束了。

我在项目中遇到过一件事:有个同事死活调不通CAN通信,查了半天发现是DLC设成了0,但后面又发了8个字节数据。接收端一看DLC=0,直接忽略后面的数据。你说冤不冤?

标准帧 vs 扩展帧

标准帧用11位标识符,扩展帧用29位。区别就在仲裁场里。扩展帧的IDE位是隐性位,标准帧是显性位。所以标准帧优先级更高——这是协议设计上的一个小细节。

2.1.2 远程帧

远程帧长得很像数据帧,但有个关键区别:RTR位是隐性位。远程帧的作用是“请求数据”——一个节点发远程帧,另一个节点收到后,就发一个对应的数据帧回来。

你想想看,远程帧本身不带数据,数据场长度是0。但DLC字段必须填上你期望接收的数据长度。我刚开始做的时候,DLC填错了,结果对方发回来的数据长度对不上,CRC校验直接挂掉。

小技巧:远程帧的优先级比数据帧低。因为RTR位是隐性位,数据帧的RTR是显性位。仲裁的时候,显性位赢。所以数据帧永远比远程帧先抢到总线。

2.1.3 错误帧

错误帧是总线上的“警报器”。任何节点检测到错误,就会立刻发错误帧。结构很简单:错误标志(6个连续相同极性的位)+ 错误界定符(8个隐性位)。

错误帧分两种:

  • 主动错误帧:6个显性位。节点处于主动错误状态时发的。
  • 被动错误帧:6个隐性位。节点处于被动错误状态时发的。

我曾经遇到过一台ECU反复发错误帧,把整个总线都拖垮了。查到最后发现是它的CAN收发器坏了,输出电平不对。换了个芯片,问题解决。嗯,硬件问题有时候比软件更隐蔽。

2.1.4 过载帧

过载帧和错误帧结构一样,但触发条件不同。过载帧是接收节点忙不过来的时候发的——比如内部缓冲区满了,或者处理速度跟不上。说白了就是“你慢点,我消化不了”。

过载帧在实际项目中不太常见。我做了这么多年,也就碰到过一两次。一般出现在高负载的网关节点上,数据量太大,CPU处理不过来。

2.2 CAN总线仲裁机制

仲裁机制是CAN总线的精髓。为什么CAN能实现多主通信?靠的就是这个。

原理其实很简单:谁先发显性位,谁就赢。CAN总线是“线与”逻辑——显性位(逻辑0)会覆盖隐性位(逻辑1)。

具体过程是这样的:

  1. 所有想发数据的节点同时发送帧起始位(SOF)。
  2. 然后开始逐位比较标识符。每个节点发送一位,同时监听总线电平。
  3. 如果自己发的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发数据。自己立刻退出,转为接收模式。
  4. 一直比下去,直到剩下最后一个节点。它赢得总线使用权。

我建议你记住一个规律:标识符数值越小,优先级越高。因为显性位是0,所以ID=0x000的节点优先级最高,ID=0x7FF的节点优先级最低。

注意:仲裁机制只在帧起始和仲裁场有效。一旦进入控制场,就不再仲裁了。所以数据场里不会发生冲突。

我在实际项目中见过一个坑:有人把两个节点的ID设成了相同的值。结果两个节点同时发数据,仲裁场完全一样,一直比到数据场才分出胜负——但数据场不仲裁啊!最后两个节点的数据混在一起,CRC校验全挂。所以,每个节点的ID必须唯一,这是铁律。

2.3 位时序与同步

位时序这东西,说实话,刚接触的时候挺绕的。但搞懂了,CAN总线的物理层就通了。

CAN总线上没有单独的时钟线。所有节点靠的是位同步来保持节奏一致。每个节点都有自己的时钟,但通过同步机制,大家能对齐到同一个位时间上。

2.3.1 位时间的组成

一个位时间分成四段:

段名作用典型长度
同步段(Sync_Seg)检测跳变沿,用于同步1个Tq
传播段(Prop_Seg)补偿物理延迟1-8个Tq
相位缓冲段1(Phase_Seg1)采样点前调整1-8个Tq
相位缓冲段2(Phase_Seg2)采样点后调整1-8个Tq

其中Tq是时间量子,由系统时钟分频得到。采样点就在Phase_Seg1和Phase_Seg2之间。

我个人的经验是:采样点位置很关键。一般建议放在位时间的70%-80%之间。太早了,信号还没稳定;太晚了,来不及处理。我调试过一个项目,采样点设在50%,结果总线速率一高就丢帧。后来改到75%,问题解决。

2.3.2 硬同步与重同步

同步分两种:

  • 硬同步:发生在帧起始(SOF)的下降沿。所有节点强制把Sync_Seg对齐到这个跳变沿上。每个帧只做一次硬同步。
  • 重同步:发生在帧内。如果某个位的跳变沿偏离了预期位置,节点会调整Phase_Seg1或Phase_Seg2的长度来“追”上去。

重同步有个限制:SJW(同步跳转宽度)。它决定了每次最多能调整多少个Tq。SJW一般设为1-4个Tq。设得太大,抗干扰能力下降;设得太小,同步跟不上。

避坑指南:我曾经把SJW设成了最大值(4个Tq),结果总线在强电磁干扰环境下频繁失步。后来改成2个Tq,稳定多了。记住,SJW不是越大越好。

2.3.3 位时序配置实战

假设我们要配一个500kbps的CAN总线,系统时钟是40MHz。怎么算?

先确定Tq。一般一个位时间由8-25个Tq组成。我们取16个Tq:

位时间 = 1 / 500kbps = 2μs
Tq = 2μs / 16 = 125ns
分频系数 = 40MHz * 125ns = 5

然后分配各段长度:

Sync_Seg = 1 Tq
Prop_Seg = 3 Tq
Phase_Seg1 = 6 Tq
Phase_Seg2 = 6 Tq
采样点位置 = (1 + 3 + 6) / 16 = 62.5%

嗯,62.5%有点偏低了。我建议调整一下:

Sync_Seg = 1 Tq
Prop_Seg = 2 Tq
Phase_Seg1 = 7 Tq
Phase_Seg2 = 6 Tq
采样点位置 = (1 + 2 + 7) / 16 = 68.75%

这样就好多了。实际项目中,采样点最好在70%左右,具体看你的总线长度和节点数量。

重要提醒:位时序配置是所有节点必须一致的。如果两个节点的采样点位置不同,通信就会出问题。我见过一个项目,A节点采样点在70%,B节点在60%,结果A发数据B能收到,B发数据A收不到。查了三天才发现是位时序没配统一。

好了,CAN总线的基础就这些。帧结构让你知道数据怎么包装,仲裁机制让你知道谁先说话,位时序让你知道怎么对齐节奏。这三样搞明白了,CAN总线就算入门了。下一章咱们聊聊CAN的物理层和收发器选型,那又是另一番天地了。