一、简介:为什么要在瑞芯微上“硬实时”驱动 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 长度,或提高工作队列优先级
热插拔节点后错误帧暴增 线缆反射 使用双绞屏蔽线,屏蔽层单端接地

七、实践建议与最佳实践

  1. 中断隔离
    相机 DMA、网络 NIC 中断绑到非 RT 核,避免抢占 CAN hardirq。

  2. 提前计算采样点
    推荐数据段采样点 75%,可用 cancalc 工具一键生成 dts 时序值。

  3. 日志分级
    生产环境关闭 can.debug,打开 can.err 级别,减少上下文切换。

  4. 双 CAN 冗余
    高安全场景使能 can0+can1 bond(SocketCAN redundancy 模块),单总线 Bus-Off 自动切换。

  5. 产线老化测试
    48 h 连续 FD 帧 5 Mbps 打流,错误帧 <0.001% 视为通过。

  6. 文档化
    把中断号、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,一样能把时序做到微秒级! 祝你调试顺利,产线永不掉帧。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐