CAN 现场总线
CAN 现场总线
一、概述
CAN(Controller Area Network,控制器局域网络)是 BOSCH 提出的差分、异步、多主现场总线,广泛用于汽车电子、工业控制、医疗设备。它用两根差分线(CAN_H/CAN_L)实现抗干扰的远距离通信,支持多主节点和报文 ID 优先级仲裁。
一句话核心:所有节点挂到 CAN_H/CAN_L 差分总线,通过报文 ID 仲裁决定谁先发(ID 越小优先级越高),用显性/隐性电平实现线与仲裁,通过错误帧保证可靠性。
二、物理层(差分)
CAN 用两根差分线传输:
┌─────┐ CAN_H ═══════════════════════════ CAN_H ┌────┐
│节点1│ CAN_L ═══════════════════════════ CAN_L │节点2│
└─────┘ └────┘| 信号 | CAN_H | CAN_L | 差分电压(V_H−V_L) | 逻辑 |
|---|---|---|---|---|
| 显性(Dominant) | 高(~3.5V) | 低(~1.5V) | ~2V(非零) | 逻辑 0 |
| 隐性(Recessive) | ~2.5V | ~2.5V | ~0V(近似零) | 逻辑 1 |
注:具体电平值因标准(ISO 11898-2)与收发器不同略有差异,关键是显性 = 差分非零 = 0,隐性 = 差分近似零 = 1。节点根据差分电压(V_H − V_L)而非单端电平判断逻辑位。
波形示意(一帧数据某段):
位类型: 0 1 0 1 0 1
差分电压: ≈2V ≈0V ≈2V ≈0V ≈2V ≈0V
(V_H−V_L) ──┐ ┌─────────┐ ┌───────────┐
(高=显性) └───────┘ └──┘ └──────
(低=隐性)(图中只画差分电压 V_H−V_L:显性位跳到 ≈2V(高位),隐性位回落到 ≈0V(低位),逻辑 0/1 由差分电压的有无决定,与两根线各自的绝对电压无关。)
为什么用差分(双线):外界电磁干扰通常同时叠加到两根线上(共模干扰),差分接收器只关心 V_H − V_L,共模干扰一减就被抵消,所以 CAN 抗干扰能力强、可在长距离(可达几百米)和高噪环境(汽车发动机舱)下可靠通信。
关键理解:CAN 是显性优先——多个节点同时发时,只要有一个发"显性(0)",总线就是显性(0)。这决定了仲裁机制:显性位(0)会覆盖隐性位(1)(原理同 I2C 的"线与",但 CAN 用的是差分线)。
为什么总线两端要各接一个 120Ω 终端电阻:
CAN 总线是高速差分传输,信号在线上传播遇到阻抗突变会反射,产生振铃/波形畸变,导致节点误判电平。为了消除反射,需要让总线特性阻抗匹配。
- CAN 传输线特性阻抗约为 120Ω,所以在总线物理两端各接一个 120Ω 终端电阻(中间节点不接,只在最远端两个节点接);
- 终端电阻让信号到达末端时"被吸收"而不是"被反射",保证波形干净、通信可靠;
- 两端电阻并联等效为
120Ω/2 = 60Ω的差分负载,与收发器匹配。
节点1 ──┬── 120Ω ──┐ ┌── 120Ω ──┬── 节点4
│ │ CAN_H │ │
│ ├─────────┤ │
│ │ CAN_L │ │
└───────────┴─────────┴──────────┘
(两端各一个 120Ω,中间节点不接)后果:若两端 120Ω 缺失,长线/高速时会出现反射、波形振铃 → 偶发错误帧(见第九节"常见坑")。短距离低速实验可以勉强不接,但正规工程必须两端都接。
三、多主仲裁(CSMA/CA)
所有节点都可主动发(多主)。当多个节点同时发时,通过非破坏性仲裁决定:
- 每个节点发 ID 时同时监听总线;
- 发送隐性位(1)但读到总线是显性(0) → 仲裁失败,立即停止发送,转为接收;
- ID 数值越小(更多显性位),仲裁优先级越高,最终获胜者继续发。
数值演算(仲裁):节点 A 发 ID 0x123、节点 B 发 ID 0x155,同时竞争。标准帧 ID 为 11 位,先转成二进制(不足 11 位高位补 0):
- A:
0x123 = 291 = 100100011(9 位)→ 补成 11 位00100100011 - B:
0x155 = 341 = 101010101(9 位)→ 补成 11 位00101010101
逐位仲裁过程(从 ID 最高位开始逐位对比,发 0 显性、发 1 隐性;同一时刻只发送一个位):
| 位序 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | … |
|---|---|---|---|---|---|---|---|---|
A 发 0x123 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | … |
B 发 0x155 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | … |
| 总线实际电平 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | … |
| 谁退出 | 都留 | 都留 | 都留 | 都留 | B 发1读0 失败 | A 独占 | A 独占 | … |
- 第 1~4 位:A、B 发的位完全相同,总线电平一致,双方都继续,胜负未分;
- 第 5 位:A 发 0(显性)、B 发 1(隐性)→ 线与结果总线为 0(显性);B 发出隐性位 1 却读到总线是显性 0,仲裁失败,立即停止发送转为接收;
- 第 6 位起:A 独占总线,继续完成整个帧的发送(剩余 ID 位 + RTR + 控制 + 数据…)。
结果:A(0x123)赢得总线,B 下次重试。
关键理解:ID 越小优先级越高。
0x123 < 0x155,且在逐位仲裁时0x123在第 5 位先出现显性 0,所以 A 胜出。仲裁不仅发生在 ID 位,也覆盖 RTR、扩展帧的 IDE/扩展 ID 等后续位——只要两个节点发出的位序列出现"一位发 1、一位发 0"就分胜负。这正是"报文 ID 即优先级"的设计。
远程帧 vs 数据帧:ID 相同时谁优先?
CAN 中相同 ID 时,数据帧优先于远程帧。原因就在 RTR 位:
| 帧类型 | RTR 位 | 电平 |
|---|---|---|
| 数据帧 | 0 | 显性 |
| 远程帧 | 1 | 隐性 |
仲裁时两者先比 ID(相同,不分胜负),接着比 RTR 位:数据帧发显性 0、远程帧发隐性 1 → 数据帧赢得仲裁,远程帧让路。
为什么这样设计:远程帧只是"请求对方发数据",而数据帧是"实际在传数据"。当某个节点发出远程帧请求,同时另一个节点刚好在发同一 ID 的数据帧时,让数据帧先走更有意义——请求方听到数据帧到来,就知道数据已经在了,无需远程帧重复请求,避免了无效传输。
四、报文帧格式
CAN 有标准帧(11 位 ID)和扩展帧(29 位 ID)两种格式,字段顺序不同,下面分别介绍。两者的 SOF、CRC、ACK、EOF、IFS 完全相同,差别只集中在前半段(仲裁 + 控制)。
4.1 标准数据帧(11 位 ID)
完整结构(按发送顺序,从左到右):
┌─────┬───────────┬─────┬─────┬─────┬──────┬──────────────┬─────────────┬─────────┬─────────┬─────┬─────┐
│ SOF │ ID │ RTR │ IDE │ r0 │ DLC │ Data │ CRC │ACK槽│ACK分隔│ EOF │ IFS │
│ 1 │ 11 │ 1 │ 1 │ 1 │ 4 │ 0~64 bit │ 15 + 1 │ 1 │ 1 │ 7 │ 3 │
└─────┴───────────┴─────┴─────┴─────┴──────┴──────────────┴─────────────┴─────────┴─────────┴─────┴─────┘| 字段 | 长度 | 显性/隐性 | 说明 |
|---|---|---|---|
| SOF | 1 | 显性 0 | 起始帧,标志一帧开始,兼做所有节点同步参考 |
| ID | 11 | 随 ID 值 | 报文标识符,同时充当优先级(越小越优先),参与仲裁 |
| RTR | 1 | 数据帧=显性 0 / 远程帧=隐性 1 | 远程帧标志;标准帧中它是仲裁字段之一 |
| IDE | 1 | 显性 0 | 标识符扩展位(0=标准帧,1=扩展帧),标准帧固定 0 |
| r0 | 1 | 显性 0 | 保留位(固定显性 0) |
| DLC | 4 | 随 DLC 值 | 数据长度码(0~8 有效;4 位可表示 0~15,但 >8 为非法) |
| Data | 0~8 字节 | 随数据 | 数据负载(0~64 bit) |
| CRC | 15+1 | 随 CRC 值 + 分隔符隐性 1 | 15 位 CRC + 1 位 CRC 分隔符(固定隐性) |
| ACK 槽 | 1 | 发送方发隐性 1,接收方拉显性 0 | 应答位(见下方"ACK 槽机制") |
| ACK 分隔符 | 1 | 隐性 1 | 固定隐性,隔离 ACK 槽与 EOF |
| EOF | 7 | 隐性 1 | 帧结束(固定隐性) |
| IFS | 3 | 隐性 1 | 帧间隔(发送节点在连续两帧间必须等待的最小间隔) |
4.2 扩展数据帧(29 位 ID)
扩展帧在 11 位 ID 之后多了 SRR、IDE、18 位扩展 ID,并把 RTR 移到后面,因此仲裁字段更长:
┌─────┬────────────┬─────┬─────┬──────────────┬─────┬─────┬─────┬──────┬──────────────┬─────────────┬─────────┬─────────┬─────┬─────┐
│ SOF │ ID(11) │ SRR │ IDE │ 扩展ID(18) │ RTR │ r1 │ r0 │ DLC │ Data │ CRC │ACK槽│ACK分隔│ EOF │ IFS │
│ 1 │ 11 │ 1 │ 1 │ 18 │ 1 │ 1 │ 1 │ 4 │ 0~64 bit │ 15 + 1 │ 1 │ 1 │ 7 │ 3 │
└─────┴────────────┴─────┴─────┴──────────────┴─────┴─────┴─────┴──────┴──────────────┴─────────────┴─────────┴─────────┴─────┴─────┘| 字段 | 长度 | 显性/隐性 | 说明 |
|---|---|---|---|
| SOF | 1 | 显性 0 | 起始帧 |
| ID | 11 | 随 ID 值 | 基础标识符,参与仲裁 |
| SRR | 1 | 隐性 1 | 替代远程请求位,固定隐性,取代标准帧中 RTR 的位置 |
| IDE | 1 | 隐性 1 | 标识符扩展位(扩展帧固定 1);扩展帧中它参与仲裁 |
| 扩展 ID | 18 | 随 ID 值 | 附加标识符(共 11+18=29 位),参与仲裁 |
| RTR | 1 | 数据帧=显性 0 / 远程帧=隐性 1 | 远程帧标志,移到扩展 ID 之后 |
| r1 | 1 | 隐性 1 | 保留位(固定隐性 1) |
| r0 | 1 | 显性 0 | 保留位(固定显性 0) |
| DLC | 4 | 随 DLC 值 | 数据长度码(0~8) |
| Data | 0~8 字节 | 随数据 | 数据负载 |
| CRC | 15+1 | 随 CRC 值 + 分隔符隐性 1 | 15 位 CRC + 1 位 CRC 分隔符(固定隐性) |
| ACK 槽 | 1 | 发送方发隐性 1,接收方拉显性 0 | 应答位 |
| ACK 分隔符 | 1 | 隐性 1 | 固定隐性 |
| EOF | 7 | 隐性 1 | 帧结束 |
| IFS | 3 | 隐性 1 | 帧间隔 |
标准帧 vs 扩展帧的核心差异:
- 仲裁字段:标准帧为
ID + RTR(13 位);扩展帧为ID + SRR + IDE + 扩展ID + RTR(32 位)。节点逐位发送这些字段并监听总线,发现"自己发隐性 1 却读到显性 0"即仲裁失败(见第三节)。- 字段数:扩展帧比标准帧多了
SRR、扩展ID、r1三个字段,且RTR位置后移。- 仲裁结果:相同 ID 优先的标准帧(IDE=0 显性)会赢得对扩展帧(IDE=1 隐性)的仲裁,所以标准帧优先级高于扩展帧。
ACK 槽机制(可靠性关键):发送节点在 ACK 槽位发出隐性 1,然后释放总线;接收节点只要校验通过就立刻把该位拉成显性 0。发送节点在 ACK 槽后采样:读到 0 = 至少有一个节点正确接收(ACK),读到 1 = 没有任何节点应答(ACK 错误,触发错误处理)。这就是 CAN"发后即知是否被正确接收"的机制。
四种帧类型:
- 数据帧:传输数据(RTR=0);
- 远程帧:请求对方发指定 ID 数据(RTR=1,无 Data);
- 错误帧:节点发现错误时发出,由错误标志+错误分隔符组成;
- 过载帧:节点过忙时请求延迟后续帧。
4.3 报文 ID 从哪来(不是"获取",而是"分配")
关键认知:CAN 的 ID 不是设备地址(不像 I2C 从机地址那样每台设备固定一个),而是报文类型 + 优先级的标识。它由系统设计时人为分配,而不是靠工具扫描获取。
| 场景 | 谁决定 ID | 说明 |
|---|---|---|
| 整车 / 工业 CAN 网络 | 网络设计者(系统架构师) | 设计阶段就定义"报文矩阵",给每种报文分配固定 ID |
| 单板自用 CAN | 开发者自己 | 自己约定各报文的 ID |
| 标准协议(CANopen、J1939) | 协议标准规定 | 如 CANopen 按对象字典分配、J1939 按 PGN 编码 |
分配 ID 的三条原则:
- ID 决定优先级:ID 越小越优先(仲裁先发),所以关键报文给小 ID、次要报文给大 ID;
- ID 决定接收过滤:接收节点按 ID 过滤,只处理自己关心的报文;
- 每个 ID 全网唯一:同一网络上,两个不同报文不能用同一个 ID,否则接收方无法区分。
ID 空间:标准帧 11 位 = 2^11 = 2048 个;扩展帧 29 位 = 2^29 ≈ 5.4 亿个。报文多、需避免冲突时用扩展帧。
避免冲突的常见做法:用"报文矩阵/ID 规划表"统一登记;把 ID 空间按功能分段分配(如 0x000~0x0FF 留安全关键、0x100~0x1FF 常规控制、0x200 以上诊断/信息)。
实际例子(车门控制报文 ID 分配):
0x001 刹车踏板位置 (安全关键,最小 ID,最高优先级)
0x100 车速信号
0x200 车窗状态 (常规控制)
0x300 仪表显示信息 (信息类,最大 ID,最低优先级)刹车报文 ID 最小 → 总线上永远优先发送,哪怕其他报文排队,紧急数据也能先到。这就是为什么例子里 0x123 这类 ID 是设计者预先分配好的,而不是运行时查出来的。
4.4 例:标准数据帧逐字段构造
需求:发送一个标准数据帧,ID = 0x123,数据 = 0x11 0x22 0x33(3 字节),数据帧(非远程)。
第 1 步:把 ID 转成 11 位二进制
0x123 = 291→ 11 位补 0:00100100011
第 2 步:逐字段填值(从左到右)
| 字段 | 长度 | 值 | 说明 |
|---|---|---|---|
| SOF | 1 | 0 | 固定显性 |
| ID | 11 | 00100100011 | = 0x123 |
| RTR | 1 | 0 | 数据帧 = 显性 0 |
| IDE | 1 | 0 | 标准帧 = 显性 0 |
| r0 | 1 | 0 | 保留位 = 显性 0 |
| DLC | 4 | 0011 | = 3(3 字节数据) |
| Data | 24 | 00010001 00100010 00110011 | = 0x11 0x22 0x33 |
| CRC | 16 | (硬件计算) | 15 位 CRC + 1 位隐性分隔符 |
| ACK 槽 | 1 | 发1收0 | 接收方校验通过则拉显性 |
| ACK 分隔 | 1 | 1 | 隐性 |
| EOF | 7 | 1111111 | 隐性 |
| IFS | 3 | 111 | 隐性 |
第 3 步:合并成完整位流(前半段可手工核对,CRC/ACK 由硬件处理):
0 | 00100100011 | 0 | 0 | 0 | 0011 | 00010001 00100010 00110011 | CRC | ACK | 1111111 | 111- 前半段长度:
1 + 11 + 1 + 1 + 1 + 4 + 24 = 43位; - 数据段共 3 字节(24 位)与 DLC=3 对应;
- CRC 段由发送节点按位流实时计算,接收节点校验。
要点:DLC 和 Data 必须匹配——DLC=3 就发 3 字节。若发 0 字节(远程帧或空数据帧),DLC=0000 且无 Data 段。
4.5 CRC 是怎么算的?
CRC(Cyclic Redundancy Check,循环冗余校验) 是一种检错码:发送方按规则从数据算出一个"校验值"附在帧尾,接收方用同样的规则重算,若结果一致说明数据传输无误,不一致则报 CRC 错误(见第六节)。
核心思想:把要保护的数据看成一个二进制数,用模 2 除法(异或除法)去除以某个固定的"多项式",得到的余数就是 CRC。任何一位翻转都会导致余数改变,从而被检测出来。
模 2 除法 vs 普通除法:普通除法减法要"借位",模 2 除法每一步用异或(XOR)替代减法——异或规则:
0⊕0=0、0⊕1=1、1⊕0=1、1⊕1=0(相同为 0,不同为 1)。这是 CRC 能用手算演示的关键。
CAN 用的具体参数(硬件计算前需要确定的):
| 参数 | 值 | 说明 |
|---|---|---|
| 多项式 | x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1 | 规范规定的固定多项式,硬件据此搭反馈 |
| 对应二进制(16 位) | 1100 0101 1001 1001(即 0xC599,最高位是 x^15) | 由多项式各项系数拼出 |
| 常用简写(15 位) | 0x4599 | 去掉最高位 x^15 后的标准写法 |
| CRC 宽度 | 15 位 | 校验值占 15 个位 |
| 计算对象 | SOF + ID + RTR + IDE + r0 + DLC + Data | 仲裁+控制+数据,不含 CRC 自身 |
| 初始值 | 0 | 移位寄存器计算前清零 |
| 取反方式 | 余数逐位取反后发送 | 发送端取反,接收端收到后也取反再比 |
| 尾随补零 | 数据末尾补 15 个 0 再除 | 因为要除以 15 位多项式 |
这些参数各自的意义(理解硬件怎么算):
多项式
0x4599怎么来的:它由规范写死的各项系数组成——把x^15+x^14+x^10+x^8+x^7+x^4+x^3+1每一项的指数对应到位,指数存在写 1、不存在写 0,拼成 16 位二进制1100 0101 1001 1001,去掉最高位 x^15 就是常用简写0x4599。硬件就按这个多项式的 1 位位置搭建异或反馈。初始值为什么是 0:CAN 规定移位寄存器计算前全部清零(与 Modbus 的
0xFFFF不同,那是 Modbus 的约定)。这样保证所有节点用相同的起点算 CRC。取反方式:发送端算出的 15 位余数要逐位取反(0 变 1、1 变 0)后才填入 CRC 段;接收端把收到的 CRC 也取反后,再与本地重算的结果比对,一致才判正确。这是 ISO 11898 的约定,节点间必须一致。
为什么补 15 个 0:CRC 是"除以多项式后的余数"。要得到 15 位余数,需在数据末尾补 15 个 0 再除(位数 = CRC 宽度);这样商是整数位、余数恰好 15 位。
小结:硬件计算 CAN CRC 前,"多项式
0x4599、初始值 0、结果取反、补 15 个 0"这些参数都是 ISO 11898 规范写死的固定值——所有节点用同一套参数,才能对同一帧算出相同 CRC。程序员无需配置(硬件已内置),只需知道它们存在即可。
先看一个短的示意(帮助理解异或除法,非 CAN 真实参数):设原始数据为 1101011(7 位),多项式为 1011(对应 x^3+x+1,4 位),CRC 宽度为 3 位。
第 1 步:数据尾部补 3 个 0(因为 CRC 是 3 位,就补 3 个 0)
原始数据: 1 1 0 1 0 1 1
补 3 个 0: 1 1 0 1 0 1 1 0 0 0 ← 这就是"被除数"(10 位)第 2 步:逐位做模 2 除法。规则:每次看当前 4 位"窗口",若最高位是 1,就对多项式 1011 做异或;若最高位是 0,就不动;然后向左移入下一位数据,直到 10 位都处理完。
| 步 | 当前窗口(4位) | 最高位=1? | 是否异或 1011 | 异或结果 | 拼入下一位 |
|---|---|---|---|---|---|
| 1 | 1101 | 是 | 1101⊕1011=0110 | 0110 | 拼第 5 位 0 → 01100 |
| 2 | 取后 4 位 1100 | 是 | 1100⊕1011=0111 | 0111 | 拼第 6 位 1 → 01111 |
| 3 | 1111 | 是 | 1111⊕1011=0100 | 0100 | 拼第 7 位 1 → 01001 |
| 4 | 1001 | 是 | 1001⊕1011=0010 | 0010 | 拼第 8 位 0 → 00100 |
| 5 | 0100 | 否 | 不异或,保留 0100 | — | 拼第 9 位 0 → 01000 |
| 6 | 1000 | 是 | 1000⊕1011=0011 | 0011 | 拼第 10 位 0 → 00110 |
| 7 | 0110 | 否 | 不异或,保留 0110 | — | 数据已用完,结束 |
第 3 步:取最后 3 位作为 CRC
最后窗口为 0110,取后 3 位(CRC 宽度 = 3)→ CRC = 110。
验证思路:接收方拿到"数据 + CRC"后,用同样方法重算数据部分的 CRC,若算出的值 = 收到的 110,说明传输正确;若不同,说明数据被干扰(CRC 错误)。
规律总结:规则只有一条——当前窗口最高位是 1 就异或多项式
1011,是 0 就不动,然后移入下一位,循环直到所有位处理完,最后窗口的后 3 位就是 CRC。CAN 的 15 位 CRC 原理完全相同,只是多项式更长、位数更多,实际由硬件完成,这里用 3 位小例子只是为了看清"异或除法"的过程。
CAN 的真实情况:15 位多项式 + 最少 19 位(标准帧空数据)、最多 83 位(8 字节数据)的被除数,完整手工演算非常长,实际中全部由 CAN 控制器硬件完成——发送时硬件边发边算、自动填充 CRC 段,接收时硬件重算比对。手工算的意义在于理解"为什么能检错",而不是真的去手算。
为什么 CRC 可靠:模 2 除法能检测出所有 1 位错和绝大多数多位错;且数据越长、CRC 位越多,漏检概率越低。CAN 用 15 位 CRC,漏检概率极低,这是它可靠性高的组成部分之一。
4.6 例:扩展数据帧逐字段构造
需求:发送一个扩展数据帧,数据 = 0xAA(1 字节)。这里重点演示 29 位 ID 怎么拆、以及扩展帧各字段怎么填。
第 1 步:把 29 位 ID 拆成"11 位基础 ID + 18 位扩展 ID"
29 位 ID 从高到低分成两段:高 11 位 = 基础 ID,低 18 位 = 扩展 ID。本例取一个便于观察的 ID:
- 基础 ID(11 位)=
10101010101(= 0x555) - 扩展 ID(18 位)=
001100110011001100
两者拼接即完整 29 位 ID:10101010101 001100110011001100(中间空格仅为分段显示)。
第 2 步:逐字段填值
| 字段 | 长度 | 值 | 说明 |
|---|---|---|---|
| SOF | 1 | 0 | 固定显性 |
| ID(基础) | 11 | 10101010101 | = 0x555,参与仲裁 |
| SRR | 1 | 1 | 固定隐性,取代标准帧 RTR 的位置 |
| IDE | 1 | 1 | 扩展帧 = 隐性 1,参与仲裁 |
| 扩展 ID | 18 | 001100110011001100 | 18 位扩展 ID,参与仲裁 |
| RTR | 1 | 0 | 数据帧 = 显性 0 |
| r1 | 1 | 1 | 保留位 = 隐性 1 |
| r0 | 1 | 0 | 保留位 = 显性 0 |
| DLC | 4 | 0001 | = 1(1 字节数据) |
| Data | 8 | 10101010 | = 0xAA |
| CRC | 16 | (硬件计算) | 15 位 CRC + 1 位隐性分隔符 |
| ACK 槽 | 1 | 发1收0 | 接收方校验通过则拉显性 |
| ACK 分隔 | 1 | 1 | 隐性 |
| EOF | 7 | 1111111 | 隐性 |
| IFS | 3 | 111 | 隐性 |
第 3 步:合并:
0 | 10101010101 | 1 | 1 | 001100110011001100 | 0 | 1 | 0 | 0001 | 10101010 | CRC | ACK | 1111111 | 111- 扩展帧仲裁字段(
基础ID + SRR + IDE + 扩展ID + RTR)=10101010101 + 1 + 1 + 001100110011001100 + 0,共11+1+1+18+1 = 32位,比标准帧的 13 位仲裁字段长得多; - 前半段长度:
1 + 11 + 1 + 1 + 18 + 1 + 1 + 1 + 4 + 8 = 47位(不含 CRC/ACK/EOF/IFS)。
要点:扩展帧仲裁字段更长 → 发送同负载数据比标准帧耗时略长;但 29 位 ID 让可用报文标识符从标准帧的 2048(2^11)个扩大到约 5.4 亿(2^29)个,适用于大型网络中需要海量标识符的场景。
4.7 例:标准帧 vs 扩展帧——相同 ID 谁先发?
若节点 A 发标准帧(ID=0x123,IDE=0)、节点 B 发扩展帧(ID 前 11 位也=0x123,IDE=1),两者竞争同一总线:
| 位 | 字段 | A(标准帧) | B(扩展帧) | 总线 | 谁退出 |
|---|---|---|---|---|---|
| 1 | SOF | 0 | 0 | 0 | 都留 |
| 2~12 | ID | 0x123 各位 | 0x123 各位 | 相同 | 都留 |
| 13 | RTR / SRR | RTR=0(显性) | SRR=1(隐性) | 0 | B 退出 |
- 标准帧第 13 位是 RTR(数据帧=0 显性);扩展帧第 13 位是 SRR(固定 1 隐性);
- 这一位标准帧发 0、扩展帧发 1 → 线与为显性 0,扩展帧仲裁失败;
- 结论:ID 相同时,标准帧优先于扩展帧(因为标准帧的 IDE=0 显性、扩展帧 IDE=1 隐性)。
五、位时序与波特率
5.1 先从最基础的开始:波特率 = 每秒发几位
波特率(Baud Rate)就是每秒在总线上发送多少个位。比如 500 kbit/s = 每秒发 500,000 位。
位时间(Bit Time)就是发送一位所花的时间,它是波特率的倒数:
位时间 = 1 / 波特率
例:500 kbit/s → 位时间 = 1 / 500000 = 2 μs(每一位占 2 微秒)打个比方:波特率相当于"说话速度"(每秒几个字),位时间就是"每个字占几秒"。两者就是倒数关系——说快了,每个字就短;说慢了,每个字就长。
5.2 问题:位时间这么短,怎么保证"准时"?
CAN 是异步通信(没有独立时钟线),所有节点靠自己的晶振计时。但每个晶振都有微小误差,且总线传输、节点处理有延迟。如果节点 A 和 B 的计时稍微不同步,几千位后就会错位、采到错误的数据。
为此 CAN 把每一位时间再细分,并在每个位里安排一个"采样点",大家约定在这个点读总线。这就像两人对表——每隔一段就对一次,防止越差越远。
位时间的细分(把 1 位切成 4 小段):
┌────────────────── 1 个位时间(Bit Time)──────────────────┐
│ ┌────────┬──────────┬───────────┬────────────┐ │
│ │ 同步段 │ 传播段 │ 相位段1 │ 相位段2 │ │
│ │ (Sync) │ (Prop) │ (Phase_S1)│ (Phase_S2) │ │
│ │ 1 TQ │ 7 TQ │ 4 TQ │ 4 TQ │ │
│ └────────┴──────────┴───────────┴────────────┘ │
└──────────────────────────────────────────────────────────┘
▲
└── 采样点就在相位段1和2的交界处(本例 75%)| 段 | 作用(通俗讲) | 相当于 |
|---|---|---|
| 同步段(Sync) | 每个位开始时,节点用它来"对表",识别一位的起点 | 起点对齐 |
| 传播段(Prop) | 等信号在总线上传播到最远的节点再回来(线越长,等越久) | 等待传输延迟 |
| 相位段 1(Phase_S1) | 采样前的一小段缓冲,可延长来纠正时钟偏差 | 缓冲/校正 |
| 相位段 2(Phase_S2) | 采样后的一小段,可缩短来配合纠正 | 缓冲/校正 |
采样点为什么要放在约 75% 处:采样点太早,信号可能还没稳定(在传输/跳变中);太晚又太靠近下一位的起点,容易受下一位干扰。放在位时间的 75% 附近最稳妥——前 75% 让信号充分稳定,后 25% 留足缓冲。
5.3 TQ 是什么?怎么把 1 位切成 16 块?
TQ(Time Quantum,时间量子)是 CAN 计时的最小单位。它由节点的主时钟(如 APB1=36MHz)分频得到:
TQ 频率 = APB1 时钟 / Prescaler(预分频值)
例:36MHz / 4 = 9 MHz → 每个 TQ = 1 / 9MHz ≈ 111 ns然后一位时间由整数个 TQ 拼成(如本例 16 个 TQ)。TQ 越小,分频越细,能配出的波特率越精确;但 TQ 太短时传输延迟影响变大。
数值演算(完整走一遍):设 APB1 = 36MHz,Prescaler = 4,位时间 = 16 TQ(Sync 1 + Prop 7 + Phase1 4 + Phase2 4):
| 步骤 | 计算 | 结果 |
|---|---|---|
| ① 求 TQ 频率 | 36MHz / 4 | = 9 MHz |
| ② 求 TQ 时长 | 1 / 9MHz | ≈ 111 ns |
| ③ 位时间 | 16 × 111 ns | ≈ 1.78 μs |
| ④ 波特率 | 1 / 1.78μs | ≈ 562 kbit/s |
| ⑤ 采样点 | (1+7+4) / 16 | = 75% |
第 ⑤ 步的采样点:把 Sync、Prop、Phase_S1 三段长度加起来(
1+7+4=12),除以总长 16,得到12/16 = 75%——正好落在 Phase_S1 末尾、Phase_S2 开头,即采样点位置。
5.4 常见波特率怎么配(供参考)
要让波特率"凑成"常用的 500k/250k/125k,需要同时调 Prescaler 和 TQ 总数:
| 目标波特率 | APB1=36MHz 下一种常用方案 |
|---|---|
| 500 kbit/s | Prescaler=4,位时间 18 TQ(采样点约 75%) |
| 250 kbit/s | Prescaler=8,位时间 18 TQ(采样点约 75%) |
| 125 kbit/s | Prescaler=16,位时间 18 TQ(采样点约 75%) |
关键理解:总线上所有节点的波特率、位时序(含采样点)必须完全一致,否则通信失败——一个节点以为采样点在第 75% 处,另一个却以为在第 50% 处,就会采错数据。计算时用 TQ 整数规划位时间(位时间必须是 TQ 的整数倍),并让采样点落在 70~80% 区间。
5.5 SJW(同步跳转宽度)——允许"调多少"
前面 5.2 说相位段 1/2 可"延长/缩短"来纠正时钟偏差,但每次最多能调多少? 这个上限就是 SJW(Synchronization Jump Width,同步跳转宽度)。
| 参数 | 含义 |
|---|---|
| SJW | 每次重同步时,相位段 1 最多能延长、或相位段 2 最多能缩短的 TQ 数 |
| 取值范围 | 1~4 TQ(且 ≤ Phase_S1 和 Phase_S2 的最小值) |
| 作用 | 吸收节点间晶振误差和相位抖动,防止时钟偏差越积越大 |
为什么需要 SJW:每个节点晶振有误差(如 ±0.5%),若完全不调整,累计误差会让采样点逐渐偏离最佳位置;有了 SJW,每帧数据在同步段"对表"时,相位段可小幅伸缩,把偏差拉回来。SJW 太小,纠正能力弱;太大,相位段伸缩过度反而降低稳定性,通常取 1~2 TQ。
5.6 波特率与总线长度的关系
传播段的作用是"等信号在总线上传到最远节点再回来",所以线越长,传播延迟越大,需要的传播段(Prop)TQ 越多。而传播段占位时间比例是有限的,因此:
波特率越低 → 每位时间越长 → 能容纳更长的传播段 → 可支持的总线越长。
| 波特率 | 典型最大总线长度(约) |
|---|---|
| 1 Mbit/s | ~40 m |
| 500 kbit/s | ~100 m |
| 250 kbit/s | ~250 m |
| 125 kbit/s | ~500 m |
规律:速率每降一半,可支持的线长大致翻倍。设计时先定线长,再选波特率——线很长却硬要用 1M,会因传播延迟超过相位段容限而通信失败。这也是为什么车内部(短)能用 500k,而工厂楼宇(长)常用 125k。
六、错误处理与错误帧
CAN 有完善的错误检测机制(可靠性是 CAN 的核心卖点)。
6.1 先认识"位填充"(Bit Stuffing)——填充错误的基础
CAN 规定:从 SOF 到 CRC 字段之间(仲裁、控制、数据、CRC),发送方每发出连续 5 个相同的位,就必须插入 1 个相反的填充位。
为什么要有位填充:CAN 是异步的(无独立时钟线),节点靠每个位的电平跳变来"对表"(见 5.2 节同步段)。如果连续很多位都是相同电平(没有跳变),节点就失去同步参考,容易错位。位填充保证总线上不会出现超过 5 个的连续相同位,从而给所有节点持续提供跳变参考。
例子(发送 0x1F = 00011111,8 位):00011111 里有 5 个连续的 1,按规则在第 5 个 1 之后插入 1 个 0:
原始数据: 0 0 0 1 1 1 1 1 (第 4~8 位是 5 个连续的 1)
位填充后: 0 0 0 1 1 1 1 0 1 (在第 5 个 1 后插 0,共 9 位)
↑填充位接收方收到后自动删除填充位,还原出原始数据 00011111。
注意:位填充只作用于
SOF ~ CRC段;EOF、ACK、IFS 等固定格式段不参与位填充。因此填充错误的检测对象也只是前面那段。
6.2 五种错误类型
| 错误类型 | 检测方式 |
|---|---|
| 位错误 | 发送时监听,发现总线电平与发送不一致 |
| 填充错误 | 接收方发现连续 6 个相同位(说明没按规则插入填充位) |
| CRC 错误 | 接收方 CRC 计算不一致 |
| 格式错误 | 固定格式位不合法 |
| ACK 错误 | 发送方未收到 ACK 应答 |
有了位填充规则,"填充错误"就自然成立了:发送方本该在连续 5 个相同位后插填充位,若接收方却看到连续 6 个相同位,说明填充规则被破坏(或总线受干扰),立即报填充错误。这是位填充既用于同步、又用于检错的双重作用。
6.3 错误帧结构
节点检测到错误后立即发出,由错误标志 + 错误分隔符(8 个隐性位)组成,用于破坏当前出错帧,让所有节点都发现错误并丢弃该帧,然后重发。
错误帧结构:
┌──────────────── 错误帧 ────────────────┐
│ 错误标志(6 位) │ 错误分隔符(8 位隐性) │
│ 主动: 000000(显性) │ 11111111 │
│ 被动: 111111(隐性) │ │
└────────────────────────────────────────┘| 组成部分 | 长度 | 内容 |
|---|---|---|
| 错误标志 | 6 位 | 错误主动发 6 个显性 0;错误被动发 6 个隐性 1 |
| 错误分隔符 | 8 位 | 固定隐性 1,给其他节点缓冲、隔离错误帧与后续帧 |
例:节点检测到位错误后发出主动错误帧
假设节点 C 在发送一帧时,检测到"自己发的位和总线电平不一致"(位错误),立即停止发送,发出主动错误帧:
发送中出错: ... 0 1 0 1 ┐ ← 检测到位错误,立刻中断当前帧
主动错误标志: 000000 ← 6 个显性 0,强力占线
错误分隔符: 11111111 ← 8 个隐性 1
被破坏的帧: X X X X X X X X ← 原帧在此中断,所有节点都检测到显性违规- 节点 C 发出 6 个显性 0(错误标志),强制把总线拉成显性;
- 由于原帧的后续位本该是隐性/数据,现在突然出现 6 个显性位 → 所有其他节点也检测到"填充错误/位错误",一致认为该帧已损坏,丢弃整帧;
- 错误分隔符(8 个隐性位)让总线恢复,随后发送方重发刚才那帧。
主动错误帧 vs 被动错误帧的区别:
| 状态 | 错误标志内容 | 效果 |
|---|---|---|
| 错误主动(TEC<128) | 6 个显性 0 | 强力占线,能触发所有节点同步发现错误 |
| 错误被动(TEC≥128) | 6 个隐性 1 | 不占线(自身有故障,避免影响别人),可能被其他节点的显性错误帧覆盖 |
为什么被动错误帧用隐性:错误被动的节点已接近故障,它发隐性错误标志相当于"小声报告",若其他正常节点也在报错(显性),正常节点的显性标志会覆盖它;这样故障节点既报告了错误,又不会用显性电平干扰/抢占总线。
6.4 错误计数机制(TEC/REC)
每个节点维护两个 8 位错误计数器:
- TEC(发送错误计数):自己作为发送方出错时 +8;成功发送一次 -1;
- REC(接收错误计数):作为接收方出错时 +8;成功接收一次 -1;
- 累计超过阈值(如 128、255)就触发状态迁移。
例:一个不断出错的节点,计数如何一步步走向离线
设节点 A 因接错线/波特率不匹配,每次发送都失败(发送方出错,TEC 每次 +8):
| 事件 | TEC 变化 | TEC 当前值 | 状态 |
|---|---|---|---|
| 初始 | — | 0 | 错误主动 |
| 第 1 次发送失败 | 0 + 8 | 8 | 错误主动 |
| 第 2 次发送失败 | 8 + 8 | 16 | 错误主动 |
| …(每次失败 +8) | … | … | … |
| 第 16 次发送失败 | 120 + 8 | 128 | 进入错误被动(≥128) |
| 第 17 次发送失败 | 128 + 8 | 136 | 错误被动 |
| … | … | … | … |
| 第 32 次发送失败 | 248 + 8 | 256 | 进入总线离线(>255) |
- 每次失败 +8:从 0 到 128 需要 16 次(
128/8=16),从 128 到 256 又 16 次,共约 32 次连续失败就离线; - 一旦 TEC 超过 255,节点自动脱离总线,不再收发,只能等主机软件检测并复位。
例:一个正常节点,计数如何"用成功抵消错误"
反过来,正常节点偶尔出错后,只要持续成功就能把计数压回去:
| 事件 | 计数变化 | 当前值 |
|---|---|---|
| 某次发送出错 | TEC +8 | 8 |
| 连续 8 次发送成功 | 每次 -1,8 × (-1) | 0 |
| 期间某次接收出错 | REC +8 | 8 |
| 连续 8 次接收成功 | 每次 -1 | 0 |
规律:错误"急升"(+8)、成功"缓降"(-1)——一次错误需要 8 次成功才能抵消。所以频繁出错但偶有成功的节点,计数仍会净上升,最终走向离线;而只是偶尔抖动的正常节点,很快被成功抵消、计数归零。这就是为什么"某节点频繁报错"是排查信号——它的 TEC/REC 一定在持续爬升。
6.5 三种状态(错误主动 → 错误被动 → 总线离线)
| 状态 | 条件 | 行为 |
|---|---|---|
| 错误主动 | TEC/REC < 128 | 检测到错误发主动错误帧(6 个显性位,强力占线),破坏出错帧 |
| 错误被动 | TEC/REC 在 128~255 | 只能发被动错误帧(6 个隐性位,不占线);成功发送 128 次后自动恢复主动 |
| 总线离线 | TEC > 255 | 自动脱离总线,不再收发;需主机软件检测并复位后方可重新上线 |
关键理解:CAN 的"错误帧 + 计数 + 离线保护"保证单个故障节点不会瘫痪整个网络——故障节点计数到 255 会自行离线,其余节点继续正常工作。这是它被汽车/工业采用的重要原因。若某节点频繁出错(如接错线、波特率不符),错误计数会持续上升直到离线,这是排查"某节点突然不上线"的常见线索。
七、数据传输的完整流程
前三~六节分别讲了帧格式、仲裁、位时序、错误处理,本节把它们串起来,完整演示一帧数据从"发送方发出"到"接收方收下"的全过程。
CAN 与 I2C 最大的不同:CAN 是广播式、按报文 ID 寻址——一个节点发送,总线上所有节点都能听到,各节点用 ID 过滤决定"是否关心这条报文"(不像 I2C 只对单一从机寻址)。
7.1 发送方:一帧的发送流程
以节点 A 发送一帧标准数据帧(ID=0x123,数据 0x11 0x22 0x33)为例:
| 步骤 | 动作 | 说明 |
|---|---|---|
| ① | 检测总线空闲 | 总线连续 11 位隐性(空闲),A 获得发送权 |
| ② | 发 SOF | 发 1 个显性 0,宣布"我要占总线了",所有节点同步 |
| ③ | 发仲裁字段(ID+RTR) | 逐位发 0x123 的 ID 和 RTR;若此刻其他节点也在发,就地仲裁(见第三节) |
| ④ | 发控制+数据 | 发 IDE/r0/DLC 和 Data(0x11 0x22 0x33) |
| ⑤ | 发 CRC | 边发边算 15 位 CRC + 1 位分隔符 |
| ⑥ | 发 ACK 槽(释放) | 发 1 个隐性 1 后释放总线,等接收方应答 |
| ⑦ | 等 ACK | 采样 ACK 槽:读到显性 0 = 有节点正确接收;读到 1 = 无人应答(ACK 错误) |
| ⑧ | 发 EOF/IFS | 发 7 个隐性 EOF + 3 个隐性 IFS,帧结束,总线回到空闲 |
发送方时间线(按阶段,看谁在驱动总线):
| 阶段 | 发送方动作 | 谁驱动总线 |
|---|---|---|
空闲 | 检测总线空闲 | —(总线空闲) |
SOF | 发 1 位显性 0 | 发送方 |
ID+RTR | 逐位发仲裁字段 | 发送方(可能与其他节点仲裁) |
控制+数据 | 发 IDE/r0/DLC/Data | 发送方 |
CRC | 发 15 位 CRC + 分隔符 | 发送方 |
ACK 槽 | 发 1 位隐性 1 后释放总线 | 发送方发 1 后释放,交给接收方 |
等 ACK | 采样总线 | 接收方(校验通过拉显性 0) |
EOF / IFS | 发 7 位 EOF + 3 位 IFS | 发送方 |
空闲 | 帧结束,总线恢复空闲 | —(总线空闲) |
7.2 接收方:一帧的接收流程
总线上所有节点都同步接收这一帧,但各自按 ID 过滤:
| 步骤 | 动作 | 说明 |
|---|---|---|
| ① | 监听总线 | 所有节点一直监听,发现 SOF 显性位即开始接收 |
| ② | 收仲裁字段 | 收到 ID + RTR,与自己的验收过滤器比对:ID 不匹配则丢弃后续(不再处理) |
| ③ | 收控制+数据+CRC | ID 匹配的节点继续收 DLC、Data,并实时重算 CRC |
| ④ | 校验 CRC | 算出的 CRC 与收到的 CRC 比对:一致 → 数据完好;不一致 → 报 CRC 错误 |
| ⑤ | 回 ACK | 校验通过,在 ACK 槽把总线拉成显性 0(表示"我正确收到了") |
| ⑥ | 处理数据 | 收下数据,交由上层(应用)处理 |
接收方时间线(按阶段,看接收方在哪个节点决定"继续/退出"):
| 阶段 | 接收方动作 | 继续 / 退出 |
|---|---|---|
监听 | 所有节点监听总线,发现 SOF 显性位 | 继续 |
SOF | 确认帧开始,同步 | 继续 |
ID+RTR | 按 ID 过滤:与自己验收过滤器比对 | 不匹配 → 退出(丢弃,不再处理) |
控制+数据 | ID 匹配的节点收 DLC、Data | 继续 |
CRC | 实时重算 CRC 并与收到的比对 | 不匹配 → 报 CRC 错误 |
ACK 槽 | 校验通过则拉显性 0 回 ACK | 继续 |
EOF | 收完帧结束位 | 结束 |
7.3 ACK 交互:谁发、谁收、谁回
| 参与方 | 在 ACK 槽的动作 |
|---|---|
| 发送方 | 发隐性 1,然后释放总线 |
| 任一接收方(校验通过) | 把总线拉成显性 0(回 ACK) |
| 发送方(采样) | 读到 0 = 至少一个节点收对了;读到 1 = 无人应答 |
关键理解:ACK 不是发给"某一个"接收方,而是广播式的——只要有任意一个节点校验通过并回 ACK,发送方就认为帧被正确接收。所有接收方中哪怕只有一个 ACK,发送方也算成功;若一个都没有(如总线上根本没接这个 ID 的节点,或 CRC 全错),才报 ACK 错误。
7.4 完整示例:A 发、B 收
设节点 A 发 ID=0x123、数据 0x11 0x22 0x33,节点 B 配置了验收过滤器 0x123,节点 C 过滤的是别的 ID:
A(发送方): 空闲→SOF→ID+RTR→控制+数据→CRC→释放ACK槽→等ACK→EOF→IFS→空闲
B(接收方): 监听→ 收 →ID匹配✓→ 收 →重算CRC✓→ 拉显性回ACK → 处理数据
C(接收方): 监听→ 收 →ID不匹配✗→(丢弃,不再处理)- B 因为 ID 匹配 + CRC 通过,回 ACK,A 采样到 0,知道"帧发成功";
- C 因 ID 不匹配提前丢弃,不影响 B 接收;
- 这就是 CAN"一主发、多收、按 ID 各取所需"的广播模型。
7.5 如何获取其他节点的数据?
关键澄清:CAN 没有 I2C 那种"读操作"。I2C 是主从模型,主机靠地址字节的 R/W 位决定方向;CAN 是对等广播模型,任何节点都可主动发,节点间"读数据"通过下面两种方式实现:
方式一:远程帧请求(主动"要")
节点 A 想要节点 B 的数据时,发一个远程帧(RTR=1,ID 为 B 数据的 ID):
A: 空闲→SOF→[ID+RTR=1]→[DLC]→CRC→释放ACK槽→等ACK→EOF ← 远程帧,无数据段
B: 监听→收仲裁字段→ID匹配→(发现RTR=1,是请求)→ 回ACK → 立刻发数据帧回应
B: 空闲→SOF→[ID+RTR=0]→[控制+数据]→CRC→ACK槽→EOF ← 数据帧,带 B 的实际数据
A: 监听→收→ID匹配→校验CRC→回ACK→处理数据| 参与方 | 动作 |
|---|---|
| A(请求方) | 发远程帧(RTR=1、无数据段),等回应 |
| B(被请求方) | 收到远程帧,识别 ID 是自己,回 ACK 后立即发一个同 ID 的数据帧 |
| A(接收方) | 收到 B 的数据帧,校验后处理 |
方式二:主动上报(被动"收")——更常用
节点 B 周期性(或事件触发)主动发数据帧,节点 A 只要配置好验收过滤器,随时能收到:
B: 周期性发数据帧(ID=0x123,数据=温度)→ → → ...
A: 监听→按ID过滤接收→处理数据 ← A 无需发任何请求,被动持续收为什么主动上报更常用:远程帧请求会增加一次总线往返(多占带宽),而且"请求-应答"是点对点;主动上报则是"发一次,大家各取所需",带宽利用率高,也更适合 CAN 的广播模型。多数真实系统(如整车传感器数据)都用主动上报。
小结:CAN 的"读"不是 I2C 的寄存器读,而是"我发请求你要 / 你主动发我要"。7.4 演示的是数据帧广播(写/发送方向),本小节补全了"获取数据"(读方向)的两种方式——这样 CAN 数据传输的双向流程就完整了。
八、CAN vs RS-485 对比
| 维度 | CAN | RS-485 |
|---|---|---|
| 物理层 | 差分双线 | 差分双线 |
| 多主 | 是(仲裁) | 通常主从 |
| 报文寻址 | 报文 ID(非节点地址) | 节点地址 |
| 自动仲裁 | 是(非破坏性) | 否(需协议处理冲突) |
| 错误处理 | 完善(错误帧+离线保护) | 依赖上层协议 |
| 典型场景 | 汽车/工业控制 | 工业总线(Modbus) |
九、常见坑与排查
| 问题 | 可能原因 | 排查 |
|---|---|---|
| 完全无通信 | 波特率/位时序不一致 | 统一 TQ 配置,核对采样点 |
| 偶发错误帧 | 终端电阻缺失、线长、干扰 | 两端加 120Ω 终端电阻 |
| 单节点收发正常、多节点乱 | 某节点位时序偏差大 | 逐一核对各节点时序配置 |
| 节点离线 | 错误计数超限 | 查是否频繁错误,恢复后复位 |
| 终端电阻 | 高速总线必须 2 个 120Ω | 总线两端各一个 |