4、CAN总线数据链路层:仲裁机制、位填充、CRC校验、应答机制
好,咱们进入数据链路层。这一层是CAN总线的核心战场。
很多人学CAN,上来就背帧格式,结果越学越糊涂。我个人的习惯是,先搞懂四个关键机制:仲裁、位填充、CRC校验、应答。这四个东西搞明白了,CAN总线你就懂了一半。
4.1 仲裁机制:谁先说话?
CAN总线是多主从结构,任何节点都能随时发数据。那问题来了——两个节点同时发,听谁的?
答案很简单:谁优先级高,谁先发。
优先级怎么定?看标识符(ID)。ID越小,优先级越高。为什么?因为CAN总线用的是“线与”逻辑——显性位(逻辑0)会覆盖隐性位(逻辑1)。
举个例子:
- 节点A发ID = 0x100(二进制:0001 0000 0000)
- 节点B发ID = 0x200(二进制:0010 0000 0000)
两个节点同时发,第一位都是0,没问题。第二位,A发0,B发1。结果总线上是0(显性)。B发现自己发的1被拉成了0,就知道自己仲裁失败了,立刻停止发送,转为接收模式。
关键点:仲裁过程不破坏数据,谁赢谁继续发,输的自动退出发送。整个过程是“无损仲裁”。
我的经验:我在做车身控制模块时,遇到过优先级设计不合理的问题。一个车窗升降的报文ID设得太小,结果每次升降车窗,动力系统的关键报文都被堵住了。后来我调整了ID分配策略——动力系统用0x000~0x0FF,车身系统用0x100~0x1FF,诊断用0x700以上。这样优先级就清晰了。
4.2 位填充:保证时钟同步
你想想看,CAN总线没有单独的时钟线,所有节点靠什么同步?靠的是数据信号本身的跳变沿。
但如果连续发5个相同的位(比如00000),总线上就没有跳变了。没有跳变,节点就没办法校准时钟,时间长了就会失步。
解决办法就是位填充:
- 发送方:每连续发送5个相同位,自动插入一个相反位
- 接收方:收到5个相同位后,自动丢弃下一个位(填充位)
举个例子:
原始数据:11111 00000 111
填充后: 111110 000001 111
注意:填充位只出现在SOF到CRC段。ACK段和EOF段不填充。
避坑指南:我曾经调试过一个CAN通信异常的问题,现象是偶尔丢帧。查了半天,发现是某个节点的晶振偏差太大,导致位填充后的采样点偏移。嗯,从那以后我选晶振都要求精度在±0.1%以内。
4.3 CRC校验:数据完整性保障
CAN总线的CRC用的是15位CRC,生成多项式是:
x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1
说白了,发送方把SOF、仲裁场、控制场、数据场(如果有)这些位串起来,算出一个15位的CRC值,放在CRC段里。接收方用同样的算法算一遍,如果结果一致,说明数据没被篡改。
CRC的检错能力很强:
- 能检测所有≤5位的突发错误
- 能检测99.998%的更长突发错误
- 能检测所有奇数个错误
实际效果:我在一个项目中做过统计,运行了2000小时,CRC校验失败的次数是0。但别以为它100%可靠——理论上还是有极小概率漏检的。所以关键安全系统(比如刹车、转向)还需要额外做端到端的安全校验。
4.4 应答机制:确认收到
发送方发完数据,怎么知道对方收到了?靠的是应答(ACK)机制。
流程是这样的:
- 发送方在CRC段之后,发送一个隐性位(ACK Slot)
- 所有接收方如果CRC校验通过,就在这个位发送显性位(0)
- 发送方检测到总线上是显性位,就知道至少有一个节点收到了
注意:
- ACK是“线与”的——只要有一个节点应答成功,总线上就是显性
- 发送方不关心具体是哪个节点应答的,只关心有没有人应答
- 如果没人应答(ACK Slot保持隐性),发送方会重发
我的习惯:调试CAN通信时,我第一个看的就是ACK。如果发送方一直没收到ACK,要么是总线断了,要么是接收方CRC校验失败。我曾经遇到过一种情况——接收方的波特率设置错了0.5%,导致CRC总是算不对,ACK一直发不出来。折腾了两天才找到原因。
4.5 四个机制的协同工作
这四个机制不是孤立的,它们配合起来才构成了CAN总线的可靠性:
| 机制 | 作用 | 失败后果 |
|---|---|---|
| 仲裁 | 解决冲突,保证高优先级报文优先 | 低优先级报文被延迟 |
| 位填充 | 保证时钟同步 | 节点失步,通信中断 |
| CRC校验 | 检测数据错误 | 错误数据被接收 |
| 应答机制 | 确认接收成功 | 发送方不知道数据是否到达 |
说白了,仲裁保证了“谁先说话”,位填充保证了“说话节奏不乱”,CRC保证了“说话内容没错”,应答保证了“对方听到了”。四个缺一不可。
总结一句话:CAN总线的数据链路层,就是靠这四个机制,在一条双绞线上实现了可靠、实时、多主从的通信。我做了十几年嵌入式,还没见过第二个总线能像CAN这样,用这么简单的硬件实现这么强的可靠性。