在机器人控制系统中,1 ms 是一个经常被提及的控制周期。无论是机械臂关节控制、伺服驱动,还是基于 ROS 2、EtherCAT 与 CiA 402 的运动控制系统,工程师都可能遇到这样的需求:希望每隔 1 ms 完成一次控制计算、读取反馈、更新目标值,并将控制指令发送给伺服驱动器。
但实际开发中,一个常见误区是:只要把线程设置为实时优先级,再使用sleep(1 ms),就可以得到稳定的 1 ms 控制周期。
事实并非如此。
1 ms 控制周期不是一个简单的定时参数,而是一套涵盖线程唤醒、控制计算、EtherCAT 数据交换、总线同步、执行时间预算和异常处理的系统设计。 即使程序每秒执行 1000 次,也不意味着每次执行都能按时开始、按时完成,更不意味着机器人能够获得稳定的端到端控制性能。
对于采用 ROS 2、IgH EtherCAT Master 和实时 Linux 的机器人系统,理解 1 ms 周期背后的调度机制,并建立完整的超期检测与验证体系,比单纯追求某一个延迟数字更重要。
一、1 ms 控制周期究竟意味着什么?先区分周期、延迟、抖动与超期
假设一台机械臂使用 EtherCAT 与多个伺服驱动器通信,控制程序每隔 1 ms 读取关节反馈、计算下一时刻的目标位置,再将目标值写入 PDO 并发送给驱动器。
理论上,控制任务的启动时刻可以表示为:
tk=t0+kTt_k=t_0+kTtk=t0+kT
其中,T=1 msT=1\,\mathrm{ms}T=1ms,t0t_0t0 是起始时刻,kkk 是周期序号。
这个公式描述的是理想的周期计划,并不意味着操作系统一定能够在每个 tkt_ktk 准时调度线程。
在实际系统中,至少需要区分四个概念。
1. 控制周期(Period)
控制周期是相邻两次计划执行之间的时间间隔。1 ms 周期对应理论上的 1000 Hz 执行频率。
但需要注意,控制任务的执行频率、EtherCAT 主站的发送频率、从站的同步周期,以及驱动器内部的位置环或电流环周期,并不必然相同。
例如,机器人上层轨迹规划可以以较低频率更新目标,而底层伺服控制任务以 1 ms 周期交换数据。驱动器内部还可能运行更高频率的控制环。具体周期关系取决于控制架构和设备能力。
因此,不能仅凭主站每 1 ms 发送一次报文,就认定整个机器人系统的所有控制环都以 1 kHz 工作。
2. 唤醒延迟(Wake-up Latency)
唤醒延迟是线程计划开始执行的时刻与实际开始执行的时刻之间的差值。
假设某一周期计划在 10:00:00.001000 开始,但线程直到 10:00:00.001080 才获得 CPU,那么本次唤醒偏差就是 80 μs。
如果这个偏差不断变化,即使平均值很低,也可能影响控制任务的稳定性。
唤醒延迟与操作系统调度有关,但它并不是全部的 EtherCAT 通信延迟,也不能直接代表伺服执行器的响应时间。
3. 执行时间(Execution Time)
执行时间是控制任务从开始执行到完成指定工作所消耗的时间。
对于一个典型的 EtherCAT 周期任务,执行内容可能包括:
接收并处理 EtherCAT 数据。
读取输入 PDO。
计算关节控制量或更新目标位置。
写入输出 PDO。
将过程数据排入发送队列并发送报文。
完成必要的时钟同步操作和状态检查。
如果任务执行时间为 300 μs,并不意味着它一定能够满足 1 ms 周期。还需要考虑线程何时被唤醒、其他任务是否抢占 CPU、报文是否及时完成交换,以及数据是否能够在驱动器需要的时间点被采纳。
4. 抖动与周期超期(Jitter and Deadline Miss)
周期抖动通常用于描述实际执行时刻或周期长度相对计划值的变化。不同测试工具对抖动的定义可能不同,因此比较测试结果时必须先确认统计口径。
周期超期则是更直接的工程问题:任务没有在规定的截止时间之前完成所需工作。
例如,一个周期计划在第 1000 μs 开始,任务到第 1100 μs 才完成,而下一周期已经到来。这时系统需要判断本次控制数据是否仍然有效、下一周期如何恢复,以及是否应当触发降级或安全策略。
对于机器人而言,真正需要关注的不是单一的平均延迟,而是:每个周期是否能够在规定时间内完成,超期发生的频率是多少,超期时系统会如何响应。
二、IgH EtherCAT Master 的周期任务应该如何组织?
在 Linux 机器人控制系统中,IgH EtherCAT Master 负责主站侧的 EtherCAT 通信与过程数据管理。它并不等同于实时操作系统,也不能单独决定整个系统的实时性能。
要建立稳定的 1 ms 控制周期,首先需要把初始化工作和周期性工作分离。
1. 初始化阶段与周期阶段分开
初始化阶段可以完成主站请求、域和 PDO 配置、从站状态检查、主站激活,以及其他不需要在每个控制周期重复执行的工作。
进入实时周期后,则应尽可能减少不可预测的操作,将工作限定在必要的数据收发、状态读取、控制计算和结果写入范围内。
一个简化的架构如下:
初始化阶段
配置主站、PDO 映射、域、从站与同步参数
周期线程:每 1 ms 计划执行一次
等待周期 → 接收报文 → 处理输入数据
读取 PDO → 执行控制计算 → 写入 PDO
排队发送 → 记录周期统计
监控与异常处理
检测超期、通信异常、驱动器状态异常
按预先定义的策略恢复、降级或停止运动
这个架构的重点是:初始化、实时执行与异常诊断具有不同的时间要求,不能把所有工作都塞进一个无限循环中。
尤其是实时周期内,应避免不必要的动态内存分配、文件读写、同步等待、复杂日志输出,以及可能引发不可预测阻塞的操作。
2. 理解 EtherCAT 周期内的数据交换顺序
对于 IgH EtherCAT Master,工程中经常涉及以下 API:
ecrt_master_receive():接收主站侧可用的 EtherCAT 数据。ecrt_domain_process():处理过程数据域,更新域的数据状态。ecrt_domain_queue():将域的过程数据排入发送队列。ecrt_master_send():发送已经排队的数据。
在支持相应功能的配置中,还可能涉及应用时间与分布式时钟同步相关接口,例如ecrt_master_application_time()、ecrt_master_sync_reference_clock()和ecrt_master_sync_slave_clocks()。
这些 API 并不是可以不加区分地随意排列的通用步骤。实际调用顺序、同步调用的位置以及每个接口的适用条件,应以正在使用的 IgH 版本、官方示例、ecrt.h接口定义和设备同步配置为准。
从逻辑上看,周期任务需要完成的工作包括:
及时处理本周期可用的输入过程数据。
读取并校验驱动器反馈和相关状态。
根据当前控制模式执行必要的控制计算。
将本周期目标值写入正确映射的输出 PDO。
按主站接口要求将过程数据排队并发送。
根据配置完成必要的分布式时钟同步和状态监控。
需要特别注意:接收、处理、计算、发送只是软件层面的工作流程,不代表驱动器一定会在同一个周期立即执行新目标值。实际生效时刻还与从站同步方式、SYNC0 配置、驱动器采样时序以及网络交换时序有关。
如果使用分布式时钟(DC),还需要检查主站应用时间、参考时钟与从站时钟之间的关系,确保同步策略与从站配置相匹配。仅仅调用同步 API,并不能证明所有从站已经获得满足要求的同步精度。
3. 为什么不能把 ROS 2 的订阅回调直接当作 EtherCAT 周期线程?
假设 ROS 2 的上层节点不断发布关节目标位置,底层 EtherCAT 控制程序通过订阅回调获取目标值。
如果每次收到消息后才执行一次 EtherCAT 发送,那么发送频率就会受到消息到达时间、Executor 调度、回调执行时间及系统负载等因素影响。
这会让通信周期与上层消息节奏耦合在一起。
更合理的设计通常是:
ROS 2 负责轨迹规划、任务协调和目标更新。
独立的实时控制线程按照确定的周期执行。
上层目标通过明确的数据交接机制传递给实时线程。
实时线程根据目标的新鲜程度、控制模式和异常状态决定是否采纳数据。
底层通信与控制不因普通消息回调暂时没有运行而停止执行周期任务。
数据交接可以使用固定大小的队列、双缓冲或经过严格设计的无锁结构,但不能仅凭“无锁”两个字就认定实现安全。必须考虑多线程读写一致性、内存序、数据覆盖、缓存可见性以及目标值更新与读取之间的竞态。
同时,还需要为目标值定义有效期。若 ROS 2 节点暂时卡顿,底层线程不能不加判断地无限期重复使用陈旧目标。
三、如何让 Linux 上的周期线程更稳定?
完成程序结构设计后,接下来需要处理操作系统调度与 CPU 资源竞争问题。
1 ms 周期任务不仅需要尽量按时唤醒,还需要在运行过程中获得足够的 CPU 时间,并避免被无关工作持续干扰。
1. 使用绝对时间等待,避免相对 sleep 的误差累积
常见的错误写法是每次执行完成后,再休眠 1 ms:
C
while (running) { do_control_cycle(); sleep_for_1ms(); }这里的问题在于,do_control_cycle()本身需要时间。假设控制计算用了 200 μs,等待又用了约 1 ms,那么相邻两次执行的起始间隔可能接近 1.2 ms,还没有计算线程调度带来的额外延迟。
即使换成nanosleep(),如果每次都按照相对时间等待,也仍然可能产生周期漂移。
更适合周期任务的方式,是依据单调时钟维护绝对计划时刻,并使用clock_nanosleep()的TIMER_ABSTIME模式等待。
以下是一个简化的 C 语言示意代码,用于说明周期调度方法,并非可直接用于所有设备的完整 EtherCAT 程序:
C
#define _GNU_SOURCE #include <time.h> #include <errno.h> #include <stdbool.h> static void add_ns(struct timespec *t, long ns) { t->tv_nsec += ns; while (t->tv_nsec >= 1000000000L) { t->tv_nsec -= 1000000000L; t->tv_sec++; } } int main(void) { const long period_ns = 1000000L; /* 1 ms */ struct timespec next; clock_gettime(CLOCK_MONOTONIC, &next); while (true) { add_ns(&next, period_ns); int ret; do { ret = clock_nanosleep( CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL ); } while (ret == EINTR); if (ret != 0) { /* 记录错误,并执行适当的异常处理 */ break; } /* * 此处放置经过实时性设计的周期任务: * 接收与处理 EtherCAT 数据、 * 读取 PDO、计算控制量、写入 PDO、 * 排队发送,以及周期统计。 */ } return 0; }需要注意几个细节。
首先,这段代码演示的是绝对时间等待,并没有实现 IgH 主站初始化、实时调度设置、数据处理和故障恢复。
其次,实际程序应在启动阶段明确首个计划时刻,并将后续周期与固定时间基准关联,而不是在每次循环结束后重新计算一个相对等待时间。
再次,绝对时间等待可以避免相对休眠造成的累计漂移,但不能保证线程一定准时运行。 如果系统负载过高,或者更高优先级线程持续占用 CPU,线程仍然可能晚于计划时刻被唤醒。
此外,示例代码中的while (true)只是为了展示调度逻辑。实际机器人系统还应具备明确的退出条件、停止流程和异常处理机制。
2. 实时优先级不是越高越好
Linux 上的SCHED_FIFO和SCHED_RR是常见的实时调度策略。
SCHED_FIFO下,同优先级线程通常按照先运行、后让出的方式执行;SCHED_RR则在相应调度规则下引入时间片轮转。它们都不同于普通的SCHED_OTHER调度方式。
对于需要稳定运行的 EtherCAT 周期线程,可以评估实时调度策略,但需要同时考虑其他实时线程、内核线程、中断处理和系统服务。
例如,如果某个周期线程被设置为过高优先级,却在异常情况下进入无限循环,其他必要任务可能长时间无法运行。因此,应当结合执行时间预算、优先级关系、线程阻塞行为及看门狗机制进行设计。
实时线程还应尽量避免:
在周期内申请和释放大量动态内存。
输出高频同步日志。
执行文件系统读写。
进行不可控的网络通信等待。
持有可能导致长时间阻塞的锁。
调用执行时间没有边界或缺乏评估的函数。
对于控制程序来说,优化的目标不是让线程始终占据 CPU,而是在规定时间内完成必要工作,并让系统其他重要任务仍能按设计运行。
3. 内存锁定与 CPU 亲和性应该如何使用?
在经过评估的实时 Linux 配置中,可以考虑使用mlockall()锁定进程内存,降低实时执行过程中因内存换页带来的不确定性。
但调用mlockall()并不意味着所有内存问题都消失了。应用仍需考虑栈空间、预分配、内存访问模式、权限限制,以及实时线程启动前是否已经完成必要的内存准备。
CPU 亲和性则用于限制线程可以在哪些 CPU 上运行。例如,使用pthread_setaffinity_np()将 EtherCAT 周期线程限制在特定 CPU 上,可以减少线程在不同 CPU 之间迁移所带来的部分开销。
然而,绑核不等于核心隔离。
即使周期线程只运行在 CPU 2 上,其他普通任务、内核线程或硬件中断仍可能干扰该 CPU,具体取决于系统配置。
因此,机器人实时系统需要进一步评估:
周期线程的 CPU 亲和性。
相关中断的 CPU 分布与亲和性。
普通任务是否会在目标 CPU 上运行。
内核线程是否可能产生干扰。
电源管理、CPU 频率变化与平台固件行为。
网络接口、网卡驱动和硬件中断处理路径。
这也是为什么核心隔离需要从整个系统资源分配的角度设计,而不是简单地给某个进程指定一个 CPU 编号。
对于望获OS的实时应用评估,望获rtLinux 可以作为候选运行平台之一。实际效果仍需要结合具体内核版本、硬件平台、网卡与驱动、IgH 版本及隔离配置进行测量,不能仅凭产品名称或某一项调度设置推断性能。
四、当 1 ms 周期超期时,系统应该怎么办?
实时控制系统的可靠性,不只体现在正常运行时的周期稳定性,还体现在超期发生后是否能够识别问题、避免错误累积,并按照预先设计的策略恢复。
假设周期线程计划每 1 ms 执行一次,但由于系统负载突然上升,某次任务直到计划时刻之后才开始执行。接下来应该直接执行控制计算吗?如果发现已经错过一个周期,是否应该立即连续补跑两次?如果连续数个周期都超期,又应该怎么办?
这些问题都需要在系统设计阶段明确。
1. 不要只看平均执行时间
建议至少采集以下几类数据:
| 监测指标 | 主要用途 |
|---|---|
| 计划唤醒时刻与实际启动时刻的偏差 | 观察线程唤醒是否稳定 |
| 每周期执行时间 | 判断控制任务是否在预算内完成 |
| 周期起始时间间隔 | 检查实际周期长度及其变化 |
| 截止时间超期次数 | 统计任务未按时完成的情况 |
| EtherCAT 域与从站状态 | 识别过程数据或通信状态异常 |
| 控制计算和数据交换各阶段耗时 | 定位周期预算被消耗的位置 |
例如,假设某个任务的平均执行时间只有 250 μs,但少数周期会突然增长到 900 μs,甚至超过 1 ms。平均值看起来很理想,却可能掩盖真正影响控制稳定性的长尾事件。
因此,不能只记录平均值。还应关注最大观测值、分位数、超期次数、连续超期情况,以及这些事件发生时的系统负载和通信状态。
需要强调的是,有限时间的压力测试得到的最大观测值,并不自动等于系统在所有可能条件下的最坏执行时间。
2. 为什么不应该盲目补跑错过的周期?
一种看似合理、实际上可能引发问题的设计是:发现任务已经超期,就立即把漏掉的周期全部补跑。
例如,某次任务晚了 2 ms,程序试图连续执行多次控制计算,追赶原本的计划进度。
这种方式可能进一步挤占后续任务的 CPU 时间,造成连续超期,甚至形成不断追赶的循环。
更重要的是,运动控制任务往往需要最新且有效的状态反馈。如果在同一个时刻连续计算几次过去的目标,却没有获得相应时刻的新反馈,这些补跑结果未必具有实际控制意义。
因此,系统需要明确超期后的策略。常见的设计方向包括:
保持有效目标: 在符合应用要求的前提下,短暂保持上一个经过验证的目标或控制输出。
重新同步计划时刻: 根据系统状态和控制架构恢复后续周期,避免无限追赶已错过的时间点。
进入受控降级: 当超期持续发生或超过设定阈值时,降低运动要求、暂停轨迹更新,或转入预先定义的降级模式。
执行受控停止: 当超期可能导致不可接受的控制风险时,按系统风险分析和安全设计采取停止措施。
这些策略没有一个能够适用于所有机器人。保持上一次目标可能适合某些位置控制场景,却可能不适合所有速度、力矩或过程控制场景。
如果涉及人机协作、危险运动或功能安全,不能把软件层面的“保持最后一次输出”视为通用安全措施。安全停止和保护行为必须依据风险评估、驱动器能力及适用的安全规范进行设计与验证。
3. 如何区分操作系统问题与 EtherCAT 通信问题?
当机器人出现运动不平滑、偶发卡顿或控制周期异常时,工程师容易把所有问题都归结为实时性不足。
但实际故障可能来自不同层次:
操作系统没有及时调度周期线程。
控制计算超出预设执行时间预算。
EtherCAT 数据交换没有在预期时间内完成。
从站状态或工作计数器出现异常。
分布式时钟配置与设备要求不匹配。
目标值更新不及时,导致底层使用了陈旧数据。
驱动器状态机、运行模式或外部使能条件不满足。
例如,EtherCAT 的工作计数器(WKC)用于反映相关数据报文处理情况,具体预期值取决于配置。WKC 异常可以帮助定位通信或过程数据问题,但它不是线程唤醒延迟指标,也不能直接证明网络抖动大小。
同样,cyclictest可以用于测量 Linux 线程的定时唤醒延迟,帮助评估调度环境,却不能单独证明 IgH 周期任务、EtherCAT 网络交换和驱动器执行构成的完整链路满足实时要求。
合理的诊断方式,是将操作系统调度、EtherCAT 数据交换、驱动器状态机和机器人控制结果分层观测,并使用统一时间基准或可关联的事件记录建立问题之间的联系。
五、如何验证 1 ms 控制周期?从单项测试走向系统级验证
完成周期线程和通信逻辑后,最后一步不是看到程序每秒执行 1000 次就结束,而是建立可以复现、比较和追踪的验证流程。
对于 ROS 2 + IgH EtherCAT Master + 实时 Linux 的机器人系统,可以按照以下顺序开展验证。
第一步:验证操作系统调度。
使用cyclictest等工具观察不同负载条件下的唤醒延迟分布。测试时应记录内核版本、实时调度策略、CPU 亲和性、核心隔离配置、测试持续时间和系统负载。
此阶段的目的,是评估调度环境,而不是直接证明 EtherCAT 系统已经满足要求。
第二步:验证 EtherCAT 周期任务。
在实际周期线程中记录计划启动时刻、实际启动时刻、各阶段执行时间、发送与接收状态,以及周期超期事件。
测试应覆盖正常负载和有代表性的压力负载,并观察是否存在偶发长尾、连续超期或周期漂移。
第三步:验证从站与驱动器状态。
检查 EtherCAT 主站及从站状态、过程数据域状态、PDO 映射、CiA 402 状态机和运行模式。对于使用分布式时钟的系统,还应验证同步配置和实际同步行为。
不能把从站进入 OP 状态直接视为伺服已经处于 Operation Enabled,也不能把通信正常等同于运动控制正确。
第四步:验证 ROS 2 与底层控制的数据链路。
检查目标值的更新时间、数据一致性、陈旧数据处理、控制周期与上层规划周期之间的关系,并在 ROS 2 节点繁忙、消息突发或短时停顿时观察底层周期任务是否仍按设计运行。
第五步:验证异常条件下的行为。
在安全可控的测试环境中,评估 CPU 负载增加、日志量上升、网络接口异常、从站掉线、驱动器故障及目标数据过期等情况。
重点不只是观察系统能否继续运行,还要确认它是否能够正确报告故障、避免错误目标持续传递,并按预先定义的策略恢复或停止。
望获rtLinux 在这一架构中的作用
在这类系统中,望获rtLinux 可以作为实时运行平台的评估选项。工程验证应重点关注实际硬件环境下的调度延迟、周期超期、CPU 与中断干扰,以及与 EtherCAT 主站、网卡驱动和 ROS 2 工作负载的协同表现。
其中,核心隔离值得单独评估:它不仅关系到周期线程运行在哪个 CPU 上,还涉及普通任务、中断与内核活动是否会干扰关键控制路径。
但需要明确,实时操作系统、PREEMPT_RT、实时调度策略和核心隔离,都不能单独构成系统满足硬实时要求的充分证据。最终结论必须建立在明确的截止时间要求、完整的控制链路分析,以及实际平台上的系统级测试之上。
对于 1 ms EtherCAT 控制周期,真正重要的不是程序能否达到 1 kHz 的名义执行频率,而是它能否在可验证的时间预算内持续完成必要工作,并在超期和故障发生时做出可预测的响应。
这才是从“程序能够运行”走向“机器人控制系统能够可靠运行”的关键。