2. CAN协议核心:CAN数据帧结构
各位同学,咱们今天来啃CAN协议里最硬核的一块——数据帧结构。说实话,我刚入行那会儿,看着那一堆场(Field)的名字就头大。什么SOF、CRC、ACK,感觉像在背密码表。但后来我发现,搞懂这个帧结构,就像拿到了CAN总线的“通关文牒”。
CAN总线上一共就四种帧:数据帧、远程帧、错误帧和过载帧。咱们一个一个来拆解。我个人习惯,先从最常用的数据帧下手。
2.1 数据帧的七段“旅程”
一个标准的数据帧,从开始到结束,要经历七个阶段。我把它比作一次快递的完整流程:
| 场名称 | 英文缩写 | 位数(标准帧) | 作用(大白话) |
|---|---|---|---|
| 帧起始 | SOF | 1 bit | “发车了!”——告诉所有节点,我要开始传数据了 |
| 仲裁场 | Arbitration Field | 12 bits | “谁优先级高谁先走”——抢总线用的 |
| 控制场 | Control Field | 6 bits | “我这包裹里装了多少东西”——数据长度说明 |
| 数据场 | Data Field | 0~64 bits | “包裹本体”——你要传的真正数据 |
| CRC场 | CRC Field | 16 bits | “防伪标签”——检查数据有没有被篡改 |
| ACK场 | ACK Field | 2 bits | “收到了!”——接收节点给的回应 |
| 帧结束 | EOF | 7 bits | “到站了”——结束标志 |
2.2 逐段拆解:从SOF到EOF
2.2.1 帧起始(SOF)—— 一声号令
SOF只有1个显性位(逻辑0)。为什么是显性?因为总线空闲时是隐性(逻辑1)。一旦有节点拉低总线,所有节点都知道:“有人要说话了!”
我曾经在调试一个多节点项目时,发现总线莫名其妙一直报错。查了半天,原来是一个节点的SOF波形上升沿不够陡,导致其他节点误判了起始时刻。嗯,这里要注意:SOF的边沿质量很重要。
2.2.2 仲裁场 —— 谁拳头硬谁说话
仲裁场包含两部分:11位标识符(ID)和1位RTR位。标准帧的ID范围是0x000~0x7FF。
说白了,这就是CAN总线最巧妙的地方——无损仲裁。多个节点同时发送时,ID值越小的节点,优先级越高。为什么?因为显性位(0)会覆盖隐性位(1)。
你想想看,如果节点A发ID=0x100,节点B发ID=0x200。在第一位上,A发1(隐性),B发0(显性)。总线被拉成显性,A发现自己发的和总线不一致,立刻闭嘴退出。B继续发。整个过程没有数据损失,这就是“无损”的含义。
重要提醒:ID不只是地址,更是优先级!我见过很多新手把ID当IP地址用,结果高优先级的节点把低优先级的节点“饿死”了。设计ID分配时,一定要按实时性要求来排。
2.2.3 控制场 —— 说明包裹大小
控制场有6位:IDE位(1位)、保留位r0(1位)、DLC(4位)。IDE位用来区分标准帧和扩展帧(标准帧IDE=0)。DLC表示数据场有多少个字节,范围是0~8。
这里有个坑:DLC虽然能表示0~15,但CAN协议规定数据场最多8字节。所以DLC超过8时,接收方怎么处理?我曾经在一个项目中,发送方DLC设成了9,接收方的旧版驱动直接崩溃了。所以,DLC千万不要超过8,这是血的教训。
2.2.4 数据场 —— 真正的干货
数据场长度由DLC决定,0~8字节。你可以放任何东西:车速、转速、温度、状态位……但要注意字节序(Byte Order)。
我记得有一次,A节点发了一个16位整数0x1234,B节点收到后解析成了0x3412。为什么?因为A用大端(Big-Endian),B用小端(Little-Endian)。所以团队内部一定要统一字节序规范,否则就是一场灾难。
2.2.5 CRC场 —— 数据的“指纹”
CRC场包含15位CRC序列和1位CRC界定符(隐性)。发送节点会计算从SOF到数据场的CRC校验值,接收节点收到后重新计算一遍。如果对不上,说明数据在传输过程中被干扰了。
CRC的算法是固定的,你不需要自己实现,CAN控制器硬件会自动完成。但你要知道:CRC只能检错,不能纠错。发现错误怎么办?靠错误帧来通知。
2.2.6 ACK场 —— 确认收货
ACK场有2位:ACK槽(1位)和ACK界定符(1位,隐性)。发送节点在ACK槽发送隐性位,任何正确接收到数据的节点,都会在这个位置把总线拉成显性。如果发送节点发现ACK槽还是隐性,就知道“没人收到我的数据”,会重发。
小技巧:调试时,用示波器抓ACK场波形,能快速判断总线通信是否正常。如果ACK槽一直是隐性,说明接收节点有问题,或者根本没节点在听。
2.2.7 帧结束(EOF)—— 画上句号
EOF是7个连续的隐性位。为什么是7个?因为位填充(Bit Stuffing)机制规定,连续5个相同电平后必须插入一个相反电平。7个隐性位足够让所有节点确认“帧结束了”。
2.3 远程帧 —— 只问不买
远程帧的结构和数据帧几乎一样,唯一的区别是:RTR位为隐性(1),且没有数据场。
远程帧的作用是:“喂,谁有ID=0x123的数据?发给我!”发送远程帧的节点,其实是在请求数据。被请求的节点收到后,会发送一个同ID的数据帧作为响应。
说实话,远程帧在实际项目中用得不多。为什么?因为大多数场景下,数据是周期性发送的,不需要“请求-响应”这种模式。而且远程帧容易引发总线风暴——万一多个节点同时请求同一个ID,响应节点会频繁发送,把总线塞满。
避坑指南:我曾经在一个项目中,为了省事,用远程帧来获取传感器数据。结果传感器节点每收到一个远程帧就发一次数据,而主节点每10ms发一次远程帧。传感器节点忙得连自己的主任务都跑不完了。后来改成传感器主动周期发送,问题解决。
2.4 错误帧 —— 总线的“警报器”
错误帧由两部分组成:错误标志(6个显性位)和错误界定符(8个隐性位)。任何节点检测到总线错误(比如CRC错误、格式错误、位错误等),都会主动发送错误帧。
错误帧有两种:
- 主动错误帧:6个显性位。发送节点处于“主动错误”状态时发出。
- 被动错误帧:6个隐性位。发送节点处于“被动错误”状态时发出。
错误帧一旦发出,当前正在传输的数据帧就会被破坏,所有节点都会丢弃这个帧。然后总线会重新进入空闲状态,等待下一次发送。
你想想看,如果一个节点频繁发送错误帧,整个总线都会被它拖垮。所以CAN协议有错误计数机制——错误次数多了,节点会从“主动错误”降级为“被动错误”,最后变成“总线关闭”(Bus Off),彻底闭嘴。
2.5 过载帧 —— 我太忙了,慢点
过载帧的结构和错误帧很像:过载标志(6个显性位)和过载界定符(8个隐性位)。
过载帧的作用是:“我还没准备好,请等一下。”它通常发生在两种情况下:
- 接收节点内部缓冲区满了,来不及处理。
- 接收节点检测到总线上的位填充错误(但还没到触发错误帧的程度)。
过载帧在实际项目中很少见。因为现在的CAN控制器硬件缓冲区都比较大,而且处理速度也快。如果你发现总线上频繁出现过载帧,那说明你的系统设计有问题——要么缓冲区太小,要么CPU处理不过来。
2.6 小结与经验
好了,四种帧的结构咱们都过了一遍。最后分享几点我的个人经验:
- 数据帧是主角,90%以上的总线流量都是数据帧。把数据帧搞透,CAN通信就掌握了八成。
- 远程帧慎用,能用周期发送就别用请求响应。
- 错误帧是诊断利器,总线上出现大量错误帧,说明物理层或协议层有问题。先查终端电阻、再查线缆、最后查节点驱动。
- 过载帧出现,先检查接收节点的处理能力,别急着怀疑硬件。
下一章,咱们会深入讲位时序与同步——为什么CAN总线能跑得又快又稳?答案就在那里。