2. CAN协议核心:数据帧、远程帧、错误帧与仲裁机制

CAN协议的核心,说白了就是一套“谁先说话谁有理”的通信规则。你想想看,总线上那么多节点,同时发数据怎么办?谁先发?发错了怎么处理?这些问题的答案,都藏在CAN的帧结构和仲裁机制里。

我个人习惯把CAN帧理解成“带信封的快递”。信封上写着优先级(仲裁段),里面装着数据(数据段)。快递员(总线)看到两个信封同时出现,会自动比较谁的优先级更高,然后让高优先级的先走。

2.1 CAN数据帧结构详解

数据帧是CAN通信中最常用的帧类型。它负责把数据从一个节点发送到另一个或多个节点。标准格式的数据帧有11位标识符,扩展格式有29位。我建议初学者先吃透标准格式,扩展格式只是标识符变长了,结构逻辑完全一样。

一个完整的数据帧由7个段组成:

段名称 位数 作用
帧起始(SOF) 1 同步所有节点,标志数据帧开始
仲裁段 12或32 包含标识符和RTR位,决定优先级
控制段 6 包含IDE位、保留位和DLC(数据长度码)
数据段 0~64 实际要传输的数据,最多8字节
CRC段 16 循环冗余校验,检测传输错误
ACK段 2 接收节点确认收到正确数据
帧结束(EOF) 7 标志数据帧结束

这里有个关键点:数据段最多8字节。为什么是8?不是16或32?我记得早期设计时,工程师们认为汽车控制信号通常不会超过8字节,而且短帧有利于实时性。这个设计一直沿用至今,虽然现在有些应用觉得8字节不够用,但CAN FD(灵活数据速率)已经扩展到了64字节,那是后话了。

避坑指南:我曾经在一个项目中,看到有人把超过8字节的数据硬塞进CAN帧里,结果数据被截断,接收端解析出来的值全是错的。记住:CAN数据段最多8字节,超过的部分必须分包发送。

2.2 CAN远程帧与错误帧

除了数据帧,CAN还有两种重要的帧类型:远程帧和错误帧。

2.2.1 远程帧

远程帧的作用是“请求数据”。一个节点可以发送远程帧,告诉另一个节点:“请把你某条ID的数据发给我”。远程帧的结构和数据帧几乎一样,唯一的区别是RTR位(远程传输请求位)。

  • 数据帧的RTR位:显性(0),表示有数据要发送
  • 远程帧的RTR位:隐性(1),表示请求数据

远程帧没有数据段,所以DLC(数据长度码)虽然存在,但实际不传输数据。DLC的作用是告诉被请求节点:你发回来的数据应该有多长。

个人经验:远程帧在实际项目中用得不多。我见过很多工程师直接用数据帧轮询,而不是用远程帧。原因很简单:远程帧需要接收节点额外处理,而数据帧轮询更直观。但如果你追求总线利用率,远程帧确实能减少不必要的广播流量。

2.2.2 错误帧

错误帧是CAN协议的自愈机制。当任何节点检测到总线错误时,它会立即发送错误帧,通知所有节点:“刚才那帧数据有问题,大家别用”。

错误帧由两部分组成:

  1. 错误标志:6个连续的显性位(主动错误)或6个连续的隐性位(被动错误)
  2. 错误界定符:8个连续的隐性位

CAN协议定义了5种错误类型:

错误类型 检测条件 我遇到过的案例
位错误 发送节点监控总线,发现发送的电平和实际电平不一致 曾经有个节点驱动能力不足,发送显性位时拉不到底,被自己检测到位错误
填充错误 连续6个相同电平出现(违反位填充规则) 某次干扰导致总线出现连续6个隐性位,整个网络瞬间报错
CRC错误 接收节点计算的CRC与发送节点的不一致 线束过长导致信号衰减,CRC经常算不对
形式错误 帧格式中固定为隐性/显性的位出现相反电平 很少单独出现,通常伴随其他错误
ACK错误 发送节点没有收到ACK确认 节点离线或总线断开时最常见

重要提醒:错误帧不是用来“处理”的,而是用来“避免”的。如果你的系统频繁出现错误帧,说明硬件设计或软件配置有问题。我曾经调试过一个项目,错误帧占到了总线流量的30%,最后发现是终端电阻没焊好。

2.3 CAN总线仲裁机制

仲裁机制是CAN协议最精妙的设计之一。它解决了“多个节点同时发送时谁先发”的问题,而且不需要中央调度器。

仲裁的原理很简单:标识符越小,优先级越高。所有节点同时发送时,总线会逐位比较标识符。显性位(0)会覆盖隐性位(1)。谁先发送隐性位而别人发送显性位,谁就失去仲裁,自动转为接收状态。

举个例子:

  • 节点A发送标识符:0x123(二进制:0001 0010 0011)
  • 节点B发送标识符:0x456(二进制:0100 0101 0110)

从最高位开始比较:

  1. 第1位:A发0,B发0 → 相同,继续
  2. 第2位:A发0,B发1 → A的显性位覆盖B的隐性位 → B失去仲裁

B检测到自己失去仲裁后,立即停止发送,转为接收模式。A继续发送剩余数据,整个过程对A来说是透明的,它根本不知道B曾经想发数据。

关键理解:仲裁机制保证了“高优先级帧零延迟”。低优先级帧不会阻塞高优先级帧,因为仲裁是在发送过程中实时完成的。这比传统的令牌环或主从轮询方式高效得多。

下面这张图展示了仲裁过程的完整流程:

CAN总线仲裁流程图 CAN总线仲裁流程 节点A (ID=0x123) 节点B (ID=0x456) CAN 总线 仲裁过程(逐位比较) 位1: A=0, B=0 → 相同 位2: A=0, B=1 → A获胜 位3~11: A继续发送 节点A:继续发送数据帧 节点B:失去仲裁,转为接收 说明:标识符越小优先级越高。显性位(0)覆盖隐性位(1),失去仲裁的节点自动转为接收。 整个过程对获胜节点透明,它不知道有其他节点曾尝试发送。

仲裁机制有几个重要特性:

  • 非破坏性:获胜节点不受影响,继续发送
  • 实时性:高优先级帧的延迟只取决于自身长度,不受低优先级帧影响
  • 公平性:所有节点都有机会发送,只是优先级不同

设计建议:分配标识符时,把实时性要求高的信号(如发动机转速、刹车状态)放在小ID,把非实时信号(如车窗状态、座椅调节)放在大ID。我习惯把ID范围分成几个区间:0x000~0x0FF给关键控制信号,0x100~0x3FF给状态信息,0x400~0x7FF给诊断和配置数据。

2.4 位填充机制

位填充是CAN协议保证时钟同步的重要手段。规则很简单:连续发送5个相同电平后,自动插入一个相反电平。

为什么要这么做?因为CAN节点靠电平跳变来同步时钟。如果长时间没有跳变(比如连续发送30个隐性位),节点的时钟就会漂移,导致采样错误。

位填充的影响:

  • 数据帧的实际长度 = 固定位 + 数据位 + 填充位(最多增加约20%)
  • 填充位由发送节点自动插入,接收节点自动移除,软件不需要关心
  • 错误帧和帧结束段不进行位填充

曾经踩过的坑:有一次我在做总线负载率计算时,忘了考虑位填充的影响。结果实际负载率比理论值高了15%,导致系统在高负载下频繁丢帧。从那以后,我计算负载率时都会预留20%的余量给位填充。

2.5 本章小结

CAN协议的核心就三件事:帧结构、错误处理、仲裁机制。数据帧是主力,远程帧是辅助,错误帧是保镖。仲裁机制保证了高优先级帧的实时性,位填充保证了时钟同步的可靠性。

理解这些基础,你才能设计出稳定可靠的CAN通信系统。下一层我们要深入CAN控制器的硬件设计,看看这些协议是如何在芯片层面实现的。