第2章:CAN协议基础——帧结构、仲裁机制与位时序

大家好,我是你们的嵌入式讲师。今天咱们来啃CAN协议最核心的部分——帧结构、仲裁机制和位时序。说实话,这三块内容你要是吃透了,CAN总线通信你就掌握了七成。我在做车载项目时,就因为帧结构理解不到位,排查了一整天的总线静默问题,后来发现是远程帧用错了地方。嗯,咱们今天就把这些坑提前填上。

2.1 CAN帧结构:五种帧类型

CAN总线上一共定义了五种帧,分别是:数据帧、远程帧、错误帧、过载帧和帧间隔。我个人习惯把它们分成两类:一类是正常通信用的(数据帧、远程帧),另一类是总线维护用的(错误帧、过载帧、帧间隔)。

2.1.1 数据帧——最常用的帧

数据帧负责把数据从一个节点发送到另一个节点。它由7个段组成:

段名称 作用 位数(标准帧)
帧起始 同步所有节点 1位显性
仲裁段 决定谁有发送权 12位(ID+控制位)
控制段 标识数据长度 6位
数据段 实际传输的数据 0~64位(0~8字节)
CRC段 校验数据完整性 16位(含1位界定符)
ACK段 接收节点确认 2位
帧结束 标识帧结束 7位隐性

这里有个细节我提醒一下:数据段最多8个字节。为什么是8?不是16或32?我当年也纳闷过。后来做项目才明白,CAN总线设计初衷是用于实时控制,8字节刚好够传递一个发动机转速+温度+油压,再多就会影响实时性。你想想看,如果一次传64字节,总线被占着,紧急刹车信号就发不出去了。

重点记忆:标准帧ID是11位,扩展帧ID是29位。扩展帧的仲裁段更长,但优先级计算方式一样。

2.1.2 远程帧——请求数据的帧

远程帧说白了就是“我要数据,你给我发”。它的结构和数据帧几乎一样,区别在于:

  • 远程帧的RTR位是隐性(1),数据帧的RTR位是显性(0)
  • 远程帧没有数据段
  • 远程帧的控制段里,DLC表示请求的数据长度

我曾经在调试一个传感器节点时,发现主节点发了远程帧,但传感器就是不回数据。查了半天,原来是传感器节点的ID滤波配置错了,它没认出这个远程帧是发给自己的。嗯,这里要注意:远程帧的ID必须和目标节点的接收ID匹配。

2.1.3 错误帧——总线纠错机制

错误帧由错误标志和错误界定符组成。任何节点检测到总线错误,就会主动发送错误帧。错误标志是6个连续的显性位,这会破坏其他节点的位填充规则,从而让所有节点都知道出错了。

避坑指南:我曾经遇到过一个问题:某个节点频繁发送错误帧,导致整个总线瘫痪。后来发现是它的CAN收发器供电不稳,导致输出电平异常。所以,当总线出现大量错误帧时,先检查硬件供电和终端电阻。

2.1.4 过载帧——流量控制

过载帧用于节点告诉总线“我忙不过来,慢点发”。它和错误帧结构类似,但触发条件不同。过载帧通常发生在:

  • 节点内部接收缓冲区满
  • 节点在帧间隔期间检测到显性位

说实话,过载帧在实际项目中用得不多。现在的CAN控制器都有足够的FIFO深度,很少出现缓冲区溢出的情况。

2.1.5 帧间隔——帧与帧之间的喘息

帧间隔是3个隐性位,用于分隔两个连续的帧。它给总线一点时间恢复隐性状态,也给了其他节点竞争发送的机会。

2.2 CAN仲裁机制——谁先说话?

CAN总线的仲裁机制,是我觉得最巧妙的设计之一。它实现了“无破坏性逐位仲裁”,说白了就是:多个节点同时发送时,ID最小的那个自动获胜,而且不会损坏数据。

2.2.1 仲裁原理

仲裁发生在仲裁段。每个节点在发送ID位时,会同时监听总线电平。如果自己发送的是隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发送,自己就乖乖退出。

举个例子:

  • 节点A发送ID:0x123(二进制:0001 0010 0011)
  • 节点B发送ID:0x456(二进制:0100 0101 0110)

从最高位开始比较:

  • 第1位:A发0,B发0,相同,继续
  • 第2位:A发0,B发1,A是显性,B是隐性,B检测到总线是显性,B退出

所以A获胜,继续发送剩余数据。

个人经验:我建议在设计系统时,把紧急消息的ID设小一些。比如刹车信号用0x001,车窗控制用0x100。这样紧急消息总能优先发送。我在一个工程机械项目中,把安全相关的消息ID都分配在0x000~0x0FF范围内,运行效果很好。

2.2.2 数据帧 vs 远程帧的仲裁

如果数据帧和远程帧同时发送,且ID相同,数据帧会获胜。因为数据帧的RTR位是显性(0),远程帧的RTR位是隐性(1)。所以,数据帧的优先级天然高于远程帧。

2.2.3 扩展帧 vs 标准帧的仲裁

扩展帧的ID是29位,标准帧是11位。当两者同时发送时,标准帧的优先级更高。因为标准帧的SRR位是显性,扩展帧的SRR位是隐性。所以,如果你想让某个消息优先发送,用标准帧就好。

2.3 CAN位时序——时钟的奥秘

位时序是CAN总线通信的底层基础。它决定了每个位的时间长度,以及采样点的位置。我刚开始学的时候觉得这部分很枯燥,直到有一次因为位时序配置错误,导致两个节点无法通信,才老老实实把它啃下来。

2.3.1 位时间的组成

一个位时间由4个段组成:

段名称 作用 典型长度
同步段(SS) 用于同步各节点时钟 1个时间量子
传播段(PTS) 补偿信号传播延迟 1~8个时间量子
相位缓冲段1(PBS1) 调整采样点位置 1~8个时间量子
相位缓冲段2(PBS2) 调整采样点位置 1~8个时间量子

采样点位于PBS1和PBS2之间。我个人习惯把采样点设置在87.5%的位置,这样能最大程度避免信号抖动的影响。

2.3.2 时间量子与波特率

时间量子(Tq)是CAN控制器的最小时间单位。波特率 = 1 / (位时间)。位时间 = (1 + PTS + PBS1 + PBS2) × Tq。

举个例子:如果CAN时钟是36MHz,预分频器设为4,那么Tq = 4/36MHz ≈ 111ns。如果PTS=3,PBS1=3,PBS2=3,那么位时间 = (1+3+3+3)×111ns = 1110ns,波特率 ≈ 900kbps。

计算公式:波特率 = CAN时钟 / (预分频器 × (1 + PTS + PBS1 + PBS2))

2.3.3 同步与重同步

CAN总线没有独立的时钟线,所有节点靠位时序同步。同步分为硬同步和重同步:

  • 硬同步:在帧起始的下降沿触发,所有节点调整自己的位时间
  • 重同步:在帧内,如果检测到边沿位置偏差,通过调整PBS1或PBS2来修正

重同步的调整范围由SJW(同步跳转宽度)决定。SJW通常设为1~4个Tq。我建议SJW不要设太大,否则容易误同步。

避坑指南:我曾经在一个项目中,把SJW设成了4,结果总线在高速通信时频繁出错。后来把SJW改回1,问题就解决了。原因是SJW太大,导致节点对噪声边沿过于敏感,产生了错误的同步调整。

2.4 实战配置建议

最后,我给大家一个STM32的CAN位时序配置参考:

// 以STM32F103为例,CAN时钟36MHz,目标波特率500kbps
// 预分频器 = 4,Tq = 4/36M = 111ns
// 位时间 = 1 + 3 + 3 + 3 = 10 Tq = 1110ns
// 波特率 = 1/1110ns ≈ 900kbps ❌ 不对

// 重新计算:目标500kbps,位时间 = 2us
// Tq = 2us / 10 = 200ns
// 预分频器 = 200ns × 36MHz = 7.2,取整为7
// 实际Tq = 7/36M ≈ 194ns
// 实际位时间 = 10 × 194ns = 1.94us
// 实际波特率 ≈ 515kbps ✅ 误差在可接受范围

CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Prescaler = 7;

嗯,这里要注意:实际波特率和目标波特率允许有1%~2%的误差。如果误差太大,节点之间会频繁出现位错误。

个人建议:在做CAN通信调试时,先用示波器看CAN_H和CAN_L的波形,确认位时间是否正确。我每次调试新板子,第一件事就是抓波形,这能省下大量排查时间。

好了,这一章的内容就到这里。帧结构、仲裁机制、位时序,这三块是CAN协议的基石。下一章咱们会讲STM32的CAN外设配置,到时候会用到今天学的位时序知识。如果你在项目中遇到CAN通信问题,欢迎在评论区交流,我会尽量回复。