CAN 总线与车载网络通信

从物理层差分信号到仲裁机制与位定时,系统讲解 CAN 总线通信:帧格式与优先级、采样点计算、错误计数与总线关闭恢复、DBC 数据库设计、Linux SocketCAN 编程、网络管理与总线负载分析,并给出抖动控制与故障排查的实战参数取值和优化建议。

引言

CAN(Controller Area Network)由博世在 1986 年推出,至今仍是车载网络绝对的主力。它的设计目标很朴素:让几十个 ECU 用两根线就能可靠通信,且安全关键的报文永远优先。这个「非破坏性仲裁」机制是 CAN 的灵魂——ID 越小优先级越高,仲裁失败的一方自动退让,不会破坏正在传输的高优先级帧。

CAN 的工程难点不在协议本身(协议相当简单),而在「时序」与「故障定位」。位定时参数配错会让采样点落在信号边沿上,表现为偶发错误帧;终端电阻缺失会让反射干扰通信;总线负载过高会让低优先级报文延迟激增。这些问题在台架上很难复现,往往到实车才暴露。

本文按「物理层 → 帧格式 → 仲裁 → 位定时 → 错误处理 → DBC → SocketCAN → 网络管理 → 负载分析」展开,每节给出具体参数与可运行的代码。读完你应当能独立设计一个 CAN 网段、配置位定时、用 SocketCAN 收发报文,并在总线出问题时快速定位。

目录

  1. CAN 物理层与差分信号
  2. 帧格式:标准帧与扩展帧
  3. 非破坏性仲裁机制
  4. 位定时与采样点计算
  5. 错误处理与总线关闭
  6. 报文数据库 DBC
  7. Linux SocketCAN 编程
  8. CAN 网络管理 NM
  9. 总线负载与延迟分析

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 kbps250 kbps动力/底盘用 500k,车身用 125~250k
帧格式标准帧扩展帧车厂私有 ID 用扩展帧,标准 ID 留给通用
采样点75%87.5%高速(500k+)用 80%,低速用 87.5%
终端电阻双端 120Ω单端必须双端,分支 < 0.3 m
Bus Off 恢复快恢复慢恢复慢恢复 + 次数限制,避免反复冲击
错误处理应用层重试硬件自动重传安全关键报文禁用自动重传,由应用确认

常见坑清单

  1. 采样点配置不一致:不同 ECU 采样点差异过大,实车偶发错误帧,全网上限须统一。
  2. 终端电阻缺失或过多:示波器见振铃,长线时通信不稳,测 CAN_H-CAN_L 应为 60Ω。
  3. 星型拓扑:分支过长引起反射,必须总线型,分支 < 0.3 m。
  4. 忽视位填充开销:按理论 107 位算时序,实际约 128 位,最坏延迟算错。
  5. Bus Off 不恢复:节点永久离线,必须实现恢复状态机并做次数限制。
  6. 远程帧滥用:多数车厂禁用 RTR,用周期报文替代请求-应答。
  7. DBC 字节序搞错:Motorola 信号解析全错,务必用已知报文验证。
  8. 负载算成平均值:突发时刻才是瓶颈,需按最坏情况做 RTA。
  9. 晶振精度不足:用陶瓷谐振器(±1%)在长总线上易失步,应选 ±0.5% 以内晶振。
  10. NM 报文冲突:多节点抢占唤醒导致无法休眠,需统一 NM 协议与超时。

小结

CAN 的可靠性来自两个设计:差分信号抗干扰、非破坏性仲裁保证优先级。工程上的所有问题几乎都能归到三类——物理层(终端电阻、拓扑、线长)、时序(位定时、采样点、负载)、以及故障处理(错误计数、Bus Off 恢复)。掌握这三类,CAN 调试就不再是玄学。

入门实践建议:用 SocketCAN + vcan 先在本地跑通收发与周期发送,再用 USB-CAN 适配器接真实网段,用 candump 抓包对照 DBC 验证解析。若你要做车载以太网,车载以太网与 DoIP 是下一步;网络诊断与刷写则见 诊断协议 UDS 与 OBD 。

CAN 报文的时间同步对分布式系统至关重要,若你的场景需要跨网段统一时基,可参考 网络时间同步 PTP ;Linux 侧的 SocketCAN 驱动开发思路与 Linux 网络子系统一脉相承。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「车载软件」更多文章

  1. 三电与动力总成软件
  2. 车载软件测试与 HIL 台架
  3. ADAS 决策规划与横纵向控制