尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RK3588上EtherCAT主站实时性调优实践:从IGH部署到微秒级抖动控制

RK3588上EtherCAT主站实时性调优实践:从IGH部署到微秒级抖动控制 去年我从客户那边接手了一个用RK3588做机器人控制器的项目硬件已经定板主控就是RK3588需要通过EtherCAT总线下挂伺服驱动器和远程IO模块。客户给的指标很明确——周期1ms抖动尽量控制在50微秒以内。这个需求放在传统x86工控机上加一张网卡基本是常规操作但要在一个SoC板子上把EtherCAT主站跑出实时性里面门道就多了。这篇文章把我从内核编译到IGH主站部署、再到最后压抖动压到几十微秒的整个实操过程完整拆出来包括踩过的坑和验证过的调优手段希望能让打算在RK3588上做EtherCAT主站的同行少走两步弯路。文章适合几类人正在RK3588或其他ARM平台做机器人和运动控制开发的工程师想把IGHIgH EtherCAT Master跑在实时Linux上的同学以及那些遇到实时抖动超标、从站扫不到、周期不稳定等问题但不知道怎么下手的朋友。我尽量把每个步骤背后的原因也讲清楚这样即使你手头的板子不一样也能按相同思路推到自己的硬件上。1. 为什么选RK3588做EtherCAT主站方案与整体架构拆解1.1 RK3588在运动控制里的定位RK3588是一颗8核64位处理器采用4个Cortex-A76大核加4个Cortex-A55小核的架构最高主频能到2.4GHz左右接口资源非常丰富——PCIe、USB3.1、双千兆以太网、多路CAN、UART这些都是现成的。做机器人控制器时一颗RK3588既能跑视觉算法又能跑运动规划还能挂EtherCAT总线下发周期指令这种“把边缘计算和实时控制放在同一颗SoC上”的做法这几年在协作机器人、AGV控制器、中小型运动控制平台上越来越常见。从成本和控制器的集成度来看它确实有吸引力不用额外加一块运动控制卡不用搞一台上位机加一台PLC的双机方案单板就能把HMI、视觉、逻辑控制、总线运动控制全部包圆。代价就是——EtherCAT主站的实时性必须靠你手动调出来不是装个驱动就完事的。我在选型阶段考虑过两种替代方案一种是用STM32MP1这类异构芯片让Cortex-M核跑EtherCAT主站另一种是外接专用EtherCAT主站芯片比如瑞萨的R-IN32M3或者Microchip的LAN9253方案。但前者意味着要额外开发MCU侧的主站逻辑后者还得重新设计硬件接口板。用RK3588跑软件主站配合一个实时内核和IGH主站协议栈是性价比和灵活度最高的路线也最符合我们“用软件方式消化掉总线实时性”的整体思路。1.2 EtherCAT实时主站必须解决的三大问题EtherCAT本身是一个基于以太网帧的工业实时协议它的从站在硬件层面就能做到纳秒级同步。但主站侧的实时性完全依赖软件栈这就要跨越三座山第一座山是操作系统的实时性。Linux默认是分时调度一个线程随时可能被别的进程打断延迟到几毫秒甚至几十毫秒都正常。要做实时主站内核必须打PREEMPT_RT补丁让内核变成完全可抢占把中断线程化同时引入高精度定时器支持这是整个方案的地基。第二座山是EtherCAT主站协议栈。IGHIgH EtherCAT Master是Linux下最主流的开源EtherCAT主站实现它把 mailbox、FMMU、PDO 这些协议逻辑封装成API同时通过实时网卡驱动把数据帧在周期中断里填进网卡。IGH跑在实时内核上才能发挥它的全部能力两者搭配是业界非常常见的组合。IGH支持的网卡类型有限所以网卡选型这一步很关键后面我会单独讲。第三座山是应用层的实时调度。协议栈只是保证了“把帧发出去”这一下是实时的但如果你应用层的控制线程还跑在普通优先级、没有锁页、定时丢精度那整个控制周期的抖动依然会一塌糊涂。所以最终能不能压住抖动考验的是你在用户态怎么写这个周期任务怎么给线程分配CPU怎么把中断隔离出去。1.3 完整方案链路从物理链路到控制线程这里先给出一张整体的方案链路图后面所有操作都围绕这条链路展开硬件层RK3588板卡 千兆以太网卡支持IGH实时驱动 EtherCAT从站设备。内核层Linux 5.10或6.1 LTS内核 对应PREEMPT_RT补丁 打开完全抢占配置 启动参数CPU隔离。驱动层IGH实时网卡驱动模块如ec_e1000e——它负责接管网卡的中断和数据收发和主站内核模块ec_master通信。协议栈层IGH主站内核模块 用户态命令行工具ethercat命令。应用层基于ecrt库的周期控制程序使用SCHED_FIFO实时线程、mlockall锁页、clock_nanosleep绝对时间定时周期完成后通过PDO数据驱动伺服计算和上抛。这个链路最大的特点就是每一层都有“实时”的把握实时内核负责调度IGH实时驱动负责把网卡中断转化成实时任务应用层用严格的时间基准塞数据。任何一层掉了链子都会直接反映到总线抖动上。2. 实时内核构建从补丁到启动验证的完整过程2.1 内核版本与PREEMPT_RT补丁选择在RK3588上构建实时内核第一步是选对内核版本。Rockchip官方SDK一般是基于Linux 5.10或者6.1而PREEMPT_RT补丁在kernel.org上有对应版本的维护分支。我的建议是直接选5.10或6.1这两个LTS版本原因有两个一是Rockchip BSP和板级补丁在这两个版本上基本都有现成的二是RT补丁的维护比较活跃遇到问题能在邮件列表和论坛里搜到类似案例。我当时用的是5.10版本对应的是kernel.org上的patch-5.10.x-rt系列补丁。需要注意补丁版本必须和内核小版本严格匹配比如内核源码是5.10.110那补丁也要找5.10.110-rt的版本差一个小版本都可能打不上。如果你不想自己打补丁也可以看看你手上板子的厂商是否已经提供了RT预编译内核。mentor/ubuntu也有RT内核包但那种通用包往往没有针对RK3588的板级配置缺少设备树或者某些外设驱动对量产项目反而不如自己编来得可控。所以我的建议是把“自己打补丁编译”当作基本功来练跑通了之后整体可控性高很多。2.2 RK3588内核编译的关键配置项打补丁这一步比较机械真正需要小心的是内核配置。先用Rockchip默认配置铺底然后进menuconfig手动调整实时性相关的选项。具体步骤大致是这样# 解开内核源码打RT补丁 cd kernel-5.10 make rockchip_linux_defconfig # 进入图形化配置界面做关键修改 make menuconfig需要重点确认的配置项有这几个General setup - Preemption Model - Fully Preemptible (Real-Time) (CONFIG_PREEMPT_RT)General setup - Timers subsystem - High Resolution Timers (CONFIG_HIGH_RES_TIMERS) 确认打开Kernel Features - Timer frequency 建议选 1000 Hz (CONFIG_HZ1000)CPU Power Management - CPU Frequency scaling 可以保留但后面会在启动参数里关掉调频Networking support - Ethernet 里把你要用的网卡驱动编成模块比如CONFIG_IGB、CONFIG_E1000E第一个选项是RT内核的开关这一步没打开后面全白搭编完之后可以在/boot/config-$(uname -r)里查CONFIG_PREEMPT_RTy来确认。关于HZ的选择RT补丁下面HZ1000有利于周期定时器精度而默认的250Hz高精度定时器其实还够用但是1000Hz对计时精度更友好实测来说周期抖动会小一些代价是内核软中断开销略高。在性能强的RK3588上这个代价可以接受。编译命令按Rockchip文档走就行export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- make Image -j$(nproc) make dtbs make modules -j$(nproc)编完之后把Image、dtb和modules部署到板子上刷机重启用uname -a确认内核版本里带PREEMPT_RT字样。2.3 启动参数调整与实时性快速验证光把RT内核跑起来还不够还得在启动参数里做两件事CPU隔离和关闭动态调频。在/boot/uEnv.txt或你使用的boot.scr/bootargs里加上isolcpus2,3 nohz_full2,3 rcu_nocbs2,3这样内核会把CPU2和CPU3从普通调度器的负载均衡中隔离开来专门给实时任务用。 RK3588的大小核架构里CPU4-7是A55小核CPU0-3是A76大核所以隔离CPU2和CPU3这两个大核是合理的选择。nohz_full关掉了这两个核上的周期tick减少定时器中断对实时线程的干扰rcu_nocbs则把RCU回调从这些核上挪走。动态调频也是抖动的大敌CPU频率忽上忽下任务执行时间就会飘。一种做法是在启动参数里加cpufreq.off1彻底关掉调频器另一种是跑实时任务前手动把CPU都固定在最高频率cpupower frequency-set -g performance之后验证实时性就很顺理成章了。用cyclictest做一个最直接的测试sudo cyclictest -S -p 90 -i 1000 -d 0 -m -n在RK3588上正常四五个微秒以内的延迟才是RT内核该有的表现。如果延迟飙到几十上百微秒十有八九是隔离没生效、调频没关掉或者是某个驱动频繁占用中断。这块基础没打好后面EtherCAT的抖动一定压不住。3. IGH主站部署编译安装、网卡适配与命令行验证3.1 IGH版本选择与编译参数IGH主站的版本选择我分了两种情况。如果你想用最稳定的经典路线选1.5.2它在社区里用了很多年资料最多遇到问题基本一搜就有答案。如果你更想用相对新的内核和更丰富的网卡驱动IGH还有1.6.0以及基于RTDM的新分支。1.5.2的优点是简单直接和5.10内核配合没问题我的项目就是用这个版本跑通的。IGH编译的过程不复杂但有几个细节容易忽略。它是基于autotools构建的默认安装前缀是/opt/etherlab。编译命令./configure --prefix/opt/etherlab --enable-rtdm --enable-e1000e --disable-8139too make sudo make modules sudo make modules_install sudo make install这里--enable-rtdm是把IGH的RTDM接口打开让实时应用可以通过RTDM设备访问主站一般建议开后面那一串网卡模块的参数按你实际用的网卡来勾选。IGH编译时会扫描内核源码所以你得保证内核源码配置和当前运行内核一致最好就是在你编译内核的同一套源码里编译IGH。这点我踩过坑IGH编好之后一加载模块就报内核版本或符号表不匹配。安装完IGH之后ethercat命令在/opt/etherlab/sbin/目录下同时会生成/etc/ethercat.conf配置文件和启动脚本/etc/init.d/ethercat。3.2 网卡选型与IGH实时驱动适配最容易被忽略的一步IGH实时主站能接管网卡靠的是它的实时网卡驱动模块。IGH 1.5.2内置的驱动包括ec_e1000e对应Intel千兆网卡、ec_r8169对应Realtek 8169系列、ec_e1000、ec_8139too等所以选网卡时优先选这些驱动支持的芯片。这里要特别提醒一下RK3588板载的双千兆以太网往往是SoC内部GMAC加外部PHY的组合驱动走的是stmmac或者Rockchip自家的驱动栈IGH 1.5.2默认并不包含对应的实时驱动模块。如果你直接拿板载网卡跑IGH主站会提示找不到设备或者只能用非实时模式工作周期抖动会大到不可用。所以我强烈建议在做EtherCAT主站时外扩一张PCIe千兆网卡优先选择Intel I210/I211芯片的卡。I210/I211在IGH和Linux下支持都很成熟实测周期性非常稳。在RK3588的PCIe接口上插一张小尺寸网卡是工业控制板里很常见的做法。如果你的设计已经定了板载GMAC、没法改硬件也不是完全没救。可以试试IGH 2.x分支或者手工给IGH补上对应stmmac驱动的支持——这个工作量不小而且需要自己维护补丁。更省心的替代方案是用SOEM这类用户态协议栈配合AF_PACKET但实时性需要额外依赖自己精心调度的线程对应用层要求高很多不建议新手一开始就走这条路。3.3 主站初始化与从站扫描验证网卡确认OK之后编辑/etc/ethercat.conf设置主站使用的网卡名MASTER0_DEVICEeth1然后启动主站sudo /etc/init.d/ethercat start用ethercat master查看主站状态如果能看到Master 0并显示链路已连接说明IGH已经建立起周期通信骨架。接着把从站设备接上总线执行sudo ethercat slaves如果从站扫描不出来可以先检查网线、从站供电、网卡类型然后确认IGH的网卡模块是否确实被加载占用lsmod | grep ec_这里有个重点IGH一旦加载了实时网卡模块这张网卡就不再被Linux网络栈管理了eth1的IP、DHCP这些统统会失效。如果之后还要复用网卡做普通网络通信得先把IGH主站停掉。设计网络拓扑时要预留好管理网口别把唯一的网卡拿去跑EtherCAT否则后面远程调试会很痛苦。IGH识别到从站之后会提示部分从站没有Vendor ID或者Product Code信息需要导入厂商提供的从站XML文件。把XML放到IGH从站目录一般是/opt/etherlab/etc/sii/重新扫描就能正确识别设备。这一步在做实际伺服驱动器对接时几乎一定会遇到伺服厂商官网下载XML的路径要提前准备好。3.4 周期任务骨架IGH客户端接口的基本套路IGH提供了一套用户态API基于这套API写一个周期任务的大逻辑是固定的用ecrt_request_master拿到主站句柄;用ecrt_master_create_domain创建数据域把从站的PDO映射进去;激活主站ecrt_master_activate;在实时线程的每个周期里分别调用ecrt_master_application_time、ecrt_master_sync_reference_clock同步分布式时钟再调用ecrt_domain_process和ecrt_master_send把PDO数据发出去。IGH附带的examples里有完整的周期程序模板拿rt_user.c做底座改成自己的应用是最快的起手式。应用层的实时调度细节我把它们放在第4章单独展开。4. 抖动分析与实战调优从周期任务到系统级隔离4.1 先建立抖动测量基准没有测量就没有调优。跑周期任务时一定要先写一个测量逻辑每次循环开始时读一次clock_gettime(CLOCK_MONOTONIC)和期望的周期做差把差值记录下来。我通常用一个1万次循环的缓冲区把抖动样本存下来结束后统计最大值、平均值和超过阈值的样本比例。一开始在RK3588上做1ms周期测试结果并不好看抖动最大值能到两三百微秒平均值也有十几微秒。这个水平跑普通IO都没问题但对伺服控制来说很悬需要一步步压下来。这种大抖动的根源一般有两个一是实时线程被更高优先级任务或者中断打断了二是定时器本身不准。前者要靠CPU隔离、中断亲和性、优先级调整来解决后者要确保使用绝对时间点式的定时方法并且关闭可能影响clock精度的因素。4.2 绝对时间定时周期任务的核心写法周期任务最忌讳的是用usleep或者多次sleep累计出周期时间。sleep系列函数的误差是会累积的跑一百个周期之后你的任务时间点已经漂得没边儿了。正确写法是用clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...)把下一个周期的绝对时间点作为参数传给内核让内核在准确的世界时钟线上唤醒你。伪代码大概是struct timespec next; clock_gettime(CLOCK_MONOTONIC, next); next.tv_nsec 1000000; // 1ms if (next.tv_nsec 1000000000) { next.tv_sec; next.tv_nsec - 1000000000; } while (1) { clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, next, NULL); // 这里塞EtherCAT数据收发 next.tv_nsec 1000000; if (next.tv_nsec 1000000000) { next.tv_sec; next.tv_nsec - 1000000000; } }这个写法保证每次唤醒的时间点都是基于同一个单调时钟计算的。内核的hrtimer会在请求的时间点精确唤醒实时线程只要线程没有被打断周期就是稳定的。实测下来改用绝对时间定时之后抖动的下限从几十微秒直接降到了个位数。同时线程属性也要设置好使用pthread_attr_setschedpolicy设置SCHED_FIFO优先级设为80到95之间太高可能把系统关键线程也饿死太低又会被中断线程抢走周期再用mlockall(MCL_CURRENT | MCL_FUTURE)把内存锁在物理内存里防止页面换出造成不可预期的延迟。4.3 中断亲和性与CPU隔离把干扰赶出实时核实时任务固定在CPU2/3上之后还必须处理中断归属问题。EtherCAT网卡中断、其它外设中断如果落在CPU2或CPU3上依然会随时打断实时线程。解决方法是把所有非实时必需的中断的affinity强制迁移到CPU0/1上同时把EtherCAT网卡所在的中断也挪到非实时核?——等等IGH实时网卡驱动在实时模式下接管网卡后其实并不走通用中断路径而是有自己的处理逻辑。所以这里重点是清理其他外设中断尤其是那些高频的定时器中断、USB中断、串口中断。查看中断分布cat /proc/interrupts找到高频率的中断源然后写中断亲和性echo 3 /proc/irq/irq_number/smp_affinity3表示只允许CPU0和CPU1处理该中断。这种操作比较琐碎建议写成启动脚本一次性执行完。另外IGH实时网卡驱动对中断处理的优先级也通过内核线程参数体现。IGH主站模块加载之后会生成类似IRQ-xx-ec_e1000e的内核线程或中断默认优先级已经比较高一般不用额外调整。如果实在遇到网卡中断和实时线程抢CPU的情况可以通过chrt命令手动提高IGH中断线程的优先级但要注意别高过实时线程否则易导致死锁。4.4 实测数据对比优化前后的差别我把自己最终压出来的数据做了一个简单对比表格给大家一个直观参考配置阶段平均抖动最大抖动说明非RT内核 普通线程150us以上2-3ms基本不可用RT内核 SCHED_FIFO 绝对定时10-20us60-100us可以用但不稳加上CPU隔离、中断亲和性、关调频、锁页3-8us20-30us满足1ms周期控制需求这个效果在1ms周期下相当于99.9%以上的周期偏差在30微秒以内。对大部分伺服控制和IO刷新来说已经非常够用。如果还想更狠可以把定时器频率和网卡的处理再细化。比如把IGH主站模块参数里的EtherCAT周期设成500us用sched_setaffinity把控制线程死绑在一个A76大核上再让IGH中断线程绑定到另一个空闲大核。这种一核负责收发、一核负责算的架构设计在很多高端运动控制器里是标准做法RK3588的8核资源完全撑得住。5. 常见问题与排查技巧实录5.1 主站加载失败网卡被其他驱动占用如果你执行modprobe ec_e1000e时报设备忙或者ethercat master显示No master found最常见原因是内核原生的e1000e驱动已经把网卡占用了IGH的实时驱动模块抢不到设备。排查思路# 查看网卡当前驱动 ethtool -i eth1 # 列出使用该网卡的内核模块 lsmod | grep -E e1000e|igb解决办法是把原生驱动从自动加载里去掉然后卸载它sudo rmmod e1000e如果原生驱动被其他服务依赖可能要先停掉NetworkManager或systemd-networkd。这一步建议放在IGH启动脚本的前置位置。我在实际项目里就因为这个折腾了一个晚上换了网卡也没解决最后发现是内核原生驱动先绑定了设备。5.2 从站扫描不到链路已连接但Slaves为空这个现象分两种情况如果链路状态是ACTIVE但没有从站信息大概率是从站XML缺失导致IGH无法识别设备如果链路状态显示DOWN那就先查物理层——网线、从站电源、从站是否处在OP状态。IGH对从站识别依赖SII从站信息接口里的Vendor ID和Product Code。如果你的从站没有预设这些信息IGH会提示Unknown device这时需要从厂商拿XML文件放到/opt/etherlab/etc/sii/重新加载主站或者重启从站电源再扫描。还有一种情况比较隐蔽总线上挂了混合从站其中某些从站不支持自动配置需要手动设置FMMU和SM通道。遇到这种设备建议先用单从站最小化系统测试逐步往总线上加节点能快速定位是哪个从站导致的扫描失败。5.3 抖动突然变大从50us飙到200us以上这种情况在调试中很常见而且往往不是EtherCAT本身的问题而是系统的某个角落发生了变化。我分析过几次最常见的原因有三个。第一个是CPU频率从performance变成了ondemand或schedutil某次重启之后启动脚本没执行。解决办法是把cpupower frequency-set -g performance加进开机自启。第二个是某个后台进程把CPU2/3的算力抢走了。用top -H看一眼有没有高优先级线程在跑也可以查看/proc/pid/status里的Cpus_allowed_list看看有没有线程的亲和性设置把实时核污染了。必要时直接在启动脚本里对非实时进程统一设置亲和性到CPU0/1。第三个是中断风暴。拔插USB设备、启用某些外设后中断频率会显著增高借着cat /proc/interrupts前后对比就能发现。把新高频中断的affinity写回CPU0/1即可。5.4 补充几个独家经验不要在实时线程里做动态内存分配、打印日志、加锁操作。日志打印可以用无锁的ring buffer交给非实时线程周期性地刷到文件。这些操作看着很小但一旦触发缺页或者锁竞争抖动立刻恶化。RK3588有几个型号的GPU和NPU驱动在加载时会默认打开中断或频繁触发DMA如果共享了中断号对实时任务的干扰会很严重。建议只保留当前应用必需的硬件模块把用不到的节点在设备树或模块黑名单里关掉。调试阶段建议在SD卡上刷一个独立的调试系统和量产系统的分区隔离开。实时内核在调试时容易panic如果只跑在eMMC里反复刷机既伤存储又浪费时间。最后再分享一点个人经验这套方案从原理到跑通我在实际项目中前前后后花了两周左右其中一半时间都耗在内核、驱动、中断这些“系统级”的问题上EtherCAT协议栈本身反而是最省心的部分。这也印证了一个体会在ARM SoC上做实时控制真正的难点不在于“业务逻辑怎么写”而在于“怎么把一个非实时的系统一点点调理成实时系统”。RK3588的算力和接口给足了发挥空间但最终能不能稳定跑起来拼的是对内核调度、中断路由和设备驱动的理解深度。如果你手头正好有RK3588板子先按照文章前面的步骤把RT内核和IGH跑通再回来对照第4章的调优手段逐项压抖动。每做一步就重新量一次数据你会发现优化的路径其实很清楚。等系统稳定之后再往上面叠加视觉、ROS、HMI这些应用那种“一个SoC扛下所有”的快乐确实很值。
返回列表