4、CAN数据链路层(二):位填充机制、CRC校验原理与计算、ACK应答机制、帧间隔与总线空闲

好,咱们接着聊数据链路层。上一节我们把帧结构拆了个底朝天,这一节要啃的,是CAN总线能稳定通信的几块硬骨头——位填充、CRC校验、ACK应答,还有帧间隔。这些机制,说白了就是保证数据在嘈杂的工业现场,能准确无误地从A传到B。

我刚开始接触CAN的时候,总觉得这些底层机制离应用层很远,直到有一次在产线上调试,发现报文偶尔会丢,查了三天,最后发现是位填充规则没搞对。嗯,从那以后,我再也不敢小看这些“小细节”了。

4.1 位填充机制:为什么需要“插队”?

先问一个问题:CAN总线是怎么同步时钟的?

答案是——靠边沿。每个节点都用自己的晶振,没有单独的时钟线。接收节点靠总线上的电平跳变(从显性到隐性,或隐性到显性)来同步自己的采样点。如果长时间没有跳变,时钟漂移就会累积,最终导致采样错误。

你想想看,如果连续发送了20个相同的位(比如全是0),总线电平一动不动,接收方的时钟早就跑偏了。怎么办?

位填充机制就是干这个的。规则很简单:

  • 发送方在连续发送5个相同电平的位之后,必须自动插入一个相反电平的位
  • 接收方收到数据后,会自动把这个填充位去掉,还原原始数据。

举个例子:

原始数据:11111 00000 11111
填充后:  111110 000001 111110

注意看,每5个连续相同位后面,都多了一个反相位。这个反相位就是填充位,它强制产生一个边沿,让接收节点重新同步。

重要:位填充覆盖的范围是SOF(帧起始)到CRC场结束。ACK场、EOF(帧结束)和帧间隔不参与填充。这一点我在项目中踩过坑——当时以为整个帧都填充,结果计算CRC时死活对不上。

我个人习惯,在调试时用示波器抓CAN波形,一眼就能看出填充位——它们就像“插队”的家伙,打乱了原本连续的波形。如果你看到波形上每隔5个位就有一个跳变,说明填充机制在正常工作。

4.2 CRC校验原理与计算:数据完整性的最后防线

位填充解决了同步问题,但数据在传输过程中可能被干扰(比如电磁噪声)。CRC(循环冗余校验)就是用来检测数据是否被篡改的。

CAN用的是CRC-15算法,生成多项式是:

x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1

对应的二进制是:1100010110011001(共16位,最高位x¹⁵隐含)。

计算过程其实不复杂,我尽量用白话讲:

  1. 把要校验的数据(SOF + 仲裁场 + 控制场 + 数据场)看作一个二进制数。
  2. 在这个数后面补15个0(因为CRC是15位)。
  3. 用生成多项式做模2除法(其实就是异或运算)。
  4. 得到的余数就是CRC校验码,共15位。

举个例子,假设要校验的数据是1010(简化版,实际要长得多):

数据:1010
补0后:1010 0000 0000 0000 0(补15个0)
生成多项式:1100010110011001

做模2除法(异或):
  1010 0000 0000 0000 0
^ 1100 0101 1001 1001
----------------------
  0110 0101 1001 1001 0
  ...(继续往下算,直到余数位数小于15)

最终余数:比如 0101 1010 0011 001(15位)

实战技巧:我从来不用手算CRC,太容易出错。在项目中,我一般用现成的CRC计算工具,或者直接调用芯片的硬件CRC模块。STM32的bxCAN就有硬件CRC,自动计算,省心省力。但理解原理很重要——至少要知道为什么CRC能检错。

接收方收到数据后,会用同样的方法计算一遍CRC,然后和发送方附带的CRC值比较。如果一致,说明数据没被篡改;如果不一致,就丢弃这一帧,并发送错误帧。

注意:CRC只能检测错误,不能纠正错误。而且它也不是100%可靠——理论上,不同的数据可能算出相同的CRC值(碰撞概率约1/2¹⁵)。但在工业现场,这个概率已经足够低了,配合CAN的其他检错机制,误码率可以降到10⁻¹²以下。

4.3 ACK应答机制:确认收到,没商量

数据发出去,对方到底收到没有?CAN用ACK应答机制来解决这个问题。

ACK场只有2个位:

  • ACK Slot(应答槽):1位。发送方在发送时,这一位是隐性电平(1)。
  • ACK Delimiter(应答分隔符):1位。必须是隐性电平(1)。

工作流程是这样的:

  1. 发送方发出数据帧,在ACK Slot位置输出隐性电平。
  2. 所有接收节点(包括错误节点)在ACK Slot期间,如果正确收到了帧,就主动拉低总线(输出显性电平0)。
  3. 发送方在ACK Slot结束后,检查总线电平:
    • 如果是显性(0):说明至少有一个节点正确接收,ACK成功。
    • 如果是隐性(1):说明没有任何节点确认,发送方会重发。

这里有个关键点:ACK是“线与”逻辑。只要有一个节点拉低总线,总线就是显性。所以发送方看到显性,就知道“有人收到了”,但不关心具体是谁。

我曾经在项目中遇到一个奇怪的问题:明明所有节点都正常,但发送方总是收不到ACK。查了半天,发现是一个节点的CAN控制器配置成了“只听模式”(Listen-Only Mode),它不参与ACK应答。嗯,这个坑让我记住了:ACK应答是强制性的,除非你故意关闭。

避坑指南:如果你在调试时发现发送方一直重发,先检查ACK。用示波器抓ACK Slot位置,看有没有显性位。如果没有,说明接收方没应答——可能是硬件问题,也可能是接收方认为数据有错(比如CRC校验失败)。

4.4 帧间隔与总线空闲:给总线一点喘息时间

帧与帧之间,不能无缝衔接。CAN协议规定了一个帧间隔(Interframe Space),让总线上的节点有时间处理上一帧,并为下一帧做准备。

帧间隔分为两部分:

部分 位数 说明
间歇场(Intermission) 3位 全是隐性电平(1)。这是强制性的,所有节点都必须遵守。
总线空闲(Bus Idle) 任意长度 隐性电平。只要没有节点开始发送,总线就一直空闲。

间歇场的3个隐性位,作用是让接收节点完成一些内部处理(比如把数据从移位寄存器搬到接收缓冲区)。如果发送方在间歇场就开始下一帧,接收方可能还没准备好,导致数据丢失。

总线空闲就更好理解了——没人说话的时候,总线保持隐性电平。任何节点都可以在总线空闲时发起传输。谁先拉低总线(发送SOF的显性位),谁就赢得仲裁。

这里有个细节:错误帧和过载帧没有帧间隔。它们可以紧跟在任何帧后面发送,不需要等待间歇场。为什么?因为错误是紧急情况,需要立即通知所有节点,不能等。

我的经验:在计算总线负载率时,一定要把帧间隔算进去。比如一个标准数据帧是108位(包括填充),加上3位间歇场,实际占用111位。如果总线是500kbps,一帧占用的时间就是111/500000 = 222微秒。这个数字在估算系统实时性时非常有用。

好了,这一节的内容就到这里。位填充、CRC、ACK、帧间隔——这四个机制共同保证了CAN总线在恶劣环境下的可靠通信。下一节,我们会进入CAN的物理层,看看那些电平信号和终端电阻到底是怎么回事。

记住一句话:CAN的可靠性不是靠运气,而是靠这些精心设计的底层机制。理解它们,你才能真正用好CAN。