证据边界

公开项目当前主要提供 vcan 与故障注入层面的验证,不等同于真实七轴机械臂、物理 CAN 总线或量产环境。本文中的示波器记录、故障频率和修复后数字没有随公开仓库提供原始数据,应作为排查案例而非公开可复现结论。

复现偶发卡顿检查物理层分析错误帧与计数器检查 SocketCAN 接收路径核对业务响应时序
物理层终端电阻、线长、波形和总线速率。协议层ACK bit、仲裁、错误帧、error-passive 与 bus-off。内核层控制器 FIFO、NAPI/offload 队列、socket 缓冲和驱动统计。应用层发送节奏、command ID、response 超时和 watchdog。MCU业务 response 突发可能形成接收压力,但由 CAN 仲裁串行发送。验证故障注入与真实总线测量必须分开标注。

一、问题现场

七轴机械臂使用CAN总线进行关节控制通信,生产环境出现了偶发性故障:

系统架构

1
2
3
4
5
主控制器(ROS 2
↓ SocketCAN
CAN总线(500kbps)

7个关节控制器(MCU)

故障现象

  • 机械臂运行中偶发卡顿(约1-2次/小时)
  • CAN通信无规律丢包
  • 高负载场景下更频繁
  • 示波器看不出问题

日志片段

1
2
3
4
5
[INFO] Joint1: cmd=100, response=OK
[INFO] Joint2: cmd=150, response=OK
[ERROR] Joint3: cmd=200, timeout (no business response)
[INFO] Joint4: cmd=120, response=OK
[WARN] Watchdog triggered, emergency stop

影响

  • 装配任务失败率15%
  • 客户投诉产品不稳定
  • 安全风险(紧急停止触发)

二、问题定位(五层排查法)

第一层:硬件信号质量检查

假设:CAN总线信号完整性问题

检查1:示波器抓取CAN信号

1
2
3
4
5
6
7
8
# 测量点:CAN_H和CAN_L差分信号
# 工具:Tektronix MDO3024示波器

测量结果:
- 差分电压:2.5V(符合ISO 11898标准)
- 上升/下降时间:<100ns(正常)
- 无振铃、过冲
- 波形干净

结论:当前采样窗口没有观察到明显波形异常,但这只能降低物理层假设的优先级;error frame 若持续出现,仍要在故障时刻触发并回看波形。


检查2:终端电阻验证

1
2
3
4
# CAN总线两端应该有120Ω终端电阻
# 测量总阻抗:
万用表测量CAN_H和CAN_L之间:60Ω ✅
计算:120Ω || 120Ω = 60Ω(正确)

结论:静态阻值符合两个 120 Ω 终端并联的预期;还需确认终端位置、支线拓扑和上电状态下的动态波形。


检查3:电缆长度与波特率

1
2
3
4
# 记录实际参数:
# bitrate:500 kbit/s
# 主干与最长支线长度
# 收发器传播延迟、采样点和时钟误差

CAN 可用线长不是只由 bitrate 决定,还受收发器、线缆、拓扑、采样点和时钟误差影响。这里不再引用“500 kbit/s 必然可达 100 m”作为排除依据。


第二层:CAN帧分析

工具:CANalyzer + Kvaser USB接口

抓包配置

1
2
3
波特率:500kbps
过滤器:接收所有帧(ID 0x000-0x7FF)
触发条件:Error Frame

关键发现

1
2
3
4
5
6
7
时间戳        CAN ID   数据                类型
----------------------------------------------------
10.123456 0x101 [00 64 00 00 00 00 00 00] Data
10.124123 0x201 [00 96 00 00 00 00 00 00] Data
10.125789 0x301 [00 C8 00 00 00 00 00 00] Data
10.126234 ERROR - Error Frame ← 错误帧!
10.127456 0x301 [00 C8 00 00 00 00 00 00] Data (重传)

Error Frame详情

1
2
3
4
5
错误类型:Bit Error
错误位置:分析工具标记在 ACK slot 附近
发送节点:Joint3(ID 0x301)

可能含义:发送节点在 ACK slot 未检测到 dominant bit,或者在相邻位观察到其他位错误。必须结合发送/接收错误计数器、错误状态和物理层波形确认;不能仅凭一行解码就断言具体根因。

这里的 ACK slot 是 CAN 链路层确认位:任何正确接收该帧的节点都可以拉低 ACK bit。它与应用协议中的“命令执行完成 response 帧”是两回事。后文统一把 0x200-0x2FF 一类业务消息称为 response,避免混淆。

统计数据(1小时抓包):

1
2
3
4
总帧数:1,200,000
错误帧:127 (0.01%)
重传成功:124 (97.6%)
重传失败:3 (2.4%) ← 导致超时

第三层:SocketCAN驱动分析

查看CAN接口统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ip -s -d link show can0

# 输出:
can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT
link/can
can state ERROR-ACTIVE restart-ms 100
bitrate 500000 sample-point 0.875
tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1

RX: packets errs drop ovr mcast
1234567 127 23 0 0

TX: packets errs drop ovr mcast
1234560 15 0 0 0

错误统计:
- RX errors: 127
- RX dropped: 23
- TX errors: 15

这些计数说明接收错误、发送错误和接收丢弃都发生过,但不同驱动如何映射 rx_errorsrx_dropped 需要查对应驱动实现。它们不能仅凭名称就一一对应 CANalyzer 中的 error frame 数量。


查看驱动日志

1
2
3
4
5
6
dmesg | grep can0

# 输出:
[12345.678] can0: RX overflow, dropping frame
[12389.123] can0: RX overflow, dropping frame
[12456.789] can0: RX overflow, dropping frame

如果驱动明确报告硬件 FIFO overrun,才能确认控制器来不及搬走帧;如果只是 socket 接收缓冲溢出,则应检查应用消费速度和 SO_RCVBUF。两者修复位置不同,不能都归结为“驱动队列太小”。


第四层:定时与Guard机制

查看发送时序

1
2
3
4
# ROS 2控制节点(Python)
for joint_id in range(1, 8):
send_can_frame(joint_id, command)
# ❌ 没有间隔!立即发送下一帧

时序图

1
2
3
4
5
6
7
8
主控发送:
T0: [Joint1] [Joint2] [Joint3] [Joint4] [Joint5] [Joint6] [Joint7]
↓ ↓ ↓ ↓ ↓ ↓ ↓
随后:多个关节几乎同时准备发送业务 response
CAN 按标识符逐帧仲裁,低优先级帧等待后重试,不发生以太网式碰撞

主控 RX FIFO: [RESP1] [RESP2] [RESP3]
后续帧继续串行到达;若驱动/NAPI/应用消费不及时,才可能发生 overrun

待验证假设

  • 7个命令连续发送,无间隔
  • 7个控制器形成 response burst
  • CAN 仲裁把 burst 串行化,但高优先级 response 可能让低优先级帧等待
  • 主控控制器 FIFO、内核接收队列或应用 socket 中任一层消费不足,都可能表现为业务 response 超时

Watchdog检测机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 关节控制器MCU代码
void can_rx_handler(can_frame_t *frame) {
if (frame->id == JOINT_ID) {
// 收到命令,执行并发送业务 response
execute_command(frame->data);
send_response();

// 重置watchdog
watchdog_reset(); // 100ms超时
}
}

// Watchdog超时处理
void watchdog_timeout_handler(void) {
// 100ms内未收到命令 -> 紧急停止
emergency_stop();
set_safe_state();
}

问题

  • Watchdog超时100ms
  • CAN丢包导致某个关节>100ms未收到命令
  • 触发紧急停止

第五层:SocketCAN内核源码审查

接收路径示意

1
2
3
4
5
CAN controller RX FIFO
-> driver IRQ/NAPI or CAN RX offload queue
-> SocketCAN receive path
-> per-socket receive buffer
-> application recvmsg()/read()

Linux 的 CAN RX offload 队列上限由具体控制器/驱动初始化,不是一个可假定为 10 的通用全局参数;ip link set can0 txqueuelen ... 调整的是发送队列,不会扩大硬件 RX FIFO 或 socket receive buffer。

因此要分别收集:控制器 overrun/error counter、ip -s -d link、驱动 tracepoint、SO_RXQ_OVFL/socket 缓冲统计和应用接收时间戳,再确定丢弃发生在哪一层。


三、解决方案(三步优化)

优化1:对命令与 response 做可计算的整形

原理:限制一个控制周期内的最坏 burst,让总线利用率、response deadline 和接收路径服务能力都留有余量。固定 sleep(1 ms) 只能作为实验手段,正式方案应使用单调时钟、绝对周期和明确的帧预算。

代码改进

1
2
3
4
5
6
7
8
# 原始代码(❌ 错误)
for joint_id in range(1, 8):
send_can_frame(joint_id, command)

# 实验性整形:正式实现应使用绝对时间调度,并检查超期
for joint_id in range(1, 8):
send_can_frame(joint_id, command)
wait_until_next_tx_slot()

时序对比

1
2
3
4
5
6
7
8
9
10
11
12
优化前:
T0: [J1][J2][J3][J4][J5][J6][J7] ← 连续发送
随后: [RESP1][RESP2][RESP3][RESP4]... ← response burst

优化后:
T0: [J1] ← 发送J1
T0.5ms: [RESP1] ← J1回复
T1ms: [J2] ← 发送J2
T1.5ms: [RESP2] ← J2回复
...
T6ms: [J7]
T6.5ms: [RESP7] ← 错开时间,降低峰值积压

是否有效要用硬件 overrun、socket overflow、response latency 和总线利用率共同验证,不能只看业务层“超时次数”。


优化2:按丢弃层级调优驱动与 socket

txqueuelen 只影响发送 qdisc。若发送侧出现 ENOBUFS 或排队延迟,可以评估发送队列;若是接收丢弃,应检查其他层:

1
2
3
4
5
6
7
8
9
# 发送队列与链路状态
ip -s -d link show can0

# 应用设置并读取 socket 接收缓冲
sysctl net.core.rmem_max

# 驱动/控制器特定统计与中断负载
ethtool -S can0 2>/dev/null || true
cat /proc/interrupts

应用可启用 SO_RXQ_OVFL 获取 socket 丢包辅助计数,并提高接收线程优先级或批量读取;硬件 FIFO、watermark、DMA、IRQ affinity 与 NAPI/offload 配置则必须查具体控制器 binding 和驱动实现,不能假定存在通用 rx-fifo-depth 属性。

扩大缓冲只能吸收有限 burst,也会增加排队延迟;如果平均到达率持续高于消费率,最终仍会溢出。


优化3:为业务 response 设计确定性时槽

这里讨论的是业务 response 数据帧,不是 CAN ACK bit。多个节点同时准备发送时,CAN 会通过 ID 仲裁选择一个 winner,其余节点等待;问题是 response burst、优先级饥饿和 deadline,而不是“总线碰撞”。

随机延迟会增加不可预测性。更稳妥的方案是由 command ID/周期号定义 response 窗口,或为每个 joint 分配确定性 offset,并对过期 response 丢弃而不是无限排队。

代码改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 原始代码(❌ 立即回复)
void can_rx_handler(can_frame_t *frame) {
execute_command(frame->data);
send_response();
}

// 示例:按 joint ID 使用确定性时槽
void can_rx_handler(can_frame_t *frame) {
execute_command(frame->data);

uint32_t slot_offset_us = response_slot_us(joint_id);
schedule_response(frame->command_id, slot_offset_us);

// 到时再次确认 command_id 仍是当前代,再发送 response
}

时序对比

1
2
3
4
5
6
7
8
9
10
11
优化前:
T0: 主控发送 [J1]
T0.5ms: 多个节点准备发送
[RESP1][RESP2][RESP3]... ← 由仲裁串行,但形成burst

优化后:
T0: 主控发送 [J1]
T0.5ms: J1的确定性时槽 [RESP1]
T0.6ms: J2的确定性时槽 [RESP2]
T0.7ms: J3的确定性时槽 [RESP3]
... ← 可计算的峰值与最坏响应延迟

时槽长度必须包含帧位数、stuff bit 上界、总线速率、仲裁/错误重传余量和时钟误差,并用真实 trace 验证。


优化4:分离通信健康监控与安全停止条件

不能为了减少误停就简单允许“连续丢 3 次”。控制系统是否还能安全运行取决于命令新鲜度、执行器本地安全状态、速度/力矩和风险分析。

更稳妥的状态机至少区分:单帧业务 response 缺失、command deadline 超期、设备通信失联和安全监控触发。业务 response 缺失可以重试或降级;超过设备允许的数据年龄则应进入经过安全分析的 safe state。

代码改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 原始代码(❌ 一次超时就停止)
void watchdog_timeout_handler(void) {
emergency_stop();
}

// 优化后(✅ margin机制)
#define COMMAND_MAX_AGE_NS 3000000ULL

static uint64_t last_valid_command_ns;

void can_rx_handler(can_frame_t *frame) {
if (validate_command_id(frame) && validate_crc(frame)) {
last_valid_command_ns = monotonic_now_ns();
}
}

void watchdog_timeout_handler(void) {
if (monotonic_now_ns() - last_valid_command_ns > COMMAND_MAX_AGE_NS) {
enter_validated_safe_state();
}
}

阈值必须来自控制周期、制动时间和危害分析;博客示例不能替代 ISO 13849/IEC 61508 等适用流程或真机安全验证。


四、验证矩阵与容量预算

测试环境

原稿曾写“500 kbit/s、7 个关节、每关节 1 kHz,并且每周期都有命令和 response”。这个组合在经典 CAN 上无法成立:即使不精确展开 bit stuffing,一个 8 字节经典 CAN 数据帧在总线上的开销也远大于 64 bit;每毫秒发送 14 帧会明显超过 500 kbit/s。

因此应区分:

  • 关节 MCU 内部可以运行 1 kHz 本地控制环;
  • CAN 命令/状态更新率必须根据标识符长度、payload、bit stuffing 上界、response 数量、错误重传和目标利用率单独预算;
  • 不能用“应用想要 1 kHz”代替链路容量计算。

必须同时记录的指标

层级 指标 目的
CAN 控制器 TEC/REC、error-active/passive、bus-off、FIFO overrun 区分协议/物理错误与接收搬运不及时
netdev/驱动 rx_errors、rx_dropped、tx_errors、队列停顿 定位内核与驱动路径异常
socket SO_RXQ_OVFL、接收缓冲占用、recvmsg() 间隔 判断应用是否消费过慢
业务协议 command ID 缺口、重复、迟到 response、response deadline 判断任务语义是否完整
安全状态机 命令数据年龄、降级次数、safe-state 进入原因 验证安全边界没有被“容错”绕过

测试阶段

  1. 空载基线:单节点、低频率,确认 bit timing、终端和 error counter 正常。
  2. 阶梯负载:逐步增加命令/response 频率,记录第一个出现排队、overrun 或 deadline miss 的层级。
  3. 突发测试:在相同平均带宽下改变 burst 大小,验证接收路径容量。
  4. 优先级测试:构造高优先级持续流,确认低优先级 response 不会饥饿。
  5. 故障注入:使用 vcan 验证业务状态机,真实 CAN 上再验证错误帧、bus-off 和恢复。
  6. 长稳测试:保存原始 trace、软件版本、总帧数和每层计数器,才能支持“未再复现”的结论。

总线负载观测

优化前

1
2
canbusload can0@500000 -r
# 保存观察窗口、帧 ID 分布与错误计数器

平均利用率无法反映短时 burst、优先级饥饿和错误重传。正式报告应给出观察窗口、峰值定义、帧 ID 分布与 error counter,而不是只列一组百分比。


五、调试工具链

工具1:CANalyzer

用途:实时监控、错误分析

配置

1
2
3
4
5
6
7
8
过滤器:
- Data Frames: 0x100-0x1FF (命令)
- Data Frames: 0x200-0x2FF (业务response)
- Error Frames: All

触发器:
- 错误帧出现时停止录制
- 保存前后各1000帧

工具2:SocketCAN工具集

candump(抓包):

1
2
3
4
5
6
7
8
9
10
11
# 实时显示所有CAN帧
candump can0

# 带时间戳
candump -t a can0

# 过滤特定ID
candump can0,301:7FF # 只看ID 0x301

# 保存到文件
candump -l can0

cansend(发送测试帧):

1
2
3
4
5
# 发送单帧
cansend can0 301#0102030405060708

# 发送周期帧(100ms)
cangen can0 -g 100 -I 301 -D r

cansequence(测试丢包):

1
2
3
4
5
6
7
# 发送序列号递增的帧
cansequence can0 -v

# 在接收端检测丢包
cansequence can0 -r -v
# 输出:
# Missing: 1234 (expected 1235)

工具3:示波器 + 逻辑分析仪

测量CAN差分信号

1
2
3
4
5
CH1: CAN_H
CH2: CAN_L
Math: CH1 - CH2 (差分)

触发条件:Error Frame

验证时序

1
2
3
4
逻辑分析仪 + CAN解码:
- 测量发送间隔
- 分别验证链路层 ACK slot 与业务 response 延迟
- 检测总线仲裁

六、经验总结

CAN总线调试方法论

五层排查法

  1. 硬件层:信号质量、终端电阻、电缆长度
  2. 协议层:帧结构、错误类型、重传机制
  3. 驱动层:FIFO深度、队列长度、统计信息
  4. 应用层:发送时序、负载分布、Guard机制
  5. 系统层:Watchdog策略、容错设计

常见坑点

坑1:忘记配置终端电阻

1
2
3
症状:通信不稳定,错误率高
检查:万用表测量CAN_H/CAN_L阻抗
应该:60Ω(两个120Ω并联)

坑2:bit timing 只抄一个“推荐采样点”

1
2
3
4
5
# 错误:不看控制器时钟、收发器、线长和其他节点就照抄参数
ip link set can0 type can bitrate 500000 sample-point 0.5

# 正确方向:按硬件手册和全网节点约束计算并验证;87.5%只是常见起点之一
ip link set can0 type can bitrate 500000 sample-point 0.875

坑3:高负载下RX FIFO满

1
2
3
4
5
# 同时检查控制器、netdev和socket,不把txqueuelen当RX参数
ip -s -d link show can0
cat /proc/interrupts

# 应用侧读取SO_RXQ_OVFL,并记录recvmsg间隔

Trade-off分析

方案 优点 缺点 适用场景
应用层间隔 简单 增加周期 低频控制
分层接收路径调优 能针对真实丢弃点 依赖控制器和驱动 已定位 FIFO/socket overflow
确定性response时槽 峰值和最坏延迟可计算 需要时钟与协议设计 多节点周期控制
数据年龄安全状态机 把业务重试与安全边界分开 需要危害分析与真机验证 安全关键控制

七、后续优化方向

  1. CAN FD升级:更高带宽(5Mbps),更大payload
  2. 时间触发CAN(TTCAN):确定性调度
  3. 冗余总线:双CAN总线,互为备份

八、总结

通过五层排查法,可以把同一个“业务 response 超时”拆成不同候选原因:

  • 应用层:命令发送形成 burst,业务 deadline 或 command ID 处理不完整;
  • 接收路径:控制器 FIFO、驱动/offload 队列或 socket 消费不足;
  • 协议/物理层:ACK error、bit error、错误重传、error-passive 或 bus-off。

修复应和证据一一对应:

  • 按链路预算整形命令与 response,而不是随意 sleep
  • 只在确认丢弃层级后调整 FIFO、IRQ、socket 缓冲或消费者;
  • response 使用确定性时槽和 command ID,过期代际不得污染当前任务;
  • 通信健康与安全停止条件分离,但都不能绕过命令数据年龄上限。

效果

  • 原稿记录中业务 response 超时和误触发停止均显著下降;原始 trace 未公开,因此不把“0%”写成可复现保证
  • 总线负载、错误计数器、FIFO overrun、socket overflow 与 response deadline 应分别报告

关键经验

  • 硬件信号正常≠系统稳定
  • 工具组合:CANalyzer + SocketCAN + 示波器
  • 调试要分层:硬件 -> 协议 -> 驱动 -> 应用

参考资料