2. CAN总线基础
CAN总线上跑的数据,说白了就是几种固定格式的“信封”。我刚开始接触CAN协议栈时,总觉得帧结构就是背几个表格的事。直到有一次调试一个电机控制器,总线莫名其妙挂掉,翻来覆去找不到原因——最后发现是远程帧的RTR位被硬件自动置位了,而我根本没处理这个细节。嗯,从那以后我再看帧结构,就不敢只看表面了。
这一章咱们把CAN的5种帧类型和位填充机制彻底讲透。你想想看,搞懂这些,你就能读懂总线上的任何一段波形。
2.1 帧类型概览
CAN总线定义了5种帧,每种干不同的活:
| 帧类型 | 用途 | 谁发送 | 关键特征 |
|---|---|---|---|
| 数据帧 | 发送节点传输数据 | 任意节点 | 携带0~8字节数据 |
| 远程帧 | 请求其他节点发送数据 | 任意节点 | 无数据场,RTR位=1 |
| 错误帧 | 检测到总线错误时通知所有节点 | 检测到错误的节点 | 6个显性位+8个隐性位 |
| 过载帧 | 接收节点请求延迟下一帧 | 接收节点 | 与错误帧结构类似 |
| 帧间隔 | 分隔数据帧/远程帧 | 总线空闲 | 3个隐性位 |
核心要点:数据帧和远程帧是“业务帧”,错误帧和过载帧是“管理帧”,帧间隔是“分隔符”。调试时90%的问题出在数据帧和远程帧上。
2.2 数据帧——最常用的帧
数据帧是CAN总线上的主角。我习惯把它拆成7段来看:
- 帧起始(SOF):1个显性位,同步所有节点时钟。
- 仲裁场:11位(标准帧)或29位(扩展帧)ID + RTR位 + IDE位 + r0/r1。
- 控制场:6位,包含DLC(数据长度码)。
- 数据场:0~8字节,真正的载荷。
- CRC场:15位CRC + 1位CRC界定符(隐性)。
- 应答场:1位应答间隙 + 1位应答界定符。
- 帧结束(EOF):7个隐性位。
标准帧和扩展帧的区别主要在仲裁场:
| 特性 | 标准帧(CAN 2.0A) | 扩展帧(CAN 2.0B) |
|---|---|---|
| ID长度 | 11位 | 29位 |
| IDE位 | 0(显性) | 1(隐性) |
| SRR位 | 无 | 替代RTR位,始终隐性 |
| 最大数据长度 | 8字节 | 8字节 |
| 总线利用率 | 较高 | 较低(帧更长) |
我的经验:在J1939协议栈中,扩展帧是标配。但如果你做的是简单的传感器网络,标准帧就够了——帧短,实时性更好。我曾经在一个项目里硬用扩展帧,结果总线负载率飙到80%,后来全改成标准帧才压下来。
2.3 远程帧——请求数据的“问句”
远程帧的结构和数据帧几乎一样,唯一的区别是:RTR位=1(隐性),且没有数据场。
远程帧的典型应用场景:
- 主节点轮询从节点数据
- 诊断工具请求ECU信息
- 网关转发数据请求
避坑指南:我曾经在一个项目中,远程帧的DLC设置错误——请求8字节数据,但目标节点只发了4字节。结果接收节点一直等数据,总线直接卡死。记住:远程帧的DLC必须与目标节点实际发送的数据长度一致。
2.4 错误帧——总线的“警报器”
错误帧由两部分组成:
- 错误标志:6个连续显性位(主动错误)或6个连续隐性位(被动错误)
- 错误界定符:8个隐性位
CAN协议定义了5种错误类型:
| 错误类型 | 检测位置 | 触发条件 |
|---|---|---|
| 位错误 | 发送节点 | 发送的位与总线电平不一致 |
| 填充错误 | 所有节点 | 连续6个相同位出现 |
| CRC错误 | 接收节点 | 计算CRC与接收CRC不匹配 |
| 形式错误 | 所有节点 | 固定格式位(如EOF)电平错误 |
| 应答错误 | 发送节点 | 应答间隙未检测到显性位 |
调试技巧:用示波器抓波形时,看到6个连续显性位,基本可以断定是错误帧。这时候别慌,先看是哪种错误——我遇到过最多的是位错误和填充错误,往往是终端电阻没接好或者波特率不匹配。
2.5 过载帧——接收节点的“刹车”
过载帧的结构和错误帧很像:
- 过载标志:6个显性位
- 过载界定符:8个隐性位
触发过载帧的条件:
- 接收节点内部缓冲区满,来不及处理
- 接收节点在帧间隔期间检测到显性位(非法)
我的建议:过载帧在低速CAN网络中很少见。如果你频繁看到过载帧,说明接收节点的处理能力跟不上总线速率——要么降低波特率,要么优化接收中断处理逻辑。我曾在125kbps的网络上见过过载帧,后来发现是接收中断里做了太多浮点运算,改成查表法就解决了。
2.6 帧间隔——总线的“呼吸”
帧间隔是数据帧/远程帧之间的空闲时段,由3个隐性位组成。注意:错误帧和过载帧之间不需要帧间隔。
帧间隔的作用:
- 给接收节点时间处理刚收到的帧
- 让总线状态从“活跃”切换到“空闲”
- 为下一次仲裁做准备
2.7 位填充机制——保证同步的“小聪明”
位填充是CAN协议里一个巧妙的设计。规则很简单:连续发送5个相同位后,自动插入1个相反位。
为什么要这么做?
- 防止接收节点失去同步(长时间无跳变沿)
- 保证时钟恢复的可靠性
- 提供错误检测手段(连续6个相同位就是填充错误)
位填充的适用范围:
| 帧段 | 是否位填充 | 说明 |
|---|---|---|
| 帧起始(SOF) | 否 | 固定显性位 |
| 仲裁场 | 是 | ID + RTR + IDE + r0/r1 |
| 控制场 | 是 | DLC + 保留位 |
| 数据场 | 是 | 0~8字节数据 |
| CRC场 | 是 | CRC序列(不含界定符) |
| 应答场 | 否 | 固定格式 |
| 帧结束(EOF) | 否 | 7个隐性位 |
| 错误帧/过载帧 | 否 | 固定格式 |
| 帧间隔 | 否 | 固定隐性位 |
注意:位填充会增加帧长度。一个8字节数据帧,理论上最多可能插入多少个填充位?答案是:数据场全0x00或全0xFF时,填充位最多。我算过,标准帧最多能到~130位左右。设计实时系统时,这个“最差情况”必须考虑进去。
2.8 帧结构全景图
下面这张图把标准数据帧的完整结构画出来了,包括位填充的位置:
2.9 实战中的帧分析
我调试CAN总线时,最常用的工具就是逻辑分析仪。拿到一段波形,我习惯按这个顺序排查:
- 看帧起始:找到SOF的下降沿,确认同步点
- 读ID:从SOF后开始,按位解析ID(注意标准帧和扩展帧的IDE位)
- 看RTR位:判断是数据帧还是远程帧
- 读DLC:确认数据长度
- 读数据:注意位填充的影响,实际数据可能比理论值长
- 检查CRC:手动计算CRC,确认数据完整性
- 看应答:确认是否有节点应答(应答间隙应为显性)
一句话总结:CAN的帧结构设计得非常精巧——位填充保证同步,CRC保证完整性,错误帧保证可靠性。搞懂这5种帧和位填充机制,你就能读懂总线上的任何一段对话。