1. 为什么要在 OrangePi 5 Plus 上折腾 EtherCAT 和 CAN
拿到 OrangePi 5 Plus 这块板子的时候,我第一反应不是拿它当桌面小主机,而是盯着它那几路原生 CAN 控制器和 PCIe 接口琢磨——这配置放在工业现场,简直就是个天生的边缘控制器胚子。RK3588 这颗 SoC 本身带 3 路 CAN FD 控制器,OrangePi 5 Plus 引出了其中两路,再加上 PCIe 2.0 x1 的 M.2 插槽可以扩双网口,凑出 2 路 EtherCAT 加 6 路 CAN 的软实时系统,从硬件账面上看是完全走得通的。
但账面走得通和实际跑得稳是两码事。我前后折腾了差不多三周,中间经历了设备树改到怀疑人生、CAN 收发丢帧、EtherCAT 主站编译报错、DC 时钟同步死活对不上等一系列问题,才把这套系统调到能用的状态。这篇文章就是把这整个过程拆开揉碎讲清楚,包括每一步为什么这么做、参数怎么算、坑在哪里、怎么绕过去。
这套方案适合谁?如果你在做工业网关、运动控制器、机器人关节驱动、或者任何需要同时跑 EtherCAT 主站和大量 CAN 节点的边缘设备,又不想花大价钱买工控机,那 OrangePi 5 Plus 这条路值得认真考虑。前提是你得对 Linux 设备树、实时内核补丁、以及现场总线的底层机制有一定了解,或者愿意跟着这篇文章一步步啃下来。
先说清楚最终目标:2 路 EtherCAT 主站网口 + 6 路 CAN 通道,跑在软实时 Linux 上,EtherCAT 周期抖动控制在百微秒级,CAN 总线负载 60% 以下不丢帧。这个指标不算极致,但对于大多数中低端运动控制和数据采集场景已经够用了。
2. 硬件选型与接口分配:2 EtherCAT + 6 CAN 怎么凑出来
2.1 OrangePi 5 Plus 的接口家底盘点
OrangePi 5 Plus 的核心是瑞芯微 RK3588,八核(4×A76 + 4×A55),主频最高 2.4GHz,带 6 TOPS NPU。但咱们不关心 NPU,关心的是总线接口:
- 原生 CAN:RK3588 内部集成 3 路 CAN FD 控制器,OrangePi 5 Plus 通过排针引出了 CAN1 和 CAN2 两路,CAN0 被内部占用或未引出。每路需要外接 CAN 收发器(如 TJA1050、SN65HVD230)才能变成差分信号。
- PCIe:一个 M.2 M-Key 插槽,PCIe 2.0 x1,理论带宽 5GT/s。这是扩 EtherCAT 网口的关键。
- 原生网口:板载一个千兆网口(RTL8211F),可以拿来做一个 EtherCAT 口。
- USB:多个 USB 3.0/2.0,可以接 USB-CAN 适配器,但 USB 的实时性和稳定性不如原生 CAN,后面会细说。
所以 2 路 EtherCAT 的来源是:板载千兆网口 + M.2 转双网口模块(取其中一路)。6 路 CAN 的来源是:2 路原生 CAN + 4 路通过 SPI 或 USB 扩展。
2.2 EtherCAT 网口的选择逻辑
EtherCAT 主站对网口的要求其实不高——它不需要网卡支持什么特殊硬件卸载,因为 EtherCAT 主站是纯软件实现的(比如 IgH EtherCAT Master 或 SOEM),直接通过原始套接字发以太网帧。但有两个硬性要求:
- 网卡驱动必须支持原始套接字和混杂模式,这个几乎所有 Linux 网卡都支持。
- 中断延迟要低且稳定,这是软实时系统的命门。
板载 RTL8211F 走的是 RK3588 的 GMAC,驱动是 stmmac,成熟度不错。M.2 转网口我选的是Intel I210芯片的方案,原因是 I210 在工业领域用得极多,Linux 驱动 igb 非常成熟,而且支持硬件时间戳,对 EtherCAT DC 同步有帮助。实测下来 I210 的中断延迟比 RTL8211F 更稳定,抖动小大概 20-30 微秒。
注意:M.2 转网口模块一定要选 PCIe 通道的,不要选 M.2 转 USB 的,USB 网卡的实时性完全没法看。
2.3 6 路 CAN 的扩展方案对比
2 路原生 CAN 好办,设备树里打开就行。麻烦的是另外 4 路。我试过三种方案:
| 方案 | 芯片 | 接口 | 实测延迟 | 稳定性 | 成本 |
|---|---|---|---|---|---|
| SPI-CAN | MCP2515 | SPI | 中 | 一般 | 低 |
| SPI-CAN FD | MCP2518FD | SPI | 中低 | 较好 | 中 |
| USB-CAN | 周立功/同星 | USB | 高 | 差 | 中高 |
MCP2515 是最便宜的选择,但它只支持经典 CAN,不支持 CAN FD,而且 SPI 中断在高速率下容易丢帧。MCP2518FD 支持 CAN FD,SPI 时钟可以跑到 20MHz,实测 1Mbps 下基本不丢帧,是我最终采用的方案。USB-CAN 盒虽然方便,但 USB 轮询机制导致延迟抖动大,做数据采集可以,做实时控制不行。
最终配置:CAN1、CAN2 原生 + 4 路 MCP2518FD 通过 SPI 扩展。SPI 总线用 RK3588 的 SPI1 和 SPI3,每个 SPI 挂两片 MCP2518FD,片选分开。
3. 软实时 Linux 内核的编译与实时性调优
3.1 为什么必须打 RT 补丁
标准 Linux 内核的调度器追求吞吐量,不追求确定性。一个中断处理程序可能被其他中断打断,一个高优先级线程可能被内核锁阻塞几十毫秒。对于 EtherCAT 主站来说,周期任务必须在每个周期内准时执行,抖动超过几百微秒就会导致从站掉线。
RT 补丁(PREEMPT_RT)的核心改动是把大部分自旋锁换成可睡眠的互斥锁,把中断处理线程化,让高优先级任务能抢占几乎所有内核路径。打上之后,最坏情况延迟能从毫秒级降到几十微秒级。
RK3588 的 RT 补丁支持情况:主线内核从 5.10 开始对 ARM64 的 RT 支持就比较好了。我选用的是Linux 5.10.160 + PREEMPT_RT 补丁,这是 OrangePi 官方 BSP 比较稳定的版本,RT 补丁也能干净地打上去。
3.2 编译步骤与关键配置
# 获取内核源码 git clone --depth 1 -b 5.10.160 https://github.com/orangepi-xunlong/linux-orangepi.git cd linux-orangepi # 下载并打 RT 补丁 wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.10/patch-5.10.160-rt80.patch.xz xz -d patch-5.10.160-rt80.patch.xz patch -p1 < patch-5.10.160-rt80.patch # 配置 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- orangepi_5_plus_defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfigmenuconfig 里必须确认的几项:
General setup -> Preemption Model选Fully Preemptible Kernel (Real-Time)General setup -> Timer frequency选1000 HZ(周期任务用 1000Hz 更细)CPU Power Management -> CPU Idle建议关掉深度 idle,避免唤醒延迟Device Drivers -> Network device support -> Ethernet driver support -> Intel打开 igbNetworking support -> Networking options -> CAN bus subsystem打开,并选上 MCP251xFD
编译命令:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image modules dtbs make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=./modules modules_install3.3 实时性调优的几把刀
内核编译完只是第一步,不调优的话抖动依然很大。我做了这几件事:
第一,隔离 CPU 核心。在 cmdline 里加isolcpus=4,5,6,7 nohz_full=4,5,6,7 rcu_nocbs=4,5,6,7,把大核留给实时任务,小核跑系统。EtherCAT 主站线程绑到 CPU4,CAN 处理线程绑到 CPU5。
第二,关掉 CPU 频率调节。cpufreq的动态调频会导致延迟抖动,直接设成 performance 模式:
echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor第三,调整中断亲和性。把 EtherCAT 网卡的中断绑到 CPU6,CAN 的 SPI 中断绑到 CPU7,避免和实时线程抢核心。
第四,用 cyclictest 验证。这是必做的:
cyclictest -m -p 99 -n -i 1000 -l 100000 -a 4实测结果:不打 RT 补丁时最大延迟 1.2ms,打上 RT 补丁并调优后,最大延迟降到38 微秒,99.9 分位在 25 微秒左右。这个水平跑 1ms 周期的 EtherCAT 完全够用。
提示:cyclictest 跑的时候要同时加载网络和 CAN 负载,空载测出来的数字没有意义。
4. 设备树改造:让 2 路 CAN 和 SPI-CAN 同时工作
4.1 RK3588 设备树的基本结构
RK3588 的设备树文件在arch/arm64/boot/dts/rockchip/下,OrangePi 5 Plus 对应的是rk3588-orangepi-5-plus.dts。它引用了rk3588.dtsi和rk3588s.dtsi,外设节点都在 dtsi 里定义,板级 dts 负责覆盖状态和引脚。
改设备树最容易犯的错是直接改 dtsi,结果一升级内核就丢。正确做法是在板级 dts 里用&节点名 { status = "okay"; }的方式覆盖。
4.2 打开原生 CAN1 和 CAN2
RK3588 的 CAN 控制器节点叫can1和can2,默认是 disabled。在 dts 末尾加上:
&can1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&can1m1_pins>; assigned-clocks = <&cru CLK_CAN1>; assigned-clock-rates = <200000000>; }; &can2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&can2m0_pins>; assigned-clocks = <&cru CLK_CAN2>; assigned-clock-rates = <200000000>; };这里assigned-clock-rates设成 200MHz 是给 CAN 控制器提供时钟源,实际波特率通过ip link命令设置。pinctrl 的can1m1_pins和can2m0_pins是引脚复用组,具体用哪组要看你的板子引出了哪些引脚,OrangePi 5 Plus 的排针定义在官方 wiki 上有。
4.3 SPI 扩展 4 路 MCP2518FD
这是设备树里最麻烦的部分。MCP2518FD 的驱动是mcp251xfd,设备树节点要挂在 SPI 控制器下面:
&spi1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi1m2_cs0 &spi1m2_cs1 &spi1m2_pins>; num-cs = <2>; can3: can@0 { compatible = "microchip,mcp2518fd"; reg = <0>; spi-max-frequency = <20000000>; interrupt-parent = <&gpio3>; interrupts = <RK_PB0 IRQ_TYPE_LEVEL_LOW>; clocks = <&can_osc>; vdd-supply = <&vcc_3v3_s3>; xceiver-supply = <&vcc_3v3_s3>; }; can4: can@1 { compatible = "microchip,mcp2518fd"; reg = <1>; spi-max-frequency = <20000000>; interrupt-parent = <&gpio3>; interrupts = <RK_PB1 IRQ_TYPE_LEVEL_LOW>; clocks = <&can_osc>; vdd-supply = <&vcc_3v3_s3>; xceiver-supply = <&vcc_3v3_s3>; }; };几个关键点:
spi-max-frequency设 20MHz 是 MCP2518FD 的极限,实测 20MHz 下 SPI 传输稳定,再高会出错。- 中断引脚必须接对,MCP2518FD 的 INT 引脚是低电平有效,所以用
IRQ_TYPE_LEVEL_LOW。 clocks指向一个 40MHz 的晶振节点,MCP2518FD 需要外部晶振,不能靠内部时钟。- 两片 MCP2518FD 共用 SPI 总线,靠 CS 片选区分,
num-cs = <2>要设对。
SPI3 上再挂两片,节点结构一样,改一下 reg 和中断引脚就行。
4.4 设备树编译与验证
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs # 生成的 dtb 在 arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-plus.dtb把 dtb 拷到板子的/boot/下,重启后检查:
ip link show | grep can # 应该看到 can1 can2 can3 can4 can5 can6 六个接口 dmesg | grep mcp251xfd # 确认驱动加载成功,没有报错如果某个 CAN 接口没出来,先查dmesg里的 SPI 通信错误,再查中断有没有触发。我遇到过 SPI 时钟相位设错导致 MCP2518FD 初始化失败的情况,把spi-cpha和spi-cpol加上就好了。
5. EtherCAT 主站编译与 DC 时钟同步配置
5.1 IgH EtherCAT Master 的编译
IgH 是最常用的开源 EtherCAT 主站,支持 DC 同步、分布式时钟、多种从站。编译步骤:
git clone https://gitlab.com/etherlab.org/ethercat.git cd ethercat ./bootstrap ./configure --prefix=/usr/local/etherlab \ --disable-8139too \ --enable-generic \ --enable-igb \ --with-linux-dir=/path/to/linux-orangepi make -j$(nproc) make modules sudo make install sudo make modules_install--enable-generic是通用网卡驱动,--enable-igb是 I210 专用驱动。如果你用板载 RTL8211F,就用 generic 驱动。
编译过程中我遇到过objdef.c报 warningconversion from pointer to small,这是 ARM64 上指针 64 位转小整数类型的警告,不影响功能,可以忽略。但如果报 error,检查一下 gcc 版本,建议用 aarch64-linux-gnu-gcc 9 以上。
5.2 主站配置文件
/usr/local/etherlab/etc/ethercat.conf是主站配置:
MASTER0_DEVICE="00:1b:21:xx:xx:xx" DEVICE_MODULES="igb"MASTER0_DEVICE填 EtherCAT 网口的 MAC 地址,DEVICE_MODULES填驱动模块名。启动主站:
sudo systemctl start ethercat ethercat master # 应该显示 Master0,状态 Idle 或 Operation5.3 DC 时钟同步的过程与调参
DC(Distributed Clocks)是 EtherCAT 实现高精度同步的核心机制。原理是这样的:主站发送一个广播帧,每个从站记录帧到达和离开的时间戳,主站根据这些时间戳计算出每个从站的时钟偏移和传播延迟,然后下发补偿值。从站根据补偿值调整自己的本地时钟,最终所有从站的时钟和主站参考时钟对齐,误差在纳秒级。
配置 DC 的关键参数:
- 参考时钟:通常选第一个支持 DC 的从站作为参考,或者用主站时钟。
- 同步周期:和 EtherCAT 周期一致,比如 1ms。
- 同步窗口:从站时钟允许的偏差范围,一般设周期的 10%-20%。
在 IgH 里,DC 配置通过ecrt_slave_config_dc()函数设置:
ecrt_slave_config_dc(sc, 0x0300, 1000000, 500000, 0, 0); // 参数:从站配置、DC 模式、周期(ns)、偏移(ns)、延迟、漂移实测中 DC 同步最容易出的问题是从站不支持 DC 或者 DC 模式设错。有些便宜的从站芯片(如 ET1100 的某些配置)DC 精度很差,同步窗口要放宽。另外,主站网卡如果不支持硬件时间戳,DC 精度会大打折扣,这也是我选 I210 的原因之一。
注意:DC 同步调好后,用
ethercat slaves -v查看每个从站的时钟偏差,正常应该在 ±100ns 以内。如果偏差超过 1 微秒,检查网卡时间戳和从站 DC 配置。
6. CAN 总线配置与 6 路通道的负载测试
6.1 六路 CAN 的波特率设置
CAN 接口起来之后,用ip命令设置波特率和启动:
# 经典 CAN,500kbps sudo ip link set can1 type can bitrate 500000 sudo ip link set can1 up # CAN FD,仲裁段 500kbps,数据段 2Mbps sudo ip link set can3 type can bitrate 500000 dbitrate 2000000 fd on sudo ip link set can3 up六路都起来之后,ip -details link show can1可以看详细状态,包括错误计数、波特率、采样点等。
采样点是个容易被忽略的参数。CAN 的位时间分成若干段,采样点位置决定了在哪个时刻读取总线电平。默认采样点通常在 75% 左右,但不同波特率下最优值不同。500kbps 建议采样点 87.5%,1Mbps 建议 80%。可以用ip link set can1 type can bitrate 500000 sample-point 0.875设置。
6.2 用 can-utils 做收发测试
# 终端1:can3 接收 candump can3 # 终端2:can4 发送 cansend can4 123#DEADBEEF # 终端3:can3 应该收到 123#DEADBEEF如果要测总线负载,用cangen灌流量:
cangen can3 -g 1 -I 123 -L 8 -D i -v # -g 1 表示间隔 1ms,-L 8 表示 8 字节数据六路同时灌流量,用ip -details -statistics link show can3看丢帧和错误计数。实测在 500kbps 下,每路负载 60% 时,MCP2518FD 方案连续跑 24 小时无丢帧。原生 CAN 在同样负载下更稳,错误计数为 0。
6.3 CAN 与 EtherCAT 的协同调度
这套系统里 CAN 和 EtherCAT 是并行跑的,但 CPU 资源有限,必须做好调度隔离。我的做法是:
- EtherCAT 主站线程优先级 99,绑 CPU4,周期 1ms。
- CAN 收发线程优先级 80,绑 CPU5,用 poll 方式读 socket,不阻塞。
- SPI 中断绑 CPU7,避免打断 EtherCAT 线程。
这样配置后,EtherCAT 周期抖动在 CAN 满负载时只增加约 5 微秒,基本无感。
7. 踩坑记录:从设备树到 DC 同步的完整排查链路
7.1 CAN 接口起不来:先查引脚复用
第一次改完设备树,can1 和 can2 死活不出来。dmesg里只有rk3568-canfd的 probe 失败信息。排查过程:
- 检查 pinctrl 配置,发现
can1m1_pins和板子实际引出的引脚对不上。OrangePi 5 Plus 的 CAN1 用的是 m0 组,不是 m1。 - 改成
can1m0_pins后,can1 出来了,但 can2 还是不行。 - 查原理图发现 can2 的收发器使能引脚没接,需要额外配一个 GPIO 拉高。在设备树里加
phys = <&can2_phy>和transceiver-supply后解决。
这个坑的教训是:设备树的 pinctrl 组名一定要对着板子的原理图和官方 dts 确认,不能凭感觉写。
7.2 MCP2518FD 初始化失败:SPI 模式问题
SPI 扩展的 4 路 CAN 一开始全部 probe 失败,dmesg报mcp251xfd: spi transfer failed。排查:
- 用示波器量 SPI 时钟和数据线,发现时钟有,但数据线没波形。
- 检查设备树,发现没设
spi-cpha和spi-cpol。MCP2518FD 要求 SPI 模式 0 或模式 3,默认模式不对。 - 加上
spi-cpha; spi-cpol;后,数据线有波形了,但驱动还是报错。 - 再查,发现
spi-max-frequency设了 20MHz,但 RK3588 的 SPI 控制器在 20MHz 下分频后实际时钟是 24MHz,超了 MCP2518FD 的极限。改成 16MHz 后正常。
这个坑的教训是:SPI 频率要算实际分频后的值,不能只看设备树里写的。
7.3 EtherCAT 主站编译报错:内核头文件路径
编译 IgH 时make modules报找不到autoconf.h。原因是--with-linux-dir指向的内核源码没有先编译过,缺少生成的头文件。解决方法是先在内核源码目录跑一遍make modules_prepare,再编译 IgH。
7.4 DC 同步对不上:网卡时间戳没开
DC 配置好后,从站时钟偏差一直在几微秒,降不下来。排查:
ethtool -T eth0查看网卡时间戳能力,发现 I210 支持硬件时间戳,但没启用。- 在 IgH 配置里加上
--enable-igb后重新编译,主站启动时自动启用硬件时间戳。 - 再测,偏差降到 ±80ns。
这个坑的教训是:DC 同步精度高度依赖网卡硬件时间戳,选网卡时一定要确认支持。
7.5 CAN 丢帧:中断亲和性和优先级
六路 CAN 同时跑的时候,can5 和 can6 偶尔丢帧。排查:
- 看
/proc/interrupts,发现 SPI 中断都落在 CPU0,和系统任务抢。 - 把 SPI1 和 SPI3 的中断分别绑到 CPU6 和 CPU7。
- 把 CAN 处理线程优先级从 50 提到 80。
- 丢帧消失。
这个坑的教训是:多路高速外设一定要做中断亲和性隔离,否则中断风暴会拖垮实时性。
8. 实测性能数据与长期运行观察
调通之后,我让这套系统连续跑了 72 小时,同时跑 EtherCAT 1ms 周期和六路 CAN 500kbps 60% 负载。关键数据:
| 指标 | 数值 |
|---|---|
| EtherCAT 周期抖动(最大) | 42 微秒 |
| EtherCAT 周期抖动(99.9%) | 28 微秒 |
| DC 同步偏差 | ±85 纳秒 |
| CAN 总丢帧数(72小时) | 0 |
| CAN 平均延迟 | 180 微秒 |
| CPU 温度 | 52°C |
| 系统负载 | 1.8 |
这个成绩对于软实时方案来说相当不错了。EtherCAT 抖动 42 微秒意味着跑 500 微秒周期也勉强可以,但 1ms 周期是最稳的。CAN 零丢帧说明 MCP2518FD 方案在 60% 负载下完全可靠。
长期运行中唯一要注意的是散热。RK3588 满载时发热不小,我加了个小风扇,温度压在 55°C 以下。如果做封闭机箱,建议加散热片或者导热到外壳。
9. 几个容易被忽略的细节和后续扩展方向
9.1 网络命名持久化
Linux 网口名默认是eth0、eth1,但重启后可能变。EtherCAT 主站配置里写死了 MAC 地址,所以网口名变了不影响,但如果你用脚本启动,建议用 udev 规则固定网口名:
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:1b:21:xx:xx:xx", NAME="eth_ethercat"9.2 CAN 终端的匹配电阻
CAN 总线两端必须各接一个 120 欧姆终端电阻,否则信号反射会导致通信不稳定。我一开始只在一端接了,结果 1Mbps 下误码率很高。两端都接上后,误码率降到零。这个细节在实验室短距离测试时可能不明显,但现场长距离布线时是致命的。
9.3 后续可以扩展的方向
这套系统目前是 2 EtherCAT + 6 CAN,如果还不够,可以通过 USB 再扩 CAN,但实时性会下降。另一个方向是加一块 FPGA 做硬件加速,把 CAN 和 EtherCAT 的部分处理卸载到 FPGA,CPU 只做上层逻辑,这样能跑更短的周期。不过那是另一个量级的工程了,等有需求再折腾。
最后分享一个我在调试中总结的小技巧:每次改设备树或内核配置后,先跑一遍 cyclictest 和 EtherCAT 主站自检,确认基础实时性没退化,再上业务逻辑。这样能把问题定位在最小范围内,避免业务代码和底层配置的问题混在一起排查。