【瑞芯微平台实时Linux方案系列】第十三篇 - 瑞芯微平台CAN总线实时通信方案
本文介绍了在瑞芯微RK3568/RK3588平台上实现CAN硬实时驱动的方案。针对工业网关和机械臂控制需求,通过PREEMPT_RT补丁、中断线程化改造、CPU隔离等技术,将CAN中断响应时间从80-120μs降至≤30μs。详细说明了硬件连接、设备树配置、驱动修改、中断绑定等实现步骤,并提供了故障自恢复机制。测试结果显示周期抖动≤30μs,总线错误恢复时间约110ms,满足工业场景的实时性和可靠
一、简介:为什么要在瑞芯微上“硬实时”驱动 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 can0
5.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,一样能把时序做到微秒级! 祝你调试顺利,产线永不掉帧。
更多推荐
所有评论(0)