2. CAN总线协议基础:CAN2.0A/B协议、CAN帧类型与帧结构详解

好,咱们进入正题。上一章聊了CAN总线能干什么,这一章咱们得把它的“语言”搞清楚——也就是CAN协议本身。说白了,协议就是一套规矩,规定数据怎么打包、怎么发送、怎么识别。你想想看,总线上那么多节点,要是没个规矩,那不乱套了?

2.1 CAN2.0A与CAN2.0B:标准帧和扩展帧的“前世今生”

CAN协议最早是德国Bosch公司搞出来的。早期版本叫CAN 2.0A,后来发现11位的ID不够用了,就升级成了CAN 2.0B。我个人习惯把2.0A叫“标准帧”,2.0B叫“扩展帧”。

它们最大的区别在哪?仲裁场的长度。

  • CAN 2.0A(标准帧):标识符(ID)只有11位。这意味着总线上最多能定义2^11 = 2048个不同的消息ID。在早期的车上,够用了。
  • CAN 2.0B(扩展帧):标识符(ID)扩展到29位。2^29 = 5亿多个ID,几乎用不完。现在的车,尤其是高端车,节点多、功能复杂,基本都用扩展帧。

关键点:CAN 2.0B的控制器是向下兼容CAN 2.0A的。也就是说,一个支持2.0B的节点,可以正常收发2.0A的标准帧。但反过来,一个只支持2.0A的老旧节点,收到扩展帧时可能会报错。我在项目里就遇到过这种兼容性问题,后来统一升级了控制器才解决。

咱们用个表格对比一下,一目了然:

特性 CAN 2.0A(标准帧) CAN 2.0B(扩展帧)
标识符(ID)长度 11位 29位
最大ID数量 2048 5亿+
帧头开销 较小 较大(多出17位)
兼容性 基础版本 向下兼容2.0A
应用场景 简单系统、老车型 复杂系统、新车型

2.2 CAN帧的四种类型:数据、远程、错误、过载

CAN总线上跑的数据,都是以“帧”为单位的。就像你寄快递,得有个包裹。CAN帧一共有四种,每种干的事不一样。

2.2.1 数据帧(Data Frame)

这是最常用的帧,用来发送实际数据。比如发动机转速、车速、车门状态,都装在数据帧里。一个数据帧最多能带8个字节的数据。为什么是8个?这是早期设计时定的,考虑到实时性和总线负载,8个字节够用了。我做过一个项目,需要传输一个32位的浮点数,加上一个16位的状态字,刚好6个字节,一个帧搞定。

2.2.2 远程帧(Remote Frame)

这个有点意思。远程帧本身不带数据,它的作用是“请求”别的节点发数据。比如,一个节点想获取某个传感器的值,它就可以发一个远程帧,目标节点收到后,就会回复一个数据帧。

避坑指南:我曾经在调试一个多节点系统时,发现总线负载异常高。查了半天,发现是一个节点不停地发远程帧请求数据,而目标节点响应不过来,导致总线拥堵。后来我加了一个超时机制,才解决了这个问题。所以,远程帧要慎用,别滥用。

2.2.3 错误帧(Error Frame)

当节点检测到总线上的数据有问题时,就会主动发送错误帧,通知其他节点“刚才那包数据有问题,别用了”。错误帧由6个连续的显性位组成,这违反了CAN的位填充规则,所以其他节点也能检测到,并做出相应处理。

2.2.4 过载帧(Overload Frame)

这个帧用来表示“我忙不过来了,慢点发”。当一个节点接收数据的速度跟不上发送速度时,它会发送过载帧,让发送方等一等。过载帧的结构和错误帧有点像,但用途完全不同。

咱们总结一下这四种帧的用途:

  • 数据帧:发送数据。
  • 远程帧:请求数据。
  • 错误帧:报告错误。
  • 过载帧:请求延迟。

2.3 帧结构详解:一个数据帧的“五脏六腑”

咱们以最常用的数据帧为例,拆开看看里面到底有什么。一个标准数据帧,从开始到结束,包含以下几个部分:

  1. 帧起始(SOF):1个显性位。表示一帧的开始,同步所有节点的时钟。
  2. 仲裁场:包含11位ID(标准帧)和1位RTR位(远程帧请求位)。RTR位为0表示数据帧,为1表示远程帧。
  3. 控制场:包含6位。其中4位是DLC(数据长度码),表示数据场有多少个字节(0-8)。
  4. 数据场:0-8个字节。这就是你要发送的实际数据。
  5. CRC场:15位CRC校验码 + 1位CRC界定符。用来检查数据在传输过程中有没有出错。
  6. 应答场(ACK):2位。发送方发完数据后,会释放总线,接收方如果正确收到,就会在ACK槽位发送一个显性位,告诉发送方“我收到了”。
  7. 帧结束(EOF):7个隐性位。表示一帧的结束。

嗯,这里要注意,位填充是个隐藏的规则。CAN协议规定,如果连续出现5个相同的电平(全是0或全是1),就必须插入一个相反的电平。这是为了保证时钟同步。所以,你实际在总线上看到的波形,和理论上的帧结构,中间还隔了一层位填充。

下面这张图,是我用SVG画的,把标准数据帧的结构展示得清清楚楚:

CAN标准数据帧结构图 CAN标准数据帧(2.0A)结构 SOF 1位 仲裁场 11位ID + RTR 共12位 控制场 IDE + r0 + DLC 共6位 数据场 0 ~ 8 字节 0 ~ 64位 CRC场 15位CRC + 界定符 共16位 ACK 2位 EOF 7位 ITM 3位 说明: 1. SOF:帧起始,1个显性位,同步所有节点。 2. 仲裁场:11位ID决定优先级,RTR位区分数据帧/远程帧。 3. 控制场:IDE位标识标准帧/扩展帧,DLC位表示数据长度。 4. 数据场:0~8字节,实际传输的数据。 5. CRC场:15位CRC校验 + 1位界定符,保证数据完整性。 6. ACK场:接收方应答,确认收到。 7. EOF:帧结束,7个隐性位。

2.4 扩展帧的结构差异

扩展帧(CAN 2.0B)和标准帧的结构基本一样,主要区别在仲裁场。扩展帧的仲裁场包含29位ID,分成了两部分:11位基本ID + 18位扩展ID。控制场里多了一个IDE位(标识符扩展位),用来区分是标准帧还是扩展帧。

IDE位为0,表示标准帧;IDE位为1,表示扩展帧。这个位在仲裁时也很关键,因为它会影响帧的优先级。标准帧的IDE位在控制场,而扩展帧的IDE位在仲裁场。所以,标准帧的优先级天然高于扩展帧。嗯,这是协议设计时故意这么做的,为了保证兼容性。

注意:在同一个网络中,标准帧和扩展帧可以共存。但你要小心,它们的ID空间是独立的。也就是说,标准帧的ID 0x100和扩展帧的ID 0x100,是两个完全不同的消息。我在一个项目中就踩过这个坑,两个节点用了相同的ID,但一个是标准帧,一个是扩展帧,结果通信一直对不上。

2.5 位填充:一个容易被忽略的细节

前面提到了位填充,这里再强调一下。CAN协议规定,在SOF到CRC场之间,如果连续出现5个相同的位,就必须插入一个相反的电平。比如,你连续发了5个显性位(0),那么在第5个位之后,必须插入一个隐性位(1)。

为什么要这么做?因为CAN总线没有单独的时钟线,所有节点靠检测电平的跳变来同步时钟。如果长时间没有跳变,节点的时钟就会漂移,导致采样错误。位填充保证了总线上有足够的跳变,让所有节点保持同步。

接收方在收到数据后,会自动去掉这些填充位,恢复原始数据。所以,你作为开发者,不需要手动处理位填充,但你要知道它的存在。尤其是在计算总线负载时,位填充会增加额外的开销。一个8字节的数据帧,理论上是108位(标准帧),但加上位填充后,实际可能达到120位左右。