type
Post
status
Published
date
Mar 7, 2025
slug
2025/03/07/EtherCAT-master-IgH-learned-the-xenomai_posix-routine-code
summary
tags
Xenomai 3
EtherCAT
category
EtherCAT
created days
new update day
icon
password
Created_time
Mar 7, 2025 02:20 AM
Last edited time
Mar 7, 2025 03:29 AM
0 缘起
最近项目上需要使用 xenomai 的实时内核。看了一下 ethercat igh 主站协议中有对 xenomai 的官方例程代码。于是就进行学习修改了也一下。下面也是记录一下修改记录,与对应的编码逻辑。
官方提供了两种 xenomai 的例程。我首先学习的是 xenomai_posix 的例程代码。
drwxr-xr-x 4 root root 4096 Mar 7 10:16 xenomai drwxr-xr-x 4 root root 4096 Mar 6 16:50 xenomai_posix
下面是完整的例程代码,点击打开可以显示完全。
/***************************************************************************** * * Copyright (C) 2011 IgH Andreas Stewering-Bone * 2012 Florian Pose <fp@igh.de> * * This file is part of the IgH EtherCAT master * * The IgH EtherCAT Master is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2, as * published by the Free Software Foundation. * * The IgH EtherCAT master is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with the IgH EtherCAT master. If not, see <http://www.gnu.org/licenses/>. * ****************************************************************************/ #include <errno.h> #include <mqueue.h> #include <signal.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <limits.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <time.h> #ifndef XENOMAI_API_V3 #include <rtdm/rtdm.h> #include <rtdk.h> #endif #include "ecrt.h" #define NSEC_PER_SEC 1000000000 static unsigned int cycle_us = 1000; /* 1 ms */ static pthread_t cyclic_thread; static volatile sig_atomic_t run = 1; /****************************************************************************/ // EtherCAT static ec_master_t *master = NULL; static ec_master_state_t master_state = {}; static ec_domain_t *domain1 = NULL; static ec_domain_state_t domain1_state = {}; static uint8_t *domain1_pd = NULL; static ec_slave_config_t *sc_dig_out_01 = NULL; /****************************************************************************/ // process data #define BusCoupler01_Pos 0, 0 #define DigOutSlave01_Pos 0, 1 #define Beckhoff_EK1100 0x00000002, 0x044c2c52 #define Beckhoff_EL2004 0x00000002, 0x07d43052 // offsets for PDO entries static unsigned int off_dig_out0 = 0; // process data const static ec_pdo_entry_reg_t domain1_regs[] = { {DigOutSlave01_Pos, Beckhoff_EL2004, 0x7000, 0x01, &off_dig_out0, NULL}, {} }; /****************************************************************************/ /* Slave 1, "EL2004" * Vendor ID: 0x00000002 * Product code: 0x07d43052 * Revision number: 0x00100000 */ const ec_pdo_entry_info_t slave_1_pdo_entries[] = { {0x7000, 0x01, 1}, /* Output */ {0x7010, 0x01, 1}, /* Output */ {0x7020, 0x01, 1}, /* Output */ {0x7030, 0x01, 1}, /* Output */ }; const ec_pdo_info_t slave_1_pdos[] = { {0x1600, 1, slave_1_pdo_entries + 0}, /* Channel 1 */ {0x1601, 1, slave_1_pdo_entries + 1}, /* Channel 2 */ {0x1602, 1, slave_1_pdo_entries + 2}, /* Channel 3 */ {0x1603, 1, slave_1_pdo_entries + 3}, /* Channel 4 */ }; const ec_sync_info_t slave_1_syncs[] = { {0, EC_DIR_OUTPUT, 4, slave_1_pdos + 0, EC_WD_ENABLE}, {0xff} }; /***************************************************************************** * Realtime task ****************************************************************************/ void rt_check_domain_state(void) { ec_domain_state_t ds = {}; ecrt_domain_state(domain1, &ds); if (ds.working_counter != domain1_state.working_counter) { rt_printf("Domain1: WC %u.\n", ds.working_counter); } if (ds.wc_state != domain1_state.wc_state) { rt_printf("Domain1: State %u.\n", ds.wc_state); } domain1_state = ds; } /****************************************************************************/ void rt_check_master_state(void) { ec_master_state_t ms; ecrt_master_state(master, &ms); if (ms.slaves_responding != master_state.slaves_responding) { rt_printf("%u slave(s).\n", ms.slaves_responding); } if (ms.al_states != master_state.al_states) { rt_printf("AL states: 0x%02X.\n", ms.al_states); } if (ms.link_up != master_state.link_up) { rt_printf("Link is %s.\n", ms.link_up ? "up" : "down"); } master_state = ms; } /****************************************************************************/ void *my_thread(void *arg) { struct timespec next_period; int cycle_counter = 0; unsigned int blink = 0; clock_gettime(CLOCK_REALTIME, &next_period); while (run) { next_period.tv_nsec += cycle_us * 1000; while (next_period.tv_nsec >= NSEC_PER_SEC) { next_period.tv_nsec -= NSEC_PER_SEC; next_period.tv_sec++; } clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next_period, NULL); cycle_counter++; // receive EtherCAT ecrt_master_receive(master); ecrt_domain_process(domain1); rt_check_domain_state(); if (!(cycle_counter % 1000)) { rt_check_master_state(); } if (!(cycle_counter % 200)) { blink = !blink; } EC_WRITE_U8(domain1_pd + off_dig_out0, blink ? 0x0 : 0x0F); // send process data ecrt_domain_queue(domain1); ecrt_master_send(master); } return NULL; } /***************************************************************************** * Signal handler ****************************************************************************/ void signal_handler(int sig) { run = 0; } /***************************************************************************** * Main function ****************************************************************************/ int main(int argc, char *argv[]) { ec_slave_config_t *sc; int ret; signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); mlockall(MCL_CURRENT | MCL_FUTURE); printf("Requesting master...\n"); master = ecrt_request_master(0); if (!master) { return -1; } domain1 = ecrt_master_create_domain(master); if (!domain1) { return -1; } printf("Creating slave configurations...\n"); // Create configuration for bus coupler sc = ecrt_master_slave_config(master, BusCoupler01_Pos, Beckhoff_EK1100); if (!sc) { return -1; } sc_dig_out_01 = ecrt_master_slave_config(master, DigOutSlave01_Pos, Beckhoff_EL2004); if (!sc_dig_out_01) { fprintf(stderr, "Failed to get slave configuration.\n"); return -1; } if (ecrt_slave_config_pdos(sc_dig_out_01, EC_END, slave_1_syncs)) { fprintf(stderr, "Failed to configure PDOs.\n"); return -1; } if (ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) { fprintf(stderr, "PDO entry registration failed!\n"); return -1; } printf("Activating master...\n"); if (ecrt_master_activate(master)) { return -1; } if (!(domain1_pd = ecrt_domain_data(domain1))) { fprintf(stderr, "Failed to get domain data pointer.\n"); return -1; } /* Create cyclic RT-thread */ struct sched_param param = { .sched_priority = 82 }; pthread_attr_t thattr; pthread_attr_init(&thattr); pthread_attr_setdetachstate(&thattr, PTHREAD_CREATE_JOINABLE); pthread_attr_setinheritsched(&thattr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&thattr, SCHED_FIFO); pthread_attr_setschedparam(&thattr, ¶m); ret = pthread_create(&cyclic_thread, &thattr, &my_thread, NULL); if (ret) { fprintf(stderr, "%s: pthread_create cyclic task failed\n", strerror(-ret)); return 1; } while (run) { sched_yield(); } pthread_join(cyclic_thread, NULL); printf("End of Program\n"); ecrt_release_master(master); return 0; } /****************************************************************************/
可以看到例程代码的逻辑还是很清晰的。
1 代码关键部分
下面将对于例程代码中的关键部分进行分析。
1.1 头文件和全局变量
头文件
#include <errno.h> #include <mqueue.h> #include <signal.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <limits.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <time.h> #ifndef XENOMAI_API_V3 #include <rtdm/rtdm.h> #include <rtdk.h> #endif #include "ecrt.h"
- 头文件:包含了标准 C 库、POSIX 线程、信号处理、时间管理等功能。
- Xenomai 相关头文件:
rtdm.h
和rtdk.h
用于 Xenomai 的实时操作。
- EtherCAT 主站库:
ecrt.h
是 IgH EtherCAT Master 库的头文件,提供了 EtherCAT 主站的 API。
全局变量
#define NSEC_PER_SEC 1000000000 static unsigned int cycle_us = 1000; /* 1 ms */ static pthread_t cyclic_thread; static volatile sig_atomic_t run = 1;
- NSEC_PER_SEC:定义每秒的纳秒数,用于时间计算。
- cycle_us:定义周期时间(1 ms),即实时线程的执行周期。
- cyclic_thread:实时线程的句柄。
- run:标志变量,用于控制实时线程的运行。
1.2 EtherCAT 主站和域的定义
// EtherCAT static ec_master_t *master = NULL; static ec_master_state_t master_state = {}; static ec_domain_t *domain1 = NULL; static ec_domain_state_t domain1_state = {}; static uint8_t *domain1_pd = NULL; static ec_slave_config_t *sc_dig_out_01 = NULL;
- master:EtherCAT 主站的句柄。
- master_state:主站的状态信息。
- domain1:EtherCAT 域的句柄,用于管理过程数据。
- domain1_state:域的状态信息。
- domain1_pd:指向域过程数据的指针。
- sc_dig_out_01:从站配置的句柄。
1.3 过程数据 PDO 映射配置
// process data #define BusCoupler01_Pos 0, 0 #define DigOutSlave01_Pos 0, 1 #define Beckhoff_EK1100 0x00000002, 0x044c2c52 #define Beckhoff_EL2004 0x00000002, 0x07d43052 // offsets for PDO entries static unsigned int off_dig_out0 = 0; // process data const static ec_pdo_entry_reg_t domain1_regs[] = { {DigOutSlave01_Pos, Beckhoff_EL2004, 0x7000, 0x01, &off_dig_out0, NULL}, {} };
- BusCoupler01_Pos 与 DigOutSlave01_Pos:
- 定义了对应的从站在主站总线上的位置信息。
- Beckhoff_EK1100 与 Beckhoff_EL2004
- 定义了对应从站的 vendor ID. 和 product code.
- domain1_regs:定义了 PDO 条目的映射关系。
DigOutSlave01_Pos
:从站的位置(总线号,站号)。Beckhoff_EL2004
:从站的厂商 ID 和产品代码。0x7000, 0x01
:PDO 的索引和子索引。&off_dig_out0
:存储 PDO 条目在过程数据中的偏移量。
完整的 ec_pdo_entry_reg_t
结构体如下:
/** List record type for PDO entry mass-registration. * * This type is used for the array parameter of the * ecrt_domain_reg_pdo_entry_list() */ typedef struct { uint16_t alias; /**< Slave alias address. */ uint16_t position; /**< Slave position. */ uint32_t vendor_id; /**< Slave vendor ID. */ uint32_t product_code; /**< Slave product code. */ uint16_t index; /**< PDO entry index. */ uint8_t subindex; /**< PDO entry subindex. */ unsigned int *offset; /**< Pointer to a variable to store the PDO entry's (byte-)offset in the process data. */ unsigned int *bit_position; /**< Pointer to a variable to store a bit position (0-7) within the \a offset. Can be NULL, in which case an error is raised if the PDO entry does not byte-align. */ } ec_pdo_entry_reg_t;
1.4 从站的 PDO 和同步管理器配置
/** PDO entry configuration information. * * This is the data type of the \a entries field in ec_pdo_info_t. * * \see ecrt_slave_config_pdos(). */ typedef struct { uint16_t index; /**< PDO entry index. */ uint8_t subindex; /**< PDO entry subindex. */ uint8_t bit_length; /**< Size of the PDO entry in bit. */ } ec_pdo_entry_info_t; const ec_pdo_entry_info_t slave_1_pdo_entries[] = { {0x7000, 0x01, 1}, /* Output */ {0x7010, 0x01, 1}, /* Output */ {0x7020, 0x01, 1}, /* Output */ {0x7030, 0x01, 1}, /* Output */ }; /** PDO configuration information. * * This is the data type of the \a pdos field in ec_sync_info_t. * * \see ecrt_slave_config_pdos(). */ typedef struct { uint16_t index; /**< PDO index. */ unsigned int n_entries; /**< Number of PDO entries in \a entries to map. Zero means, that the default mapping shall be used (this can only be done if the slave is present at configuration time). */ ec_pdo_entry_info_t const *entries; /**< Array of PDO entries to map. Can either be \a NULL, or must contain at least \a n_entries values. */ } ec_pdo_info_t; const ec_pdo_info_t slave_1_pdos[] = { {0x1600, 1, slave_1_pdo_entries + 0}, /* Channel 1 */ {0x1601, 1, slave_1_pdo_entries + 1}, /* Channel 2 */ {0x1602, 1, slave_1_pdo_entries + 2}, /* Channel 3 */ {0x1603, 1, slave_1_pdo_entries + 3}, /* Channel 4 */ }; /** Sync manager configuration information. * * This can be use to configure multiple sync managers including the PDO * assignment and PDO mapping. It is used as an input parameter type in * ecrt_slave_config_pdos(). */ typedef struct { uint8_t index; /**< Sync manager index. Must be less than #EC_MAX_SYNC_MANAGERS for a valid sync manager, but can also be \a 0xff to mark the end of the list. */ ec_direction_t dir; /**< Sync manager direction. */ unsigned int n_pdos; /**< Number of PDOs in \a pdos. */ ec_pdo_info_t const *pdos; /**< Array with PDOs to assign. This must contain at least \a n_pdos PDOs. */ ec_watchdog_mode_t watchdog_mode; /**< Watchdog mode. */ } ec_sync_info_t; const ec_sync_info_t slave_1_syncs[] = { {0, EC_DIR_OUTPUT, 4, slave_1_pdos + 0, EC_WD_ENABLE}, {0xff} };
- slave_1_pdo_entries:
- 定义了从站的 PDO 条目,是从站 pdo 配置的一部分。
- slave_1_pdos:
- 定义了从站的 PDO,配置从站同步管理器的时候需要使用。
- slave_1_syncs:
- 定义了从站的同步管理器配置,可以配置对应的 pdo 的 OUTPUT 或 INPUT 模式,以及看门狗的配置。
1.5 实时线程的实现
域状态检查线程
void rt_check_domain_state(void) { ec_domain_state_t ds = {}; ecrt_domain_state(domain1, &ds); if (ds.working_counter != domain1_state.working_counter) { rt_printf("Domain1: WC %u.\n", ds.working_counter); } if (ds.wc_state != domain1_state.wc_state) { rt_printf("Domain1: State %u.\n", ds.wc_state); } domain1_state = ds; }
这个检查线程,会检查域中的正在工作的
从站数量和状态
有没有发生变化,如果发生变化了那就打印出来。主站状态检查线程
void rt_check_master_state(void) { ec_master_state_t ms; ecrt_master_state(master, &ms); if (ms.slaves_responding != master_state.slaves_responding) { rt_printf("%u slave(s).\n", ms.slaves_responding); } if (ms.al_states != master_state.al_states) { rt_printf("AL states: 0x%02X.\n", ms.al_states); } if (ms.link_up != master_state.link_up) { rt_printf("Link is %s.\n", ms.link_up ? "up" : "down"); } master_state = ms; }
这个检查线程,会检查主站下的从站数量有没有发生变化,以及对应的所有的从站状态是否变化,以及对应的链路状态是否发生改变,如果发生改变那就打印出来。
void *my_thread(void *arg) { struct timespec next_period; int cycle_counter = 0; unsigned int blink = 0; clock_gettime(CLOCK_REALTIME, &next_period); while (run) { next_period.tv_nsec += cycle_us * 1000; while (next_period.tv_nsec >= NSEC_PER_SEC) { next_period.tv_nsec -= NSEC_PER_SEC; next_period.tv_sec++; } clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next_period, NULL); cycle_counter++; // receive EtherCAT ecrt_master_receive(master); ecrt_domain_process(domain1); rt_check_domain_state(); if (!(cycle_counter % 1000)) { rt_check_master_state(); } if (!(cycle_counter % 200)) { blink = !blink; } EC_WRITE_U8(domain1_pd + off_dig_out0, blink ? 0x0 : 0x0F); // send process data ecrt_domain_queue(domain1); ecrt_master_send(master); } return NULL; }
- 实时线程:周期性地执行以下操作:
- 计算下一个周期的时间点。
- 使用
clock_nanosleep
实现精确的周期控制。 - 接收 EtherCAT 数据并处理域数据。
- 检查域和主站的状态。
- 更新过程数据(如控制数字输出)。
- 发送过程数据。
任务 | 执行周期 | 描述 |
主循环 | 1 ms | 控制线程的精确周期执行。 |
域状态检查 ( rt_check_domain_state ) | 1 ms | 检查域的工作计数器和状态。 |
主站状态检查 ( rt_check_master_state ) | 1000 ms (1 s) | 检查主站和从站的通信状态。 |
输出状态切换 ( blink ) | 200 ms | 切换输出信号状态(如 LED 闪烁)。 |
输出数据写入 | 1 ms | 向从站的输出通道写入数据。 |
过程数据发送 | 1 ms | 将更新后的过程数据发送到从站。 |
1.6 主函数
1. 初始化和信号处理
signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); mlockall(MCL_CURRENT | MCL_FUTURE);
这些代码设置了信号处理程序,以便在接收到终止信号(如
SIGTERM
或 SIGINT
)时能够正确地停止程序。mlockall
函数用于锁定进程的内存,防止内存分页,从而确保实时性能。2. 请求 EtherCAT 主站
ec_master_t *ecrt_request_master(unsigned int master_index) printf("Requesting master...\n"); master = ecrt_request_master(0); if (!master) { return -1; }
实例化主站索引为 0 的主站。
3. 创建域
domain1 = ecrt_master_create_domain(master); if (!domain1) { return -1; }
创建主站的管理域,多个从站可以使用同一个管理域进行同步配置。
4. 创建从站配置
// Create configuration for bus coupler sc = ecrt_master_slave_config(master, BusCoupler01_Pos, Beckhoff_EK1100); if (!sc) { return -1; } sc_dig_out_01 = ecrt_master_slave_config(master, DigOutSlave01_Pos, Beckhoff_EL2004); if (!sc_dig_out_01) { fprintf(stderr, "Failed to get slave configuration.\n"); return -1; }
这段代码为两个从站创建配置。如果配置失败,程序将返回错误。
5. 配置 PDO
if (ecrt_slave_config_pdos(sc_dig_out_01, EC_END, slave_1_syncs)) { fprintf(stderr, "Failed to configure PDOs.\n"); return -1; }
6. 注册 PDO 条目
if (ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) { fprintf(stderr, "PDO entry registration failed!\n"); return -1; }
对应的操作 pdo 在 domain1_regs 进行配置。
7. 激活主站
printf("Activating master...\n"); if (ecrt_master_activate(master)) { return -1; } if (!(domain1_pd = ecrt_domain_data(domain1))) { fprintf(stderr, "Failed to get domain data pointer.\n"); return -1; }
8. 创建实时线程
struct sched_param param = { .sched_priority = 82 }; pthread_attr_t thattr; pthread_attr_init(&thattr); pthread_attr_setdetachstate(&thattr, PTHREAD_CREATE_JOINABLE); pthread_attr_setinheritsched(&thattr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&thattr, SCHED_FIFO); pthread_attr_setschedparam(&thattr, ¶m); ret = pthread_create(&cyclic_thread, &thattr, &my_thread, NULL); if (ret) { fprintf(stderr, "%s: pthread_create cyclic task failed\n", strerror(-ret)); return 1; }
这段代码创建一个实时线程,用于周期性地处理 EtherCAT 数据。线程的优先级设置为 82,并使用 FIFO 调度策略。如果线程创建失败,程序将返回错误。
9. 主循环
while (run) { sched_yield(); } pthread_join(cyclic_thread, NULL);
主循环等待信号处理程序将
run
变量设置为 0,然后等待实时线程结束。10. 释放资源
printf("End of Program\n"); ecrt_release_master(master);
欢迎加入“喵星计算机技术研究院”,原创技术文章第一时间推送。

- 作者:tangcuyu
- 链接:https://expoli.tech/articles/2025/03/07/EtherCAT-master-IgH-learned-the-xenomai_posix-routine-code
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章