2. CAN协议核心:CAN数据帧结构

各位同学,咱们今天来啃CAN协议里最硬核的一块——数据帧结构。说实话,我刚入行那会儿,看着那一堆场(Field)的名字就头大。什么SOF、CRC、ACK,感觉像在背密码表。但后来我发现,搞懂这个帧结构,就像拿到了CAN总线的“通关文牒”。

CAN总线上一共就四种帧:数据帧远程帧错误帧过载帧。咱们一个一个来拆解。我个人习惯,先从最常用的数据帧下手。

2.1 数据帧的七段“旅程”

一个标准的数据帧,从开始到结束,要经历七个阶段。我把它比作一次快递的完整流程:

场名称 英文缩写 位数(标准帧) 作用(大白话)
帧起始 SOF 1 bit “发车了!”——告诉所有节点,我要开始传数据了
仲裁场 Arbitration Field 12 bits “谁优先级高谁先走”——抢总线用的
控制场 Control Field 6 bits “我这包裹里装了多少东西”——数据长度说明
数据场 Data Field 0~64 bits “包裹本体”——你要传的真正数据
CRC场 CRC Field 16 bits “防伪标签”——检查数据有没有被篡改
ACK场 ACK Field 2 bits “收到了!”——接收节点给的回应
帧结束 EOF 7 bits “到站了”——结束标志

2.2 逐段拆解:从SOF到EOF

2.2.1 帧起始(SOF)—— 一声号令

SOF只有1个显性位(逻辑0)。为什么是显性?因为总线空闲时是隐性(逻辑1)。一旦有节点拉低总线,所有节点都知道:“有人要说话了!”

我曾经在调试一个多节点项目时,发现总线莫名其妙一直报错。查了半天,原来是一个节点的SOF波形上升沿不够陡,导致其他节点误判了起始时刻。嗯,这里要注意:SOF的边沿质量很重要。

2.2.2 仲裁场 —— 谁拳头硬谁说话

仲裁场包含两部分:11位标识符(ID)1位RTR位。标准帧的ID范围是0x000~0x7FF。

说白了,这就是CAN总线最巧妙的地方——无损仲裁。多个节点同时发送时,ID值越小的节点,优先级越高。为什么?因为显性位(0)会覆盖隐性位(1)。

你想想看,如果节点A发ID=0x100,节点B发ID=0x200。在第一位上,A发1(隐性),B发0(显性)。总线被拉成显性,A发现自己发的和总线不一致,立刻闭嘴退出。B继续发。整个过程没有数据损失,这就是“无损”的含义。

重要提醒:ID不只是地址,更是优先级!我见过很多新手把ID当IP地址用,结果高优先级的节点把低优先级的节点“饿死”了。设计ID分配时,一定要按实时性要求来排。

2.2.3 控制场 —— 说明包裹大小

控制场有6位:IDE位(1位)保留位r0(1位)DLC(4位)。IDE位用来区分标准帧和扩展帧(标准帧IDE=0)。DLC表示数据场有多少个字节,范围是0~8。

这里有个坑:DLC虽然能表示0~15,但CAN协议规定数据场最多8字节。所以DLC超过8时,接收方怎么处理?我曾经在一个项目中,发送方DLC设成了9,接收方的旧版驱动直接崩溃了。所以,DLC千万不要超过8,这是血的教训。

2.2.4 数据场 —— 真正的干货

数据场长度由DLC决定,0~8字节。你可以放任何东西:车速、转速、温度、状态位……但要注意字节序(Byte Order)。

我记得有一次,A节点发了一个16位整数0x1234,B节点收到后解析成了0x3412。为什么?因为A用大端(Big-Endian),B用小端(Little-Endian)。所以团队内部一定要统一字节序规范,否则就是一场灾难。

2.2.5 CRC场 —— 数据的“指纹”

CRC场包含15位CRC序列1位CRC界定符(隐性)。发送节点会计算从SOF到数据场的CRC校验值,接收节点收到后重新计算一遍。如果对不上,说明数据在传输过程中被干扰了。

CRC的算法是固定的,你不需要自己实现,CAN控制器硬件会自动完成。但你要知道:CRC只能检错,不能纠错。发现错误怎么办?靠错误帧来通知。

2.2.6 ACK场 —— 确认收货

ACK场有2位:ACK槽(1位)ACK界定符(1位,隐性)。发送节点在ACK槽发送隐性位,任何正确接收到数据的节点,都会在这个位置把总线拉成显性。如果发送节点发现ACK槽还是隐性,就知道“没人收到我的数据”,会重发。

小技巧:调试时,用示波器抓ACK场波形,能快速判断总线通信是否正常。如果ACK槽一直是隐性,说明接收节点有问题,或者根本没节点在听。

2.2.7 帧结束(EOF)—— 画上句号

EOF是7个连续的隐性位。为什么是7个?因为位填充(Bit Stuffing)机制规定,连续5个相同电平后必须插入一个相反电平。7个隐性位足够让所有节点确认“帧结束了”。

2.3 远程帧 —— 只问不买

远程帧的结构和数据帧几乎一样,唯一的区别是:RTR位为隐性(1),且没有数据场

远程帧的作用是:“喂,谁有ID=0x123的数据?发给我!”发送远程帧的节点,其实是在请求数据。被请求的节点收到后,会发送一个同ID的数据帧作为响应。

说实话,远程帧在实际项目中用得不多。为什么?因为大多数场景下,数据是周期性发送的,不需要“请求-响应”这种模式。而且远程帧容易引发总线风暴——万一多个节点同时请求同一个ID,响应节点会频繁发送,把总线塞满。

避坑指南:我曾经在一个项目中,为了省事,用远程帧来获取传感器数据。结果传感器节点每收到一个远程帧就发一次数据,而主节点每10ms发一次远程帧。传感器节点忙得连自己的主任务都跑不完了。后来改成传感器主动周期发送,问题解决。

2.4 错误帧 —— 总线的“警报器”

错误帧由两部分组成:错误标志(6个显性位)错误界定符(8个隐性位)。任何节点检测到总线错误(比如CRC错误、格式错误、位错误等),都会主动发送错误帧。

错误帧有两种:

  • 主动错误帧:6个显性位。发送节点处于“主动错误”状态时发出。
  • 被动错误帧:6个隐性位。发送节点处于“被动错误”状态时发出。

错误帧一旦发出,当前正在传输的数据帧就会被破坏,所有节点都会丢弃这个帧。然后总线会重新进入空闲状态,等待下一次发送。

你想想看,如果一个节点频繁发送错误帧,整个总线都会被它拖垮。所以CAN协议有错误计数机制——错误次数多了,节点会从“主动错误”降级为“被动错误”,最后变成“总线关闭”(Bus Off),彻底闭嘴。

2.5 过载帧 —— 我太忙了,慢点

过载帧的结构和错误帧很像:过载标志(6个显性位)过载界定符(8个隐性位)

过载帧的作用是:“我还没准备好,请等一下。”它通常发生在两种情况下:

  1. 接收节点内部缓冲区满了,来不及处理。
  2. 接收节点检测到总线上的位填充错误(但还没到触发错误帧的程度)。

过载帧在实际项目中很少见。因为现在的CAN控制器硬件缓冲区都比较大,而且处理速度也快。如果你发现总线上频繁出现过载帧,那说明你的系统设计有问题——要么缓冲区太小,要么CPU处理不过来。

2.6 小结与经验

好了,四种帧的结构咱们都过了一遍。最后分享几点我的个人经验:

  • 数据帧是主角,90%以上的总线流量都是数据帧。把数据帧搞透,CAN通信就掌握了八成。
  • 远程帧慎用,能用周期发送就别用请求响应。
  • 错误帧是诊断利器,总线上出现大量错误帧,说明物理层或协议层有问题。先查终端电阻、再查线缆、最后查节点驱动。
  • 过载帧出现,先检查接收节点的处理能力,别急着怀疑硬件。

下一章,咱们会深入讲位时序与同步——为什么CAN总线能跑得又快又稳?答案就在那里。