4、CAN协议详解:CAN数据帧结构、仲裁机制、错误处理与位填充
各位同学,今天我们来啃一块硬骨头——CAN协议的核心机制。说实话,CAN协议在工业界和汽车电子领域已经摸爬滚打了几十年,至今仍是现场总线的中流砥柱。我个人习惯把CAN协议比作一个“有礼貌的多人电话会议”,大家都能说话,但谁优先级高谁先说,而且谁要是说错了,大家会一起喊停重来。听起来是不是很酷?我们一个一个拆开看。
4.1 CAN数据帧结构:一封严谨的“挂号信”
CAN总线上的数据,是以“帧”为单位传输的。最常见的叫数据帧。它就像一封挂号信,结构非常严谨,每个字段都有明确职责。
一个标准的数据帧(CAN 2.0A)长这样:
SOF | 11位ID | RTR | IDE | r0 | DLC | 数据段(0-8字节) | CRC | ACK | EOF
我来逐一解释,顺便聊聊我踩过的坑。
- SOF (Start of Frame):一个显性位,告诉所有节点“我要发信了”。
- ID (标识符):这是帧的“身份证”,也是仲裁的关键。标准帧11位,扩展帧29位。
- RTR (远程传输请求位):区分是数据帧还是远程帧。数据帧是显性(0),远程帧是隐性(1)。
- IDE (标识符扩展位):标准帧里是显性,扩展帧里是隐性。用来区分帧格式。
- DLC (数据长度码):4位,告诉你数据段有几个字节。范围0-8。
- 数据段:0到8个字节,真正要传的信息。嗯,这里要注意,CAN一次最多传8字节,别想着传大包。
- CRC (循环冗余校验):15位校验码,加上1位隐性定界符。用来检查传输有没有出错。
- ACK (应答):发送方发两个隐性位,接收方如果正确收到,会在第一个位拉成显性,告诉发送方“我收到了”。
- EOF (帧结束):7个隐性位,表示这帧结束了。
核心要点:CAN帧没有目标地址,只有标识符。所有节点都收,但只有ID匹配的节点才处理。这叫做“广播式”通信。
我的经验:我在项目中遇到过一个问题,DLC写错了,比如实际发了5个字节,但DLC写的是3。接收方只取了前3个字节,后面2个字节被当成了填充位,导致整个数据解析全乱套。所以,DLC一定要和数据段长度严格对应。
4.2 仲裁机制:谁优先级高,谁先说话
CAN总线是“多主”架构,任何节点都能随时发数据。那问题来了:如果两个节点同时发,总线不就打架了吗?
这就是CAN协议最巧妙的地方——非破坏性逐位仲裁。
说白了,就是靠ID来比大小。ID越小,优先级越高。仲裁过程发生在SOF之后的ID段。每个节点在发送ID位时,会同时监听总线电平。如果它发的是隐性位(1),但总线上是显性位(0),它就立刻知道自己输了,马上停止发送,转为接收模式。
为什么会这样?因为显性位(0)会覆盖隐性位(1)。所以,ID全是0的节点,优先级最高。
举个例子:
- 节点A的ID是 0x100 (二进制 001 0000 0000)
- 节点B的ID是 0x200 (二进制 010 0000 0000)
从最高位开始比。第1位,A发0,B发0,一样,继续。第2位,A发0,B发1。A发的是显性,B发的是隐性。B发现总线电平和自己发的不一样,B立刻闭嘴。A继续发完剩下的位,赢得总线使用权。
关键点:仲裁过程不破坏任何数据,输的节点自动重发,赢的节点继续发。这就是“非破坏性”的含义。
避坑指南:我曾经在一个项目中,把两个节点的ID设成了相同的值。结果一上电,总线就乱套了,CRC错误满天飞。因为ID相同,仲裁分不出胜负,两个节点同时发数据,导致位冲突。记住,CAN总线上每个节点的ID必须是唯一的。
4.3 错误处理:CAN的“容错”哲学
CAN协议在错误处理上,可以说是“宁可错杀一千,绝不放过一个”。它有一套非常完善的错误检测和故障界定机制。
错误检测有5种:
- 位错误:发送方在发送位时,会监听总线。如果发的是1,但总线上是0,就报错。我刚开始学的时候,觉得这很简单,但实际项目中,总线电容、线缆过长都可能导致位错误。
- 填充错误:这个我们下一节细说。
- CRC错误:接收方计算的CRC和发送方的不一致。
- 格式错误:帧格式不对,比如EOF应该是7个隐性位,结果出现了显性位。
- ACK错误:发送方没收到应答信号。
任何一个节点检测到错误,就会立刻发送一个错误帧。错误帧由6个连续的显性位组成,这相当于在总线上大喊一声“出错了!”。所有节点收到错误帧后,会丢弃当前正在接收的帧,然后准备重发。
CAN协议还有一个“故障界定”机制,把节点状态分为三种:
| 状态 | 描述 | 行为 |
|---|---|---|
| 主动错误 | 正常状态,可以主动发送错误帧 | 发送6个显性位 |
| 被动错误 | 错误太多,被“降级”了 | 只能发送6个隐性位,且不能主动发错误帧 |
| 总线关闭 | 错误太多,被“开除”了 | 完全脱离总线,不再参与任何通信 |
每个节点内部有两个计数器:发送错误计数器(TEC)和接收错误计数器(REC)。出错就加,成功就减。当TEC超过127,变成被动错误;超过255,直接总线关闭。
我的经验:我记得有一次调试,发现一个节点总是莫名其妙地进入总线关闭状态。查了半天,发现是它的CAN收发器供电不稳,导致发送信号畸变,被其他节点反复报错。所以,硬件电源质量对CAN通信的稳定性影响非常大。
4.4 位填充:保证时钟同步的“小技巧”
位填充,是CAN协议里一个容易被忽视,但极其重要的机制。它的目的只有一个:保证总线上的电平有足够多的跳变,让所有节点都能同步时钟。
你想想看,如果连续发送5个相同的位,比如00000,总线电平就一直保持显性。接收方的时钟如果有点漂移,就可能把第6个位误判成第5个位,导致采样错位。
CAN协议规定:在发送时,如果连续出现了5个相同的位,发送方必须自动插入一个相反极性的位。
举个例子:
- 要发送的数据:11111 00000
- 经过位填充后:111110 000001
接收方收到后,会自动把填充位去掉,还原原始数据。
位填充的范围包括:SOF、ID、RTR、IDE、r0、DLC、数据段、CRC。但不包括CRC定界符、ACK和EOF。
重要:位填充是CAN协议实现“自同步”的关键。没有它,长距离、高波特率的CAN通信根本无法稳定工作。
避坑指南:我曾经在写CAN驱动时,忘了处理位填充。结果发送一个全是0x00的数据包时,总线直接卡死。因为连续6个显性位被当成了错误帧。所以,如果你在调试时发现总线莫名其妙地报“填充错误”,先检查一下你的数据是不是有连续5个以上的相同位。
好了,CAN协议的核心机制就讲到这里。数据帧结构是骨架,仲裁机制是灵魂,错误处理是保障,位填充是润滑剂。把这四个点吃透了,CAN协议你就掌握了七八成。下一章,我们聊聊CAN的物理层和总线拓扑,看看这些协议机制是怎么在硬件上落地的。