4. CAN帧结构详解
各位好,今天我们正式进入CAN数据帧的细节。说实话,很多工程师学了几年CAN,报文能收发,但遇到总线竞争、应答异常时还是一头雾水。根本原因就是帧结构没吃透。
数据帧是CAN总线最常用的帧类型,没有之一。它负责把节点要发送的数据,从发送器传到接收器。整个帧由7个段组成,今天我们只讲前三个:帧起始(SOF)、仲裁场和控制场。这三个段决定了谁能发、发什么、发多少。
核心要点:SOF同步总线,仲裁场决定优先级,控制场告诉接收方数据长度。这三个段加起来只有不到20个位,但它们是CAN总线“无主通信”的基石。
4.1 帧起始(SOF)—— 总线的“发令枪”
SOF只有一个显性位(逻辑0)。别看它短,作用极大。
- 同步作用:所有节点在总线空闲时,都在监听。一旦某个节点拉低总线(发送显性位),其他节点立即同步到这位的边沿。
- 标志作用:SOF告诉所有节点:“我要开始发帧了,你们准备好接收。”
- 硬同步:每个节点收到SOF的下降沿时,会强制调整自己的采样点位置。这是CAN总线位同步的第一道保障。
我的经验:有一次调试一个电机控制器,总线偶尔丢帧。抓波形发现SOF之后的同步段有毛刺,导致采样点偏移。后来在硬件上加了一级施密特触发器,问题解决。嗯,SOF的边沿质量,直接影响整个帧的接收可靠性。
4.2 仲裁场 —— 谁优先级高谁先走
仲裁场由两部分组成:11位标识符(ID) + RTR位。这是CAN总线最精彩的设计之一。
4.2.1 11位标识符(ID)
ID不是地址,而是优先级标签。数值越小,优先级越高。
- 范围:0x000 ~ 0x7FF(0~2047),共2048个可用ID。
- 发送顺序:从高位(ID.10)到低位(ID.0)逐位发送。
- 仲裁机制:多个节点同时发送时,逐位比较。谁先发出显性位(0),谁就赢得总线。
为什么会这样?因为CAN总线是“线与”逻辑:显性位(0)会覆盖隐性位(1)。你想想看,如果两个节点同时发,一个发0,一个发1,总线上最终是0。发1的节点检测到总线与自己输出不一致,就知道自己输了,立即转为接收模式。
避坑指南:我曾经见过一个团队,把ID从0x700开始分配,结果所有节点优先级都很低,高优先级的紧急报文反而抢不到总线。记住:ID越小优先级越高,紧急报文用小编号ID。
4.2.2 RTR位(Remote Transmission Request)
RTR位紧跟在ID之后,用于区分数据帧和远程帧。
| 帧类型 | RTR值 | 说明 |
|---|---|---|
| 数据帧 | 显性(0) | 发送器主动发送数据 |
| 远程帧 | 隐性(1) | 请求其他节点发送数据 |
这里有个细节:如果数据帧和远程帧的ID相同,数据帧的RTR=0,远程帧的RTR=1。仲裁时,数据帧的显性位会获胜。所以远程帧的优先级永远低于同ID的数据帧。
我个人的习惯:除非有明确的远程请求需求,否则尽量少用远程帧。因为远程帧不携带数据,却占用总线带宽。而且处理不好容易造成“请求风暴”。
4.3 控制场 —— 帧格式与数据长度
控制场共6位:IDE(1位) + r0(1位保留位) + DLC(4位)。
4.3.1 IDE位(Identifier Extension)
IDE位决定当前帧是标准帧还是扩展帧。
- IDE = 0(显性):标准帧,11位ID。控制场之后直接跟数据场。
- IDE = 1(隐性):扩展帧,29位ID。控制场之后还有18位扩展ID。
注意:标准帧的IDE是显性,扩展帧的IDE是隐性。这意味着在仲裁时,标准帧的优先级天然高于同ID的扩展帧。
4.3.2 r0位(保留位)
r0是保留位,必须发送显性(0)。接收器会检查这一位,如果收到隐性(1),则视为格式错误。
我曾经踩过的坑:早期某款MCU的CAN控制器,r0位在初始化时默认配置为隐性。结果一上总线就报格式错误。查了两天才发现是寄存器配置问题。所以,保留位一定要按规范置0,别想当然。
4.3.3 DLC(Data Length Code)
DLC共4位,表示数据场包含的字节数。取值范围0~8。
| DLC值(二进制) | 数据长度(字节) | 常见用途 |
|---|---|---|
| 0000 ~ 1000 | 0 ~ 8 | 标准数据长度 |
| 1001 ~ 1111 | 保留(通常视为8) | 部分控制器会报错 |
DLC超过8(即二进制1001~1111)时,大多数CAN控制器仍然发送8字节数据,但部分严格实现会报错。我个人建议:永远不要用DLC > 8,这是给自己挖坑。
4.4 本章结构总览
下面这张图把今天讲的三个段串起来,方便你记忆。
4.5 常见问题与避坑
- ID分配问题:不要从大往小分配,紧急报文用小编号。
- RTR滥用:远程帧优先级低,且不携带数据,非必要不使用。
- DLC超限:DLC > 8时行为未定义,不同控制器处理方式不同,统一用0~8。
- 保留位r0:必须置0,不要依赖控制器自动处理。