3.25. CAN 总线基础

CAN(Controller Area Network,控制器局域网)最初由 Bosch 于 20 世纪 80 年代设计,用于将汽车中所有的电子控制单元连接到一条短小的共享总线上。它至今仍主导着汽车线束领域,但其同样出色的鲁棒性、实时性以及多主设计也使它成为自动化、机器人、农业和各类工业设备中默认的现场总线。

CAN 不像 SPI 和 I2C 那样有单一的“控制器”和一组“外设”。总线上的每个节点都是对等的,任何节点都可以在总线空闲时发送数据。使这种架构能够扩展的,是总线的“广播加仲裁”设计。

3.25.1. 带优先级仲裁的广播

CAN 总线上的每条消息都携带一个标识符——经典标准帧为 11 位,扩展帧为 29 位。该标识符并不是地址;它标记的是这条消息关于什么(发动机转速、刹车踏板位置、电池电压等)。当某个节点想要发送一个数值时,它会用相应的标识符给该值打上标签并广播出去,总线上的每个节点都能看到这次广播。每个接收方都在硬件层面对总线进行过滤,只挑选出自己关心的 ID。

如果两个节点试图同时发送,总线的电气设计会让数值较小的标识符所对应的消息胜出,且不会丢失任何位。

其中的诀窍在于总线的两种电气状态:显性(逻辑 0)和隐性(逻辑 1)。当没有节点发送数据时,CAN 线由终端电阻保持为高电平(隐性);任何节点都可以通过其收发器灌入电流,将线拉低(显性)。其结果是一种线与(wired-AND)逻辑:只要有任意一个节点将总线驱动为显性,线就读作显性;只有当每个节点都释放总线时,才读作隐性。显性总是胜出。(有些参考资料将同样的机制称为线或(wired-OR),把“显性”视为有效信号,而不是隐性位的与运算——无论哪种说法,其物理行为都完全相同。)

一条总线由一个连接到 Vcc 的终端电阻保持 为高电平。三个节点(A、B、C)挂接在总线 上。每个节点的输出级是一个二极管串联一个 接地开关——二极管由总线指向开关,因此闭合 开关会通过二极管将总线拉低,但任何节点都 无法将总线驱动为高电平。

概念形式的线与逻辑。真实的 CAN 在一对差分线(CAN_H / CAN_L)上运行同样的逻辑,收发器和终端电阻分布在两根线上,但总线上的规则是相同的:任何节点都可以拉为显性,只有当每个节点都释放时它才读作隐性。

仲裁正是直接利用了这种不对称性。每个发送节点都逐位发送其 ID,最高有效位(MSB)在前,并在发送的同时监视总线。如果某节点在线上送出一个隐性位却读回了显性位,它就知道有另一个 ID 更小的节点正在同时发送,并已在该位位置上胜出。它会立即停止驱动总线并转为侦听。与此同时,胜出的节点看到自己送出的位原样保持不变——从它的角度看,并没有发生什么异常。编号较小的 ID 之所以胜出,是因为它的显性位覆盖了编号较大的 ID 本应在相同位置发送的隐性位。

失败的节点随后会等待总线变为空闲,并自动重试。这里没有冲突,也不会丢失消息——只有确定性的优先级排序。

正是这种发布/订阅风格使 CAN 能够大规模工作:任何人都可以发送,任何人都可以接收,而 ID 使得分发隐式完成。

3.25.2. 物理总线

MCU 的 CAN 控制器并不直接驱动总线。它只暴露两个 3.3 V 的 CMOS 引脚——TX(它想要发送的位)和 RX(它在总线上看到的位)——这两个引脚连接到一颗称为 CAN 收发器(transceiver)或 PHY 的独立芯片。收发器位于控制器和真实的 CAN 线之间;它才是懂得如何与总线通信的部件。

总线本身是一对 5 V 的差分线:

  • CAN_H——这对线中的“高”线。

  • CAN_L——“低”线。

在隐性状态下,两根线都大约处于 2.5 V(总线 5 V 供电的中点)。在显性状态下,收发器将 CAN_H 拉高到约 3.5 V,将 CAN_L 拉低到约 1.5 V,从而在这对线之间产生约 2 V 的差分电压。接收方采样的是两根线之间的差值,这使得信号对长距离电缆上拾取的共模噪声免疫。

收发器的双向工作:

  • 在 TX 一侧,它读取控制器 3.3 V 的单端 TX 引脚,对于显性状态将 CAN_H 和 CAN_L 拉开,对于隐性状态则释放二者。

  • 在 RX 一侧,它读取差分的 CAN_H / CAN_L 线对,并在其 RX 引脚上向控制器报告一个单端的 3.3 V 电平。

两个 120 欧姆的终端电阻,分别位于电缆的两个物理端点,在没有节点驱动时将总线保持在隐性中点电压,并抑制反射——否则反射会在长距离传输时破坏差分信号。

3.25.3. 数据帧

标准 CAN 数据帧在线上看起来是这样的:

依次绘制的八个字段:SOF(1 位)、ID (11 位)、RTR(1 位)、控制(6 位)、数据 (0 — 8 字节)、CRC(16 位)、ACK(2 位)以及 EOF(7 位)。

标准 CAN 数据帧:SOF、ID、RTR、控制、数据、CRC、ACK 和 EOF 字段。

每个字段都有特定的作用:

  • SOF(帧起始)。一个显性位,表示一个新帧正在开始,并使每个节点的位时钟同步。

  • ID(标识符)。总线据以仲裁的 11 位标识符。数值越小 = 优先级越高。

  • RTR(远程传输请求)。当帧是对数据的请求而非对数据的发送时置位;具有匹配 ID 的接收方会通过自行发送数据来响应。

  • Control(控制)。 一个 6 位字段,编码数据长度(DLC)以及若干管理位。

  • Data(数据)。 0 到 8 字节的有效载荷(CAN Classic;CAN FD 将其扩展到 64 字节)。

  • CRC。 共 16 位:对前面各字段计算的 15 位 CRC 加上 1 位 CRC 定界符。

  • ACK。 共 2 位。在第一位——ACK 槽——发送方释放总线,任何正确接收到该帧的节点都会将其拉低;第二位是一个隐性定界符。缺少 ACK 则告诉发送方没有节点收到该帧,应当重新发送。

  • EOF(帧结束)。七个隐性位,用于结束该帧。

所有这些都由 CAN 控制器在硬件中生成和解码;软件只看到 ID、数据和少数几个标志位。

3.25.4. CAN 的定位

CAN 的设计取舍决定了它的适用领域:

  • 鲁棒。 双绞线上的差分信号、内建的错误检测、确定性的优先级仲裁以及自动重试,使 CAN 能够在 UART 和 SPI 会损坏数据的电气噪声环境中正常工作。

  • 多主。 任何节点都可以在任何时刻发送。不需要中央控制器,也没有任何节点会成为单点故障。

  • 带宽受限。 经典 CAN 的上限约为 1 Mbit/s;CAN FD 对此进行了扩展。当链路位于电路板或模块之间、通常要跨越数米电缆、且以可靠性为优先时,CAN 是正确的选择。

  • 高层协议。 裸 CAN 总线并不规定一个 ID 的含义,也不规定如何将一条长消息拆分成分片。CANopen、J1939、ISO-TP/UDS、NMEA 2000 等应用层协议在其之上叠加了这些规则。另一个值得了解的东西是 DBC 文件:一种厂商专用的文本格式,记录在某个特定车辆或系统上哪些 ID 携带哪些位级信号。DBC 文件是描述某条总线消息布局的元数据格式,本身并不是一种协议。

代码中的 CAN 总线 中的驱动处理线级的 CAN 帧;将 ID 映射到含义则是应用程序的工作。