跳到主要内容

05. CAN 报文、仲裁与 ACK

学习形式概念课
硬件要求不需要
配套 Demo
本课动作不涉及硬件操作

串口里的一帧 CAN 报文很短:

0x123 [3] 11 22 33

我们看到的只有 CAN-ID、数据长度和数据。真正在线缆上传输时,CAN 控制器还会在它们前后加入起始位、控制位、CRC、ACK 和结束位。

这一篇沿着一帧报文从开始到结束走一遍,重点看两个问题:多个节点同时发送为什么不会把报文撞坏,以及发送节点怎样知道总线上有人正确收到了报文。

先把 CAN 总线想成一个共享变量

写程序时,我们可以把一个字节拆成 8 个二进制位。比如:

十六进制 0x05
二进制 00000101

CAN 控制器发送报文时,也会把报文变成一长串 01,再一位一位送到总线上。这些工作由 CAN 控制器完成,不需要我们自己写循环拆位。

麻烦在于,CAN 不是一根发送线只接一个接收设备。多个节点共用同一条总线,两个节点可能恰好同时发送。假设这一时刻:

节点 A 想发送 0
节点 B 想发送 1

总线不可能同时既是 0 又是 1,所以 CAN 硬件必须规定最终读到哪个值。

对写代码的人,可以先用按位与 & 理解这条规则:

// 这行只是用 C 运算帮助理解总线规则,不是实际的 CAN 驱动代码。
// CAN 中 0 是显性位、1 是隐性位;只要任一节点发送 0,所有节点读回的结果就是 0。
// 因此两个节点同时发送时,总线结果可以先类比为逐位执行按位与运算。
bus_bit = node_a_bit & node_b_bit;

把两位代进去:

bus_bit = 0 & 1;
bus_bit = 0;

完整结果和我们熟悉的与运算一样:

节点 A节点 B总线最终读到
000
010
100
111

这只是帮助理解的程序模型,线缆上并没有 CPU 在执行这行 C 代码。真正的结果由 CAN 收发器和总线电路形成。

下面这张图画的是第二种情况:A 发送 0,B 发送 1,总线最终读到 0

节点 A 发送 0、节点 B 发送 1 时,总线最终读到 0

现在再给这两个值加上 CAN 里的正式名称:

  • 0 只要有一个节点发送,就能让总线变成 0,CAN 称它为 Dominant(显性位)
  • 1 必须等所有节点都没有发送 0 时才能留在总线上,CAN 称它为 Recessive(隐性位)

“显性”和“隐性”不是说一个能看见、另一个看不见。它们只是在描述两个值相遇时谁留下来。刚开始阅读时,可以暂时在脑中这样替换:

显性位 0 = 会赢的 0
隐性位 1 = 会让开的 1

图中节点 B 并没有“把 1 发坏”。节点 B 仍然可以读取总线,并发现自己想发送 1,总线上实际出现的却是 0。这个比较动作就是后面理解仲裁的关键。

到这里先只检查一件事:看到 0 & 1 = 0 时,能否理解为什么 CAN 把 0 称为显性位。这里没想通,先不要继续看帧结构。

一帧数据在线缆上的完整顺序

本教程使用标准 11 位 ID 的 Classical CAN 数据帧。下面这张图从左向右就是一帧报文在线缆上经过的顺序。先看各字段的位置,不必现在就背下所有位数。

标准 11 位 ID Classical CAN 数据帧结构

图中展示的是标准 11 位 ID 的 Classical CAN 数据帧,不包含 CAN FD 字段。

对于标准 11 位 ID 的 Classical CAN 数据帧,各部分长度为:

字段位数组成
SOF1帧起始位
Arbitration1211 位 CAN-ID + 1 位 RTR
Control6IDE + 保留位 + 4 位 DLC
Data0-640-8 个数据字节
CRC1615 位 CRC 序列 + 1 位 CRC Delimiter
ACK2ACK Slot + ACK Delimiter
EOF77 个隐性位

这是协议字段本身的长度。SOF 到 CRC 序列之间还可能插入填充位,所以在线缆上实际占用的位数可能更多。

SOF:告诉所有节点一帧开始了

SOF 是 Start of Frame 的缩写。总线空闲时保持隐性状态,发送节点先发出一个显性位 0,表示新报文开始。

其他节点看到这个变化后,会用它同步接收时序。

Arbitration:CAN-ID 在这里参与竞争

Arbitration Field 是仲裁字段。对于本教程的标准数据帧,它包含:

  • 11 位 CAN-ID;
  • RTR 位。

CAN-ID 从高位到低位发送。发送节点会一边发送,一边比较总线实际值。多个节点同时发送时,第一次出现“自己发送 1,却读到 0”的节点会退出本轮竞争。

RTR 位可以标记不同的帧类型。本教程接下来只处理携带 DATA 的普通数据帧,此时 RTR 为 0。另一种用法不影响当前的仲裁和接收实验,等真正需要使用时再解释。

Control:说明帧格式和数据长度

控制字段中包含 IDE、保留位和 DLC。

  • IDE 用于区分标准 11 位 ID 与扩展 29 位 ID;
  • DLC 是 Data Length Code,表示数据长度。

在本教程使用的 Classical CAN 数据帧中,DLC 为 0-8,对应 0-8 个数据字节。

Data:应用程序真正要传的数据

数据区可以包含 0 到 8 个字节。日志中的:

11 22 33

就是数据区,DLC 为 3。

CAN 控制器只负责传输这些字节,不知道它们代表温度、位置还是电机状态。具体含义仍由上层协议或项目约定决定。

CRC:检查传输中是否出现错误

CRC 是 Cyclic Redundancy Check,循环冗余校验。发送节点根据前面的报文内容计算 CRC,接收节点也独立计算一次。

两边结果不一致时,接收节点认为报文在传输过程中发生了错误,不会把它当成一帧正常报文。

CRC 能发现传输中的多种位错误,但它不是数据加密,也不能证明发送者身份。

ACK:接收节点在同一帧内确认

ACK 是 Acknowledgement,确认。它不是接收节点随后发送的另一帧,而是当前 CAN 帧中的一个位置。

发送节点在 ACK Slot 中发送隐性位 1。只要有一个其他节点正确接收了报文,就会在这个位置发送显性位 0。图中的箭头表示各节点在同一个 ACK Slot 对总线施加的电平:

CAN 接收节点在 ACK Slot 中发送显性位进行确认

总线最终为 0,发送节点读回这个显性位,便知道至少有一个其他节点正确收到了这一帧。

多个接收节点可以同时发送 ACK。由于它们都发送同一个显性位,发送节点只能知道“至少有一个节点确认”,无法通过 ACK 判断究竟是谁确认了。

EOF:这一帧结束

EOF 是 End of Frame。它由连续的隐性位组成,用来标记报文结束。EOF 后总线还需要保持一小段帧间隔,其他节点才能开始下一帧。

为什么抓包日志没有显示这些字段

SOF、仲裁比较、CRC 计算、ACK 检查和 EOF 处理通常由 CAN 控制器自动完成。应用程序收到的已经是控制器检查并整理后的结果,所以日志一般只显示最常用的信息:

CAN-ID + DLC + DATA

这不是日志丢失了报文内容,而是底层字段已经由硬件处理。使用 CAN 分析仪的协议视图通常也是这种显示方式;使用示波器或逻辑分析工具观察原始波形时,才能看到线上每一位的变化。

仲裁是怎样发生的

上一课提到,数值较小的 CAN-ID 优先级更高。现在用两个非常接近的标准 ID 看清原因:

节点 A:CAN-ID 0x120 = 00100100000
节点 B:CAN-ID 0x121 = 00100100001

前 10 位完全相同,两个节点都可以继续发送。下面沿着表格从左向右看,第 11 位才第一次出现不同:

CAN-ID 0x120 与 0x121 的逐位仲裁过程

节点 A 在这一位发送显性位 0,节点 B 发送隐性位 1,总线实际为 0。节点 B 发现“自己发送 1,却读到 0”,于是退出本轮仲裁。

节点 B 退出后不再影响这帧报文,并转为接收节点。节点 A 不需要重新开始,继续发送剩余字段。

因此这叫无损仲裁:胜出的报文没有被碰撞破坏,失败节点也不是发生了通信故障。总线空闲后,节点 B 可以再次尝试发送。

在相同帧格式和帧类型下,数值更小的 ID 会更早发送显性位,因此获得更高优先级。

把这一帧的过程连起来

现在回到开头,不再增加新名词。

节点 A 和节点 B 同时开始发送。两边一位一位比较,直到 A 发送 0、B 发送 1。总线结果是 0,所以 B 停止发送,A 继续把这帧报文传完。

接收节点检查报文没有问题后,在 A 这帧报文的 ACK 位置写入一个 0。A 读到这个 0,知道总线上至少有一个其他节点完整收到了报文。

两个节点同时发送

逐位比较,0x120 赢得仲裁

0x120 继续发送剩余内容

接收节点在 ACK 位置写入 0

发送节点知道“有人完整收到了”

ACK 只说明有人收到了 CAN 报文,并不等于电机已经执行了报文中的命令。怎样确认电机真正理解并处理了请求,要等学到 CANopen 的请求和响应时再判断。

这一课先停在这里。接收过滤器、发送重试、位填充和 Bus-Off 都有自己的使用场景,等实验中真正遇到对应现象时再讲。

资料链接

本篇内容参考了 CiA 对 Classical CAN 数据帧、仲裁和 ACK 机制的说明,以及 ESP-IDF 和 Bosch 的控制器文档。

CAN in Automation(CiA):CAN CC

CiA:CAN CC

这个页面可以查看标准帧结构和逐位仲裁过程。

ESP-IDF v5.5.4:ESP32-C3 TWAI 编程指南

ESP-IDF v5.5.4:ESP32-C3 TWAI

Bosch M_CAN User Manual,Revision 3.3.1

Bosch M_CAN 用户手册