3. CAN总线数据链路层:CAN帧结构深度解析

各位同学,今天我们来啃一块硬骨头——CAN帧结构。说实话,我刚接触CAN总线那会儿,看着帧结构图也是一头雾水。但后来在项目里调试过几次总线冲突之后,才真正体会到这些设计有多精妙。

数据链路层,说白了就是负责把物理层的0和1组织成有意义的“消息”。CAN总线定义了四种帧类型:数据帧、远程帧、错误帧、过载帧。咱们今天重点讲数据帧,因为这是用得最多的。

CAN数据帧结构总览 标准帧 (11位ID) SOF 仲裁段(11位ID+RTR) 控制段(6位) 数据段(0~8字节) CRC段(15+1位) 应答段(2位) EOF 扩展帧 (29位ID) SOF 基础ID(11位) SRR IDE 扩展ID(18位) RTR r1 r0 DLC 数据段(0~8字节) CRC段 ACK 仲裁段 控制段 数据段 CRC段 应答段

3.1 标准帧 vs 扩展帧——到底选哪个?

CAN 2.0A定义了标准帧,CAN 2.0B定义了扩展帧。两者的核心区别就一个:标识符长度

  • 标准帧:11位标识符,帧长度最短44位(无数据时)
  • 扩展帧:29位标识符,帧长度最短64位(无数据时)

我个人的习惯是:如果项目节点少于50个,用标准帧就够了。但如果你在做车载网络或者工业控制这种复杂系统,我建议直接用扩展帧。为什么?因为29位ID可以让你做更精细的优先级划分和消息过滤。

关键区别表

特性 标准帧 扩展帧
标识符长度 11位 29位
最大节点数(理论) 2048 5.37亿
帧开销 47位(含数据) 67位(含数据)
IDE位 显性(0) 隐性(1)
兼容性 所有控制器支持 需2.0B控制器

3.2 仲裁段——谁先说话,谁说了算

仲裁段是CAN总线最精彩的设计之一。你想想看,多个节点同时发送数据,总线怎么决定听谁的?

答案就是:逐位仲裁。每个节点在发送标识符时,同时监听总线电平。如果自己发的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发数据,自己立刻退出发送。

标准帧的仲裁段包含:

  • SOF(帧起始):1位显性,同步所有节点
  • 11位标识符:ID越小,优先级越高
  • RTR位:远程帧请求位,显性表示数据帧

扩展帧的仲裁段更复杂一些:

  • SOF + 11位基础ID
  • SRR位:替代远程请求位,总是隐性
  • IDE位:标识扩展帧,隐性(1)
  • 18位扩展ID
  • RTR位

避坑指南

我曾经在一个项目中,把两个节点的ID设成了0x100和0x101。结果发现0x100总是抢不到总线。查了半天才发现——我把ID写反了!0x100的二进制是0001 0000 0000,0x101是0001 0000 0001。0x100的优先级其实更高,但因为我配置错了发送逻辑,导致它一直在等总线空闲。记住:ID数值越小,优先级越高

3.3 控制段——告诉别人你要发多少数据

控制段只有6位,但信息量不小:

  • IDE位(标准帧中):显性,表示这是标准帧
  • r0/r1保留位:必须为显性,留给未来扩展
  • DLC(数据长度码):4位,表示数据段有多少字节

DLC的取值范围是0~8,对应0~8字节数据。但注意:DLC可以设置成9~15,不过大多数控制器会把它当成8字节处理。我建议你不要用非标值,因为不同厂家的芯片处理方式不一样,容易出兼容性问题。

注意

DLC为0时,数据段长度为0。这在远程帧中很常见——只请求数据,不发送数据。但如果你在数据帧里把DLC设成0,接收方会认为你没有数据要发,直接忽略你的帧。我见过有人用DLC=0的数据帧做心跳包,结果接收方死活收不到——因为根本没数据段可解析。

3.4 数据段——真正的“货物”在这里

数据段是CAN帧里唯一承载用户数据的地方。长度0~8字节,固定不变。为什么是8字节?因为CAN总线设计之初就考虑到了实时性——数据太长会占用总线太久,影响其他节点的响应时间。

我个人习惯把数据段按功能划分:

  • 字节0~1:命令/状态码
  • 字节2~3:参数值(如速度、温度)
  • 字节4~5:校验或时间戳
  • 字节6~7:备用或扩展

当然,这只是我的习惯。实际项目中,数据段的定义完全由你决定。但有一点要注意:多字节数据的字节序。Motorola格式(大端)和Intel格式(小端)在CAN总线里都有使用。我建议你在项目文档里明确标注,否则两个团队用不同字节序,解析出来的数据会完全错乱。

3.5 CRC段——数据到底有没有传错?

CRC段包含15位CRC校验码和1位CRC界定符(隐性)。CRC的生成多项式是:

x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1

这个多项式能检测出:

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

说实话,这个CRC的检错能力非常强。我在一个项目中遇到过总线被电机干扰的情况,CRC几乎每次都能准确报错。但要注意:CRC只能检错,不能纠错。发现错误后,接收节点会发送错误帧,要求发送方重传。

CRC计算范围

CRC校验覆盖的范围是:SOF + 仲裁段 + 控制段 + 数据段。应答段和EOF不参与CRC计算。这一点很多初学者会搞混——以为CRC只保护数据段,其实它保护的是整个帧的“有效载荷”。

3.6 应答段——你收到了吗?我收到了

应答段只有2位:

  • ACK Slot(应答槽):发送方发隐性位,接收方如果正确收到,就拉成显性
  • ACK Delimiter(应答界定符):隐性位,用于分隔

这个设计很有意思。发送方在ACK Slot发隐性位,然后释放总线。所有接收方如果校验通过,就同时拉低总线(显性)。发送方检测到显性位,就知道至少有一个节点正确收到了。

如果没有任何节点应答呢?发送方会检测到隐性位,然后触发错误处理——重发或报错。我在调试时经常用这个特性来判断总线是否连通:如果发送帧后一直收不到ACK,说明总线上没有其他节点,或者物理层有问题。

3.7 帧结束与帧间距

EOF(帧结束)是7位隐性位。之后是3位ITM(帧间距),用于总线恢复和同步。

这里有个细节:EOF之后必须至少有3位隐性位,才能开始下一帧。如果总线在EOF期间被拉低,所有节点都会认为发生了填充错误,然后发送错误帧。我曾经在项目中因为总线电容太大,导致EOF的隐性位被拉成显性,结果总线一直报错。后来加了终端电阻才解决。

我的经验

如果你在调试CAN总线时发现“莫名其妙”的错误帧,先检查EOF和帧间距。用示波器抓一下总线波形,看看隐性位是否真的达到了隐性电平。很多时候,问题出在物理层,而不是数据链路层。

好了,CAN帧结构就讲到这里。标准帧和扩展帧的区别、仲裁机制、CRC保护范围、应答逻辑——这些都是你写CAN驱动时必须理解的内容。下一节我们会讲位时序和同步,到时候你会更清楚为什么CAN总线能这么可靠。


公众号:蓝海资料掘金营,微信deep3321