ZYNQ平台部署IgH EtherCAT主站实现伺服电机同步运动控制
1. 项目概述:当ZYNQ遇上开源EtherCAT主站
在工业自动化领域,实时、可靠的现场总线通信是控制系统的神经中枢。EtherCAT以其卓越的实时性、灵活的拓扑结构和高效的带宽利用率,已成为高性能运动控制的首选方案。然而,对于许多嵌入式开发者而言,EtherCAT主站的开发门槛不低,商业主站授权费用昂贵,而开源方案又往往面临驱动适配、实时性保障等难题。
最近,我在一个基于Xilinx ZYNQ-7010的工控项目里,成功部署并验证了开源的IgH EtherCAT主站,用它来驱动两台台达伺服电机,实现了精准的同步运动控制。整个过程从环境搭建、内核配置、主站编译到应用调试,踩了不少坑,也积累了一套行之有效的实操流程。如果你也正在或即将在ZYNQ、AM335x这类嵌入式Linux平台上折腾EtherCAT,希望这篇详尽的记录能帮你少走弯路。
简单来说,这个方案的核心就是:在运行Xenomai实时补丁的Linux系统上,部署开源的IgH EtherCAT Master,通过标准的网口与支持EtherCAT的伺服驱动器通信,最终用我们自己的应用程序发送控制字,让电机转起来。它完美融合了ZYNQ的ARM+FPGA异构优势、Linux的丰富生态、Xenomai的硬实时保证以及IgH的开源灵活性。
2. 核心组件选型与平台解析
2.1 为什么选择IgH EtherCAT?
市面上开源的EtherCAT主站主要有IgH EtherCAT Master和SOEM。我选择IgH的主要原因在于其成熟度和与Linux内核的深度集成。
IgH EtherCAT Master是一个运行在Linux内核空间的主站协议栈。它通过一个内核模块(ec_master.ko)实现核心的EtherCAT状态机、数据帧处理和时间同步机制。同时,它为用户空间提供了字符设备接口(/dev/EtherCAT0等)和一套完整的命令行工具(ethercat工具集),使得应用程序和配置工具能够方便地与主站交互。这种架构既保证了实时数据处理的性能(在内核中直接处理网络数据包),又提供了用户空间开发的灵活性。
相比之下,SOEM是一个完全运行在用户空间的库,更轻量,移植更方便,但在极致的实时性要求下,数据从网卡到用户空间的多层拷贝和上下文切换可能会引入微秒级的抖动。对于ZYNQ平台,其PS端的双核ARM Cortex-A9性能足够强劲,配合Xenomai的实时内核,能够很好地驾驭IgH的内核态架构,实现稳定且高精度的1ms甚至更短的通信周期。
2.2 硬件平台:TLZ7x-EasyEVM-S评估板剖析
我使用的硬件是创龙科技的TLZ7x-EasyEVM-S评估板,其核心是一颗Xilinx Zynq-7000系列SoC(我手头的是XC7Z010)。这颗芯片的魅力在于其“All Programmable”特性:
- 处理系统(PS):双核ARM Cortex-A9,运行Linux和我们的应用程序。
- 可编程逻辑(PL):即FPGA部分,在本案例中,主要用其提供的RGMII接口连接千兆以太网PHY芯片。
评估板的网口直接连接伺服驱动器的EtherCAT IN口。这里有一个关键点:用于EtherCAT通信的网口,其MAC地址需要在主站初始化时明确指定。因为IgH主站需要绑定到特定的网络接口上。通常,我们会使用ifconfig或ip addr命令来查询网卡的MAC地址。
注意:并非所有网卡都适用于EtherCAT。一些消费级网卡的驱动可能不支持IgH所需的精确时间戳或直接内存访问(DMA)模式。建议使用芯片厂商明确支持或社区验证过的网卡驱动。ZYNQ的GEM控制器驱动在打了Xenomai补丁的内核中,表现通常很稳定。
2.3 实时性基石:Xenomai + IgH 的协同
工业运动控制对实时性的要求是严苛的。标准的Linux内核虽然功能强大,但其任务调度、中断处理并非为微秒级的确定性延迟而设计。这就是我们需要Xenomai的原因。
Xenomai是一个与Linux共内核的硬实时扩展框架。它通过“双核”或“Cobalt”内核的方式,为Linux提供了一个并行的、优先级驱动的实时执行环境。我们的IgH主站内核模块和运动控制应用程序,都可以运行在Xenomai的实时域中。
它们是如何协作的?
- 内核层面:我们编译一个集成了Xenomai补丁的Linux内核。IgH的
ec_master.ko模块在加载时,会检测到Xenomai的存在,并利用其提供的实时API(如实时线程、互斥锁、定时器)来确保EtherCAT数据帧的发送和接收过程不被普通Linux任务打断。 - 应用层面:我们的运动控制程序(
igh_ethercat_dc_motor)也会链接Xenomai的用户态库,并创建实时线程。这个线程以最高的优先级运行,每隔1ms(即EtherCAT周期)被精确唤醒一次,执行“发送控制数据-接收反馈数据-计算下一周期指令”的逻辑。
这种组合保证了从应用层指令发出,到网口数据帧送出的整个路径,延迟和抖动都被控制在极小的范围内(通常在几十微秒以内),从而满足多轴同步运动控制的需求。
3. 开发环境搭建与系统镜像制作
3.1 宿主机开发环境配置
我的开发主机是Windows 10,在上面运行VMware Workstation 15,虚拟机里安装的是Ubuntu 14.04.3 LTS 64位。选择这个相对旧的Ubuntu版本,主要是为了与PetaLinux 2017.4工具链保持兼容。如果你的PetaLinux版本更新,可能需要对应更新的Ubuntu版本。
关键工具链安装:在Ubuntu虚拟机中,除了安装PetaLinux 2017.4,还需要确保以下工具已就位:
sudo apt-get install -y build-essential git make gcc g++ net-tools libncurses5-dev tftpd-hpa nfs-kernel-serverPetaLinux的安装过程需要关注许可协议和安装路径,确保settings.sh环境脚本能被正确source。
3.2 构建带Xenomai的Linux内核
这是整个过程中最具挑战性的一步。创龙科技提供了已验证的内核镜像(zImage)和设备树(devicetree.dtb),这大大降低了入门难度。对于初次尝试者,我强烈建议直接使用他们提供的镜像,快速搭建起可运行的环境,先看到电机转起来的效果。
如果你想从源码构建,流程大致如下:
- 获取源码:获取Linux内核源码、Xenomai源码和IgH EtherCAT主站源码。
- 打补丁:首先将Xenomai的IPIPE补丁打入Linux内核源码树。这一步需要仔细对照Xenomai和内核的版本,匹配错误会导致编译失败或系统不稳定。
- 配置内核:使用
make menuconfig进行配置。关键选项包括:- 启用Xenomai相关的驱动和子系统(
CONFIG_XENOMAI,CONFIG_IPIPE)。 - 确保ZYNQ的GEM以太网驱动被编译为模块(
CONFIG_XILINX_GMII)或内建,并关注其依赖项。 - 根据IgH文档,可能需要启用一些网络和调试选项。
- 启用Xenomai相关的驱动和子系统(
- 编译内核与模块:使用交叉编译工具链(如
arm-linux-gnueabihf-)进行编译。得到zImage和一系列.ko内核模块。 - 编译设备树:根据你的板级硬件(如DDR大小、外设接口),修改或确认设备树源文件(
.dts),并编译生成.dtb文件。
实操心得:
- 版本锁死:嵌入式开发,尤其是涉及实时补丁和驱动时,保持所有组件版本的一致性是第一要务。记录下你成功搭配的版本组合:内核版本、Xenomai版本、IgH版本、交叉编译器版本。这能为你未来重现环境或排查问题节省大量时间。
- 模块管理:编译生成的内核模块(
.ko文件)必须与运行的内核版本严格匹配。使用uname -r命令查看目标板运行的内核版本,然后将模块文件放置到/lib/modules/$(uname -r)/目录下,并执行depmod命令生成模块依赖关系。
3.3 IgH EtherCAT主站的交叉编译
IgH的编译相对直接。解压源码后,进入目录,通常使用./configure进行配置,关键是指定交叉编译器和内核源码路径:
./configure --host=arm-linux-gnueabihf --with-linux-dir=/path/to/your/linux-kernel-source --enable-cycles --enable-hrtimer--host:指定交叉编译工具链前缀。--with-linux-dir:至关重要,必须指向你刚才编译的、打了Xenomai补丁的内核源码目录。IgH需要根据该内核的头文件来编译其内核模块。--enable-cycles和--enable-hrtimer:启用高精度计时器,对提升周期精度有帮助。
配置完成后,执行make和make install。make install会生成一个_install目录,里面包含了主站运行所需的所有文件:内核模块(ec_master.ko,ec_generic.ko等)、用户态工具(ethercat)、库文件以及配置文件。
4. 目标板系统部署与主站启动
4.1 文件系统准备
假设你的ZYNQ评估板通过SD卡启动,SD卡通常分为两个分区:FAT分区(存放zImage和devicetree.dtb)和EXT4分区(根文件系统rootfs)。
- 替换内核:将编译好的或提供的
zImage和devicetree.dtb拷贝到SD卡的FAT分区(通常是/boot目录)。 - 部署内核模块:将内核模块(例如
4.9.0-xilinx-g7645980.tar.gz解压出的整个目录)拷贝到rootfs分区的/lib/modules/下。确保目录名与uname -r的输出一致。 - 部署IgH主站:将编译生成的
_install整个目录拷贝到rootfs的某个位置,例如/home/root/下。
4.2 启动主站服务
上电启动开发板,进入Linux系统。
第一步:加载主站内核模块首先,需要知道用于EtherCAT通信的网卡的MAC地址。
ifconfig eth0记下HWaddr后面的地址,例如00:0a:35:00:01:22。
然后,以指定主设备(Master)的方式加载ec_master模块。这是最关键的一步,它告诉IgH使用哪个网卡。
insmod /home/root/_install/modules/ec_master.ko main_devices=00:0a:35:00:01:22main_devices参数可以指定一个或多个MAC地址。加载成功后,使用lsmod应该能看到ec_master模块。
第二步:配置并启动主站服务IgH提供了一套SysVinit风格的启动脚本。
mkdir -p /etc/sysconfig cp /home/root/_install/etc/sysconfig/ethercat /etc/sysconfig/ cp /home/root/_install/modules/ec_master.ko /lib/modules/$(uname -r)/ depmod -a /home/root/_install/etc/init.d/ethercat start- 拷贝
ethercat配置文件,里面可以定义主站参数,如周期时间、调试级别等。 - 将主站模块拷贝到标准模块目录,方便后续通过
modprobe加载。 depmod -a更新模块依赖。- 启动服务。如果成功,你会看到类似“Starting EtherCAT master”的成功信息,并且
/dev/EtherCAT0设备节点会被创建。
第三步:加载通用从站驱动并设置环境变量
insmod /home/root/_install/modules/ec_generic.ko export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/root/_install/libec_generic.ko是一个通用的从站设备驱动,它为每个被发现的EtherCAT从站(如我们的伺服驱动器)在/sys文件系统下创建接口,方便查询信息。- 设置
LD_LIBRARY_PATH,让系统能够找到IgH的用户态库文件(如libethercat.so)。
4.3 总线扫描与从站配置
主站启动后,可以使用ethercat命令行工具来探测和配置网络。
- 查看主站状态:
ethercat master - 扫描总线:
ethercat slaves。这个命令会列出总线上所有发现的从站,显示它们的别名、位置、名称、状态等。如果连接正确,你应该能看到两个台达ASD-A2驱动器的信息。 - 检查从站PDO映射:
ethercat pdos可以查看从站的过程数据对象映射,这决定了主站和从站之间循环交换的数据结构。对于伺服驱动,通常包括控制字、目标位置/速度/转矩、状态字、实际位置/速度/转矩等。 - 检查SDO字典:
ethercat sdos可以浏览从站的对象字典,这是配置从站参数(如运行模式、增益参数)的入口。
注意事项:在实际项目中,我们通常需要一个XML格式的从站描述文件(ESI, EtherCAT Slave Information)。这个文件由从站设备厂商提供,它详细定义了从站的对象字典、PDO映射等信息。IgH主站在启动时会读取
/etc/ethercat/目录下的ESI文件来正确识别和配置从站。对于常见的伺服驱动器,其ESI文件通常可以在官网下载。如果没有,可能需要根据手册手动编写,或者依赖ec_generic驱动的通用解析。
5. 运动控制应用程序深度解析
案例提供的igh_ethercat_dc_motor程序是一个典型的EtherCAT周期任务应用。我们来拆解它的核心逻辑。
5.1 程序框架与实时线程
程序的核心是一个运行在Xenomai实时域下的周期性线程。伪代码逻辑如下:
#include <ecrt.h> // IgH EtherCAT Master 用户态库头文件 #include <alchemy/task.h> // Xenomai Alchemy API 任务头文件 // 定义主站、从站、PDO等句柄 static ec_master_t *master = NULL; static ec_domain_t *domain = NULL; static ec_slave_config_t *sc_drive1 = NULL, *sc_drive2 = NULL; // 定义过程数据缓冲区指针 static uint8_t *domain_pd = NULL; // 定义指向PDO条目中具体数据的指针(如控制字、目标速度) static uint16_t *ctrl_word1, *target_vel1; static int32_t *actual_vel1; void* control_loop(void *arg) { rt_task_set_periodic(NULL, TM_NOW, 1000000); // 设置1ms周期 (1,000,000 ns) while (running) { rt_task_wait_period(NULL); // 等待下一个周期点,保证精确的1ms间隔 // 1. 接收过程数据 (Input) ecrt_master_receive(master); ecrt_domain_process(domain); // 2. 应用控制算法 (计算新的目标速度) // 例如:实现一个梯形速度曲线 static int phase = 0; static int32_t current_target_vel = 0; const int32_t max_vel = 10000; const int32_t accel = 100; // 每周期加速度 switch(phase) { case 0: // 加速段 current_target_vel += accel; if (current_target_vel >= max_vel) { current_target_vel = max_vel; phase = 1; } break; case 1: // 匀速段 (保持一段时间,案例中可能直接进入减速) // ... 可以加入计数器 phase = 2; break; case 2: // 减速段 current_target_vel -= accel; if (current_target_vel <= 0) { current_target_vel = 0; phase = 0; // 循环 } break; } // 3. 将计算值写入过程数据缓冲区 (Output) *target_vel1 = (direction == FORWARD) ? current_target_vel : -current_target_vel; // 设置控制字,例如使能电机、启动运行等 *ctrl_word1 = 0x000F; // 示例:使能、启动 // 4. 发送过程数据 (Output) ecrt_domain_queue(domain); ecrt_master_send(master); } return NULL; } int main(int argc, char** argv) { // 初始化:申请主站、创建域、配置从站、映射PDO... master = ecrt_request_master(0); domain = ecrt_master_create_domain(master); sc_drive1 = ecrt_master_slave_config(master, 0, 0x00000902); // 0号从站, 厂家ID, 产品码 // 配置PDO映射:告诉主站我们想交换哪些数据 ecrt_slave_config_pdos(sc_drive1, EC_END, sync_manager_0_pdos); ecrt_slave_config_pdos(sc_drive1, EC_END, sync_manager_2_pdos); // 注册PDO条目,获取数据指针 ecrt_domain_reg_pdo_entry_list(domain, domain_regs); // 激活主站 ecrt_master_activate(master); // 获取过程数据域指针 domain_pd = ecrt_domain_data(domain); // 将数据指针关联到具体的PDO条目 ctrl_word1 = (uint16_t *)(domain_pd + offset_ctrl_word); target_vel1 = (int32_t *)(domain_pd + offset_target_vel); actual_vel1 = (int32_t *)(domain_pd + offset_actual_vel); // 创建Xenomai实时任务并启动控制循环 rt_task_create(&control_task, "motor_ctrl", 0, 99, T_JOINABLE); rt_task_start(&control_task, &control_loop, NULL); // 主线程等待或处理其他事务 rt_task_join(&control_task, TM_INFINITE); // 清理资源 ecrt_release_master(master); return 0; }5.2 关键步骤详解
- 请求与激活主站:
ecrt_request_master获取主站句柄,ecrt_master_activate是最后一步,激活后主站开始与从站进行周期性通信。激活必须在所有配置(从站、PDO)完成后进行。 - 配置从站与PDO映射:这是连接逻辑(应用程序)和物理(驱动器)的桥梁。你需要知道从站的“厂家ID”和“产品码”(可从
ethercat slaves命令或ESI文件获得)。更重要的是,需要根据驱动器手册,配置正确的PDO映射。这决定了主站循环交换的数据包里包含哪些具体变量(如0x6040:00控制字,0x60FF:00目标速度)。 - 注册PDO条目并获取指针:通过
ecrt_domain_reg_pdo_entry_list注册我们关心的PDO条目,然后通过ecrt_domain_data获取整个过程数据域的基地址。通过计算好的偏移量,我们可以得到指向每个具体变量(如控制字、目标速度)的指针。操作这些指针,就等于直接操作发送给驱动器的数据。 - 实时周期循环:
rt_task_wait_period:这是保证周期精度的关键。Xenomai确保线程在精确的1ms间隔被唤醒。ecrt_master_receive/ecrt_domain_process:从网卡缓冲区读取最新的从站反馈数据(Input),并更新到过程数据域中。此时,actual_vel1等输入指针指向的数据就是驱动器的最新状态。- 应用层计算:根据反馈和规划算法(如案例中的梯形速度曲线),计算出新的控制指令。
- 将新指令赋值给输出指针(如
*target_vel1 = ...)。 ecrt_domain_queue/ecrt_master_send:将更新后的输出数据域打包,并通过网卡发送出去。
5.3 伺服驱动器的状态机与控制
要让伺服电机转动,仅仅发送速度值是不够的。必须遵循驱动器的状态机流程,通常是通过控制字(0x6040)来操作。一个典型的启动序列是:
- 上电:驱动器通电,进入“Switch on disabled”状态。
- 故障复位:如果存在故障,发送控制字
0x0080(清除故障)。 - 使能:发送
0x0006(准备上电),然后发送0x0007(上电),最后发送0x000F(启动运行)。 - 发送目标值:在运行状态下,持续向目标速度(0x60FF)或目标位置(0x607A)写入数值。
- 停止:发送
0x0000(快速停止)或0x0006(回到准备上电状态)。
案例程序很可能在初始化阶段通过SDO(服务数据对象)写操作,将驱动器的运行模式(0x6060)设置为“速度模式”(例如,值为3)。然后,在周期性任务中,只需要不断更新目标速度和控制字(保持为0x000F)即可。
6. 调试技巧与常见问题排查
在实际部署中,你几乎一定会遇到各种问题。下面是我总结的排查清单和工具。
6.1 问题排查速查表
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
insmod ec_master.ko失败 | 内核版本不匹配;内核配置缺少依赖项;Xenomai补丁未正确打入。 | 1. 检查uname -r与模块编译时内核版本是否一致。2. 使用 dmesg | tail查看内核详细错误信息。3. 确认编译内核时启用了 CONFIG_XENOMAI,CONFIG_IPIPE。 |
ethercat slaves无输出或显示INIT状态 | 物理连接问题;网卡未绑定;从站未上电或故障;主站周期未开始。 | 1. 检查网线、驱动器电源、指示灯。 2. 确认加载 ec_master时指定的main_devicesMAC地址正确。3. 使用 ethercat master查看主站状态,确认AL state是否为Operational。4. 使用 ethercat debug命令开启调试输出,观察报文。 |
| 电机不转动 | 驱动器未使能;运行模式设置错误;PDO映射错误;控制字序列错误。 | 1. 使用ethercat sdos查看对象字典,确认0x6060(模式)和0x6040(控制字)的值。2. 使用 ethercat pdos确认目标速度/位置的PDO条目已正确映射。3. 在应用程序中打印过程数据域的内容,确认发送的控制字和目标值是否正确。 4. 查阅驱动器手册,严格按照状态机操作。 |
| 周期时间抖动大或出现看门狗超时 | 系统实时性不足;应用程序非实时部分干扰;网络负载过重。 | 1. 使用Xenomai的latency工具测试系统基准延迟和抖动。2. 确保控制线程优先级为最高(如99)。 3. 使用 cyclictest工具测试周期任务的实时性能。4. 检查应用程序中是否有在实时线程中调用了可能导致阻塞的非实时函数(如 printf,malloc)。 |
应用程序在ecrt_master_activate时卡住或报错 | 从站配置与实际不符;PDO映射超出从站能力;同步管理器配置冲突。 | 1. 仔细核对从站配置时的厂家ID、产品码。 2. 检查PDO映射条目是否在从站的ESI文件中存在且允许映射。 3. 尝试简化配置,例如先只映射一个最基本的PDO进行测试。 |
6.2 实用调试命令与工具
IgH 命令行工具:
ethercat master:查看主站状态、周期时间、看门狗等。ethercat slaves -v:详细显示从站信息,包括当前状态、AL状态码。ethercat pdos:查看已配置的PDO映射。ethercat sdos:浏览从站对象字典,可用于在线读取(download)或写入(upload)参数。ethercat graph:生成总线拓扑图(需要Graphviz)。ethercat debug:设置调试级别,在终端打印详细的报文信息,是排查通信问题的利器。
Xenomai 实时性测试:
latency:运行一个测试程序,测量从定时器到期到任务被唤醒的延迟(抖动)。cyclictest:更全面的实时性测试工具,可以测量任务调度延迟。
系统状态监控:
dmesg:查看内核环缓冲区日志,加载模块、IgH主站初始化、错误等信息都会在这里打印。top或htop:查看系统负载和进程优先级。确保你的实时线程优先级(PRI列)最高(如RT 99)。
6.3 性能优化心得
- 隔离CPU核心:在ZYNQ的双核A9上,可以将一个核心完全分配给实时任务。通过内核启动参数
isolcpus=1隔离第二个核心,然后使用taskset命令将你的实时进程绑定到该核心上。这可以最大程度避免非实时任务对实时任务的干扰。 - 优化内核配置:关闭不需要的内核功能(如图形界面、复杂电源管理、调试符号),启用完全抢占式内核(
CONFIG_PREEMPT),并调整时钟源为高精度定时器。 - 网络优化:确保用于EtherCAT的网口中断绑定到正确的CPU核心(与实时任务同核),并考虑启用中断合并或调整NAPI参数,但需谨慎测试。
- 应用层优化:在实时线程中,避免任何可能导致阻塞或内存分配的操作。将日志输出、文件操作等非实时任务放到一个独立的非实时线程中,通过线程安全的队列进行通信。
7. 项目总结与扩展思考
通过这个完整的ZYNQ IgH EtherCAT主站项目,我们不仅实现了一个简单的电机控制,更搭建起了一个高性能的实时运动控制开发平台。这个平台的价值在于其灵活性和可扩展性。
后续可以深入的方向:
- 多轴与同步:案例控制了两个电机,但它们是异步启动的。真正的多轴同步需要利用EtherCAT的分布式时钟(DC)功能,让所有从站的时钟与主站同步,并在精确的同步时刻同时生效新的控制指令。IgH完全支持DC同步,需要在主站和从站配置中启用。
- FPGA协处理:ZYNQ的PL(FPGA)部分大有可为。可以将通信协议栈中时间要求最苛刻的部分(如EtherCAT帧的组包和解包、精确的时间戳插入)放到FPGA中实现,进一步减轻ARM CPU的负担,并实现纳秒级的同步精度。这通常需要设计一个EtherCAT IP核。
- 高级控制算法:在ARM端实现更复杂的运动轨迹规划(如S曲线、样条插补)、位置环/速度环PID控制,甚至模型预测控制(MPC)。
- 集成上层软件:将此平台与ROS(Robot Operating System)的实时控制框架(如ros_control)结合,或者开发一个基于Qt的人机界面(HMI),用于参数设置、状态监控和手动操作。
最后的建议:工业现场总线调试,耐心和细致的记录至关重要。从第一天起,就维护一个详细的实验日志,记录下每一个步骤、每一次配置更改、每一个错误信息以及最终的解决方案。这些记录将成为你最宝贵的财富,也能在团队协作中极大提升效率。EtherCAT的世界很精彩,虽然入门有些陡峭,但一旦打通,你会发现它为嵌入式高性能控制打开了一扇全新的大门。
