一、简介:为什么要在瑞芯微上“硬实时”驱动 CAN?
国产芯趋势:瑞芯微 RK3568/RK3588 四核 A55+A76,自带 PCIe 2.1、USB3.0,价格≅同行 1/2,已被大量工业网关、机械臂控制器采用。
实时痛点:原生 Linux CAN 驱动默认
threaded interrupt+ksoftirqd调度,中断延迟 80-120 μs,无法满足 ≤50 μs 轮询周期。本文目标:基于 PREEMPT_RT 5.15 + 瑞芯微 BSP,把 CAN 中断响应降到 ≤30 μs,增加“总线故障快照”与“自动重同步”机制,一套方案同时搞定实时性+可靠性。
二、核心概念:5 个关键词先搞懂
| 关键词 | 一句话 | 瑞芯微对应 |
|---|---|---|
| CAN Controller | 实现 CAN 2.0B/CAN-FD 协议逻辑 | RK3568 内置 2×M_CAN IP |
| Message RAM | M_CAN 内部 FIFO,减少 DMA 搬运 | 每条 CAN 64 kB |
| Interrupt Latency | 中断触发→ISR 第一条指令时间 | 目标 ≤30 μs |
| SocketCAN | Linux 标准 CAN 框架,用户空间socket(AF_CAN,...) | 统一接口 |
| Fault Confinement | 错误计数器 >96 警告,>255 总线关闭 | 驱动自动快照 |
三、环境准备:10 分钟搭好开发机
1. 硬件
RK3568 工业核心板(友善、-firefly、自画均可)
2×CAN-FD 收发器 TJA1057(支持 5 Mbps)
USB-CAN 卡(PC 端做对比测试)
2. 软件
| 组件 | 版本 | 获取 |
|---|---|---|
| 瑞芯微官方 SDK | v1.2.1 | GitHub 开源 |
| 实时内核 | linux-5.15-rt47 | 见下文一键脚本 |
| 交叉工具链 | gcc-linaro-11.3 | 官方预编译 |
| 测试工具 | can-utils 2021.12.0 | apt install can-utils |
3. 一键打 RT 补丁(可复制)
#!/bin/bash # rt_patch.sh VER=5.15 RT_PATCH=patch-5.15-rt47.patch.xz wget https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/${VER}/${RT_PATCH} tar -xf linux-${VER}.tar.xz cd linux-${VER} xzcat ../${RT_PATCH} | patch -p1 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_linux_defconfig ./scripts/config -e CONFIG_PREEMPT_RT make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image dtbs四、应用场景:边缘视觉+运动控制一体机
背景:3C 装配线要在 500 mm/s 速度下对手机中框拍照+打螺丝,拍摄窗口仅 10 mm → 允许延迟 ≤20 ms。
系统拓扑:
RK3568 ├─ PCIe ×1 ← 2 MP 工业相机 (MJPEG 1 Gbps) ├─ CAN0 ← 伺服驱动器 (周期 1 ms,同步 6 轴) ├─ CAN1 ← 传感器握手机制 └─ GPIO ← 触发拍照实时需求:
CAN 周期帧抖动 ≤50 μs
相机触发→GPIO 输出 ≤100 μs
总线错误恢复 ≤100 ms
本文聚焦CAN 硬实时 + 故障自恢复,视觉触发部分后续篇章展开。
五、实际案例与步骤:从设备树到用户空间
5.1 硬件连接 & 管脚复用
RK3568 CAN0 引脚:
TX: GPIO3_B5
RX: GPIO3_B6
设备树片段(rk3568.dtsi已含 M_CAN,只需使能):
&can0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&can0m0_pins>; assigned-clocks = <&cru CLK_CAN0>; assigned-clock-rates = <200000000>; /* 200 MHz */ /* * Message RAM 偏移,避免与 GPU 冲突 * 偏移 0x100000,长度 64 KB */ m_can,msg-ram = <0x100000 0x10000>; /* 中断 affinity → CPU0 (RT 核) */ interrupt-affinity = <&cpu0>; };5.2 驱动修改:中断线程化→hardirq
默认驱动drivers/net/can/m_can/m_can.c使用threaded_irq,延迟大。
改动点(diff 节选):
// 原: devm_request_threaded_irq() // 新: devm_request_irq(..., IRQF_NO_THREAD, ...)在 probe 里设置
IRQF_NO_THREAD标志,让 M_CAN 中断直接进入 hardirq,RT 内核下可绑定到隔离核。
5.3 中断 affinity & CPU 隔离
启动参数:
isolcpus=1 nohz_full=1 rcu_nocbs=1 quiet splash
把 CAN0 中断绑到 CPU0(非隔离核,保证 hardirq):
echo 1 > /proc/irq/32/smp_affinity_list # 32 为 CAN0 中断号5.4 波特率 & CAN-FD 数据段配置
# 设置 1 Mbps / 5 Mbps FD sudo ip link set can0 type can bitrate 1000000 dbitrate 5000000 fd on sudo ip link set up can05.5 实时发送测试(C 代码)
/* rt_send.c */ #include <linux/can.h> #include <linux/can/raw.h> #include <net/if.h> #include <sys/socket.h> #include <unistd.h> #include <time.h> int main() { int s = socket(PF_CAN, SOCK_RAW, CAN_RAW); struct sockaddr_can addr = { .can_family = AF_CAN }; struct ifreq ifr = { .ifr_name = "can0" }; ioctl(s, SIOCGIFINDEX, &ifr); addr.can_ifindex = ifr.ifr_ifindex; bind(s, (struct sockaddr *)&addr, sizeof(addr)); struct canfd_frame frame = { .can_id = 0x123, .len = 64, .flags = CANFD_FDF, }; /* 填充 64 byte 数据 */ for (int i = 0; i < 64; i++) frame.data[i] = i; /* 周期 1 ms,硬实时循环 */ struct timespec ts = {0, 1000*1000}; /* 1 ms */ while (1) { clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); send(s, &frame, sizeof(frame), MSG_DONTWAIT); } return 0; }编译:
aarch64-linux-gnu-gcc rt_send.c -o rt_send -pthread -O2运行后,PC 端 USB-CAN 卡可测得周期抖动≤30 μs(示波器抓帧起始)。
5.6 故障诊断与自恢复
总线错误中断:
ERR Passive → 错误计数器 > 96 Bus Off → 错误计数器 > 255驱动层处理
if (irq_status & M_CAN_IR_BO) { /* Bus Off */ stats->bus_off++; can_bus_off(ndev); /* 通知 SocketCAN 框架 */ schedule_work(&priv->bus_off_work); /* 延后复位,避免 ISR 阻塞 */ }工作函数:
static void bus_off_work(struct work_struct *work) { struct m_can *priv = container_of(work, struct m_can, bus_off_work); msleep(100); /* 等待总线安静 */ m_can_reset(priv); /* 软件复位 M_CAN */ m_can_start(priv); netif_wake_queue(priv->ndev); }用户空间感知:
ip -details link show can0 # 状态切换: UP → BUS-OFF → UP恢复时间≈110 ms,满足产线“暂停-重启”窗口。
六、常见问题与解答(FAQ)
| 问题 | 现象 | 解决 |
|---|---|---|
| 设置 5 Mbps FD 失败 | Bitrate not supported | 确认收发器 TJA1057 支持 5 Mbps,dts 里 dbitrate 与采样点正确 |
| 中断延迟 > 80 μs | 未关 threaded_irq | 驱动里改用devm_request_irq+IRQF_NO_THREAD |
| can0 up 后无数据 | 终端电阻 120 Ω 未接 | CAN_H-CAN_L 间量电阻,缺则并 120 Ω |
| 大量丢帧 | dmesg 报Rx FIFO overrun | 增大 Message RAM 长度,或提高工作队列优先级 |
| 热插拔节点后错误帧暴增 | 线缆反射 | 使用双绞屏蔽线,屏蔽层单端接地 |
七、实践建议与最佳实践
中断隔离
相机 DMA、网络 NIC 中断绑到非 RT 核,避免抢占 CAN hardirq。提前计算采样点
推荐数据段采样点 75%,可用cancalc工具一键生成 dts 时序值。日志分级
生产环境关闭can.debug,打开can.err级别,减少上下文切换。双 CAN 冗余
高安全场景使能can0+can1bond(SocketCAN redundancy 模块),单总线 Bus-Off 自动切换。产线老化测试
48 h 连续 FD 帧 5 Mbps 打流,错误帧 <0.001% 视为通过。文档化
把中断号、CPU 亲和、dbc 文件、终端电阻图纳入 Git,新人 10 分钟上手。
八、总结:一张脑图带走全部要点
瑞芯微 CAN 实时通信 ├─ 硬件:RK3568 M_CAN + TJA1057 ├─ 内核:PREEMPT_RT + hardirq + CPU 亲和 ├─ 配置:dts msg-ram + ip link fd on ├─ 测试:rt_send + cyclictest 抖动≤30 μs ├─ 容错:Bus-Off 自恢复≤110 ms └─ 文档:Git 管理 dbc + 电阻图 + 测试报告实时性 + 国产化 + 工业级容错,一套方案同时搞定。
立刻拉出你的 RK3568 板子,复制本文设备树片段,重新编译烧录,用示波器抓一帧 CAN-FD 信号——你会亲眼看到:
国产芯 + 实时 Linux,一样能把时序做到微秒级!祝你调试顺利,产线永不掉帧。