三、CAN数据链路层(一):CAN帧的种类与数据帧结构详解

数据链路层,说白了就是给原始信号“定规矩”的那一层。CAN总线之所以能在汽车上稳定跑几十年,靠的就是这一层把通信规则定得死死的。我个人习惯把数据链路层理解为“交通警察”——它告诉你什么时候该走、什么时候该停、出了事故怎么处理。

这一章我们重点看两件事:CAN帧有哪几种,以及数据帧内部到底长什么样。

3.1 CAN帧的四种类型

CAN总线上跑的“消息包”一共就四种。不多,但各司其职。

帧类型 用途 谁发 有没有数据
数据帧 发送数据 发送节点 有(0~8字节)
远程帧 请求数据 接收节点 无
错误帧 报告总线错误 检测到错误的节点 无
过载帧 请求延迟发送 接收节点(忙时) 无

实际项目中,数据帧占了95%以上的总线流量。远程帧偶尔用在诊断场景。错误帧和过载帧嘛——我希望你永远别在示波器上看到它们。

我曾经在一个ECU联调现场,发现总线上一秒出现几百个错误帧。排查了两天才找到原因:某个节点的CAN收发器供电纹波太大,导致隐性位识别失败。那之后我养成了一个习惯——先量电源,再查协议。

3.2 数据帧结构详解

数据帧是CAN通信的“主力军”。它的结构从SOF到EOF,一共七个场。我建议你把这七个场背下来,因为后面讲位时序、采样点、仲裁机制,全都要用到它们。

标准数据帧(11位ID)结构总览:

SOF + 仲裁场(12位) + 控制场(6位) + 数据场(0~64位) + CRC场(16位) + ACK场(2位) + EOF(7位)

总长度:44~108位(不含位填充)

3.2.1 SOF(帧起始)

SOF只有1位,固定为显性(0)。它干两件事:

  • 告诉所有节点:“我要发消息了,总线开始同步”
  • 作为硬同步的触发边沿

你想想看,如果总线上所有节点都在等别人先开口,那就没人说话了。SOF就是那个“我先来”的信号。我见过一些初学者把SOF当成数据的一部分去解析,结果怎么都对不上——记住,SOF不是数据,它是同步信号。

3.2.2 仲裁场

仲裁场是CAN总线最精彩的设计之一。标准帧里它包含:

  • 11位标识符(ID):决定消息优先级,数值越小优先级越高
  • 1位RTR位:数据帧为显性(0),远程帧为隐性(1)

为什么说它精彩?因为CAN的仲裁是无损的——多个节点同时发送时,优先级低的节点自动退出发送,优先级高的节点不受任何影响。这比以太网的CSMA/CD优雅太多了。

避坑指南:

我在项目中遇到过一个问题:两个节点用同一个ID发数据,结果总线一直报错。查了半天才发现是ID分配冲突。记住,CAN总线上每个消息ID必须是唯一的——不是说节点ID唯一,而是消息ID唯一。同一个ID只能由一个节点发送。

3.2.3 控制场

控制场共6位,结构如下:

位 名称 说明
1 IDE位 0=标准帧,1=扩展帧
1 保留位r0 显性(固定0)
4 DLC(数据长度码) 0~8,表示数据场字节数

DLC虽然只有4位,但有效值只有0~8。你可能会问:4位不是能表示0~15吗?嗯,CAN协议规定DLC超过8时,数据场长度按8处理。这是历史原因——早期设计时觉得8字节够用了,后来也没改。

3.2.4 数据场

数据场就是真正要传的内容。0~8字节,长度由DLC决定。传输顺序是MSB优先(高位在前)。

举个例子,如果我要发送一个16位的车速值0x1234:

// 假设车速ID = 0x100,DLC = 2
// 数据场排列:
// Byte 0: 0x12 (高8位)
// Byte 1: 0x34 (低8位)

这里有个常见的坑:字节序问题。不同厂商对多字节数据的排列方式可能不同。有的用Motorola格式(高位在前),有的用Intel格式(低位在前)。我在做BCM项目时就吃过这个亏——仪表盘显示的车速一直是0,后来发现是ECU发的车速字节序和仪表解析的不一致。

3.2.5 CRC场

CRC场包含:

  • 15位CRC序列:由SOF、仲裁场、控制场、数据场计算得出
  • 1位CRC分隔符:隐性位(1)

CAN用的CRC多项式是:x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1。这个多项式能检测出:

  • 所有单比特错误
  • 所有双比特错误
  • 所有奇数个错误
  • 所有长度≤15的突发错误
  • 99.997%的其他错误

说实话,你不需要记住这个多项式。但你要知道:CRC是CAN总线可靠性的最后一道防线。如果CRC校验失败,接收节点会直接丢弃这帧数据。

3.2.6 ACK场

ACK场只有2位:

  • ACK槽(1位):发送节点发隐性(1),接收节点如果正确接收则拉为显性(0)
  • ACK分隔符(1位):固定隐性(1)

这个设计很有意思。发送节点在ACK槽期间释放总线(输出隐性),然后监听总线。如果有任何一个节点正确接收了这帧数据,就会把ACK槽拉成显性。发送节点看到显性,就知道“有人收到了”。

我曾经遇到一个故障:某个节点发完数据后一直报“无应答”错误。用示波器抓波形发现,ACK槽一直是隐性的。后来发现是接收节点的CAN控制器初始化时把验收滤波器设错了,导致它认为所有消息都不匹配,所以不发送ACK。嗯,验收滤波器这东西,后面我们会专门讲。

3.2.7 EOF(帧结束)

EOF共7位,全部为隐性(1)。它的作用是给总线一个“安静期”,让所有节点知道这帧结束了。EOF之后紧接着是3位的ITM(帧间空间),然后才能发下一帧。

为什么是7位?因为位填充机制最多连续出现6个相同位,7个连续的隐性位是唯一不可能在数据段出现的模式——这样接收节点就能准确判断帧结束。

3.3 数据帧完整结构图

下面这张图把标准数据帧的完整结构画出来了。我建议你对照着图,在脑子里过一遍整个收发流程。

标准数据帧(11位ID)完整结构 SOF 1位 仲裁场 ID(11) + RTR(1) 12位 控制场 IDE+r0+DLC 6位 数据场 0~8字节 0~64位 CRC场 CRC(15)+分隔符(1) 16位 ACK场 ACK槽+分隔符 2位 EOF 7位 说明: 标准帧总长度 = 1 + 12 + 6 + (0~64) + 16 + 2 + 7 = 44~108位(不含位填充) 扩展帧(29位ID)仲裁场为32位,总长度 = 64~128位 位填充机制(Bit Stuffing) • 从SOF到CRC场结束,每出现5个连续相同位,自动插入1个相反位 • 目的:保证总线有足够跳变沿用于时钟同步 • 注意:ACK场、EOF、ITM不参与位填充

3.4 扩展帧 vs 标准帧

CAN 2.0B协议定义了两种帧格式。区别主要在仲裁场:

特性 标准帧 扩展帧
ID长度 11位 29位
IDE位 0(显性) 1(隐性)
仲裁场长度 12位 32位
最大ID数量 2048 536,870,912
兼容性 所有控制器都支持 需控制器支持CAN 2.0B

实际项目中,乘用车动力系统常用标准帧(11位ID够用了),商用车和诊断协议(如UDS on CAN)经常用扩展帧。我个人习惯:能用标准帧就别用扩展帧——帧短意味着总线负载率低,实时性更好。

3.5 一个完整的收发流程

把上面所有知识点串起来,一个数据帧的完整生命周期是这样的:

  1. 发送节点检测总线空闲(至少11个隐性位)
  2. 发送SOF(显性位),所有节点硬同步
  3. 逐位发送仲裁场,同时监听总线。如果发现总线电平与自己发送的不一致,说明仲裁失败,立即退出发送
  4. 仲裁胜出的节点继续发送控制场和数据场
  5. 发送节点计算CRC并发送CRC序列
  6. 发送节点释放总线,等待ACK。接收节点如果CRC校验通过,在ACK槽发送显性位
  7. 发送节点发送EOF(7个隐性位),帧结束
  8. 进入ITM(帧间空间,3个隐性位),准备下一帧

实战建议:

刚开始学CAN的时候,我建议你用CAN分析仪抓一段总线波形,然后对着波形图一个一个场去解析。我自己就是这么学的——把波形放大,从SOF数到EOF,每个位都搞清楚是什么。做过三五次之后,你对CAN帧结构的理解会比看书深刻得多。

数据帧是CAN通信的基石。搞懂了它,后面讲位时序、采样点、同步机制的时候,你就知道那些参数到底在调什么了。