惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
K
Kaspersky official blog
V
Vulnerabilities – Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
I
Intezer
TaoSecurity Blog
TaoSecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Spread Privacy
Spread Privacy
A
About on SuperTechFans
NISL@THU
NISL@THU
The GitHub Blog
The GitHub Blog
Hugging Face - Blog
Hugging Face - Blog
S
Security @ Cisco Blogs
S
SegmentFault 最新的问题
G
Google Developers Blog
B
Blog
N
News and Events Feed by Topic
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
V2EX - 技术
V2EX - 技术
V
Visual Studio Blog
MyScale Blog
MyScale Blog
Webroot Blog
Webroot Blog
Vercel News
Vercel News
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
S
Security Affairs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
博客园 - 叶小钗
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
T
Tailwind CSS Blog
F
Fortinet All Blogs
D
DataBreaches.Net
博客园 - Franky
博客园_首页
H
Heimdal Security Blog
宝玉的分享
宝玉的分享
阮一峰的网络日志
阮一峰的网络日志
Attack and Defense Labs
Attack and Defense Labs
Project Zero
Project Zero
雷峰网
雷峰网

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
【原创】如何利用网卡TSN硬件特性实现EtherCAT 确定性发帧与 DC 同步?
沐多 · 2026-06-18 · via 博客园_首页

【原创】如何利用TSN硬件特性实现EtherCAT 确定性发帧与 DC 同步?

版权声明:本文为博主原创文章,转载请注明出处 https://www.cnblogs.com/wsg1100 。如有错误,欢迎指正。本文以 Rockchip RK3576/RK3588 + Synopsys DWMAC(stmmac)+ IgH EtherCAT Master 为例,结合 Linux 6.1 内核与 IgH 源码讲解,代码行号基于代码仓库实际文件。

0. 前言

最近看到 Rockchip 最新官方 SDK 里竟然包含了 EtherCAT 主站,而且还把 stmmac 网卡驱动一并适配好了——RK 目前典型的内核版本 4.19、5.10、6.1 都提供了对应的网卡驱动。不过真正让我眼前一亮的,不是它开源了主站网卡驱动,而是发现他们居然也用上了网卡的 TSN 特性

源码详见https://gitee.com/wsg1100/ethercat_igh 来源瑞芯微开放SDK,仅供参考

这其实是我一直想动手实现的一个方案:借网卡里的 TSN 机制,把 EtherCAT 主站的发包抖动压下去。最早接触到这个思路是在 2021 年——当时我在一家德国自动化设备厂商的 EtherCAT 主站源码里看到,他们用 Intel i210/i211 系列网卡,借助 Launch Time 机制做到了 EtherCAT 过程数据帧的"无抖动"发送(工业自动化及工业实时以太网方面他们绝对的领先,咱都是玩别人几十年前推出的东西)。那套主站不是 IgH,运行在一个小型 RTOS 上、该RTOS作为Windows的一个内核模块和 Windows 内核共享内核空间,运行在一个独立的CPU上(这是 Windows 实时化的常见方案之一)。

由于那是 x86 + i210,而本文是 ARM + stmmac,两者支持的 TSN 特性并不完全相同,所以具体实现和 Rockchip 这套方案有所差别;但底层原理是一脉相承的。本文就结合 Rockchip 的源码,把这套"用 TSN 给 EtherCAT 主站做硬件级确定性发包"的技术细节讲清楚——它怎么选时钟源、EST/Launch Time 怎么一路下发到 MAC 寄存器、DC 又怎么把全网时钟拉齐。下面开始详细拆解。

1. EtherCAT 工业以太网与实时性要求

实时的本质是确定性、可预期性——不仅要算得对,还要在设置的截止时间内把结果交出去,一切造成时间不确定的因素都是实时性的敌人。

工业以太网有一个核心矛盾:它试图把一根为"尽力传递(best-effort)"而生的管道,改造成一条"绝不丢包、绝不迟到"的生命线。办公室网络丢一帧只是画面卡顿,工厂里丢一帧或晚到一帧,可能就是一次撞机、一条人命。所以这里确定性比带宽更 sacred——确定性优先于吞吐量,宁可牺牲带宽,也不能牺牲时序

几个观念先立住:

  • "实时"不是绝对概念,而是一份关于"足够晚即为失败"的契约。 不是"越快越好",而是"在 deadline 之前必须完成"。
  • 网络的本质不是连接设备,而是同步行为;时钟比数据包更重要。 时间是工业以太网里隐藏的货币,所有性能指标最终都能折算为时间。
  • 终极追求:把以太网的"不确定性"变成"有界不确定性"。 性能不是一条线,而是一条确定性边界——边界内是安全区,边界外是不可预测区,设计目标就是把这条边界尽量推宽。

EtherCAT(Ethernet for Control Automation Technology)就是在这套观念下诞生的、工业现场总线里对实时性要求最高的协议之一,典型周期要做到 1 ms 甚至 62.5 µs,抖动要压到微秒乃至亚微秒级。在 RK3576 这类通用 ARM SoC 上跑 EtherCAT 主站,确定性不是某一个"神奇硬件"给的,而是一整套把不确定因素一层层挤干净的设计。下面先把通信模型和实时性要求理清楚,这是后面看源码的基础。

1.1 EtherCAT 通信模型:一帧穿环、SYNC0 前必须送达末从站

EtherCAT 的通信模型和普通以太网很不一样,一句话概括:主站发一个以太网帧,这个帧在一根线上"穿"过所有从站,每个从站在帧经过时原地读写自己的数据,帧绕完一圈回到主站。

几个关键点:

  • 一帧搞定一个周期。主站把一个周期内对所有从站的读/写数据,打包成一个以太网帧(里面是多个数据报 datagram),不需要像传统以太网那样逐站收发。
  • 硬件级"穿透"处理。帧经过每个从站的 ESC(EtherCAT Slave Controller)时,由硬件在帧流过的同时原地取出/写入数据,没有存储转发延迟,每个从站只增加百纳秒量级的硬件延迟。
  • SYNC0 前必须送达末从站。DC(分布式时钟)让所有从站在同一时刻产生 SYNC0 中断、同时锁存输出。硬约束是:本周期的数据帧必须先穿行到总线上的最后一个从站,SYNC0 才能触发。 否则末端的从站拿不到新数据就动作,整个同步就破了。

这里要破两个常见的认知陷阱:"全双工以太网没有冲突,所以没有延迟问题"——错。 没有了冲突,取而代之的是交换机内部的排队延迟和调度开销;而"千兆比百兆快,所以实时性更好"也是错——带宽大不等于延迟低,实时性由调度机制决定,不由管道粗细决定。EtherCAT "一帧穿环、硬件穿透"的设计,恰恰是为了绕开普通以太网逐跳交换带来的排队延迟和调度抖动。

下图把一个周期的时间预算、帧穿行过程、SYNC0 触发约束画在一起,先看图,再看后面的源码就会很顺。

image-20260618221215533

EtherCAT 通信周期预算与 SYNC0 时序约束

1.2 实时性体现在哪三个维度

把 EtherCAT 的实时性拆开,其实是三个相互独立又相互制约的维度:

① RTOS 抖动(操作系统调度抖动)。主站的周期任务要靠操作系统定时唤醒,从"该唤醒"到"真正被调度运行"中间的偏差就是抖动。来源包括内核抢占延迟、中断响应延迟、其他驱动/线程抢 CPU、缺页、cache miss 等。不打实时补丁的通用 Linux 抖动可达几十上百微秒;上了 PREEMPT-RT、做好 CPU 隔离能压到几微秒。顺带一个跨领域的共鸣:RTOS 的调度理论与网络的时间调度,是同一个数学问题的两个实例——资源有限、时间有界、优先级驱动。EtherCAT 主站的周期任务跑在 RTOS 上,OS 调度抖动和网络调度抖动其实是同一类敌人。

② 网络收发确定性。主站周期到点后,要把帧交给网卡发出去、把上一帧的回包收回来。普通网卡路径(硬中断 → NAPI 软中断 → 协议栈 → skb)每一步都带不确定性,可以用洋葱模型理解:以太网是内核,外面依次裹着 MAC 层、协议层、应用层,每一层都在试图修补内层的不确定性,但每一层又自带抖动。EtherCAT 要的是"周期到点,立刻收发完"——它的办法是干脆绕过这些层(见第 3 节)。

③ 多时钟同步性能。一条总线几十台从站(伺服、I/O)要在同一纳秒级时刻动作,靠 DC 把所有从站本地时钟对齐。多轴伺服协同运动的相位一致性,完全由这一维决定。时钟同步建立的是一棵时钟树——根是参考时钟(grandmaster),枝叶是从时钟,任何节点的漂移都是整棵树的病;而且不仅要频率对齐,还要相位对齐:频率是"跑多快",相位是"起跑线在哪"。再破一个陷阱:"PTP 同步后所有设备时间就一致了"——错。同步精度受级联层数、线缆不对称、交换机内部延迟影响,理论亚微秒,现场可能几微秒——这正是 EtherCAT DC 要逐从站补偿传输延迟的原因(见第 4 节)。

1.3 通信周期与从站数、数据量、传输时延、OS 抖动的关联

回到 1.1 的预算图,一个最小可行的周期必须满足:

周期 T  ≥  T_收上帧回包  +  T_软件处理发送  +  T_往返穿行  +  T_余量
  • T_往返穿行从站数量线缆长度线性增长。线缆约 5 ns/m;每个从站 ESC 百纳秒量级。50 台从站 + 几十米线,往返就可能占到十几到几十微秒。
  • 数据量:一帧装多少数据报受以太网帧长限制,从站越多、PDO 越大,单帧越大,组帧和 DMA 处理越久;装不下还要分多帧。
  • T_软件处理发送受 OS 抖动影响。
  • T_余量是吸收抖动的安全垫。OS 抖动越大,余量必须越宽,能用的周期就越长(频率越低)。

朴素结论:从站越多、数据越大、线越长、OS 抖动越大,能稳定跑的周期就越长。 想缩短周期,就要在这四项里挤。

还有两个思维模型要带在身上:一是 WCET(最坏执行时间)——工业网络不关心"平均有多快",只关心"最慢有多慢",永远为最坏情况设计,不要为平均情况设计,抖动的尾巴会杀死你;二是 抖动累积——每经过一级交换机/从站,抖动都会加一点,级联越多抖动越大,这是拓扑深度的隐藏成本。所以评估周期能不能站稳,看的是边界(最坏情况下稳不稳),不是平均值。

1.4 极短周期要考虑哪些因素

125 µs/250 µs(8/4 kHz)是高性能应用(高速伺服、力矩控制)的常见目标。这个尺度下预算被极限压缩,余量趋零,任何一项超标都会超时:

  • 总线短、从站少,把 T_往返穿行压到几微秒以内;
  • PDO 精简,单帧尽量小;
  • 硬件级发送(EST/TBS),让帧在精确时刻由网卡硬件上线,不依赖 OS 调度;
  • 硬实时内核(PREEMPT-RT/Xenomai);
  • CPU 绑核隔离,关中断、关节能、关频率动态调节;
  • DC 强同步shift_time 精确卡在"帧到达末从站之后"的最小安全点。

这里最难压、也最关键的是OS 抖动和软件发送抖动——这正是 TSN 硬件要解决的,也是本文第 3、4 节源码走读的主线。

最后强调一句铁律:循环周期(cycle time)一旦确定,就是铁律,任何超时都是故障。 125 µs 这条确定性边界能不能站稳,全看上面这些因素压得够不够干净——这也是第 4 节 Rockchip 要把"发送时刻"下沉到 PHC 硬件的根本动因。

这里不得不提一个EtherCAT低周期通信的缺陷,因为EtherCAT通信总是由主站发起,发送一帧后要经过所有从站后返回,假设有这样一个工况,主站需要在一个周期内完成读输入---计算---写输出三个步骤,并且计算使用的是本周期读输入的数据,这时候读输入需要主站发帧进行读取,帧需要经过所有从站后传输回主站,传输时间就会成为瓶颈,虽然网络是全双工的,但是数据交互只能主站发起相当于半双工,加上100Mbps的传输速率,导致这种工况做不到极低的通信周期。

作为对比,PROFINET IRT就没有这个问题,网络配置主站可以配置从站的发送时间,相比EtherCAT PROFINET IRT就可以省下不少输入输出帧传输时延,进而实现更低的通信周期。

2. TSN 硬件特性:EST 门控、Launch Time 及可用于 EtherCAT 的机制

传统以太网会采用载波侦听多路访问/冲突检测(CSMA/CD)的机制,当两个工作站发生冲突时,必须延迟一定时间后重发报文。发生堵塞时,有的报文可能长时间发布不出去,造成通信时间的不确定性。以往对实时性要求高的数据通过实时以太网去实现。故现在信息技术和运营技术融合过程中会遇到很大的困难,为了实现部分数据传输的实时、确定性需求,有实时性要求的数据和没有实时性要求的数据往往需要通过两个网络进行传输。所有的控制器都是两个网口,一个是实时以太网,一个是标准以太网。而TSN不仅能确保数据的实时、确定性传输,还能实现时间敏感数据和非时间敏感性数据在同一网络的传输。

TSN通过一套协议标准(TSN协议族)来实现数据在同一网络的实时、确定性传输,保证对实时性要求高的信息在标准以太网的不同场景下的顺利传输。TSN协议族本身具有很高的灵活性,用户可以根据应用的具体需求来选择相应的协议组合。TSN协议族包含了时钟同步、数据调度及流量整形、可靠性、资源管理这四个类别的子协议。

tsn_ov

TSN(Time-Sensitive Networking)是 IEEE 802.1 给以太网加的一组"时间敏感"特性。和 EtherCAT 解决的是同类问题,但走"对等式以太网 + 硬件调度"路线。其中 EST 门控和 Launch Time 可以直接增强 EtherCAT 的确定性收发。

注意:TSN只统一了L2,L3到L7的割据会继续。统一了铁轨,没有统一火车。

2.1 EST 门控(taprio)

EST 是 Synopsys DWMAC 对 IEEE 802.1Qbv TAS(Time-Aware Shaper) 的硬件实现,在 Linux 里对应 taprio qdisc offload。原理:

  • 软件事先下发一个门控列表 GCL(Gate Control List)到 MAC 的 EST 硬件寄存器;
  • 每个表项 <gate_mask, interval>:在 interval 纳秒内,哪些 TX 队列"开门"(gate_mask 对应 bit=1);
  • 硬件以 base_time 为起点、按 cycle_time 周期循环执行;
  • 门关着的队列,帧在 MAC 内部被挡住,直到下一段开门窗口才上线。

对 EtherCAT 的意义:把 EtherCAT 流量映射到某个高优先级队列,给它预留独占发送窗口,软件入队抖动被 MAC 内部缓存吸收。RK demo 就是这么用的(gate_mask=0x2 开 queue1 给 EtherCAT 800 µs、0x1 开 queue0 给其它流量 200 µs)。

2.2 Launch Time / TBS(ETF)

Launch Time 对应 802.1Qbv 的 Launch Time 机制,Linux 用 ETF qdisc offload,stmmac 里叫 TBS。比 EST 更精细:不是开/关整个队列,而是给单个发送描述符打一个"预约发送时刻",网卡硬件在 PHC 走到该时刻时才把这一帧送上 MII。配合 IgH 发送时把 app_time 写进 skb->tstamp(见第 4 节),等于让每帧都在期望的绝对时刻上线。

2.3 这两个特性能解决第 1 节里的哪些因素

第 1 节的影响因素 EST 门控 Launch Time/TBS
① OS/调度抖动(发送时刻漂移) ✅ MAC 缓存吸收,窗口内准时发 ✅ 精确到 PHC 时刻发单帧
② 网络收发确定性(被其他流量干扰) ✅ 给 EtherCAT 队列独占窗口 ✅ 单帧预约不被抢占
周期下限(发送抖动吃余量) ✅ 抖动收敛到 ~10 ns ✅ 同
③ 多时钟同步(DC) ⚪ 不直接,但需 PHC 同步 ⚪ 同

一句话:EST/Launch Time 主要解决"②网络收发确定性"和"发送抖动吃周期余量",把发送时刻从 OS 调度域下沉到网卡 PHC 硬件域。代价是要先把 PHC 和系统时钟对齐(第 4 节)。

2.4 哪些网卡支持 TSN(按内核驱动实测)

那怎么知道一张网卡到底支不支持 EST 门控 / Launch Time?最可靠的依据是看它的内核驱动有没有在 ndo_setup_tc 里实现对应的 qdisc offload。在本仓库内核源码 drivers/net/ 里检索这几个符号即可定位:

  • TC_SETUP_QDISC_TAPRIO —— EST 门控(802.1Qbv TAS,2.1 节)
  • TC_SETUP_QDISC_ETF —— Launch Time / TBS(2.2 节)
  • TC_SETUP_QDISC_ETS —— ETS 带宽分配(802.1Qaz)
  • TC_SETUP_QDISC_CBS —— CBS(802.1Qav)

命名易混:Synopsys 文档里的 EST(门控) 在 Linux 里叫 taprioTC_SETUP_QDISC_TAPRIO);而 TC_SETUP_QDISC_ETS 是 802.1Qaz 的带宽分配(Enhanced Transmission Selection),是另一个特性,别和 EST 门控混为一谈。

下表是对 kernel/drivers/net/ 检索 TC_SETUP_QDISC_* 的实测结果(✓ = 该驱动的 ndo_setup_tc 实现了对应 offload):

芯片 内核驱动 / 平台 taprio(EST 门控) ETF(Launch Time/TBS) CBS ETS(带宽)
Synopsys DWMAC(Rockchip RK356x/RK3576/RK3588、Allwinner 等) stmicro/stmmac
Intel I225 / I226(2.5G) intel/igc
Intel I210 / I350 intel/igb
NXP LS1028A Layerscape(ENETC) freescale/enetc
TI Sitara AM65x/AM64x(CPSW) ti/am65-cpsw ✓¹
Microchip LAN966x microchip/lan966x
Microchip Sparx5 交换 microchip/sparx5
TSNEP(通用 TSN,多见于 FPGA/评估板) engleder/tsnep
Mellanox/Nvidia Spectrum 交换 mellanox/mlxsw
TSN 交换芯片 DSA 交换:sja1105(NXP)、hellcreek(Hirschmann)、felix_vsc9959(VSC9959) ✓²

¹ TI 老一代 ti/cpsw_priv.c 另有 CBS;² sja1105、felix 有 CBS,hellcreek 仅 taprio。

对 EtherCAT 最关键的两项是 ETF(Launch Time/TBS,逐帧预约发送)和 taprio(EST 门控,独占窗口)。在本内核里,二者都支持的以太网控制器只有三个:stmmac(DWMAC)、igc(I225/I226)、enetc(NXP LS1028A)——本方案的 Rockchip DWMAC 正是其中之一。igb(I210)有 Launch Time 但没有 taprio 门控 offload。

说明:FPE(帧抢占,802.1Qbu)不通过上面这几个 qdisc 符号暴露(由 taprio 的 SET_AND_HOLD/RELEASE 或独立接口控制),故未列入上表。选型时另需确认芯片暴露了 PHC(/dev/ptpX)——本方案的 Rockchip DWMAC(stmmac)taprio + ETF + CBS + PHC 齐全。

3. 当前 IgH 是如何保证网络收发确定性的,有哪些缺点

IgH EtherCAT Master 是 Linux 上最主流的开源 EtherCAT 主站。它不依赖任何 TSN 硬件,靠一套"绕过内核网络栈 + 用户态周期调度"做出确定性。这一节我们顺着源码走一遍它的收发路径。

3.1 接管网卡:ecdev 与 ec_poll

普通网卡的收发路径要经过:硬中断 → NAPI 软中断 → netif_receive_skb → 协议栈 → socket,每一步都带不确定性。EtherCAT 主站要的是"周期到点,立刻收发完",所以必须绕开整个内核网络栈

IgH 的做法是在 stmmac 驱动里塞一个 priv->ecdev 指针,凡是为普通网络栈准备的代码,统统加 if (!priv->ecdev) 跳过。我们来看它的代码:

/*devices/stmmac/stmmac_main-6.1-ethercat.c:7447*/
priv->ecdev = ecdev_offer(priv->dev, ec_poll, THIS_MODULE);
if (!priv->ecdev) {
    ret = register_netdev(ndev);   /*普通网卡才注册到内核网络栈*/
}
if (priv->ecdev) {
    ret = ecdev_open(priv->ecdev); /*EtherCAT 模式由主站打开*/
}

ecdev_offer 接管后,这张网卡不会 register_netdev,对 ifconfig/ip 不可见,也没有协议栈。那 EtherCAT 主站怎么收发?靠 ec_poll

/*devices/stmmac/stmmac_main-6.1-ethercat.c:5588*/
void ec_poll(struct net_device *netdev)
{
    stmmac_interrupt(netdev->irq, netdev);
}

ec_poll 只是 stmmac_interrupt 的薄封装。主站每个周期在 ecrt_master_send/receive 时主动调用它。接着往下看 DMA 中断处理里 EtherCAT 模式是怎么收发的,stmmac_napi_check 里有一个关键分叉:

/*devices/stmmac/stmmac_main-6.1-ethercat.c:2800-2820*/
if (!priv->ecdev && (status & handle_rx) ...) {
    ... __napi_schedule(rx_napi);          /*普通网卡:扔给软中断*/
} else if (priv->ecdev && (status & handle_rx)) {
    stmmac_rx(priv, 64, chan);             /*EtherCAT:当场扫 64 个描述符*/
}
if (!priv->ecdev && (status & handle_tx) ...) {
    ... __napi_schedule(tx_napi);
} else if (priv->ecdev && (status & handle_tx)) {
    stmmac_tx_clean(priv, 64, chan);       /*EtherCAT:当场回收*/
}

这两者的区别就在这里:普通网卡走 NAPI 软中断调度,EtherCAT 模式当场把 RX/TX 描述符一次扫完,不经过软中断、不经过 NAPI 调度延迟。而且 EtherCAT 模式根本不注册中断__stmmac_openif (!priv->ecdev) stmmac_request_irq(...)),DMA 中断是关闭的,全靠主站 poll。这就把"中断响应抖动 + 软中断调度抖动"两大不确定源去掉了。

总体框图:三层时钟、两条数据通路

3.2 一帧是怎么发出去的、上一帧是怎么收回来的

先看发送。主站组装好一帧后,调用 ec_device_send

/*master/device.c:331*/
void ec_device_send(ec_device_t *device, size_t size)
{
    struct sk_buff *skb = device->tx_skb[device->tx_ring_index];
    skb->len = ETH_HLEN + size;
    skb->tstamp = ns_to_ktime(device->master->app_time);   /*记住这一行,第 4 节要用*/
    device->dev->netdev_ops->ndo_start_xmit(skb, device->dev);  /*直接调 stmmac_xmit*/
    ...
}

注意 skb->tstamp = app_time 这一伏笔——它就是第 4 节 TBS 能精确发送的关键。ndo_start_xmit 直接调到 stmmac_xmit,里面凡是涉及内核队列管理的代码(netif_tx_queue_stoppedskb_tx_timestampstmmac_tx_timer_arm)都被 if (!priv->ecdev) 跳过,但 TX 描述符强制置 IC(中断完成位),保证 DMA 发完下一轮 ec_poll 能立即回收。

再看接收。DMA 收到的帧不进 skb,直接把 page 数据指针交给 ecdev_receive

/*devices/stmmac/stmmac_main-6.1-ethercat.c:5501-5516(节选)*/
if (priv->ecdev) {
    data = page_address(buf->page);
    page_pool_recycle_direct(rx_q->page_pool, buf->page);
    ecdev_receive(priv->ecdev, data, len);   /*直接送主站,零 skb 分配*/
}

零拷贝、零 skb。至此,IgH 的确定性收发就清楚了:主站周期唤醒 → 主动 ec_poll → DMA 当场扫完收发 → 绕过 NAPI/协议栈/中断

3.3 周期调度:clock_nanosleep(TIMER_ABSTIME)

收发靠绕过网络栈,周期节奏靠应用层一个 SCHED_FIFO 最高优先级、绑核的实时线程:

wakeupTime = timespec_add(wakeupTime, cycletime);                    /*+周期*/
clock_nanosleep(CLOCK_TO_USE, TIMER_ABSTIME, &wakeupTime, NULL);    /*睡到绝对时刻*/
ecrt_master_application_time(master, TIMESPEC2NS(wakeupTime));       /*喂应用时间*/
ecrt_master_receive(master);  /*收上一帧*/
... /*处理 PDO、DC 同步*/
ecrt_master_send(master);     /*发本帧*/

TIMER_ABSTIME 让线程睡到一个绝对时刻而不是"睡 N 微秒",避免误差累积。喂给主站的 app_time 用的是"目标唤醒时间"(比实测时间更稳定)。

3.4 缺点

IgH 的纯软件方案能跑,但有几个绕不开的缺点:

  1. 发送抖动仍受 OS 调度影响。即便绕过了网络栈,"组帧 + 调 ndo_start_xmit"这步还在 CPU 上跑,受 PREEMPT-RT 抖动、其他中断抢占影响。周期越短(125 µs),这点抖动占比越大。
  2. 强依赖实时内核和系统配置。要微秒级抖动,必须 PREEMPT-RT + CPU 隔离 + 关中断/节能/调频 + mlockall。任何一项没做好抖动立刻上来。在国产 ARM + 厂商 BSP 上,非实时驱动常常是抖动主因。
  3. 帧上线时刻不可控。软件把帧交给网卡后,帧何时真正出现在 PHY 上,受网卡内部队列、DMA、其他流量影响,没有硬件级时刻保证。
  4. PHC 是"野时钟",EST/TBS 用不上。标准 IgH 里网卡 PHC 没人管,DC 走自己的从站闭环,两套时间体系割裂。

这几条在短周期、高轴数、混跑流量场景下尤其突出。第 4 节的 Rockchip TSN 方案,正是针对"发送抖动"和"帧上线时刻不可控"这两点。

4. Rockchip 的 TSN 方案:原理、步骤、流程、框架

Rockchip 这版 IgH 在标准 IgH 之上加了 TSN 扩展,让应用能直接配置网卡 EST/TBS,把"发送时刻"下沉到 PHC 硬件。这一节是全文重点,我们顺着源码把整体框架、时钟源选择、EST 下发链路、DC 同步、PHC 重建全部走一遍。

4.1 整体框架:三套时钟、两条同步链路

整套方案涉及三套时钟:

  • OS 时钟(应用 app_time):CLOCK_REALTIME/CLOCK_MONOTONIC,应用线程周期采样后喂给主站。
  • 网卡 PHC/dev/ptpX):DWMAC 内的 addend 累加计数器,EST/TBS 的时间基准
  • 从站 DC 时钟:每个支持 DC 的从站的本地时钟(寄存器 0x0910 System Time + 0x0920 Offset),其中一台当"参考从站"作为总线 DC 主时钟。

它们通过两条独立的同步链路联系起来:

三层时钟同步架构

  • 链路①(linuxptp):外部时间源 → ptp4l → PHC → phc2sysCLOCK_REALTIME。给 EST/TBS 提供 PHC 基准,并把系统时钟和 PHC 对齐。
  • 链路②(EtherCAT DC)app_time → 主站 ecrt_master_sync_reference_clock_to(FPWR 写 0x0910)→ 参考从站 → ecrt_master_sync_slave_clocks(FRMW 0x0910)→ 其他从站。给 SYNC0 提供总线时钟基准。

app_time 是两条链路的交汇点。

4.2 以谁为整体时间同步的时钟源

这是整套方案最关键的选择。Rockchip 方案的答案是:以网卡 PHC 作为整体时间同步的时钟源根节点(PHC 自由运行,或由外部 PTP grandmaster 经 ptp4l 校准)。链路方向是这样的(箭头 = "被同步到"):

以网卡 PHC 为根的整体时钟同步链路

为什么以 PHC 为根,而不是以参考从站为根?因为 EST/TBS 的发包时刻必须由网卡 PHC 触发——PHC 是"发包时间"的物理基准。把 PHC 设为根,PHC → 系统时钟 → 从站 DC 这条链就自然同向。注意这不矛盾:PHC 是整体同步的根,参考从站是总线 DC 闭环里的主。主站把 PHC 域的 app_time 灌给参考从站,参考从站再分发,于是总线 DC 也跟着 PHC 走。

4.3 EST 确定性发包的完整链路(源码走读)

先看应用怎么配 EST。demo 里 ec_master_test_RK3568_MADHT1505BA1.c 是无条件开 EST 的:

/*demo/ec_master_test_RK3568_MADHT1505BA1.c:430-444*/
est_entries[0].gate_mask = 0x2;   est_entries[0].interval = 800000;  /*queue1 开 800µs*/
est_entries[1].gate_mask = 0x1;   est_entries[1].interval = 200000;  /*queue0 开 200µs*/
est_qopt.num_entries = 2;  est_qopt.enable = 1;  est_qopt.entries = est_entries;
est_qopt.cycle_time = TIMESPEC2NS(cycletime);                       /*1ms*/
est_qopt.base_time = TIMESPEC2NS(wakeupTime) + est_qopt.cycle_time + 200000;  /*下周期+200µs*/
ecrt_master_set_est(master, &est_qopt);

那这一个 ecrt_master_set_est 是怎么一路下发到 MAC 硬件寄存器的?我们顺着调用链走:

应用 ecrt_master_set_est()
        |
        |  lib/master.c:751 -> ioctl(master->fd, EC_IOCTL_SET_EST, qopt)
        v
EC_IOCTL_SET_EST = EC_IOW(0x62, ec_est_qopt_offload_t)   master/ioctl.h:158
        |
        |  master/ioctl.c:4953 (case handler, copy_from_user + entries)
        v
ecrt_master_set_est()   master/master.c:3103
   |-- 翻译成内核标准 struct tc_taprio_qopt_offload
   |       .command = TC_TAPRIO_CMD_SET_GATES, .base_time = ns_to_ktime(...), ...
   `-- ndev->netdev_ops->ndo_setup_tc(ndev, TC_SETUP_QDISC_TAPRIO, qopt)
        |
        v
stmmac tc_setup_taprio()   devices/stmmac/stmmac_tc-6.1-ethercat.c:918
   |-- 压缩 GCL: gcl[i] = delta_ns | (gates << wid)        (:1017, wid=dma_cap.estwid)
   |-- stmmac_calc_tas_basetime(base_time, 当前PHC, cycle_time) (:1024)  折算到下一未过期周期点
   `-- stmmac_est_configure()
        |
        v
dwmac5_est_configure()   devices/stmmac/dwmac5-6.1-ethercat.c:594
   |-- 写 BTR_LOW/BTR_HIGH(base time)、CTR_LOW/CTR_HIGH(cycle)、LLR(表长)、GCL[](表项)
   `-- 写 MTL_EST_CONTROL:置 EEST(enable) | SSWL,配 PTOV=(1e9/ptp_rate)*6

注意 tc_setup_taprio 里这一行 GCL 压缩:

/*devices/stmmac/stmmac_tc-6.1-ethercat.c:1017*/
priv->plat->est->gcl[i] = delta_ns | (gates << wid);   /*<间隔, 门控位图>压成一个字*/

wid 来自 dma_cap.estwid(时间字段宽度 16/20/24 bit)。而 base_time 会被 stmmac_calc_tas_basetime 折算到"最近的下一个未过期周期点",避免 base_time 已经过期。

落地到 MAC 硬件后,只要 PHC 走到 base_time,EST 就自动按 GCL 循环开/关门。从此帧的上线时刻由硬件决定,不再依赖应用实时性

EST 门控窗口与帧发送时序

软件入队有抖动,但只要不晚于 base_time,帧就被 MAC 暂存、在 base_time 准时上线,抖动收敛到 PHC 精度量级(~10 ns)。

TBS 的链路同构:lib/master.c:764 -> EC_IOCTL_SET_TBS(0x63, ioctl.h:159) -> ioctl.c:4960 -> master.c:3134 -> ndo_setup_tc(ETF) -> tc_setup_etf(按队列置 STMMAC_TBS_EN)。发送时 stmmac_xmitskb->tstamp 取预约时刻写进 TBS 描述符:

/*devices/stmmac/stmmac_main-6.1-ethercat.c:4613-4618*/
if (tx_q->tbs & STMMAC_TBS_EN) {
    struct timespec64 ts = ns_to_timespec64(skb->tstamp);
    tbs_desc = &tx_q->dma_entx[first_entry];
    stmmac_set_desc_tbs(priv, tbs_desc, ts.tv_sec, ts.tv_nsec);
}

skb->tstamp 是什么?回头看 3.2 节 ec_device_send 里那一行 skb->tstamp = ns_to_ktime(device->master->app_time)两段代码一接,应用给的 app_time 就成了 TBS 的硬件发送时刻——这就是为什么 TBS 模式下 demo 给 app_time 加 200 µs 偏移(ec_master_test_MADHT1505BA1.c:489),把预约发送时刻挪到开门窗口起点。

4.4 DC 同步的源码走读:app_time 怎么流到每一个从站

EST 解决的是"发送时刻",DC 解决的是"所有从站时钟对齐"。DC 的"主时钟"是一台从站(不是网卡 PHC)。选谁是 ec_master_find_dc_ref_clock()master/master.c:2242)决定的:默认选总线上第一个"支持 DC 且有系统时间寄存器"的从站。

app_time 是怎么流到每一个从站的?分三步,对应三个 API。第一步,喂 app_time

/*master/master.c:3092*/
void ecrt_master_application_time(ec_master_t *master, uint64_t app_time)
{
    master->app_time = app_time;                  /*只存变量,不发任何报文*/
    if (unlikely(!master->dc_ref_time))
        master->dc_ref_time = app_time;           /*首次锁定相位基准*/
}

很多人以为 ecrt_master_application_time 会"发报文把时间发给从站"。其实它什么报文都不发,只把时间存进 master->app_time。那它有什么用?app_time 会在三个地方被用到:给每个发出的数据报盖戳(master.c:1135 datagram->app_time_sent)、sync_reference_clock 把它写进参考从站、SYNC0 相位计算用它。

第二步,粗同步参考从站。把系统时间写进参考从站,靠的是 FPWR(配置地址物理写)写 0x0910

/*master/master.c:3182*/
void ecrt_master_sync_reference_clock_to(ec_master_t *master, uint64_t sync_time)
{
    if (master->dc_ref_clock) {
        EC_WRITE_U32(master->ref_sync_datagram.data, sync_time);
        ec_master_queue_datagram(master, &master->ref_sync_datagram);
    }
}

这里 FPWR 写的 0x0910 是 System Time(系统时间)寄存器。EtherCAT DC 寄存器映射(master/fsm_master.c:1261-1266 注释证实):0x0910=System Time、0x0920=System Time Offset、0x0928=Receive Delay、0x092c=System Time Difference。System Time Offset(0x0920)是主站 FSM 在校准阶段单独写的(见后文)。

第三步,让所有从站对齐到参考从站。这是每周期都调的"漂移补偿":

/*master/master.c:3195*/
void ecrt_master_sync_slave_clocks(ec_master_t *master)
{
    if (master->dc_ref_clock && master->dc_offset_valid) {
        ec_datagram_zero(&master->sync_datagram);   /*写入值清零*/
        ec_master_queue_datagram(master, &master->sync_datagram);
    }
}

sync_datagram 是 FRMW 到参考从站 0x0910。这里有个很容易踩的坑:FRMW 是 Configured-address Physical Read-Modify-Write(数据报类型码 0x19),寻址的是单个参考从站的配置地址ec_datagram_frmw 的参数就叫 configured_addressmaster/datagram.c:349),不是"自动递增/广播遍历所有从站"——那是 ARMW(0x18)。

那为什么一条只发给参考从站的报文能让所有从站对齐?因为 EtherCAT 硬件 DC 单元的工作方式是:这条数据报在总线上物理穿行经过每个从站时,每个支持 DC 的从站都会捕获数据报通过自己端口的时刻,结合预先校准的 offset/delay 自动补偿本地时钟漂移。所以"对齐所有从站"靠的是硬件 DC 传输时刻机制,不是靠这条报文逐个寻址它们。

顺便纠一个常见误解:有人把 sync_slave_clocks 的 FRMW 说成"自动遍历所有从站各自读 0x0910"。这是把 FRMW 和 ARMW 搞混了。FRMW 单播参考从站,所有从站对齐靠硬件 DC 传输时刻机制。

那"预先校准的 offset/delay"又是怎么来的?主站内核 FSM 线程里(fsm_master.cdc_read_offset -> dc_write_offset -> dc_reset_filter 状态链)对每个支持 DC 的从站做一次:

/*master/fsm_master.c:1398-1400*/
system_time = EC_READ_U64(datagram->data);     /*0x0910*/
old_offset  = EC_READ_U64(datagram->data + 16);/*0x0920*/
old_delay   = EC_READ_U32(datagram->data + 24);/*0x0928*/

/*master/fsm_master.c:1327(简化)*/
time_diff = app_time_sent - system_time;
if (EC_ABS(time_diff) > EC_SYSTEM_TIME_TOLERANCE_NS)   /*容差 1000 ns*/
    new_offset = time_diff + old_offset;               /*新偏移*/

含义:调整 offset 使得 system_time + new_offset == app_time_sent,让从站"对外时间"对齐到主站时间。然后 FPWR 写 0x0920 共 12 字节(fsm_master.c:1428),把 new_offset(0x0920) 和该从站的 transmission_delay(0x0928) 一起写回。transmission_delay 是总线扫描期测出来的线缆延迟(参考从站自己是 0,递归累加到每个从站),让从站补偿"参考时间到达本从站的滞后"。

至此 DC 同步的全过程就清楚了,总结一下 app_time 的流向:

  1. 应用 clock_gettimeapp_time,调 ecrt_master_application_time 存进 master->app_time(不发报文)。
  2. ecrt_master_sync_reference_clock_to 用 FPWR 写参考从站 0x0910(System Time),粗同步参考从站。
  3. 主站 FSM 周期性 FPRD 读 0x0910+0x0920+0x0928,算 new_offset = app_time_sent − system_time + old_offset,FPWR 写 0x0920+0x0928,精密校准每个从站。
  4. ecrt_master_sync_slave_clocks 每周期发 FRMW 到参考从站,报文物理穿行时各从站 DC 硬件自动漂移补偿,全总线对齐。
  5. 各从站按 DC 时间相位产生 SYNC0(ecrt_slave_config_dc 设周期和 shift_time),驱动伺服在固定相位采样/输出。

应用时间调用点ecrt_master_application_time()ecrt_master_sync_reference_clock_to() 之间不要再有耗时操作,否则 DC 锁相环看到的相位抖动会变大。

4.5 PHC 相位跳变时 EST 自动重建

EST 的 base_time 表达在 PHC 域。那 PHC 被外部 ptp4l/phc2sys 拽着走、发生相位跳变时,旧 base_time 就"过期"了(对应到 PHC 的过去时刻,GCL 错位)。这事儿没有看起来那么简单,stmmac 在 stmmac_adjust_time() 里专门处理:

/*devices/stmmac/stmmac_ptp-6.1-ethercat.c:79-114*/
/* If EST is enabled, disabled it before adjust ptp time. */
if (priv->plat->est && priv->plat->est->enable) {
    est_rst = true;
    priv->plat->est->enable = false;
    stmmac_est_configure(...);                 /*① 先暂停 EST*/
}
stmmac_adjust_systime(...);                    /*② PHC 寄存器原子跳相位*/

if (est_rst) {
    priv->ptp_clock_ops.gettime64(..., &current_time);          /*读新 PHC 当前时间*/
    time.tv_nsec = priv->plat->est->btr_reserve[0];             /*③ 取保留的原始 base_time*/
    time.tv_sec  = priv->plat->est->btr_reserve[1];
    basetime = timespec64_to_ktime(time);
    time = stmmac_calc_tas_basetime(basetime, current_time_ns, cycle_time); /*④ 折算下一未过期点*/
    priv->plat->est->btr[0] = (u32)time.tv_nsec;  priv->plat->est->btr[1] = (u32)time.tv_sec;
    priv->plat->est->enable = true;                             /*⑤ 写回新 base_time*/
    stmmac_est_configure(...);                                  /*⑥ 重启 EST*/
}

PHC 相位跳变时 EST 自动重建

stmmac_calc_tas_basetimestmmac_tc-6.1-ethercat.c:895)做的就是朴素的 while (basetime < now) basetime += cycle_time,把 reserve 的原始 base_time 平移到跳变后 PHC 的下一个等效周期点。这段代码反过来印证:EST 的时间基准就是 PHC——PHC 一跳,EST 必须跟着重建。所以要长期稳定,PHC 必须被可靠同步。

4.6 OS 时钟 / 网卡时钟 / 从站时钟如何同步

把三套时钟同步关系落实到可执行步骤,并说清楚一个很容易踩的坑

CLOCK_MONOTONIC 是只读单调时钟,clock_settime() 对它返回 EINVAL,任何工具都改不了它(只能用 adjtimex 微调速率,调不了绝对时刻)。phc2sys 能动的只有 CLOCK_REALTIME(默认目标)或 CLOCK_TAICLOCK_MONOTONICCLOCK_REALTIME 共享同一套内核 timekeeping、走同一速率,只差一个常数"墙钟偏移";phc2sys 的绝对时刻校准只改 CLOCK_REALTIME,碰不到 CLOCK_MONOTONIC

因此正确姿势是:

  1. phc2sys 把 PHC 同步到 CLOCK_REALTIME(见第 5 节命令),使两者同域。
  2. 开 EST/TBS 时,应用 app_time 改用 CLOCK_REALTIME(而非 CLOCK_MONOTONIC),这样 app_time ≈ PHC 值,EST/TBS 的硬件时刻与 app_time 同域、不错位。demo 正是这么做的:ec_master_test_MADHT1505BA1.c:41-45#if (USE_TSN_MODE_EST||USE_TSN_MODE_TBS) 下把 CLOCK_TO_USE 切成 CLOCK_REALTIME。不开 EST/TBS 时仍可用 CLOCK_MONOTONIC(不会被 NTP 跳变,周期更稳)。
  3. 主站周期性把 app_time 写给参考从站sync_reference_clock_to FPWR 0x0910),再用 sync_slave_clocks(FRMW 0x0910)把全总线时钟拉齐到参考从站。
  4. SYNC0 由各从站按 DC 时间相位产生,驱动伺服在固定相位采样/输出。

5. 使用说明

5.1 系统配置(性能模式、关闭 NTP/chrony)

跑 EtherCAT + TSN 前,先把系统调到"低抖动"状态。把 SoC 调到性能模式,CPU 运行在最高频率(消除动态调频抖动):

# 调整 soc 为性能模式,cpu 时钟运行在最高频率
echo performance | tee $(find /sys/ -name *governor)

关闭网络时间同步,防止它与 TSN 争抢系统时钟。这一步很关键:NTP/chrony 会改 CLOCK_REALTIME,而本方案要求 CLOCK_REALTIME 只由 phc2sys 跟随 PHC 来调整,两者冲突会破坏 app_time 域、让 EST/TBS 时刻错位。

# 关闭 ntp 同步时间(debian)
systemctl stop ntpsec
systemctl disable ntpsec

# 关闭 chrony 时间同步(buildroot)
killall chronyd
ls /etc/init.d/S*chronyd | xargs rm

别忘了实时任务该有的标配:PREEMPT-RT 内核、周期线程 SCHED_FIFO 最高优先级 + 绑核隔离、mlockall 锁内存。demo 里的 thread_bind_cpu + sched_setscheduler 就是干这个的。

本节只列了 EtherCAT + TSN 必须的两项(性能模式 + 关时间同步)。完整的实时系统调优——CPU 隔离 isolcpus、Full Dynamic Tick nohz_full、RCU 卸载 rcu_nocbs、关调频 cpufreq.off=1、关 irqbalancehugepages、内核构建选项以及 BIOS/SMI 处理——见 《有利于提高 xenomai / PREEMPT-RT 实时性的一些配置建议》,按需照做即可进一步压低周期唤醒抖动。

5.2 开启 TSN 与系统时钟同步(phc2sys)

找到 EtherCAT 网口对应的 PTP 设备(RK 平台通常每个 GMAC 一个,注意可能是 /dev/ptp1 而不是 ptp0,以实际为准):

ls -l /sys/class/ptp

安装 linuxptp 并启动 phc2sys,把系统时间和 GMAC PHC 同步(-s 源 PHC、-c 被同步方系统 CLOCK_REALTIME-O 0 同基准):

apt install linuxptp
phc2sys -s /dev/ptp1 -c CLOCK_REALTIME -O 0 &

这条命令实现的就是 4.1 节的"链路①正向":以 PHC 为源、CLOCK_REALTIME 跟随 PHC。之后 app_time(用 CLOCK_REALTIME 取)就与 PHC 同域,EST/TBS 才能正确工作。若整机要锁定到外部 PTP grandmaster,再加一条 ptp4l -i ethX -2 -m 先把 PHC 校到外部时间。

⚠ 切勿期望 phc2sys 能同步 CLOCK_MONOTONIC——它改不了。所以开 TSN 时务必让应用 app_timeCLOCK_REALTIME(demo 的 USE_TSN_MODE_* 开关已自动处理)。

5.3 编译运行 demo

cd demo
./build.sh   # 用 SDK 交叉编译器链接 IgH 用户库 libethercat

ec_master_test_RK3568_MADHT1505BA1.c 是"全开"版(DC + 无条件 EST,CLOCK_REALTIME),最适合验证 EST 硬件门控发包;ec_master_test_MADHT1505BA1.c 是通用可移植版,用 USE_TSN_MODE_EST/TBS 宏隔离 TSN 代码(默认关,仅 rk3588/rk3576 置 1)。

文件 时钟域 DC EST/TBS
ec_master_test_RK3568_MADHT1505BA1.c CLOCK_REALTIME config_dc(0x300,1ms,0) ecrt_master_set_est 无条件(queue1 800µs/queue0 200µs)
ec_master_test_MADHT1505BA1.c REALTIME(TSN)/MONOTONIC config_dc(0x300,1ms,0) USE_TSN_MODE_EST/TBS 开关(默认 0)
ec_master_test_RK3568_..._ras.c CLOCK_MONOTONIC
Rockchip_MADHT1505BA1.c(编为 .so) CLOCK_MONOTONIC ❌ 注释掉 ❌ 纯软件路线

运行前确认:网卡已被 IgH 接管(ecdev_offer,对 ifconfig 不可见是正常的)、phc2sys 已起、app_time 用了 CLOCK_REALTIME。这样 EtherCAT 帧就会在 EST 的 base_time 时刻由硬件准时上线,发送抖动收敛到 ~10 ns。

6. 总结

  • 网卡 EST/TBS:把"软件何时把帧交给网卡"变成"网卡硬件在 PHC 走到 base_time 时主动送上线"。IgH 通过标准的 ndo_setup_tc(TAPRIO/ETF) 把应用下发的 GCL 透传到 stmmac 的 EST 寄存器或 TBS 描述符;发送抖动因此被硬件吸收,不再依赖应用实时性。

  • 网卡时钟 ↔主站周期应用调度(系统时钟)↔ (参考时钟从站 ↔ 其他从站):网卡stmmac PHC 是独立硬件时钟,由用户态 ptp4l/phc2sys 将系统时钟CLOCK_REALTIME与网卡同步;IgH 显式地用 ecrt_master_sync_reference_clock_to() 写 0x0910 寄存器把参考从站 DC 拉到 CLOCK_REALTIME,再用 ecrt_master_sync_slave_clocks() 做总线内广播对齐。

    以上解决了主站应用、网卡时钟、从站时钟三者的同步问题,以及主站输出(发帧)的确定性,增强了系统的稳定性和确定性,但是操作系统的抖动仍未改善,对于低周期通信,操作系统的抖动是最大影响因素。此外还需要注意网卡时钟精度、ecrt_master_sync_reference_clock_to()调用间隔等。


参考资料

作者:wsg1100 出处:https://www.cnblogs.com/wsg1100/