跳转至

第 9 章 工具链与抓包排障

工具链把前八章的机制落到"能观测、能定位故障"的最后一公里:SocketCAN 内核栈与 can-utils 命令行工具集、python-can 用户态库与 backend 差异、DBC 信号级解析、逻辑分析仪与示波器看位、总线负载率与丢包的算式、常见故障模式的定位流程。

Modbus 侧工具(pymodbus / diagslave / Wireshark)在第 6–7 章已内嵌示例,这里以 CAN 家族为主。分工上,这一层提供驱动开发之下的观测与调试手段,ros2_control 在其上做控制抽象,两者接口的衔接点见 ROS2-note 第 6 章。

按层排查,先物理后应用:

flowchart TD
    phy["物理层:万用表 / 示波器<br/>终端电阻 / 电平"] --> link["链路层:candump、错误计数"]
    link --> proto["协议层:candump -L、cantools 解码"]
    proto --> app["应用层:DS 402 状态 / Modbus 寄存器"]

1. SocketCAN 栈与内核侧

SocketCAN 是 Linux 内核对 CAN 的原生支持(2.6.25 引入),把 CAN 接口暴露成 AF_CAN 套接字,配套网络子系统工具(ip link、ss)直接可用。整套栈分三层:

用户态:应用(自写 / ros2_control)→ python-can / 厂商 SDK → libc
内核态:SocketCAN core → CAN 设备驱动(peak_usb / kvaser_usb / mcp251x / vcan 等)
硬件:  USB-CAN 适配器 / PCIe CAN 卡 / SoC 内置 CAN 控制器(如 i.MX、TI AM335x、STM32)

/sys/class/net/can0/ 下的属性与 ip -details -statistics link show can0 反映运行期状态与错误计数(berr-cnt、tx/rx error)。

1.1 vcan 虚拟总线与调试

vcan(Virtual CAN)是内核自带的虚拟 CAN 接口,无需任何硬件即可收发。用途:单元测试、CI、无硬件时的协议栈联调。

## 运行环境:Ubuntu 22.04(Linux 5.15),can-utils 20210224
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

## 终端 A
candump vcan0
## 终端 B
cansend vcan0 123##4deadbeef       # 与第 3 章「最小 SocketCAN 收发与错误观测」同款

vcan 默认 MTU 为 CAN_MTU(16 字节),只能承载经典帧;sudo ip link set vcan0 mtu 72 把 MTU 提到 CANFD_MTU,FD 帧即可完整跨进程收发。建议:CI 里跑 python-can 用例时用 vcan,避免绑定特定 USB-CAN 硬件。

1.2 slcand 与串口型 CAN

真实硬件接入有两条常见路径:

  • 原生驱动:peak_usb、kvaser_usb、gs_usb(CANable 等)、mcp251x(SPI-to-CAN 芯片)等内核模块直接暴露 canX。插上 USB-CAN 适配器后 dmesg | tail 会看到新接口注册与重命名为 can0 的日志。
  • 串口转换层:UART 上的 CAN 芯片(SLCAN 协议固件)通过 slcand(Serial Line CAN daemon)把串口字符流转成 CAN 帧并挂成 canX;也可用 slcan_attach 手动挂载 tty line discipline。

slcand 最小接入:

## 运行环境:Ubuntu 22.04,can-utils 20210224;假设硬件通过 /dev/ttyUSB0 出 UART CAN 帧
sudo slcand -o -c -s6 /dev/ttyUSB0 can0     # -s6 = 500 kbps,见 slcan 波特码表
sudo ip link set can0 up

slcand 的 -s 参数用波特码(0=10k、1=25k、2=50k、3=100k、4=125k、5=250k、6=500k、7=800k、8=1M);-s6 = 500 kbps。

SocketCAN 通过 netlink 下发位时序;ip 命令行同时支持"直接给波特率"与"给四段参数"两种模式:

## 简单模式:只给 bitrate,让内核驱动自己算位时序
sudo ip link set can0 type can bitrate 500000

## FD 双速率:仲裁 500k、数据 2M
sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on

## 精细模式:给完整位时序段(与第 3 章「位时序与采样点」的 500 kbps 算例一致)
sudo ip link set can0 type can bitrate 500000 \
     sample-point 0.800 sjw 2 \
     tq 100 prop-seg 10 phase-seg1 5 phase-seg2 4

## 打开接口,观察实际生效参数
sudo ip link set can0 up
ip -details link show can0

ip -details 输出包含 bitrate 500000 sample-point 0.800、tq 100、prop-seg 10 phase-seg1 5 phase-seg2 4 sjw 2——与手算对照(1 + 10 + 5 + 4 = 20 tq,20 × 100 ns = 2 μs,即 500 kbps)。接口 down 才能改位时序:ip link set can0 down 后再 type can 配置。

2. can-utils 工具集

can-utils(linux-can 项目)是 SocketCAN 的配套工具集,全部走 AF_CAN 套接字。日常最常用六个:

命令 用途 关键选项
candump 监听并按行打印帧 -L(log 格式,可回放)、-t tz(时间戳)、-x(扩展帧格式)、can0,123:7FF(ID 过滤)
cansend 单帧发送 can0 123#DEADBEEF(经典)、can0 123##1DEADBEEF(FD,## 后第一位是 BRS)
cangen 生成随机帧压总线 -g 最小间隔、-i 随机 FD 使能;负载测试常用
canplayer 回放 candump -L 日志 -I log、vcan0=can0(接口重映射)、-t(忽略时间戳立即发)
cansniffer 交互式监视字节变化 高亮"变化的字节",抓协议差异好用
cansequence 发送递增字节序列、校验错乱 用于压力与位错诊断

组合示例:抓一条 CAN FD 帧日志并回放。

## 录制(终端 A)
candump -L can0 > fd_session.log

## 回放(终端 B,把日志里的 can0 帧发到 vcan0;-t 忽略原始时间戳立即发送)
canplayer -I fd_session.log vcan0=can0 -t

日志行示例:

(1698985000.123456) can0 123##1DEADBEEFCAFEBABE

括号内是 Unix 秒.微秒;##1 表示 FD + BRS;后面是数据(本例 10 字节)。第 8 章「on-the-fly 处理与逻辑寻址」提到的逻辑寻址,用 cansniffer 挂上总线即可直观看谁在什么周期改哪些字节。

3. python-can 与 backend

python-can(can.readthedocs.io)是 Python 生态最常用的 CAN 收发库,抽象出统一的 Bus / Message 接口,底下支持多种 backend。

3.1 Bus / Message 抽象

## 运行环境:Ubuntu 22.04(Python 3.10),python-can 4.3
import can

## Bus 打开一个接口;socketcan 的位速率由 ip link 预先配置,Bus 只负责套接字
## 前置命令:sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on
bus = can.Bus(interface="socketcan", channel="can0",
              receive_own_messages=False, fd=True)

## 构造消息:Message 覆盖经典与 FD 属性
msg = can.Message(arbitration_id=0x123, is_extended_id=False,
                  is_fd=True, bitrate_switch=True,
                  data=bytes([0xDE, 0xAD, 0xBE, 0xEF]))
bus.send(msg)

## 同步接收,超时 1 秒
rx = bus.recv(timeout=1.0)
if rx is not None:
    print(f"{rx.timestamp:.6f} {rx.arbitration_id:03X} "
          f"dlc={len(rx.data)} data={rx.data.hex()}")

bus.shutdown()

Message 关键属性:arbitration_id、is_extended_id(29 位 ID)、is_remote_frame(远程帧)、is_error_frame(错误帧)、is_fd、bitrate_switch、error_state_indicator、dlc、data。

3.2 backend 差异与 FD 支持

常用 backend 一览:

backend 依赖 FD 支持 平台 备注
socketcan Linux 内核原生 是 Linux 首选;无额外驱动
pcan PEAK 驱动 + libpcan 是 Linux / Windows PCAN-USB 系列
kvaser Kvaser CANlib 是 Linux / Windows Leaf / UsbCan 系列
ixxat IXXAT VCI 是 Windows 为主 支持 FD 的 SDK 版本
slcan 无(走 pyserial) 部分(取决于设备) 跨平台 CANable 等 UART CAN
virtual 同机自测(模拟总线) 是 跨平台 与 vcan 类似,纯 Python
candleLight candleLight 固件 是 跨平台 CANable 2.0 等
cantact CANtact 固件 是 跨平台 开源硬件方案

跨 backend 一致性不保证:位时序参数命名、FD 数据段长度上限、错误帧上报格式各实现略有差异。工程上优先用 SocketCAN(Linux)或厂商 SDK 直连,再考虑 python-can 抽象。

3.3 Logger / Listener 与异步

python-can 提供广播接口(Notifier + Listener)替代 recv() 阻塞循环:

## 运行环境:同上
import can

class PrintListener(can.Listener):
    # Listener 的回调方法是 on_message_received,不是 on_message
    def on_message_received(self, msg: can.Message):
        if not msg.is_remote_frame and not msg.is_error_frame:
            print(f"rx: 0x{msg.arbitration_id:03X} dlc={msg.dlc}")

bus = can.Bus(interface="socketcan", channel="vcan0")
notifier = can.Notifier(bus, [PrintListener()])
## ... 运行若干秒 ...
notifier.stop()
bus.shutdown()

can.Logger 是内置 Listener,把消息按 .asc / .blf / .csv 格式落盘;can.LogReader 反向读取。can.AsyncBufferedReader 提供 asyncio 接口,用于异步栈(ros2_canopen 等项目使用)。

4. DBC 文件与信号级解析

CAN 帧本身只有 ID 与 8/64 字节数据;怎么把字节拆成"发动机转速""制动压力"这类有物理意义的信号交给描述文件。汽车与传统工业生态主要用 DBC(Vector DBC Format)文件,AUTOSAR 用 ARXML。机器人 CANopen 场景一般用 EDS(电子数据表)+ 对象字典替代 DBC。

4.1 DBC 结构

一个最小 DBC 片段:

BO_ 256 EngineData 8: ECU
 SG_ RPM     : 0|16@1+ (0.1,0) [0|6500] "rpm" Dashboard
 SG_ Coolant : 16|8@1+ (1,-40) [-40|215] "degC" Dashboard
 SG_ OilTemp : 24|8@1+ (1,-40) [-40|215] "degC" Dashboard

CM_ BO_ 256 "Engine status broadcast at 100 Hz.";

字段解读:

  • BO_ <ID> <Name> <DLC>: <TransmitterNode>:报文定义。ID 256 = 0x100、8 字节、发送者 ECU。
  • SG_ <Signal> : <StartBit>|<Length>@<Order><Sign> (<Factor>,<Offset>) [<Min>|<Max>] "<Unit>" <Receivers>:信号定义。@1+ 小端无符号;@0+ 大端 Motorola。物理值 = 原始值 × Factor + Offset。
  • CM_:注释段,工具链展示用。

4.2 cantools 编解码

cantools(github.com/cantools/cantools)是 Python 生态事实标准:

## 运行环境:Ubuntu 22.04(Python 3.10),cantools 39.4.5
import cantools

db = cantools.database.load_file("vehicle.dbc")
msg_def = db.get_message_by_name("EngineData")

## 解码一帧原始字节(RPM=0x4E20,Coolant=0x82,OilTemp=0x77)
raw = bytes([0x20, 0x4E, 0x82, 0x77, 0x00, 0x00, 0x00, 0x00])
decoded = msg_def.decode(raw)
## 期望:{'RPM': 2000.0, 'Coolant': 90, 'OilTemp': 79}   # 按 Factor/Offset 换算

## 编码回字节
encoded = msg_def.encode({'RPM': 2000.0, 'Coolant': 90, 'OilTemp': 79})

cantools 命令行:cantools decode vehicle.dbc < canlog 可把 candump 输出直接翻成信号名,比裸字节直观。

5. 逻辑分析仪与示波器

位级调试绕不开看电平波形与位边界。

5.1 位流采样与协议解码

  • 逻辑分析仪(Saleae Logic 8 / 16、开源 DSView / PulseView + 兼容硬件):两通道接 CAN_H / CAN_L 或差分接收发器输出,采样率 ≥ 位速率 × 8(1 Mbps 需 ≥ 8 MSa/s、5 Mbps FD 需 ≥ 40 MSa/s),配合软件里的 CAN 协议解码器把位流转成帧。PulseView 内置 CAN / CAN FD 解码器,能识别 FDF / BRS / ESI 位。
  • 示波器:看边沿与幅度——显性/隐性的差分电压、共模漂移、终端电阻缺失导致的振铃;用眼图模式(很多示波器有 CAN 眼图模板)判断信号完整性是否满足 ISO 11898-2 一致性测试。

5.2 显性 / 隐性电平判读

ISO 11898-2 定义的差分电平(在 60 Ω 等效负载下,终端 120 Ω × 2 并联):

状态 CAN_H CAN_L 差分 \(V_{CAN\_H} - V_{CAN\_L}\)
显性(Dominant,逻辑 0) 约 3.5 V 约 1.5 V ≥ 1.5 V(门限以上)
隐性(Recessive,逻辑 1) 约 2.5 V 约 2.5 V ≤ 0.5 V(门限以下)

常见示波器异常与判读:

  • 差分幅度不到 1.5 V → 终端电阻偏大、线过长、驱动器老化。
  • 显性→隐性边沿振铃过冲 > 4 V → 终端不匹配,反射叠加。
  • CAN_H 与 CAN_L 共模在 2.5 V 上下对称漂移 → 正常;单边偏离 2.5 V 超过 ±0.5 V → 收发器或地参考异常。
  • 隐性状态下差分不为 0 → 存在偏置电阻误接(CAN 上不该有 RS-485 的偏置)。

6. 总线负载率与丢包

总线负载率(bus load)是单位时间里"总线上有帧的时间占比",与位速率、帧长、发送频率相关。设计总线前手算一次,避免上量后翻车。

6.1 单帧位时间预算

经典 CAN 一帧最大位数(含位填充):

  • SOF + 仲裁 + 控制 + 数据 + CRC + ACK + EOF + IFS(不含填充):标准帧 8 字节数据 ≈ 111 位;扩展帧 ≈ 130 位。
  • 位填充最坏增加约 20%(每 5 位插 1 位),设计上限取 约 130–160 位。

以 500 kbps 为例:一条 8 字节标准帧典型 约 230–260 μs。

CAN FD 一帧(BRS 打开、数据段 5 Mbps):仲裁段 500 kbps、数据段 5 Mbps;64 字节帧位时间 ≈ 仲裁与帧尾(约 70 位 @ 500k = 140 μs)+ 数据与 CRC(约 550 位 @ 5M = 110 μs)≈ 250 μs,是经典 CAN 8 字节的约 1 倍时间装 8 倍数据。

6.2 综合负载算式与经验上限

\[\text{bus load} = \frac{\sum_i (N_i \cdot T_i)}{\text{period}}\]

\(N_i\) = 类型 \(i\) 的帧每秒发送次数、\(T_i\) = 该类型单帧时间(含位填充余量)。

例:一台六轴协作臂的关节 CAN FD 总线按 1 Mbps 仲裁 / 5 Mbps 数据运行,控制周期 1 ms;每轴每周期各一帧 TPDO 与 RPDO,16 字节载荷单帧约 100 μs;SYNC 一帧约 20 μs:

\[\text{load} = \frac{6 \times (100 + 100) + 20}{1000} = 1.22 = 122\%\]

配置跑不动。把控制周期降到 500 Hz(2 ms)后为 61%;或每轴两个方向合并为一帧,1 kHz 下为 62%,均落在经验上限内。

经验上限:工程上建议总线负载 ≤ 50–70%,实时性要求高的段按 30–50% 设计——留出突发错误重发与诊断帧余量。> 80% 时仲裁冲突明显、高 ID 帧等待时间快速上升、错误惩罚可能触发 error-passive。

7. 典型故障模式与定位流程

现象 首查 常见定位手段
完全收不到帧 线是否接反(CAN_H ↔ CAN_L)、终端电阻是否缺失、接口是否 up ip -details link show can0 看 state ERROR PASSIVE / BUS-OFF;示波器差分看电平
收到错乱 ID Node-ID 冲突(同一 CANopen 域出现两个相同 Node-ID) candump 观察同一 ID 上数据交替;NMT 逐个上线
周期不达标(> 预期) 总线负载过高、优先级低帧饿死 cangen 打压力 + candump -t tz 看间隔;算负载
偶发 ERROR 行 位时序不匹配、终端电阻不匹配、地环路 示波器看位边沿;ip -details -statistics link show can0 看 berr-cnt 增速
频繁进 bus-off 驱动器"卡在显性"(发送器故障)、波特率不一致 隔离可疑节点、逐个上总线;观察 TEC / REC 增速
Modbus 从站无响应 波特率 / 数据位 / 校验位不匹配、地址错、CRC 错 用串口助手直接看原始字节;打开 pymodbus 日志(logging.basicConfig(level=logging.DEBUG))看 ADU
EtherCAT 从站进 INIT / PREOP 卡住 主站周期不足以覆盖全线传输时延、DC 未收敛 IgH / TwinCAT 主站日志、Wireshark 抓 0x88A4 帧、看 AL Status Code

定位通用步骤,自下而上五层:物理层先用示波器 / 万用表确认差分电平、终端电阻与共模范围;再用 candump / tcpdump / EtherCAT 主站日志证明"帧在流";接着是时序与负载,用 cansniffer、逻辑分析仪与负载算式对齐设计预算;错误计数查 ip -details -statistics link 与 python-can 的 is_error_frame,TEC / REC 的增速区分故障出在发送侧还是接收侧;最后核对位时序、Node-ID / Slave-Addr、波特率与字序——两端不一致是绝大多数"看似随机"故障的根因。

8. 要点

  • SocketCAN 是 Linux 原生方案:ip link 配置位时序、vcan 做无硬件调试、slcand 桥接 UART 型 CAN;ip -details 是运行期状态的第一观测点。
  • can-utils 六个高频命令:candump 抓包、cansend 发单帧、cangen 压力、canplayer 回放、cansniffer 交互看字节变化、cansequence 校验位错——覆盖日常 95% 场景。
  • python-can 抽象 Bus / Message / Listener,backend 差异不保证:SocketCAN 首选;跨厂商 backend 的 FD 支持与位时序命名有差异,位速率仍由各平台驱动侧配置。
  • DBC 把字节拆成信号:BO_ 定报文、SG_ 定信号;cantools 编解码 + candump 管道可直接看物理量;机器人 CANopen 场景改用 EDS + 对象字典。
  • 负载率算式落到纸面:\(load = \sum N_i T_i / \text{period}\),经验上限 50–70%;超出时仲裁延迟非线性上升、error-passive 频繁触发。
  • 定位从物理到协议:示波器看电平 → candump 看帧流 → cansniffer 看周期与负载 → 读错误计数器与状态机 → 核对两端配置。绝大多数"随机故障"最终落在配置一致性上。

参考文献与延伸阅读

  • can-utils. 命令行工具仓库. 仓库
  • Linux Kernel. SocketCAN 文档. 文档
  • Wireshark. 文档. 文档