2. CAN协议详解:五种帧结构

CAN协议里最核心的,就是这五种帧。我刚开始接触CAN时,总觉得帧类型太多记不住。后来在项目里调了几个月,发现其实就两类:数据帧和远程帧是正常通信用的,剩下三种——错误帧、过载帧、帧间隔——都是总线管理和异常处理用的。

咱们一个一个拆开看。

2.1 数据帧:最常用的帧

数据帧就是用来发数据的。你想想看,一个传感器采集到温度值,要告诉ECU,用的就是数据帧。

数据帧分两种格式:标准帧(11位ID)和扩展帧(29位ID)。我个人习惯在项目初期就定好用哪种,免得后期改协议栈。

数据帧结构(标准帧):

SOF → 仲裁场(11位ID+RTR) → 控制场(IDE+r0+DLC) → 数据场(0~8字节) → CRC场(15位CRC+CRC分隔符) → ACK场(ACK槽+ACK分隔符) → EOF(7位隐性位)

这里有个关键点:数据场最多8字节。为什么是8?因为CAN总线设计时考虑实时性,8字节刚好够一个控制命令或传感器数据。我在做电机控制器时,经常要把电流、转速、温度三个值塞进一帧里,8字节其实挺紧的。

字段 长度 说明
SOF 1 bit 帧起始,显性位,同步所有节点
仲裁场 12 bit 11位ID + 1位RTR(数据帧RTR=0)
控制场 6 bit IDE + r0 + DLC(4位)
数据场 0~64 bit 实际数据,最多8字节
CRC场 16 bit 15位CRC + 1位CRC分隔符
ACK场 2 bit ACK槽 + ACK分隔符
EOF 7 bit 帧结束,全部隐性位

小技巧:DLC(数据长度码)可以设为0~8,但实际数据场长度必须≤DLC。我曾经见过一个bug,DLC设了8但只发了3字节,接收方读到的是垃圾数据。所以发送前一定要把数据场清零。

2.2 远程帧:问别人要数据

远程帧说白了就是“请求帧”。一个节点想从另一个节点拿数据,就发个远程帧。远程帧和数据帧长得几乎一样,唯一的区别是RTR位:数据帧RTR=0,远程帧RTR=1。

远程帧没有数据场。你想想看,既然只是请求数据,带数据干嘛?

注意:远程帧的DLC必须等于期望接收的数据长度。比如你想请求8字节数据,DLC就设8。如果设错了,接收方可能不响应。

我在做BMS(电池管理系统)时用过远程帧。主控发一个远程帧,ID=0x100,DLC=8,从机收到后立刻回复数据帧。这种方式省去了轮询的麻烦。

2.3 错误帧:总线出问题时的信号

错误帧是CAN协议里最“暴力”的帧。一旦某个节点检测到错误,它会立刻发送错误帧,把整个总线上的数据都破坏掉。嗯,听起来很粗暴,但这就是CAN的设计哲学——错误优先。

错误帧分两部分:

  • 错误标志:6个连续显性位(主动错误)或6个连续隐性位(被动错误)
  • 错误分隔符:8个连续隐性位

我曾经在项目里遇到过一个问题:总线上一有错误帧,整个网络就瘫痪了。后来发现是某个节点的终端电阻没焊好,导致信号反射。所以硬件设计时,终端电阻一定要焊牢。

错误类型:

  • 位错误:发送的位和总线上的位不一致
  • 填充错误:连续5个相同位后没有填充位
  • CRC错误:接收方计算的CRC和发送方的不一致
  • 形式错误:固定格式的位(如EOF)值不对
  • ACK错误:发送方没收到ACK应答

2.4 过载帧:我忙,别发数据了

过载帧和错误帧长得很像,但用途完全不同。过载帧是节点告诉总线:“我处理不过来了,暂停一下。”

过载帧的结构:

  • 过载标志:6个连续显性位
  • 过载分隔符:8个连续隐性位

你可能会问:这和错误帧有什么区别?区别在于触发条件。错误帧是检测到错误才发,过载帧是节点内部缓冲区满了才发。我在做高速CAN(1Mbps)时,如果接收中断处理太慢,就会触发过载帧。后来我把中断优先级调高,问题就解决了。

避坑指南:过载帧不能连续发太多。CAN协议规定,两个过载帧之间至少间隔3个位时间。我曾经在测试时连续触发过载帧,结果总线直接挂了。后来加了延时才稳定。

2.5 帧间隔:总线休息时间

帧间隔就是两帧之间的空闲时间。CAN协议规定,每帧结束后必须有至少3个隐性位作为帧间隔。这3个位叫间歇场。

帧间隔的作用:

  • 给节点时间处理接收到的数据
  • 让总线恢复隐性状态
  • 防止连续帧冲突

我记得有一次调试,发现数据丢帧严重。用示波器一看,帧间隔只有1个位时间。原来是软件里把帧间隔设得太短了。改成3个位后,一切正常。

2.6 五种帧的关系图

下面这张图展示了五种帧在总线上的关系。你可以看到,正常通信时只有数据帧和远程帧,错误帧和过载帧是异常情况才出现的。

CAN五种帧关系图 CAN五种帧关系图 数据帧 远程帧 帧间隔 错误帧 过载帧 检测到错误 缓冲区满 说明: 正常通信流程:数据帧/远程帧 → 帧间隔 → 下一帧 异常情况:检测到错误 → 错误帧;接收缓冲区满 → 过载帧 错误帧和过载帧发送后,总线恢复帧间隔,然后继续正常通信

2.7 位填充机制

CAN协议有个很有意思的机制——位填充。发送方在连续发送5个相同位后,必须插入一个相反位。比如你发了5个1,第6位必须插个0。

为什么要这么做?

  • 保证时钟同步:接收方靠跳变沿同步时钟
  • 防止误判:连续相同位太多,接收方可能丢失同步

位填充的范围是从SOF到CRC场结束。EOF、ACK场、帧间隔不参与填充。我在做CAN驱动时,位填充逻辑是硬件自动完成的,但调试时得知道这个机制,不然看到波形上的“多余位”会懵。

位填充示例:

原始数据:11111 00000 10101

填充后:111110 000001 10101

注意:填充位用粗体标出

2.8 仲裁机制:谁优先级高谁先发

CAN总线是多主架构,多个节点可以同时发数据。那冲突了怎么办?靠仲裁。

仲裁的原理很简单:ID越小,优先级越高。发送时,每个节点逐位比较自己的ID和总线上的电平。如果某个节点发送了隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发数据,这个节点就自动退出发送。

我举个例子:

  • 节点A:ID = 0x100(二进制:001 0000 0000)
  • 节点B:ID = 0x200(二进制:010 0000 0000)

节点A的第2位是0(显性),节点B的第2位是1(隐性)。所以节点A胜出,节点B退出发送。整个过程在硬件层面完成,软件完全无感。

经验之谈:设计CAN网络时,把实时性要求高的节点(如刹车、转向)分配小ID,普通节点(如车窗、灯光)分配大ID。我在做车控项目时,刹车节点的ID是0x001,车窗是0x300,这样刹车永远优先。

2.9 总结

五种帧各有各的用途。数据帧和远程帧是干活用的,错误帧和过载帧是处理异常用的,帧间隔是让总线喘口气用的。理解这些帧结构,是写CAN驱动的基础。