
1. 项目缘起为什么一个树莓派网关要谈实时做工业设备接入这件事做了这么多年M.2 网卡配树莓派这种玩法其实早就不是新鲜事但真正把它跑成实时以太网网关的人确实不多。前阵子我搭了一套基于 Raspberry Pi 5 和 M.2 工业级网卡的网关专门用来把现场设备侧的数据实时转发到上层平台顺便做 EtherCAT 主站和 Modbus TCP 的协议转换整体跑下来效果出乎意料地稳所以这篇文章想把整个方案从头到尾拆开讲一遍。标题里最容易被忽略的是Real-Time Ethernet这几个字。很多人会问树莓派本来就有网口插个 M.2 网卡有什么差别答案是差别很大。普通网卡跑 TCP/IP 协议栈走内核网络子系统延迟动不动就几百微秒甚至毫秒级抖动也没法控制。但工业实时以太网协议比如 EtherCAT、PROFINET IRT、EtherNet/IP 这些要求的是确定性的通信周期典型场景下 EtherCAT 的 DC 同步精度要求纳秒到微秒级别周期抖动也必须控制在可接受的范围内。普通软硬件架构在这种需求下是过不了关的。所以我在这套方案里做了三件事选一块支持 PCIe 的 M.2 网卡代替板载网口给树莓派装上带 PREEMPT_RT 补丁的实时内核再做网络栈级的调优。这三件事叠加起来网关的通信抖动才能从看运气变成可预测。这套东西适合谁如果你在做产线数据采集、设备联网改造、工业物联网边缘节点或者只是想用低成本硬件跑 EtherCAT 主站做原型验证那么这个方案很值得参考。整篇文章我会按照硬件选型、系统构建、网络调优、核心实现、问题排查这个顺序来写所有步骤都是我实际跑过的。2. 硬件选型M.2 网卡与树莓派的组合要点2.1 树莓派型号怎么选5 代是分水岭先说板子。 Raspberry Pi 4 和 CM4 其实也有 PCIe 接口但需要通过额外 HAT 转接而且 PCIe 带宽只有 Gen2 x1。到了 Raspberry Pi 5 这代板载 PCIe 2.0 x1 接口直接引出来了配合官方的 M.2 HAT插 M.2 网卡就变成了很顺理成章的操作。PCIe 2.0 x1 的单向带宽是 500MB/s 左右按 8b/10b 编码算实际可用大约 400MB/s这个带宽跑 2.5Gbps 网卡绰绰有余跑 10Gbps 网卡就不太够看所以正常情况下我推荐选 2.5G 或双千兆的 M.2 网卡。如果你手头是树莓派 4也不是不能用但要注意几个点CM4 可以通过 IO Board 引出 PCIe普通 Pi 4 主板想上 M.2 网卡需要接那种支持 PCIe 转接的 HAT稳定性取决于转接板的质量和供电。 Pi 5 加 M.2 HAT 的电气设计相对成熟少踩很多坑。我在实际项目里用的是 Pi 5 8GB 版本运行 Ubuntu 24.04 Server跑实时内核加 EtherCAT 主站内存占用大概 2GB 出头8GB 版本留给后续跑容器和边缘计算富余很多。如果预算紧张4GB 版本其实也够用但别买 1GB/2GB 的网关还要跑数据缓存和日志内存太小会频繁触发 swap实时性直接废掉。2.2 M.2 网卡有哪些选择M.2 网卡这个品类在消费级市场主要分两类一种是 M.2 Key AE常见于笔记本的 WiFi 网卡也有一部分是 WiFi蓝牙组合卡不适合做实时以太网。另一种是 M.2 Key M 或 BM这种接口上可以插 NVMe 固态也有厂商做 M.2 转多口以太网的扩展卡绝大多数是通过 Realtek RTL8111/RTL8125 或 Intel I225/I226 芯片实现的。我在选型时重点看了一眼芯片方案。 Intel I225/I226 在 Linux 下的驱动是 igc对 TSN时间敏感网络的硬件支持最好支持 802.1Qbv时间感知整形和 802.1Qbu帧抢占的部分硬件卸载能力这在后面做更深入的实时网络实验时非常关键。Realtek RTL8125 的驱动 r8169 其实也很成熟日常跑 EtherCAT 主站没问题但如果要做 TSN 硬件卸载RTL8125 的支持明显不如 Intel。另外还有一个非常值得留意的品类工业级的 M.2 实时以太网接口卡比如 Hilscher 的 netX 90 M.2 卡。这种卡不单纯是网卡它内部自带协议处理芯片可以把 EtherCAT、PROFINET、EtherNet/IP 等协议栈直接固化在硬件里主 CPU 只负责应用逻辑。这种卡的实时性和确定性表现远胜于纯软件方案当然价格也高得多。如果你做的是商业化产品而不是原型验证我建议重点评估这一类。我整理了一个选型对照表供你参考网卡方案芯片/型号驱动TSN 支持适用场景参考价位消费级千兆RTL8111Hr8169无普通 EtherCAT 主站原型偏低消费级2.5GRTL8125BGr8169基础较少数据采集、长报文传输中等消费级2.5GIntel I226-Vigc支持 Qbv/QbuTSN、EtherCAT 要求高场景中等偏高工业级多协议Hilscher netX 90专有完整商用品、多协议转换较高2.3 安装和供电的实操细节M.2 网卡装到 Pi 5 上除了 HAT 和螺丝固定之外有几个容易忽略的细节。第一是供电。 M.2 HAT 通常直接从 Pi 的 5V 供电取电单块消费级网卡功耗也就 2-3W问题不大但如果你同时挂 M.2 SSD 和网卡有些 HAT 支持双 M.2 位功耗叠加后可能超过 Pi 5 官方电源 5V/5A 的冗余范围最好实测一下。我用的是官方 27W USB-C 电源实测整机空闲功耗 5W 左右满载跑 EtherCAT 周期任务时大约到 8-9W余量充足。第二个细节是散热。 I226 这颗芯片虽然功耗不高但 M.2 卡在密闭外壳里温度很容易到 80 度以上而温度过高会触发 PCIe 降速或网卡丢包。我在 HAT 上加了一个 30mm 的涡轮风扇温度稳定在 55 度左右简单有效。强调一点做实时网络最怕的是热降频和丢包千万不要因为图省事把小散热片拆了。第三个细节是 PCIe 链路的稳定性。 Pi 5 默认 PCIe 速率可能在不同固件版本下有差异装好网卡后务必执行lspci -vv确认链路状态如果看到速率只有 Gen1 或 LnkSta 异常可以在/boot/firmware/config.txtUbuntu 下路径略有差异中加dtparampciex1_gen3或dtparampciex1_gen2强制指定。需要说明的是Pi 5 实际标称 Gen2硬拉 Gen3 不一定能稳定通常 Gen2 就够用。3. 构建实时系统从内核到用户态的准备工作3.1 操作系统选型与内核方案树莓派官方 Raspberry Pi OS 适合桌面应用跑网关和实时任务我建议用 Ubuntu Server 或者带实时的 Pi OS Lite。原因是实时内核的获取方式更清晰而且 Ubuntu 对 IoT 和协议栈的软件生态更友好docker、ThingsBoard Edge 这些上面都有现成包。实时性的核心在于内核。 Linux 内核默认的调度策略是通用的为了让实时任务优先执行、减少不可屏蔽中断的干扰需要打 PREEMPT_RT 补丁。T 这说简单也简单Ubuntu 上直接安装实时内核包就行不用自己编译。我用的命令是sudo apt update sudo apt install linux-image-rt-raspi sudo reboot重启后执行uname -r确认内核名带-rt后缀比如6.8.0-1010-raspi-rt这就说明实时内核已经生效。如果你手头内核版本比较特殊或者想自己编译带特定补丁的内核也可以走源码编译路线从 kernel.org 下载对应版本的内核源码和patch-*.patch实时补丁按常规流程打补丁后配置编译。这条路线比较费时间但好处是能针对你的板子把不必要的驱动全部裁掉启动更快中断更少。作为网关项目我建议优先用现成的-rt包。这里也解释一下为什么不用 Xenomai。 Xenomai 是另一种 Linux 实时化方案它引入了双内核机制和 Cobalt 核实时性上限确实比 PREEMPT_RT 更高。但在树莓派上我要么用 buildroot 交叉编译一套完整 Xenomai 镜像要么手工移植维护成本很高而且树莓派的 SoC 在缓存一致性方面对 Xenomai 并不友好实时中断路径上的性能收益会被内存访问延迟吃掉不少。 PREEMPT_RT 直接基于主内核软件生态兼容性最好对网关这种实时通信协议转换边缘计算混合负载来说是性价比最高的选择。3.2 用 cyclictest 验证实时性基线装完实时内核之后先别急着配协议栈先测一下板子的实时基线。Cyclictest 是 rt-tests 套件里的标准测试工具用来测量线程调度延迟sudo apt install rt-tests sudo cyclictest -m -n -S -p 90 -i 1000 -d 0 -q -D 600命令拆开看-m锁定内存防止换页-n使用 clock_nanosleep-S按 SMP 架构对每个核开测试线程-p 90设置实时优先级-i 1000表示测试周期是 1000 微秒-D 600跑 600 秒。实际跑出来的结果在未做任何调优的树莓派 5 上最大延迟通常在 100 到 200 微秒之间看起来不算很好但这只是基线。如果这个时候你就开始跑 EtherCAT 周期任务大概率会出现周期抖动过大甚至丢帧。别慌接下来的网络栈和内核参数调优会把最大延迟压到 30-50 微秒左右。我在测试时还习惯把结果存成 log 文件用histogram模式观察延迟分布的尾部sudo cyclictest -m -n -S -p 90 -i 1000 -q -h 1000 -D 600 /tmp/cyclictest_hist.txt尾部如果有特别大的孤立毛刺去找中断源常见的是 WiFi 模块、蓝牙、USB 设备这也是我为什么在网关方案里建议直接禁用 WiFi 和蓝牙。4. 网络栈实时化几个立竿见影的调优动作4.1 CPU 隔离与中断亲和性实时任务最怕两件事被其他进程抢 CPU以及被中断风暴打断。第一步先把 CPU 隔离出来专门给实时任务和网络中断用。在/boot/firmware/cmdline.txtPi OS或/boot/firmware/cmdline.txtUbuntu on Pi的内核引导参数里追加isolcpus3 irqaffinity0-2 nohz_full3 rcu_nocbs3含义是这样的CPU0-2 跑系统进程和普通业务CPU3 专门跑实时任务irqaffinity0-2把中断都指定到非实时核避免频繁中断打断 CPU3nohz_full3让 CPU3 上关闭周期 tick减少定时器中断rcu_nocbs3把 RCU 回调从 CPU3 卸掉。这套组合拳打完CPU3 上的调度延迟会显著下降。第二步是把网卡的中断也固定到指定的核上。先看当前网卡中断号grep eth0 /proc/interrupts然后用irqbalance或手动写/proc/irq/irq/smp_affinity来指定中断亲和性。我通常直接把网卡中断绑到 CPU2这样 CPU3 完全不被网络中断打扰echo 4 /proc/irq/$(grep eth0 /proc/interrupts | awk -F: {print $1})/smp_affinity这里4是二进制100表示 CPU2。改完可以再观察/proc/interrupts里中断计数的分布确认中断不再落到 CPU3 上。4.2 网卡参数关闭合并、加大环形队列实时以太网的帧非常密集EtherCAT 一个周期就是一堆小帧如果网卡开启了中断合并interrupt coalescing软件层面必须等攒够一批帧才发中断延迟和抖动都会急剧恶化。所以调优的第一步是用 ethtool 把这些关闭sudo ethtool -C eth0 rx-usecs 0 tx-usecs 0 sudo ethtool -C eth0 rx-frames 0 tx-frames 0 sudo ethtool -K eth0 napi-hw-cache off关闭合并后中断频率会大幅上升这时候要保证环形队列足够深不然容易在突发流量下丢包。我一般把 RX/TX ring 都调大sudo ethtool -G eth0 rx 4096 tx 4096另外还要把网卡的节能特性关掉。EEEEnergy Efficient Ethernet是省电特性但它会在低流量时让 PHY 进入低功耗模式唤醒需要额外时间这属于隐藏的抖动源sudo ethtool --set-eee eth0 eee off最后检查一下网卡的 offload 设置。对于 EtherCAT 这种小帧密集场景巨型帧jumbo frame和 TCP offload 通常用不上但 TSO/GRO 这类合并卸载要谨慎。EtherCAT 主站数据一般走 raw socket不太受这些影响但如果你在同一块网卡上同时跑 TCP 业务还是建议把 GRO 关掉避免接收路径上的延迟聚合效应sudo ethtool -K eth0 gro off gso off tso off4.3 内核参数与调度策略打开/etc/sysctl.conf追加这几条。第一个是允许实时任务消耗 100% CPU 时间默认sched_rt_runtime_us是 950000含义是每 1 秒里实时任务最多占 950 毫秒剩下 50 毫秒强制让给普通任务。对网关这种常驻实时进程来说直接放开kernel.sched_rt_runtime_us -1第二个是关闭 NMI watchdog它本身是一个周期性高优先级中断会带来可观的延迟毛刺kernel.nmi_watchdog 0第三个是增加内存锁定限制让实时进程可以放心用mlockall锁定内存避免页错误导致的延迟vm.swappiness 10在应用层面实时主站进程需要显式切换到实时调度策略和优先级。用chrt可以临时测sudo chrt -f -p 80 $(pgrep ethercat_master)SCHED_FIFO 优先级范围是 1-99我选 80 的原因是要给内核的软中断处理留一点空间不要高到 99 把内核 RT 线程都堵死。调优完成后记得再跑一次 cyclictest对比前后数据。我实测的日志显示最大延迟从 132 微秒降到了 41 微秒平均延迟稳定在 10 微秒以内作为 EtherCAT 主站来说这个水平已经足够支撑 1ms 甚至 500us 的周期任务。5. 核心实现把 M.2 网卡跑成实时以太网主站5.1 EtherCAT 主站为什么不需要专用硬件很多人第一次接触 EtherCAT 会有一个误解觉得主站也得用专用卡。其实 EtherCAT 的帧格式就是普通的以太网帧EtherType 是0x88A4主站侧理论上只需要一块能够收发 EtherType 帧的标准网卡协议处理完全可以在 CPU 上完成。真正需要专用芯片的是从站每个从站里有一颗 ESCEtherCAT Slave Controller芯片在硬件层面做帧的实时转发和数据处理。所以树莓派 普通网卡做主站是完全可以的这块 M.2 网卡的优势在于低延迟收发和稳定的驱动而不是因为它能理解 EtherCAT。明白这个原理后实现路径就很清晰了用 raw socket 直接收发 Echo 帧或者用现成的开源主站库。我推荐这两条路线里选 SOEMSimple Open EtherCAT Master它轻量、代码结构清晰而且不需要内核模块纯用户态网络栈直接跑非常适合网关这种需要频繁升级协议逻辑的场景。IgH EtherCAT Master 是另一个选择它跑在内核态实时性上限更高但它依赖内核模块和导出的字符设备接口升级内核时要重新编译适配维护成本偏高。对于这个 M.2 RPi 网关项目我最终选择 SOEM理由主要有三点一是纯用户态代码便于和业务逻辑集成二是遇到驱动问题不用折腾内核模块三是 SOEM 自带 SOES 从站库如果以后要用树莓派模拟从站做测试也能直接复用。5.2 SOEM 主站的搭建步骤SOEM 的编译很简单git clone https://github.com/OpenEtherCATsociety/SOEM.git cd SOEM mkdir build cd build cmake .. -DBUILD_TESTSON make sudo make install安装后先跑一下自带的简单测试用simple_test枚举总线上的从站并读取基本信息。注意 EtherCAT 总线必须是链式拓扑不能经过普通交换机。现场如果测试时把 EtherCAT 从站链直接插到了交换机端口上主站绝对扫描不到任何从站这一点必须牢记。实际工程里我写了一个主站循环核心代码逻辑大概是这样#include soem/ethercat.h ec_master_init(); ec_config_init(FALSE); // 枚举总线 ec_config_map(io_map); // 建立 PDO 映射 ec_configdc(); // 配置 DC 时钟同步然后在周期循环里while (run) { clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, next_cycle, NULL); ec_send_processdata(); ec_receive_processdata(usec_timeout); next_cycle.tv_nsec CYCLE_NS; // 在这里处理数据映射并与上层协议交换 }clock_nanosleep使用TIMER_ABSTIME模式是关键它基于绝对时间点休眠不会触发每次循环结束再等一个周期的累积误差。我在代码里用CLOCK_MONOTONIC而不是CLOCK_REALTIME也是为了避免系统时间被 NTP 或手动校时干扰导致周期跳变。5.3 网关协议转换把实时数据变成业务能用的数据主站跑起来后网关的真正价值在于数据流动。我在这套方案里做的路由是EtherCAT 总线上接了几个分布式 IO 从站和一个伺服驱动器主站以 1ms 周期采集 IO 状态和伺服的位置、速度、状态字然后把这些数据做三件事第一通过 MQTT 发布到本地 brokerMosquitto上层边缘平台订阅后做数据展示和报警。用 Paho MQTT C 库发布频率不用太高100ms 聚合一次足够避免高频连接对 broker 造成的压力。第二把部分关键数据通过 Modbus TCP 提供给老旧的 SCADA 系统。 Modbus 寄存器映射表我单独做成了一份配置 JSON上层要改地址不用重新编译主站程序也算是在网关设计里做了一个简单可配置化。第三写入本地的 SQLite 数据库做断点缓存。现场网络抖动是常态如果 MQTT broker 不在同一台设备上网络断开期间的数据必须能够本地缓存恢复后再补发。这个机制在工业现场很实用我们遇到过现场光纤被叉车碰断的情况网关端两个小时的数据一条没丢恢复后在 30 秒内全部补传完毕。网关的协议转换代码我放在一个独立进程里与 EtherCAT 主站进程通过共享内存环形缓冲区通信这样即使业务侧进程崩溃重启也不会影响实时周期的稳定性。这是我在经历了多次业务进程 OOM 把主站拖死的教训后总结出的架构红线实时链路和业务链路必须隔离。6. 问题排查与实战经验速查6.1 定时任务出现周期性延迟尖峰这个是最常见的问题。现象是 cyclictest 或 EtherCAT 周期耗时每隔固定间隔就会出现一个几十到几百微秒的尖峰。我排查的思路是先看powertop和turbostat确认 CPU 频率调节器是否处于 performance 模式sudo cpupower frequency-set -g performance第二步看定时器和中断。用trace-cmd或perf sched记录调度事件通常能定位出是谁抢占了实时线程。在我这个项目里最后查到是两个元凶一个是树莓派内置的 WiFi 模块哪怕没连接任何网络它的扫描中断也是周期性触发的另一个是蓝牙模块。解决方案是在 config.txt 里直接屏蔽掉dtoverlaydisable-wifi dtoverlaydisable-bt关掉之后延迟毛刺立刻消失。后来我把这块板子装箱当工业网关部署就再没用过无线功能实测非常稳定。6.2 M.2 网卡在 PCIe 总线上不识别这个坑出现在安装后第一次开机。现象是lspci能看到网卡但ip link里没有 eth 口。排查步骤依次是先确认 PCIe 链路状态lspci -vv看 Status 里是否出现 Unsupported Request 或 Completion Timeout然后检查驱动是否加载lsmod | grep igc没有的话手动modprobe最后检查固件版本。我遇到的情况是 M.2 HAT 的供电不够网卡需要额外 3A 的 3.3V 供电而部分便宜的 HAT 只提供 1.5A导致网卡上电失败。解决方法是换用官方 HAT 或者带独立供电的转接板同时确认电源适配器余量足够。另外提醒一下Pi 5 的 PCIe 端口对热插拔支持不佳务必断电后再插拔 M.2 卡我见过有人带电插拔直接把网卡烧了。6.3 EtherCAT 周期不稳定或从站偶发掉线如果主站扫描正常但跑一段时间后从站掉线先看日志的丢帧计数。 SOEM 的ec_receive_processdata返回值如果持续为 0基本可以确认是帧丢了。原因排查优先级一是网线质量问题。 EtherCAT 对线序和屏蔽要求很高普通的网络跳线在长距离和强干扰环境下丢帧明显建议用带屏蔽的工业以太网线接线端子压接要到位。二是 M.2 网卡的流控和电源管理设置。把能源效率以太网关掉后故障率能下降不少。我实验对比过开启 EEE 时偶发丢帧概率大约是关掉后的十倍这对实时通信来说是不可接受的。三是检查是否跨越了交换机。 EtherCAT 帧不能穿越标准交换机因为从站之间的转发是硬件直通交换机加缓冲处理会破坏帧的时序。如果现场必须走更长距离可以考虑光电转换器但一定要确认转换器是透传模式。6.4 网关 Web 控制台报 502 Bad Gateway最后聊一个和标题里Gateway直接相关的题外话。很多基于树莓派的网关都会跑一个 Web 控制台比如 ThingsBoard Edge、Node-RED、Grafana 之类的服务。有次我配置完 ThingsBoard Edge 后打开管理页面一直报 502 Bad Gateway浏览器控制台里写着 unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:1572。502 的本质是反向代理Nginx/Caddy转发请求到后端服务时后端没有在超时时间内给出响应。排查方向一般是这样先看后端进程systemctl status thingsboard是否起来了如果起来了就看日志里有没有端口绑定失败再确认代理配置文件里的 upstream 地址和端口有没有写错。结合 127.0.0.1:1572 这个特征基本能断定为后端服务绑定到了 IPv6 的 localhost::1而代理转发到了 IPv4 的 127.0.0.1或者反过来。解决方法是统一代理配置和后端监听地址全部改成127.0.0.1并用一致端口。这个坑和网关本身没关系但在树莓派部署多服务场景里极其常见顺手记录一下希望能帮大家少走弯路。最后再分享一个我个人的体会。这套 M.2 RPi 网关方案从硬件到软件全部跑通之后最值钱的不是性能数字本身而是它让我意识到实时以太网网关的门槛其实正在被低成本硬件大幅拉低。以前做 EtherCAT 主站要买几千上万的运动控制卡现在一块几百块的树莓派加一张二三百的 M.2 网卡就能跑出可用的原型这对小团队做产品验证是很大的红利。如果你要照着这个方案往下做我的建议是先别急着上完整业务花两天时间把实时内核、中断亲和性、网卡调优这三层地基打好再跑 EtherCAT 主站后面的开发会顺畅很多。至于 M.2 网卡选 Intel 还是 Realtek看你对 TSN 有没有需求没有的话 RTL8125 的性价比就挺好。踩过的坑我都写在上面了照着排查能省不少事。