4、CAN总线数据链路层:位填充机制、CRC校验、错误处理机制、CAN 2.0A与2.0B区别

各位同学,今天我们聊聊CAN总线数据链路层的几个核心机制。说实话,这部分内容在项目调试中踩坑最多。我自己刚入行那会儿,就因为没搞懂位填充机制,在高速通信时丢了一整包数据,排查了整整两天。嗯,咱们一个一个来看。

4.1 位填充机制:为什么要有它?

先问大家一个问题:CAN总线是差分信号,收发器靠什么来同步时钟?答案是——电平跳变。如果连续发送5个相同电平的位,接收器的时钟就会慢慢漂移,最终采样出错。

位填充机制就是为了解决这个问题。规则很简单:发送器在连续发送5个相同电平的位之后,必须插入一个相反电平的位。接收器收到后,会自动把这个填充位剔除掉。

举个例子:

假设你要发送的数据是:11111 00000 111

经过位填充后变成:111110 000001 111

注意看,每5个连续相同位后面,我加了一个反相的填充位(加粗部分)。

我个人习惯在调试时,用示波器抓一下CAN总线波形。如果你看到波形上有规律的「额外跳变」,那就是位填充在起作用。我曾经遇到一个案例,某款MCU的CAN控制器在发送0x55(二进制01010101)时,因为电平跳变太频繁,反而触发了位填充的异常处理——这属于硬件bug,后来换了批次才解决。

避坑指南:

我曾经在一条长距离(约50米)的CAN总线上,因为线缆阻抗不匹配导致信号反射,位填充后的波形出现了振铃。接收器误判了填充位的位置,整个报文都废了。解决办法是在终端电阻上并联一个小电容(约4.7nF),滤掉高频振铃。

4.2 CRC校验:15位多项式如何保平安?

CAN 2.0的CRC校验用的是15位多项式:x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1。说白了,就是发送器把数据帧里的所有位(包括标识符、控制场、数据场)当作一个多项式,除以这个生成多项式,余数就是CRC校验码。

接收器收到后,用同样的方法计算一遍。如果余数不为0,说明数据在传输过程中被篡改了。

CRC计算范围:

  • SOF(帧起始)
  • 仲裁场(11位或29位标识符 + RTR + IDE + SRR)
  • 控制场(DLC + r0/r1)
  • 数据场(0~8字节)

注意:CRC校验码本身和后面的ACK场、EOF场不参与计算。

我记得有一次做ECU间的OTA升级,数据量很大,每包8字节。有个同事发现偶尔会出现「校验失败」的错误计数。排查下来,原来是他的代码里把CRC计算范围多算了2个位——把填充位也算进去了。你想想看,填充位是物理层动态插入的,数据链路层根本不知道它的存在,这能不报错吗?

重要提醒:

CRC校验只能检测错误,不能纠正错误。一旦发现CRC错误,接收器会发送错误帧,要求发送器重传。在实时性要求高的系统中(比如动力CAN),重传可能导致时序超差。我建议在设计阶段就评估好最坏情况下的重传次数。

4.3 错误处理机制:五种错误与三种状态

CAN总线的错误处理机制,可以说是所有车载总线里最健壮的。它定义了五种错误类型:

错误类型 触发条件 个人经验
位错误 发送器监控总线,发现发送的电平与监控到的电平不一致 最常见于总线短路或节点故障
填充错误 接收器检测到连续6个相同电平(违反位填充规则) 我遇到过因晶振偏差导致的填充错误
CRC错误 接收器计算的CRC与发送器的不一致 线缆过长或干扰严重时高发
格式错误 帧格式中的固定位(如EOF、ACK)出现错误电平 通常是软件配置问题
ACK错误 发送器在ACK槽位没有检测到显性电平 总线上没有其他节点应答

每个CAN节点内部维护着两个计数器:发送错误计数器(TEC)接收错误计数器(REC)。根据这两个计数器的值,节点会进入三种状态:

  • 主动错误状态(TEC < 128,REC < 128):正常通信,检测到错误时发送主动错误帧(6个显性位)
  • 被动错误状态(TEC > 127 或 REC > 127):可以接收,但发送错误帧时只能发被动错误帧(6个隐性位),且发送前要等待8个隐性位
  • 总线关闭状态(TEC > 255):彻底断开与总线的连接,不再参与任何通信

调试技巧:

我曾经在实车上遇到一个现象:某节点偶尔会「掉线」几秒钟,然后又恢复正常。用CANalyzer抓了TEC/REC寄存器值,发现TEC在短时间内从0飙升到256。原因是该节点的CAN控制器时钟精度不够,在高温下漂移严重,导致位采样点偏移。解决办法是改用外部晶振,并调整采样点位置(从70%调整到80%)。

4.4 CAN 2.0A与2.0B:11位还是29位?

这个问题其实很简单,但很多新手容易搞混。我直接说结论:

  • CAN 2.0A:标准帧,标识符长度11位。最大可寻址范围:2¹¹ = 2048个ID
  • CAN 2.0B:扩展帧,标识符长度29位。最大可寻址范围:2²⁹ ≈ 5.3亿个ID

你可能会问:为什么需要29位?因为现代汽车电子系统越来越复杂,一个动力域可能有几十个ECU,再加上车身、信息娱乐、ADAS,11位ID根本不够分。而且29位ID可以携带更多优先级信息,方便做报文路由。

帧格式区别:

字段 CAN 2.0A(标准帧) CAN 2.0B(扩展帧)
标识符 11位 29位(11位基础ID + 18位扩展ID)
IDE位 显性(0) 隐性(1)
SRR位 替代远程请求位(隐性)
RTR位 数据帧为显性,远程帧为隐性 同左
控制场 IDE + r0 + DLC r1 + r0 + DLC

这里有个容易踩的坑:兼容性问题。CAN 2.0B的控制器可以收发2.0A的报文,但反过来不行。如果你在2.0A的控制器上发送扩展帧,它会因为IDE位是隐性而直接报格式错误。我当年做售后诊断时,就遇到过一台老款ECU(只支持2.0A),被诊断仪发了个扩展帧请求,结果ECU直接进入总线关闭状态——嗯,后来我们给诊断仪加了个配置项,强制使用标准帧。

实际项目建议:

除非你的网络中有老旧的2.0A节点,否则我建议统一使用CAN 2.0B扩展帧。原因有三:

  1. ID资源更充裕,方便后期扩展功能
  2. 29位ID可以编码更多信息(比如源地址、目标地址、报文类型)
  3. 主流MCU的CAN控制器都支持2.0B,成本没有增加

但要注意:扩展帧的仲裁场更长,总线利用率会略低于标准帧。在带宽紧张的场合(比如500kbps的CAN FD),需要权衡一下。

4.5 小结:这些机制如何协同工作?

咱们把今天的内容串起来想想看:

  1. 发送器先组装好数据帧(11位或29位ID + 控制场 + 数据场)
  2. 计算CRC校验码,附加在数据场后面
  3. 发送过程中,硬件自动进行位填充(每5个相同位插入一个反相位)
  4. 接收器收到后,先剔除填充位,再计算CRC
  5. 如果CRC正确,发送ACK应答;如果错误,发送错误帧
  6. 错误帧会破坏当前报文,所有节点检测到错误后丢弃该报文
  7. 发送器根据错误类型和计数器状态,决定是否重传或进入被动/关闭状态

这一整套机制,保证了CAN总线在恶劣的车载电磁环境下,依然能可靠传输数据。我个人觉得,理解这些底层机制,比单纯会用CAN工具更重要——因为只有懂了原理,你才能在出问题时快速定位根因。

下一章,咱们聊聊CAN控制器和收发器的硬件选型,以及如何设计一个可靠的CAN节点电路。到时候我会分享一些我在EMC测试中踩过的坑,敬请期待。