引言
CAN(Controller Area Network)由博世在 1986 年推出,至今仍是车载网络绝对的主力。它的设计目标很朴素:让几十个 ECU 用两根线就能可靠通信,且安全关键的报文永远优先。这个「非破坏性仲裁」机制是 CAN 的灵魂——ID 越小优先级越高,仲裁失败的一方自动退让,不会破坏正在传输的高优先级帧。
CAN 的工程难点不在协议本身(协议相当简单),而在「时序」与「故障定位」。位定时参数配错会让采样点落在信号边沿上,表现为偶发错误帧;终端电阻缺失会让反射干扰通信;总线负载过高会让低优先级报文延迟激增。这些问题在台架上很难复现,往往到实车才暴露。
本文按「物理层 → 帧格式 → 仲裁 → 位定时 → 错误处理 → DBC → SocketCAN → 网络管理 → 负载分析」展开,每节给出具体参数与可运行的代码。读完你应当能独立设计一个 CAN 网段、配置位定时、用 SocketCAN 收发报文,并在总线出问题时快速定位。
目录
- CAN 物理层与差分信号
- 帧格式:标准帧与扩展帧
- 非破坏性仲裁机制
- 位定时与采样点计算
- 错误处理与总线关闭
- 报文数据库 DBC
- Linux SocketCAN 编程
- CAN 网络管理 NM
- 总线负载与延迟分析
1. CAN 物理层与差分信号
CAN 用一对双绞线传输差分信号,显性位(dominant,逻辑 0)拉低 CAN_H/CAN_L 之间的电压差,隐性位(recessive,逻辑 1)让电压差接近 0:
ISO 11898-2 高速 CAN 电平(5V 收发器):
显性位(0):CAN_H = 3.5V,CAN_L = 1.5V,差值 2.0V
隐性位(1):CAN_H = 2.5V,CAN_L = 2.5V,差值 0V
阈值:> 0.9V 判显性,< 0.5V 判隐性
关键:显性位「压倒」隐性位
两个节点同时发,一个发显性一个发隐性 → 总线呈显性
这是仲裁的物理基础
终端电阻:
总线两端各 120Ω,并联后 60Ω
作用:消除信号反射
测量方法:断电测 CAN_H-CAN_L 电阻 ≈ 60Ω
若为 120Ω → 只接了一个终端电阻
若为 40Ω → 接了三个
常见故障:终端电阻缺失或过多会让信号反射,示波器上表现为边沿振铃,低速时可能正常,高速或长线时错误率飙升。总线拓扑要避免星型分支,分支长度一般 < 0.3 m。
2. 帧格式:标准帧与扩展帧
CAN 有两种帧格式,区别在 ID 位宽:
标准帧(CAN 2.0A):11 位 ID,ID 范围 0x000~0x7FF
扩展帧(CAN 2.0B):29 位 ID,ID 范围 0x00000000~0x1FFFFFFF
数据帧结构(标准帧):
SOF(1) ID(11) RTR(1) IDE(1) r0(1) DLC(4) Data(0~8) CRC(15) ACK(2) EOF(7)
起始 标识 远程帧 标识扩展 保留 长度 数据 校验 应答 结束
远程帧(RTR=1):
无数据段,用于「请求」某个 ID 的数据
实际项目中很少用,多数车厂禁用
位填充(bit stuffing):CAN 在连续 5 个相同电平后强制插入一个相反电平,接收端自动去除。这保证了信号有足够跳变供时钟同步,但也意味着最坏情况下数据段会膨胀约 20%。计算帧时长时必须考虑。
帧总长计算(标准帧,8 字节数据):
理论位(不含填充):
1 + 11 + 1 + 1 + 1 + 4 + 64 + 15 + 2 + 7 = 107 位
加填充最坏情况:
约 107 × 1.2 ≈ 128 位
500 kbps 下单帧最坏耗时:
128 / 500000 ≈ 256 us
3. 非破坏性仲裁机制
仲裁是 CAN 最优雅的设计。多个节点同时发送时,逐位比较 ID,发隐性位却检测到显性位的节点立即退出:
两个节点同时发:
节点 A:ID = 0x100 (0001 0000 0000)
节点 B:ID = 0x200 (0010 0000 0000)
逐位比较:
bit1: A=0, B=0 → 都是显性,继续
bit2: A=0, B=0 → 继续
bit3: A=0, B=1 → A 发显性,B 发隐性
总线呈显性(显性压倒隐性)
B 检测到「自己发隐性但总线是显性」→ B 退出
A 继续发送,B 等待下次空闲
结果:ID 小的赢,且不破坏数据
因此 CAN ID 本身就是优先级。设计 DBC 时必须按实时性要求分配 ID:安全关键报文(刹车、转向)用最小 ID,舒适性报文(空调、座椅)用大 ID。一个常见的分段约定:
| ID 范围 | 用途 | 优先级 |
|---|---|---|
| 0x000~0x0FF | 安全关键(制动、转向) | 最高 |
| 0x100~0x2FF | 动力总成(电机、电池) | 高 |
| 0x300~0x4FF | 车身控制 | 中 |
| 0x500~0x6FF | 诊断与网络管理 | 低 |
| 0x700~0x7FF | 诊断响应 | 最低 |
4. 位定时与采样点计算
位定时是 CAN 最容易配错的地方。一个位时间被分成若干时间份额(TQ),采样点在位时间的某个比例处:
位时间 = SYNC_SEG + PROP_SEG + PHASE_SEG1 + PHASE_SEG2
通常:PROP_SEG + PHASE_SEG1 = 采样点之前
PHASE_SEG2 = 采样点之后
采样点位置 = (SYNC_SEG + PROP_SEG + PHASE_SEG1) / 总 TQ 数
行业经验值:
500 kbps:采样点 75%~87.5%(常用 80%)
250 kbps:采样点 80%~87.5%
125 kbps:采样点 87.5%
125 kbps 以下:采样点 87.5%
计算示例(500 kbps,8 MHz 时钟,16 TQ):
位时间 = 16 TQ,每 TQ = 1/8MHz = 125 ns
位时间总长 = 16 × 125 ns = 2 us → 500 kbps ✓
采样点 80% → 第 12.8 个 TQ,取 SYNC=1, PROP=5, PS1=6, PS2=4
采样点 = (1+5+6)/16 = 75%
或 SYNC=1, PROP=6, PS1=6, PS2=3 → 采样点 = 13/16 = 81.25%
SJW(同步跳转宽度)通常设为 1~4 TQ,用于吸收振荡器误差。CAN 允许节点间时钟误差累计约 1.58%(取决于位定时配置),所以晶振精度要求通常 ±0.5% 或更好。用陶瓷谐振器(±0.5%~1%)时要特别小心。
配错采样点的典型症状:台架(短线)正常,实车(长线,传播延迟大)出现错误帧,因为采样点落在了信号边沿附近。
5. 错误处理与总线关闭
CAN 有五类错误,节点通过发送错误帧来破坏当前传输:
位错误:发的位与读回的位不一致(仲裁区除外)
填充错误:连续 6 个相同电平
CRC 错误:校验不通过
格式错误:固定格式位不合法
应答错误:发送方没收到 ACK
错误计数器(每个节点独立):
TEC(发送错误计数)、REC(接收错误计数)
错误 +8(发送)/+1(接收),成功 -1
状态机:
错误主动(Error Active):TEC/REC < 128,正常发错误帧
错误被动(Error Passive):TEC 或 REC > 127,只能发被动错误标志
总线关闭(Bus Off):TEC > 255,节点自动脱离总线
总线关闭恢复:
快恢复:连续检测 128 次 11 个隐性位后恢复(易反复)
慢恢复:等待一段时间(如 100 ms)再尝试
工程建议:慢恢复 + 限制次数(如 5 次后永久离线)
Bus Off 是现场最常见的故障。原因通常是:节点自身收发器故障、位定时不匹配、或总线短路。诊断时先看是单节点反复 Bus Off(节点问题)还是全总线出错(物理层问题)。
6. 报文数据库 DBC
DBC 是 CAN 的「接口契约」,描述每个报文的 ID、周期、信号布局:
DBC 文件核心元素:
BO_ <id> <name>: <dlc> <transmitter>
SG_ <signal_name> : <start>|<len>@<byteorder><sign> (<factor>,<offset>) [<min>|<max>] "<unit>" <receivers>
示例:
BO_ 256 EngineData: 8 ECU_Engine
SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8191.875] "rpm" ECU_Cluster,ECU_TCU
SG_ EngineTemp : 16|8@1+ (1,-40) [-40|215] "degC" ECU_Cluster
字节序是 DBC 最坑的地方:
- Intel(小端):
@1+,信号从起始位向高位字节扩展,最常见。 - Motorola(大端):
@0+,信号从起始位向低位字节扩展,多字节信号必须注意。
信号布局要避免跨字节不对齐导致解析困难,但为节省带宽又常被压缩。工具链(CANdb++、Vector)会自动生成解析代码,手写解析器时务必用已知报文验证字节序。
7. Linux SocketCAN 编程
Linux 把 CAN 抽象成网络设备,用 socket API 收发,是嵌入式调试的利器:
# 加载驱动并配置比特率
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
# 真实硬件(SocketCAN 支持多款 USB-CAN 适配器)
sudo ip link set can0 type can bitrate 500000 sample-point 0.8
sudo ip link set up can0
# 抓包
candump can0
# 发送单帧(ID 0x123,数据 8 字节)
cansend can0 123#1122334455667788
# 回放日志
canplayer -I log.txt
# 查看统计(错误计数、Bus Off 次数)
ip -details -statistics link show can0
用 Python 的 python-can 库编程:
import can
bus = can.interface.Bus(channel='can0', bustype='socketcan',
bitrate=500000)
msg = can.Message(arbitration_id=0x123, data=[0x11, 0x22],
is_extended_id=False)
bus.send(msg)
# 周期性发送
task = bus.send_periodic(msg, 0.01) # 10 ms
task.stop()
# 接收
for m in bus:
print(hex(m.arbitration_id), m.data.hex())
SocketCAN 的错误帧会作为特殊报文出现在总线上,可用来捕获 Bus Off 事件并做统计。
8. CAN 网络管理 NM
整车有几十个 ECU,不能永远全功率运行。网络管理(NM)协调「谁可以睡、谁必须醒」:
AUTOSAR CanNm 机制:
每个节点周期发送 NM 报文(含源节点 ID)
收到任何 NM 报文 → 保持网络唤醒,重置定时器
一段时间无 NM 报文 → 进入 Prepare Bus-Sleep
再等一段时间 → Bus-Sleep(收发器进入低功耗)
状态机:
Bus-Sleep → Network Requested → Repeat Message
→ Normal Operation → Ready Sleep
→ Prepare Bus-Sleep → Bus-Sleep
关键参数:
NM 报文周期:通常 100 ms 或 500 ms
NM 超时:通常 2~6 秒
Wait Bus-Sleep 时间:通常 2 秒
网络管理的目标是让整车在熄火后把静态电流降到 < 1 mA(长时间停放不亏电)。设计时要注意「网络唤醒源」——某个 ECU 持续发 NM 报文会让整车无法休眠,是常见的亏电投诉原因。
9. 总线负载与延迟分析
总线负载直接决定通信质量,必须做最坏情况分析:
负载率 = 单位时间内所有报文的位时间之和 / 单位时间
一个 8 字节标准帧的位时间(含填充)约 130 位:
500 kbps 下约 260 us
假设网段有 20 个报文,各 10 ms 周期:
每 10 ms 有 20 帧 × 260 us = 5.2 ms
负载率 = 5.2 / 10 = 52% ← 偏高,需优化
工程阈值:
稳态负载 < 50%:安全
50%~70%:需关注,突发时可能超时
> 70%:危险,低优先级报文延迟激增
> 90%:几乎不可用
最坏情况响应时间分析(RTA):
对一个低优先级报文,考虑所有高优先级报文的阻塞
公式:R = 阻塞时间 + Σ(高优先级报文传输时间 × 干扰次数)
优化手段:合并报文(把多个信号塞进一个 8 字节帧)、提高波特率(500 kbps → CAN FD 2 Mbps)、拆分网段(把高负载功能独立成网段)。CAN FD 的引入正是为了缓解负载压力,详见 CAN FD 与车载以太网 。
权衡取舍
| 决策点 | 选项 A | 选项 B | 建议 |
|---|---|---|---|
| 波特率 | 500 kbps | 250 kbps | 动力/底盘用 500k,车身用 125~250k |
| 帧格式 | 标准帧 | 扩展帧 | 车厂私有 ID 用扩展帧,标准 ID 留给通用 |
| 采样点 | 75% | 87.5% | 高速(500k+)用 80%,低速用 87.5% |
| 终端电阻 | 双端 120Ω | 单端 | 必须双端,分支 < 0.3 m |
| Bus Off 恢复 | 快恢复 | 慢恢复 | 慢恢复 + 次数限制,避免反复冲击 |
| 错误处理 | 应用层重试 | 硬件自动重传 | 安全关键报文禁用自动重传,由应用确认 |
常见坑清单
- 采样点配置不一致:不同 ECU 采样点差异过大,实车偶发错误帧,全网上限须统一。
- 终端电阻缺失或过多:示波器见振铃,长线时通信不稳,测 CAN_H-CAN_L 应为 60Ω。
- 星型拓扑:分支过长引起反射,必须总线型,分支 < 0.3 m。
- 忽视位填充开销:按理论 107 位算时序,实际约 128 位,最坏延迟算错。
- Bus Off 不恢复:节点永久离线,必须实现恢复状态机并做次数限制。
- 远程帧滥用:多数车厂禁用 RTR,用周期报文替代请求-应答。
- DBC 字节序搞错:Motorola 信号解析全错,务必用已知报文验证。
- 负载算成平均值:突发时刻才是瓶颈,需按最坏情况做 RTA。
- 晶振精度不足:用陶瓷谐振器(±1%)在长总线上易失步,应选 ±0.5% 以内晶振。
- NM 报文冲突:多节点抢占唤醒导致无法休眠,需统一 NM 协议与超时。
小结
CAN 的可靠性来自两个设计:差分信号抗干扰、非破坏性仲裁保证优先级。工程上的所有问题几乎都能归到三类——物理层(终端电阻、拓扑、线长)、时序(位定时、采样点、负载)、以及故障处理(错误计数、Bus Off 恢复)。掌握这三类,CAN 调试就不再是玄学。
入门实践建议:用 SocketCAN + vcan 先在本地跑通收发与周期发送,再用 USB-CAN 适配器接真实网段,用 candump 抓包对照 DBC 验证解析。若你要做车载以太网,车载以太网与 DoIP 是下一步;网络诊断与刷写则见 诊断协议 UDS 与 OBD 。
CAN 报文的时间同步对分布式系统至关重要,若你的场景需要跨网段统一时基,可参考 网络时间同步 PTP ;Linux 侧的 SocketCAN 驱动开发思路与 Linux 网络子系统一脉相承。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。