EtherCAT 从站开发全流程实战:手把手教你配置 XML、实现 PDO 映射与伺服通信
·
EtherCAT 从站开发实战:从 XML 配置文件到伺服驱动器通信
摘要:EtherCAT 作为工业自动化领域的高性能实时以太网协议,其从站开发是国产高端数控系统、机器人控制器等设备的核心技术。本文通过一个完整的伺服驱动器从站开发案例,详细讲解 EtherCAT 从站 XML 配置文件的编写、PDO 映射、同步时钟配置,并提供基于 IgH EtherCAT Master 的 Linux 平台 C 代码实现,以及 Wireshark 抓包分析、同步时钟调试等实战技巧。
关键词:EtherCAT, 工业以太网, 伺服驱动, 实时通信, XML 配置, PDO 映射, 同步时钟, IgH Master
目录
- EtherCAT 从站开发流程概览
- 第一步:编写从站 XML 配置文件
- 第二步:配置 PDO 映射与同步管理器
- 第三步:基于 IgH EtherCAT Master 的代码实现
- 第四步:Wireshark 抓包分析与协议验证
- 第五步:同步时钟调试与性能优化
- 常见故障排查表
- 完整代码仓库
EtherCAT 从站开发流程概览
一个完整的 EtherCAT 从站开发流程包括:
1. 硬件设计(FPGA/ASIC 实现 EtherCAT 从站控制器)
2. XML 配置文件编写(描述从站对象字典、PDO 映射等)
3. EtherCAT Master 集成(Linux 平台常用 IgH Master)
4. 通信测试与性能调优
本文聚焦于 2-4 步,假设硬件已具备 EtherCAT 从站接口(如 ET1100、LAN9252 等芯片)。
第一步:编写从站 XML 配置文件
EtherCAT 从站通过 XML 配置文件向主站描述其能力。以下是一个伺服驱动器的简化配置文件 servo_drive.xml:
<?xml version="1.0" encoding="UTF-8"?>
<EtherCATConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="EtherCATConfig.xsd">
<Config>
<Groups>
<Group Name="ServoDrive">
<Type>Servo</Type>
</Group>
</Groups>
<Devices>
<Device ProductCode="0x12345678" RevisionNo="0x00010000"
Type="ServoDrive" Name="SV660 Servo Drive">
<!-- 厂商信息 -->
<Vendor Id="0x00001234">Inovance</Vendor>
<Description>SV660 Series Servo Drive</Description>
<!-- 邮箱通信(CoE)配置 -->
<Mailbox>
<CoE SdoInfo="true" PdoAssign="true" PdoConfig="true" />
<SupportedProtocols>
<CoE />
</SupportedProtocols>
</Mailbox>
<!-- 同步管理器配置 -->
<Sm Num="0">
<Enable>1</Enable>
<StartAddress>0x1000</StartAddress>
<DefaultSize>128</DefaultSize>
<ControlByte>0x26</ControlByte>
<Enable>1</Enable>
</Sm>
<Sm Num="1">
<Enable>1</Enable>
<StartAddress>0x1100</StartAddress>
<DefaultSize>128</DefaultSize>
<ControlByte>0x22</ControlByte>
<Enable>1</Enable>
</Sm>
<Sm Num="2">
<Enable>1</Enable>
<StartAddress>0x1200</StartAddress>
<DefaultSize>128</DefaultSize>
<ControlByte>0x24</ControlByte>
<Enable>1</Enable>
</Sm>
<Sm Num="3">
<Enable>1</Enable>
<StartAddress>0x1300</StartAddress>
<DefaultSize>64</DefaultSize>
<ControlByte>0x20</ControlByte>
<Enable>1</Enable>
</Sm>
<!-- 对象字典 -->
<Dictionary>
<!-- 设备基本信息 -->
<Object Index="0x1000" DataType="UINT32" BitSize="32" Access="ro">
<Name>Device Type</Name>
<DefaultValue>0x00020192</DefaultValue>
</Object>
<!-- 伺服控制字 (0x6040) -->
<Object Index="0x6040" DataType="UINT16" BitSize="16" Access="rw">
<Name>Control Word</Name>
<DefaultValue>0x0000</DefaultValue>
<Info>
<Bit Offs="0">Switch On</Bit>
<Bit Offs="1">Enable Voltage</Bit>
<Bit Offs="2">Quick Stop</Bit>
<Bit Offs="3">Enable Operation</Bit>
<Bit Offs="4">Operation Mode Specific</Bit>
<Bit Offs="5">Operation Mode Specific</Bit>
<Bit Offs="6">Operation Mode Specific</Bit>
<Bit Offs="7">Fault Reset</Bit>
<Bit Offs="8">Halt</Bit>
<Bit Offs="9">Operation Mode Specific</Bit>
<Bit Offs="10">Reserved</Bit>
<Bit Offs="11">Reserved</Bit>
<Bit Offs="12">Reserved</Bit>
<Bit Offs="13">Reserved</Bit>
<Bit Offs="14">Reserved</Bit>
<Bit Offs="15">Reserved</Bit>
</Info>
</Object>
<!-- 伺服状态字 (0x6041) -->
<Object Index="0x6041" DataType="UINT16" BitSize="16" Access="ro">
<Name>Status Word</Name>
<DefaultValue>0x0000</DefaultValue>
</Object>
<!-- 目标位置 (0x607A) -->
<Object Index="0x607A" DataType="INT32" BitSize="32" Access="rw">
<Name>Target Position</Name>
<DefaultValue>0</DefaultValue>
<Info>Unit: pulse</Info>
</Object>
<!-- 实际位置 (0x6064) -->
<Object Index="0x6064" DataType="INT32" BitSize="32" Access="ro">
<Name>Actual Position</Name>
<DefaultValue>0</DefaultValue>
<Info>Unit: pulse</Info>
</Object>
<!-- 模式选择 (0x6060) -->
<Object Index="0x6060" DataType="INT8" BitSize="8" Access="rw">
<Name>Modes of Operation</Name>
<DefaultValue>8</DefaultValue> <!-- 8 = Cyclic Synchronous Position Mode -->
<Info>
<Enum>1: Profile Position Mode</Enum>
<Enum>3: Profile Velocity Mode</Enum>
<Enum>4: Torque Profile Mode</Enum>
<Enum>6: Homing Mode</Enum>
<Enum>8: Cyclic Synchronous Position Mode (CSP)</Enum>
<Enum>9: Cyclic Synchronous Velocity Mode (CSV)</Enum>
<Enum>10: Cyclic Synchronous Torque Mode (CST)</Enum>
</Info>
</Object>
</Dictionary>
</Device>
</Devices>
</Config>
</EtherCATConfig>
关键配置项说明
| 配置项 | 说明 | 典型值 |
|---|---|---|
ProductCode |
产品代码,主站用于识别从站 | 0x12345678 |
RevisionNo |
硬件版本号 | 0x00010000 |
Sm |
同步管理器,SM0/1 用于邮箱,SM2/3 用于过程数据 | SM0: 0x1000, SM2: 0x1200 |
ControlByte |
同步管理器控制字节 | 0x26: 邮箱写,0x22: 邮箱读,0x24: PDO 输出,0x20: PDO 输入 |
Index |
对象字典索引 | 0x6040: 控制字,0x6041: 状态字,0x607A: 目标位置 |
第二步:配置 PDO 映射与同步管理器
PDO(过程数据对象)用于周期性实时数据交换。在 XML 中配置 PDO 映射:
<!-- 在 Device 标签内添加 -->
<TxPdo Fixed="true" Sm="2" Index="0x1A00">
<Name>Servo Inputs</Name>
<Entry>
<Index>0x6041</Index> <!-- 状态字 -->
<SubIndex>0</SubIndex>
<BitLen>16</BitLen>
</Entry>
<Entry>
<Index>0x6064</Index> <!-- 实际位置 -->
<SubIndex>0</SubIndex>
<BitLen>32</BitLen>
</Entry>
</TxPdo>
<RxPdo Fixed="true" Sm="3" Index="0x1600">
<Name>Servo Outputs</Name>
<Entry>
<Index>0x6040</Index> <!-- 控制字 -->
<SubIndex>0</SubIndex>
<BitLen>16</BitLen>
</Entry>
<Entry>
<Index>0x607A</Index> <!-- 目标位置 -->
<SubIndex>0</SubIndex>
<BitLen>32</BitLen>
</Entry>
</RxPdo>
PDO 映射计算
- TxPdo(输入,从站→主站):状态字(16bit) + 实际位置(32bit) = 48bit = 6 字节
- RxPdo(输出,主站→从站):控制字(16bit) + 目标位置(32bit) = 48bit = 6 字节
总过程数据长度:12 字节,满足 EtherCAT 帧内最大 1486 字节的限制。
第三步:基于 IgH EtherCAT Master 的代码实现
IgH EtherCAT Master 是 Linux 平台最常用的开源主站实现。以下是一个简化的从站控制程序:
/**
* servo_ecat.c
* EtherCAT 伺服驱动器控制示例
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <ecrt.h>
// 从站配置
#define SLAVE_POS 0 // 从站在总线上的位置
#define VENDOR_ID 0x00001234
#define PRODUCT_CODE 0x12345678
// PDO 映射偏移量(需与 XML 配置一致)
#define OFFSET_CONTROL_WORD 0
#define OFFSET_TARGET_POS 2
#define OFFSET_STATUS_WORD 0
#define OFFSET_ACTUAL_POS 2
// 伺服状态机定义
typedef enum {
STATE_INIT = 0,
STATE_PRE_OP,
STATE_SAFE_OP,
STATE_OP
} servo_state_t;
// 全局变量
static ec_master_t *master = NULL;
static ec_master_state_t master_state;
static ec_domain_t *domain = NULL;
static ec_domain_state_t domain_state;
static ec_slave_config_t *sc = NULL;
static ec_slave_config_state_t sc_state;
// 过程数据缓冲区
static uint8_t *domain_pd = NULL;
static uint16_t *control_word = NULL;
static int32_t *target_position = NULL;
static uint16_t *status_word = NULL;
static int32_t *actual_position = NULL;
// EtherCAT 主站初始化
static int init_ethercat(void) {
printf("Initializing EtherCAT master...\n");
// 申请主站
master = ecrt_request_master(0);
if (!master) {
fprintf(stderr, "Failed to request master\n");
return -1;
}
// 创建域
domain = ecrt_master_create_domain(master);
if (!domain) {
fprintf(stderr, "Failed to create domain\n");
return -1;
}
// 配置从站
sc = ecrt_master_slave_config(master, SLAVE_POS,
VENDOR_ID, PRODUCT_CODE);
if (!sc) {
fprintf(stderr, "Failed to configure slave\n");
return -1;
}
// 配置 PDO 映射
ec_pdo_entry_reg_t regs[] = {
// RxPdo (输出)
{SLAVE_POS, 0x6040, 0, &control_word}, // 控制字
{SLAVE_POS, 0x607A, 0, &target_position}, // 目标位置
// TxPdo (输入)
{SLAVE_POS, 0x6041, 0, &status_word}, // 状态字
{SLAVE_POS, 0x6064, 0, &actual_position}, // 实际位置
{}
};
if (ecrt_domain_reg_pdo_entry_list(domain, regs) < 0) {
fprintf(stderr, "PDO entry registration failed\n");
return -1;
}
// 激活主站
if (ecrt_master_activate(master)) {
fprintf(stderr, "Failed to activate master\n");
return -1;
}
// 获取域数据指针
domain_pd = ecrt_domain_data(domain);
// 设置指针偏移
control_word = (uint16_t*)(domain_pd + OFFSET_CONTROL_WORD);
target_position = (int32_t*)(domain_pd + OFFSET_TARGET_POS);
status_word = (uint16_t*)(domain_pd + OFFSET_STATUS_WORD);
actual_position = (int32_t*)(domain_pd + OFFSET_ACTUAL_POS);
printf("EtherCAT master initialized successfully\n");
return 0;
}
// 伺服状态机
static servo_state_t servo_state_machine(uint16_t status) {
uint8_t state = (status >> 4) & 0x0F;
switch (state) {
case 0: return STATE_INIT;
case 1: return STATE_PRE_OP;
case 2: return STATE_SAFE_OP;
case 3: return STATE_OP;
default: return STATE_INIT;
}
}
// 伺服使能序列
static int enable_servo(void) {
printf("Starting servo enable sequence...\n");
// 1. 等待状态字显示"Ready to switch on"
int timeout = 100;
while ((*status_word & 0x004F) != 0x0040 && timeout-- > 0) {
ecrt_master_application_time(master, 1000000); // 1ms
ecrt_domain_queue(domain);
ecrt_master_send(master);
ecrt_domain_receive(domain);
usleep(1000);
}
if (timeout <= 0) {
fprintf(stderr, "Timeout waiting for 'Ready to switch on'\n");
return -1;
}
// 2. 发送"Switch on"命令
*control_word = 0x0006; // Switch on + Enable voltage
ecrt_domain_queue(domain);
ecrt_master_send(master);
usleep(1000);
// 3. 等待"Switched on"
timeout = 100;
while ((*status_word & 0x004F) != 0x0021 && timeout-- > 0) {
ecrt_domain_receive(domain);
usleep(1000);
}
// 4. 发送"Enable operation"
*control_word = 0x000F;
ecrt_domain_queue(domain);
ecrt_master_send(master);
usleep(1000);
// 5. 等待"Operation enabled"
timeout = 100;
while ((*status_word & 0x004F) != 0x0027 && timeout-- > 0) {
ecrt_domain_receive(domain);
usleep(1000);
}
printf("Servo enabled successfully\n");
return 0;
}
// 位置模式运动
static int move_to_position(int32_t target) {
printf("Moving to position: %d pulses\n", target);
// 检查是否在 OP 状态
if (servo_state_machine(*status_word) != STATE_OP) {
fprintf(stderr, "Servo not in OP state\n");
return -1;
}
// 设置目标位置
*target_position = target;
// 发送数据
ecrt_domain_queue(domain);
ecrt_master_send(master);
// 等待到达目标位置
int timeout = 5000; // 5秒超时
while (abs(*actual_position - target) > 10 && timeout-- > 0) {
ecrt_domain_receive(domain);
usleep(1000);
}
if (timeout <= 0) {
fprintf(stderr, "Position move timeout\n");
return -1;
}
printf("Position reached: %d pulses\n", *actual_position);
return 0;
}
// 主循环
static void run_control_loop(void) {
printf("Starting control loop...\n");
// 使能伺服
if (enable_servo() < 0) {
return;
}
// 简单的位置往返运动
for (int i = 0; i < 5; i++) {
// 正向运动
if (move_to_position(10000) < 0) break;
usleep(500000); // 暂停500ms
// 反向运动
if (move_to_position(0) < 0) break;
usleep(500000);
}
// 停止运动
*control_word = 0x0000;
ecrt_domain_queue(domain);
ecrt_master_send(master);
}
// 信号处理
static volatile int running = 1;
static void signal_handler(int sig) {
running = 0;
}
// 主函数
int main(int argc, char **argv) {
// 注册信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// 初始化 EtherCAT
if (init_ethercat() < 0) {
return 1;
}
// 运行控制循环
run_control_loop();
// 清理
printf("Shutting down...\n");
if (master) {
ecrt_release_master(master);
}
return 0;
}
编译与运行
# 安装 IgH EtherCAT Master
sudo apt-get install ethercat
# 编译
gcc -o servo_ecat servo_ecat.c -lethercat -lpthread
# 运行(需要 root 权限)
sudo ./servo_ecat
第四步:Wireshark 抓包分析与协议验证
4.1 配置 Wireshark 抓取 EtherCAT 帧
# 安装 Wireshark
sudo apt-get install wireshark
# 设置网卡为混杂模式
sudo ip link set eth0 promisc on
# 使用 tcpdump 抓包
sudo tcpdump -i eth0 -w ethercat.pcap
4.2 关键帧分析
| 帧类型 | 特征 | 用途 |
|---|---|---|
| APRD | EtherType 0x88A4,OpCode 0x01 | 读取从站寄存器 |
| APWR | EtherType 0x88A4,OpCode 0x02 | 写入从站寄存器 |
| FPWR | EtherType 0x88A4,OpCode 0x03 | 快速写入(广播) |
| LRD | EtherType 0x88A4,OpCode 0x04 | 逻辑读取 |
| LWR | EtherType 0x88A4,OpCode 0x05 | 逻辑写入 |
| LRW | EtherType 0x88A4,OpCode 0x06 | 逻辑读写 |
4.3 常见问题排查
在 Wireshark 过滤器中输入 ecat 查看 EtherCAT 帧,重点关注:
- 初始化阶段:检查
INIT → PRE-OP → SAFE-OP → OP状态转换 - PDO 映射:查看
SDO Upload/Download是否正确配置 PDO - 同步时钟:检查
SYNC0/SYNC1周期是否一致 - 数据一致性:检查输入/输出 PDO 数据是否按预期更新
第五步:同步时钟调试与性能优化
5.1 分布式时钟(DC)配置
// 在 init_ethercat 函数中添加
ec_slave_config_dc(sc, 0x0300, 1000000, 500000, 0, 0);
参数说明:
0x0300:同步模式(0x0300 = SYNC0 激活)1000000:周期时间(1ms,单位 ns)500000:同步0偏移(500μs)0:同步1周期(0 = 禁用)0:同步1偏移
5.2 抖动测量与优化
# 使用 IgH 工具测量抖动
sudo ethercat -d eth0 dc
# 输出示例:
# Slave Position DC SysTime Diff MaxDiff Jitter
# 0 0 yes 1000000 0 5 2
抖动优化建议:
| 抖动源 | 优化措施 |
|---|---|
| 网络中断 | 设置 CPU 亲和性,绑定到独立核心 |
| 系统负载 | 使用 isolcpus 内核参数隔离核心 |
| 内存访问 | 使用 mlockall() 锁定内存,避免换页 |
| 电源管理 | 禁用 CPU 频率调节(performance 模式) |
5.3 实时性配置
# 安装实时内核
sudo apt-get install linux-rt
# 配置实时优先级
sudo sysctl -w kernel.sched_rt_runtime_us=950000
sudo sysctl -w kernel.sched_rt_period_us=1000000
# 设置进程优先级
sudo chrt -f -p 99 $(pidof servo_ecat)
常见故障排查表
| 故障现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 从站无法识别 | 1. 物理连接问题 2. XML 配置错误 3. 产品代码不匹配 |
1. 检查网线、指示灯 2. 验证 XML 语法 3. 使用 ethercat slaves 查看识别结果 |
| PDO 映射失败 | 1. 同步管理器配置错误 2. 对象字典索引错误 3. 位长度不匹配 |
1. 检查 SM 控制字节 2. 验证对象字典索引 3. 使用 Wireshark 抓包分析 SDO 通信 |
| 同步时钟不同步 | 1. DC 配置错误 2. 网络拓扑过长 3. 从站不支持 DC |
1. 检查 ec_slave_config_dc 参数2. 测量网络延迟 3. 检查从站手册是否支持 DC |
| 通信周期抖动大 | 1. 系统负载过高 2. 中断冲突 3. 内存访问延迟 |
1. 使用 top 查看系统负载2. 检查 /proc/interrupts3. 使用 cyclictest 测量延迟 |
| 伺服无法使能 | 1. 状态机序列错误 2. 控制字位设置错误 3. 驱动器故障 |
1. 逐步打印状态字 2. 对照 DS402 状态机图 3. 检查驱动器报警代码 |
完整代码仓库
本文所有代码、配置文件、测试脚本已整理至 GitHub:
https://github.com/xunfeng-lab/ethercat-servo-driver
仓库包含:
config/:XML 配置文件、设备描述文件src/:IgH Master 示例代码、测试程序tools/:Wireshark 过滤脚本、性能测试工具docs/:DS402 状态机图、接线图、调试指南
本文由「洵锋」原创,10 年工业自动化全栈工程师。EtherCAT 开发涉及硬件、固件、驱动、应用多个层面,建议从简单从站开始逐步深入。评论区欢迎交流实际开发中的问题。
更多推荐



所有评论(0)