3. CAN总线数据链路层:CAN帧结构深度解析
各位同学,今天我们来啃一块硬骨头——CAN帧结构。说实话,我刚接触CAN总线那会儿,看着帧结构图也是一头雾水。但后来在项目里调试过几次总线冲突之后,才真正体会到这些设计有多精妙。
数据链路层,说白了就是负责把物理层的0和1组织成有意义的“消息”。CAN总线定义了四种帧类型:数据帧、远程帧、错误帧、过载帧。咱们今天重点讲数据帧,因为这是用得最多的。
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