2. CAN数据链路层详解:帧格式与核心机制

大家好,我是你们的嵌入式实战讲师。今天我们来啃一块硬骨头——CAN的数据链路层。说实话,很多工程师做CAN开发好几年,对帧格式还是一知半解。我当年刚入行时也是这样,直到有一次在项目现场被一个错误帧折腾了三天三夜……嗯,从那以后我就把数据链路层翻来覆去看了十几遍。

这一章,我会把CAN的四种帧格式、仲裁机制和位填充讲透。你想想看,搞懂了这些,你就能真正理解CAN为什么这么可靠,为什么能在汽车和工业领域称霸几十年。

2.1 四种帧格式:CAN总线的“语言”

CAN总线上跑的数据,说白了就四种“话”:数据帧、远程帧、错误帧、过载帧。我习惯把它们分成两类——正常通信用的(前两种)和异常处理用的(后两种)。

2.1.1 数据帧——干活的主力

数据帧是CAN总线最常用的帧。它负责把数据从一个节点传到另一个节点。标准CAN的数据帧长这样:

SOF + 11位ID + RTR + IDE + r0 + DLC + 0~8字节数据 + CRC + ACK + EOF

这里我重点说几个关键字段:

  • SOF(帧起始):一个显性位,告诉所有节点“我要发数据了”。
  • ID(标识符):11位(标准帧)或29位(扩展帧)。它不光标识数据内容,还决定了优先级——数值越小,优先级越高。
  • RTR(远程发送请求):数据帧里是显性(0),远程帧里是隐性(1)。
  • DLC(数据长度码):4位,表示数据字节数,范围0~8。
  • 数据段:0~8字节,这就是你要传的实际数据。
  • CRC(循环冗余校验):15位CRC码 + 1位CRC界定符,用来检查数据有没有传错。
  • ACK(应答):接收节点如果正确收到数据,会在ACK槽里拉低总线(显性),告诉发送方“我收到了”。
  • EOF(帧结束):7个隐性位,表示帧的结束。

重点记忆:数据帧的RTR位是显性(0),远程帧的RTR位是隐性(1)。这个区别在仲裁时很关键。

2.1.2 远程帧——我要数据,你给我发

远程帧的作用是“请求数据”。比如一个传感器节点可以发远程帧,让另一个节点把数据发过来。远程帧和数据帧的结构几乎一样,区别只有两点:

  • RTR位是隐性(1)
  • 没有数据段(DLC虽然存在,但数据长度为0)

我记得有一次做车载诊断系统,需要读取ECU的故障码。当时我用远程帧去请求数据,结果发现对方没响应。排查了半天,原来是DLC设置错了——远程帧的DLC必须和期望响应的数据帧DLC一致,否则接收节点会忽略你的请求。这个坑,我踩过。

2.1.3 错误帧——出问题了,大家注意

错误帧是CAN总线自我保护的机制。任何节点检测到总线错误,都会立即发送错误帧。错误帧由两部分组成:

  • 错误标志:6个连续相同极性的位(显性或隐性)
  • 错误界定符:8个隐性位

错误帧有两种:主动错误帧和被动错误帧。主动错误帧发6个显性位,被动错误帧发6个隐性位。我刚开始做CAN开发时,总以为错误帧是“坏事”,后来才明白——这是CAN的容错机制,它保证了错误不会扩散。

避坑指南:我曾经在一个项目中,发现总线频繁出现错误帧。用示波器一看,原来是终端电阻没焊好,导致信号反射。所以,遇到错误帧先别慌,检查硬件连接往往比查软件更快。

2.1.4 过载帧——我忙不过来,等等

过载帧和错误帧结构类似,但作用完全不同。过载帧是接收节点告诉总线“我处理不过来了,请暂停发送”。它由过载标志(6个显性位)和过载界定符(8个隐性位)组成。

过载帧在实际项目中用得不多。我做了这么多年CAN开发,只在一些老旧的ECU上见过。现在的CAN控制器基本都能处理好接收缓冲,很少需要主动发过载帧。

2.2 仲裁机制:谁优先级高谁先说话

CAN总线是“多主”架构——所有节点都能同时发数据。那问题来了:如果两个节点同时发,总线会不会乱?

答案是不会。CAN用了一种叫“非破坏性逐位仲裁”的机制。说白了就是:谁发的ID数值小(优先级高),谁就能继续发;优先级低的自动退出发送。

仲裁过程是这样的:

  1. 所有想发送的节点同时发送SOF位(显性)
  2. 然后逐位发送ID。如果某个节点发送隐性位(1),但总线上是显性(0),说明有更高优先级的节点在发,这个节点就停止发送
  3. 仲裁失败的节点转为接收模式,等总线空闲后再重试

举个例子:节点A的ID是0x100,节点B的ID是0x200。二进制分别是:

A: 001 0000 0000
B: 010 0000 0000

从最高位开始比。第1位都是0,没问题。第2位A是0(显性),B是1(隐性)。总线被A拉成显性,B检测到总线和自己发的不一致,就知道自己输了,乖乖退出发送。

实战技巧:我设计CAN网络时,习惯把关键控制信号的ID设小一点(比如0x001~0x010),把诊断和配置信息的ID设大一点(比如0x700以上)。这样紧急消息总能优先通过。

2.3 位填充:保证时钟同步的“小聪明”

位填充是CAN协议里一个容易被忽视但极其重要的机制。它的规则很简单:

发送方在连续发送5个相同极性的位后,自动插入一个相反极性的位。

为什么要这么做?因为CAN节点是靠跳变沿来同步时钟的。如果总线上连续出现太多相同极性的位(比如00000000),接收节点就没办法从信号中提取时钟信息,容易导致位采样错误。

举个例子:你要发送的数据是0x00(二进制00000000)。经过位填充后,实际发送的是:

原始数据: 0000 0000
填充后:   0000 0100 0001 0

注意看:每5个0后面插入一个1,这样接收方就能从1→0或0→1的跳变中恢复时钟。

位填充覆盖的范围是:从SOF到CRC段(包括CRC界定符之前)。EOF、ACK、错误帧和过载帧不进行位填充。

关键点:位填充是自动完成的,CAN控制器硬件会处理。但作为开发者,你要知道它的存在。我曾经遇到过一个奇怪的问题:发送特定数据模式时,总线总是出错。后来发现是位填充导致数据帧长度变化,超出了某些老式CAN控制器的处理能力。

2.4 实战总结:这些坑我替你踩过了

好了,这一章的内容就这些。最后我给大家总结几个实战中容易踩的坑:

  • ID分配:别把所有节点ID都设成相近的值,否则仲裁时冲突概率高,影响总线利用率
  • DLC设置:远程帧的DLC必须和期望响应的数据帧一致,否则对方不搭理你
  • 位填充影响:计算总线负载率时,要预留位填充带来的额外位(大约20%的余量)
  • 错误帧处理:别一看到错误帧就慌,先检查物理层(终端电阻、线缆、接插件)

下一章我会讲CAN的物理层——差分信号、位时序和采样点设置。这些东西搞不懂,你的CAN网络可能连100kbps都跑不稳。咱们下节课见。