在前面的文章中,我们已经从多个角度拆解了 IgH EtherCAT Master。第 2 篇认识了 Master、Slave、Domain 和 Datagram;第 3 篇从源码目录入手,建立了 IgH 的代码地图;第 4 篇分析了 INIT、PREOP、SAFEOP、OP 以及 Master FSM、Slave FSM;第 5 篇进一步进入 Datagram、EtherCAT Frame 和 Working Counter,理解了数据究竟如何从 Linux 应用程序走向 EtherCAT 从站。
但是,对于真正做 EtherCAT 工业控制的人来说,还有一个更加直接的问题:伺服的位置、速度、控制字、状态字,到底是怎么在主站和从站之间每 1 ms、500 μs 甚至更短周期持续交换的?
如果每次都像普通参数配置一样,通过 SDO 去读写:
0x6040 Controlword 0x6041 Statusword 0x6064 Position Actual Value 0x607A Target Position那么一个多轴运动控制系统很快就会遇到效率和确定性方面的问题。EtherCAT 真正适合周期性工业控制的关键机制之一,就是 PDO——Process Data Object,过程数据对象。而在 IgH 中,PDO 又不是孤立存在的,它与 PDO Entry、PDO Mapping、Domain、Datagram 和 Process Data Memory 共同形成了一整套实时数据交换链路。
可以先把整个关系简单理解成:
Object Dictionary ↓ PDO Entry ↓ PDO ↓ PDO Mapping ↓ Domain ↓ Process Data Memory ↓ Datagram ↓ EtherCAT Frame ↓ Slave因此,如果真正想理解 IgH 的实时通信机制,PDO 是绕不过去的一层。
一、PDO 到底是什么?为什么工业控制通常需要 PDO,而不是把所有数据都通过 SDO 传输?
先看一个典型的伺服控制场景。假设控制器连接 8 台伺服驱动器,每台伺服每 1 ms 都需要交换控制数据。主站发送给从站的数据可能包括:
Controlword Target Position Target Velocity Target Torque从站返回给主站的数据可能包括:
Statusword Actual Position Actual Velocity Actual Torque如果把这些数据全部通过 SDO 完成,就会形成大量请求和响应:
Master │ ├── SDO → Servo 1 ├── SDO → Servo 2 ├── SDO → Servo 3 ├── SDO → Servo 4 ├── SDO → Servo 5 ├── SDO → Servo 6 ├── SDO → Servo 7 └── SDO → Servo 8而每一个对象访问又涉及 Request、Mailbox、Response、状态处理等过程。SDO 非常适合参数配置、设备初始化、诊断和非周期性数据访问,但并不适合作为高速运动控制系统每一个周期都执行的主要数据通道。
所以 EtherCAT 工程中通常会把数据划分成两种典型类型:
EtherCAT Data │ ┌──────────┴──────────┐ │ │ SDO PDO │ │ 非周期性数据 周期性过程数据 │ │ 参数/配置/诊断 位置/速度/状态/IO可以用一句话记忆:
SDO 更像“配置和询问”,PDO 更像“周期性实时交换”。
这里需要特别注意,不能简单地把 SDO 说成“非实时”,或者把 PDO 说成“天然硬实时”。两者首先是不同的数据交换机制。SDO 主要面向对象字典访问和 Mailbox 通信,而 PDO 则面向过程数据的周期性传输。
PDO 的核心价值在于:把控制系统每个周期都需要交换的数据,提前组织成确定的数据结构,在运行阶段直接进入过程数据通道,而不需要每次重新进行复杂的对象访问。
例如,一个伺服可能配置:
RxPDO 0x6040:00 Controlword 0x607A:00 Target Position 0x60FF:00 Target Velocity TxPDO 0x6041:00 Statusword 0x6064:00 Position Actual Value 0x606C:00 Velocity Actual Value其中:
0x6040:00 0x607A:00 0x60FF:00就是具体的 PDO Entry。PDO 可以理解为对多个过程数据项的组织,而 PDO Entry 才对应具体的数据对象。
这时还需要建立一个非常重要的概念:对象字典里的对象,并不等于 PDO。
一个 EtherCAT 从站可能有数百甚至更多对象,但真正需要每个周期传输的可能只有其中几十个。因此,工程人员必须进一步确认这些对象是否已经被配置到 PDO Mapping 中。
二、从 PDO Entry 到 Domain:IgH 为什么要把 EtherCAT 数据变成 Linux 内存?
理解 IgH 的关键,就是理解它如何把 EtherCAT 网络中的过程数据转换成应用程序可以直接访问的数据结构。
假设一个伺服设备有:
0x6040:00 Controlword 0x607A:00 Target Position 0x60FF:00 Target Velocity 0x6041:00 Statusword 0x6064:00 Position Actual Value经过 PDO Mapping 后,可能形成这样的过程数据布局:
Process Data Byte 0-1 Controlword Byte 2-5 Target Position Byte 6-9 Target Velocity Byte 10-11 Statusword Byte 12-15 Actual Position这里真正重要的就不再只是:
“我要访问 0x6064。”而变成:
“0x6064:00 对应的过程数据在主站内存中的哪个位置?”这正是 IgH Domain 发挥作用的地方。
在 IgH 中,可以通过:
domain = ecrt_master_create_domain(master);创建一个过程数据 Domain。官方 Application Interface 文档将 Domain 描述为一组过程数据的逻辑集合,应用程序可以通过ecrt_domain_data()获取过程数据内存,并通过ecrt_domain_process()与ecrt_domain_queue()完成过程数据的接收处理和发送准备。
因此可以把 Domain 理解成:
Domain │ ├── PDO Entry A ├── PDO Entry B ├── PDO Entry C ├── PDO Entry D └── ...最终形成:
连续的 Process Data Memory应用程序通过:
domain_data = ecrt_domain_data(domain);获得过程数据内存指针。然后通过 PDO Entry 对应的 offset 访问具体数据。例如:
EC_WRITE_U16( domain_data + control_word_offset, 0x000F ); int32_t position = EC_READ_S32( domain_data + position_offset );于是原本属于 EtherCAT 对象字典中的:
0x6040:00 0x6064:00最终在应用程序中表现为:
Memory Offset ↓ C/C++ Variable这就是 IgH 非常重要的一层抽象。
为什么这种方式适合实时控制?因为初始化阶段完成 PDO Mapping、Domain Registration 和 Offset Assignment 后,进入周期运行阶段,应用程序主要进行的就变成了内存读写,而不是每一个控制周期重新解析对象字典、发起对象访问和处理 Mailbox。
可以简单理解成:
初始化阶段: “确定数据在哪里” 实时运行阶段: “直接访问数据”这也是 IgH Domain 的核心意义。
需要注意的是,Domain 并不是一个简单的“字节数组”。PDO Entry 可以具有不同的数据长度,例如 1 bit、8 bit、16 bit、32 bit 或 64 bit,因此过程数据映射中除了 byte offset,还可能涉及 bit position。也正因为如此,工程上不能凭经验直接写死:
Controlword = offset 0 Target Position = offset 2 Statusword = offset 6正确方式应该是让 IgH 根据实际 PDO 注册和映射关系确定 offset,而不是人工猜测内存位置。
三、IgH 是怎么把 PDO 注册进 Domain 的?一个完整的过程数据初始化过程
理解 Domain 后,再来看 IgH 的实际 API 就会比较清楚。
一个典型的 EtherCAT Master 初始化过程大致是:
Request Master ↓ Create Domain ↓ Get Slave Configuration ↓ Configure PDO ↓ Register PDO Entry ↓ Activate Master ↓ Get Domain Data ↓ Enter Cyclic Task首先申请 Master:
master = ecrt_request_master(0);然后创建 Domain:
domain = ecrt_master_create_domain(master);接下来获取具体从站配置:
sc = ecrt_master_slave_config( master, alias, position, vendor_id, product_code );这里的 Alias、Position、Vendor ID 和 Product Code 用于定位具体 EtherCAT 从站。
然后根据设备实际情况进行 PDO 配置,例如:
ecrt_slave_config_pdos( sc, EC_END, syncs );其中会涉及 Sync Manager、PDO Assignment 和 PDO Mapping 等配置。
随后通过:
ecrt_domain_reg_pdo_entry_list(domain_regs);注册需要使用的 PDO Entry。一个典型结构可能类似:
static const ec_pdo_entry_reg_t domain_regs[] = { {0, 0, VENDOR_ID, PRODUCT_CODE, 0x6040, 0, &control_word_offset}, {0, 0, VENDOR_ID, PRODUCT_CODE, 0x607A, 0, &target_position_offset}, {0, 0, VENDOR_ID, PRODUCT_CODE, 0x6041, 0, &status_word_offset}, {0, 0, VENDOR_ID, PRODUCT_CODE, 0x6064, 0, &actual_position_offset}, {} };这里最值得注意的不是代码本身,而是它表达出的关系:
从站 ↓ Index / Subindex ↓ PDO Entry ↓ Domain ↓ Offset注册完成以后,应用程序就可以通过:
control_word_offset target_position_offset status_word_offset actual_position_offset访问对应的过程数据。
最后:
ecrt_master_activate(master);激活 Master,并获取 Domain 数据:
domain_data = ecrt_domain_data(domain);到这里,EtherCAT 对象已经逐渐转换成 Linux 应用程序能够直接访问的内存。
这也是为什么在阅读 IgH 源码时,不能只盯着ecrt_master_send()和ecrt_master_receive()。如果想真正理解实时过程数据,还必须把:
ecrt_master_create_domain() ecrt_slave_config_pdos() ecrt_domain_reg_pdo_entry_list() ecrt_domain_data()这几层串起来。
其中ecrt_domain_reg_pdo_entry_list()尤其重要,因为它建立了PDO Entry 与 Domain 内存 offset 之间的关系。
四、一个 1 ms 控制周期里,PDO 和 Domain 到底发生了什么?
初始化完成以后,系统真正进入工业控制最核心的周期任务。
典型程序可以抽象成:
while (running) { ecrt_master_receive(master); ecrt_domain_process(domain); status = EC_READ_U16( domain_data + status_word_offset ); position = EC_READ_S32( domain_data + actual_position_offset ); /* control algorithm */ EC_WRITE_U16( domain_data + control_word_offset, control_word ); EC_WRITE_S32( domain_data + target_position_offset, target_position ); ecrt_domain_queue(domain); ecrt_master_send(master); wait_next_cycle(); }如果控制周期为 1 ms,那么这段逻辑理论上每秒需要执行 1000 次。
整个过程可以拆成:
EtherCAT Frame 接收 ↓ ecrt_master_receive() ↓ ecrt_domain_process() ↓ 读取 PDO ↓ 执行控制算法 ↓ 写入 PDO ↓ ecrt_domain_queue() ↓ ecrt_master_send() ↓ EtherCAT Frame 发送 ↓ 下一个周期这里ecrt_domain_process()很容易被初学者误解。它并不是直接去网卡“读取数据”,而是处理已经由 Master 接收到的过程数据 Datagram,并更新 Domain 状态,包括对 Working Counter 等信息进行处理。换句话说:
ecrt_master_receive()负责把收到的数据交给 Master 处理,而:
ecrt_domain_process()则进一步处理 Domain 对应的过程数据。
发送方向则刚好对应:
Write PDO ↓ ecrt_domain_queue() ↓ ecrt_master_send()官方 Application Interface 对ecrt_domain_queue()的定位也是将 Domain 对应的 Datagram 排队到 Master,以便随后通过ecrt_master_send()发送。
因此可以用一个非常简单的模型记忆:
接收方向: Receive ↓ Process ↓ Read 发送方向: Write ↓ Queue ↓ Send为什么 Domain 不自己直接 Send?因为 IgH Master 还需要统一组织其他 Datagram 和通信请求。一个实际控制周期里可能不仅有过程数据,还涉及 Distributed Clocks、状态处理以及其他通信需求,所以最终由 Master 对 Datagram 进行统一调度和发送。
假设系统有:
Slave 0 = Servo 1 Slave 1 = Servo 2 Slave 2 = Servo 3 Slave 3 = IO那么完全可以把多个从站的 PDO 放入一个 Domain:
Domain │ ├── Servo 1 PDO ├── Servo 2 PDO ├── Servo 3 PDO └── IO PDO最终形成统一的:
Process Data Memory例如:
Domain Memory │ ├── Servo 1 ├── Servo 2 ├── Servo 3 └── IO这样一个控制周期内就可以统一处理整个 Domain。
当然,实际工程中也可以根据不同数据的周期和实时等级划分多个 Domain。例如:
Domain 0 Motion Control 1 ms Domain 1 Status / IO 10 ms Domain 2 Diagnostic Data 100 ms但不能简单理解为“Domain 越多实时性越高”。Domain 首先是一种过程数据组织和通信抽象,如何划分需要根据系统的数据周期、通信负载和应用架构决定。
五、从 PDO 看 EtherCAT 实时性:为什么“PDO 很快”并不等于“整个系统就是硬实时”?
现在把整个数据链路完整串起来,就能更加准确地理解 EtherCAT 的实时控制。
假设伺服目标位置:
0x607A:00对应:
target_position_offset应用程序首先计算:
target_position = 100000;然后写入 Domain Memory:
EC_WRITE_S32( domain_data + target_position_offset, 100000 );随后:
ecrt_domain_queue(domain);Domain 对应的 Datagram 进入 Master 队列。
再通过:
ecrt_master_send(master);由 Master 组织并发送 EtherCAT Frame。
Frame 经过网卡和 EtherCAT 网络后,从站在通信过程中获取属于自己的过程数据,于是伺服最终得到:
Target Position = 100000反方向同样如此。
伺服反馈:
Actual Position对应:
0x6064:00数据经过 EtherCAT Frame、Datagram 和 Domain 后进入:
domain_data应用程序再通过:
position = EC_READ_S32( domain_data + actual_position_offset );得到实际位置。
于是一个典型的闭环控制过程就形成了:
Target ↓ Application ↓ Domain Memory ↓ PDO ↓ EtherCAT ↓ Servo ↓ Actual Position ↓ PDO ↓ Domain Memory ↓ Application ↓ Control Algorithm ↓ Next Target如果控制周期是 1 ms,这个过程理论上每秒重复 1000 次。
但是到了这里必须纠正一个非常重要的概念:
PDO 适合实时控制,并不意味着只要用了 PDO,整个系统就自动具备硬实时能力。
这是理解 EtherCAT 和实时 Linux 时最容易出现的误区之一。
EtherCAT 解决的是工业通信链路上的高效、周期性、确定性数据交换问题;IgH 则是在 Linux 环境中实现 EtherCAT Master 的软件。最终整个控制系统能不能稳定完成 1 ms、500 μs 甚至更短的控制周期,还受到很多其他因素影响:
Application ↓ Scheduler ↓ Real-time Task ↓ IgH ↓ Network Driver ↓ NIC ↓ EtherCAT ↓ Slave如果 EtherCAT 网络本身可以稳定工作在 1 ms 周期,但 Linux 控制线程出现:
1.00 ms 1.01 ms 1.02 ms 1.80 ms 0.99 ms那么最终控制系统依然存在明显的周期抖动。
所以真正的系统实时性应该理解成一个完整链路:
EtherCAT + IgH Master + Network Driver + IRQ + CPU + Scheduler + Application + Hardware共同决定。
这也是为什么后续分析 EtherCAT 实时控制时,不能只讨论“网络延迟”。对于运动控制系统来说,更值得关注的往往是端到端控制周期的确定性:控制线程什么时候被唤醒、什么时候完成计算、什么时候提交 EtherCAT 数据、什么时候完成发送,以及下一次反馈什么时候能够稳定到达。
从这个角度来看,IgH Domain 的设计非常重要。它把 EtherCAT 的过程数据转换成 Linux 应用程序能够直接访问的内存模型,使实时控制线程可以围绕固定的过程数据布局执行:
Receive ↓ Process ↓ Read ↓ Control ↓ Write ↓ Queue ↓ Send而不需要在每一个周期里重复处理复杂的对象字典和 Mailbox 访问。
对于工业控制、PLC、伺服、机器人、数控以及智能制造设备来说,这种设计意味着通信层和控制算法之间可以建立更加清晰的边界。
因此,理解 IgH 的 PDO 和 Domain,实际上已经不只是理解一个 API,而是在理解:
EtherCAT 如何把工业现场的控制变量,最终变成 Linux 实时任务可以直接操作的内存。
这也是从 EtherCAT 协议走向完整实时工业控制系统非常关键的一步。
下一篇,我们继续沿着这条链路向下深入。
这一篇已经知道:
Object Dictionary ↓ PDO Entry ↓ PDO ↓ Domain ↓ Process Data Memory但还有一个关键问题没有真正解决:
这些 PDO Entry 到底是谁决定放在哪里的?
为什么:
0x6040 Controlword可能位于:
Byte 0而:
0x607A Target Position可能位于:
Byte 2为什么换一款伺服驱动器之后,同样是 CiA402,对象的 PDO 位置和长度可能完全不同?
为什么很多 EtherCAT 设备在 PREOP 可以正常通信,但一配置 PDO 就无法进入 SAFEOP 或 OP?
这里真正需要深入的就是:
PDO Assignment PDO Mapping Sync Manager FMMU Object Dictionary以及:
0x1C12 0x1C13 0x1600 0x1A00这些对象之间究竟是什么关系。
下一篇:
《EtherCAT 的 PDO 映射到底怎么工作的?从对象字典到过程数据内存》
我们将进一步从源码和工程配置角度拆解 PDO Mapping,让“EtherCAT 对象为什么最终变成一个 Linux 内存 offset”这件事彻底讲清楚。