type
Post
status
Published
date
Mar 6, 2025
slug
2025/03/06/Learning-the-IGH-xenomai_posix-Sample-Code
summary
tags
Linux
Xenomai 3
IgH
EtherCAT
category
EtherCAT
created days
new update day
icon
password
Created_time
Mar 6, 2025 01:30 AM
Last edited time
Mar 6, 2025 03:07 AM
IgH 默认提供了 xenomai 的支持,也是提供了对应的示例代码,一开始没有看到,在测试环境中安装的时候,发现了对应的配置参数。
使用下面的命令,即可提供 xenomai 3.2.1 版本的支持。
./bootstrap # to create the configure script, if downloaded from the repo ./configure --with-xenomai-dir=/usr/src/xenomai-kernel-source/ --with-xenomai-config=/usr/bin/xeno-config --enable-rtdm make all modules make modules_install install depmod
这样你就可以在 xenomai_posix 目录下看到对应的例程可执行程序:
root@debian:/home/test/ethercat/examples/xenomai_posix# ls -al total 124 drwxr-xr-x 4 root root 4096 Mar 6 09:37 . drwxr-xr-x 12 root root 4096 Mar 5 10:14 .. drwxr-xr-x 2 root root 4096 Mar 6 09:37 .deps -rwxr-xr-x 1 root root 6500 Mar 6 09:37 ec_xenomai_posix_example -rw-r--r-- 1 root root 30080 Mar 6 09:37 ec_xenomai_posix_example-main.o drwxr-xr-x 2 root root 4096 Mar 6 09:37 .libs -rw-r--r-- 1 root root 15425 Mar 6 09:26 main.c -rw-r--r-- 1 root root 23427 Mar 5 10:14 Makefile -rw-r--r-- 1 root root 1476 Mar 4 15:59 Makefile.am -rw-r--r-- 1 root root 24023 Mar 5 10:14 Makefile.in
下面是对例程源码的分析,完整具体的代码连同注释已经放在了下面:
/***************************************************************************** * * 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; ///< cyclic_thread:实时线程的句柄。 static volatile sig_atomic_t run = 1; ///< run:标志变量,用于控制实时线程的运行。 /****************************************************************************/ // EtherCAT static ec_master_t *master = NULL; ///< master:EtherCAT 主站的句柄。 static ec_master_state_t master_state = {}; ///< master_state:EtherCAT 主站的状态。 static ec_domain_t *domain1 = NULL; ///< domain1:EtherCAT 域的句柄,用于管理过程数据。 static ec_domain_state_t domain1_state = {};///< domain1_state:域的状态信息。 static uint8_t *domain1_pd = NULL; ///< domain1_pd:指向域过程数据的指针。 static ec_slave_config_t *sc_dig_out_01 = NULL; ///< sc_dig_out_01:从站配置的句柄。 /****************************************************************************/ // 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 // PDO 映射配置 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 */ /// 定义了从站的 PDO 条目。 /// 定义了从站的 PDO 条目,即从站设备中可以被映射到 EtherCAT 通信中的具体数据项。 /* 参数解析: 0x7000, 0x01:PDO 条目的索引(entry index.)和子索引(Subindex)。 0x7000 是 PDO 的索引,通常表示一个特定的功能或数据区域。 0x01 是子索引,用于进一步细化索引指向的数据。 1:数据长度(以位为单位),表示该 PDO 条目的大小。 这里 1 表示 1 位,通常用于数字输出(如 EL2004 的每个通道是 1 位)。 */ const ec_pdo_entry_info_t slave_1_pdo_entries[] = { {0x7000, 0x01, 1}, /* Output 表示 EL2004 的第一个输出通道。*/ {0x7010, 0x01, 1}, /* Output 表示 EL2004 的第二个输出通道。*/ {0x7020, 0x01, 1}, /* Output */ {0x7030, 0x01, 1}, /* Output */ }; /// 定义了从站的 PDO。 /** 作用:将 PDO 条目组织成 PDO,每个 PDO 可以包含一个或多个 PDO 条目。 参数解析: 0x1600:PDO 的索引,表示一个特定的 PDO。 0x1600 是 EL2004 的第一个输出 PDO。 0x1601 是第二个输出 PDO,依此类推。 1:PDO 中包含的 PDO 条目数量。 这里每个 PDO 只包含一个 PDO 条目(即一个输出通道)。 slave_1_pdo_entries + 0:指向 PDO 条目的指针。 slave_1_pdo_entries + 0 表示第一个 PDO 条目(即第一个输出通道)。 示例: {0x1600, 1, slave_1_pdo_entries + 0} 表示 EL2004 的第一个输出 PDO,包含第一个输出通道。 */ const ec_pdo_info_t slave_1_pdos[] = { {0x1a00, 1, slave_1_pdo_entries + 0}, /* Output, Byte 0 */ {0x1a01, 1, slave_1_pdo_entries + 1}, /* Output, Byte 1 */ {0x1600, 1, slave_1_pdo_entries + 2}, /* Input, Byte 0 */ {0x1601, 1, slave_1_pdo_entries + 3}, /* Input, Byte 1 */ }; /// 定义了从站的同步管理器配置。 /* 作用:配置从站的同步管理器(Sync Manager),用于管理 PDO 的传输方向和同步行为。 参数解析: 0:同步管理器的索引。 0 表示第一个同步管理器。 EC_DIR_OUTPUT:同步管理器的方向。 EC_DIR_OUTPUT 表示输出方向(主站向从站发送数据)。 4:PDO 的数量。 这里 4 表示有 4 个 PDO(对应 EL2004 的 4 个输出通道)。 slave_1_pdos + 0:指向 PDO 的指针。 slave_1_pdos + 0 表示从第一个 PDO 开始。 EC_WD_ENABLE:启用看门狗(Watchdog)。 看门狗用于检测通信是否正常,如果通信超时,从站会进入安全状态。 {0xff}:同步管理器配置的结束标志。 0xff 表示同步管理器配置的结束。 */ const ec_sync_info_t slave_1_syncs[] = { {0, EC_DIR_OUTPUT, 4, slave_1_pdos + 0, EC_WD_ENABLE}, {0xff} }; /* 这些定义和配置的目的是确保 EtherCAT 主站能够正确地与从站通信,并将从站的输入输出数据映射到主站的过程数据中。具体来说: 4.1 PDO 条目的定义 作用:明确从站设备中哪些数据需要参与 EtherCAT 通信。 目的:将具体的硬件数据(如 EL2004 的输出通道)映射到 EtherCAT 通信中。 4.2 PDO 的定义 作用:将 PDO 条目组织成逻辑单元(PDO),便于主站和从站之间的数据传输。 目的:将多个相关的 PDO 条目组合在一起,形成一个完整的数据包。 4.3 同步管理器的配置 作用:管理 PDO 的传输方向和同步行为。 目的: 确保数据在正确的时间传输。 启用看门狗功能,提高通信的可靠性。 */ /***************************************************************************** * Realtime task ****************************************************************************/ /** 这段代码定义了一个函数 rt_check_domain_state, 用于检查 EtherCAT 域(Domain)的状态,并在状态发生变化时打印相关信息。 */ void rt_check_domain_state(void) { ec_domain_state_t ds = {}; /* domain1:要检查的域的句柄。 &ds:指向 ec_domain_state_t 结构体的指针,用于存储获取到的状态信息。 */ ecrt_domain_state(domain1, &ds); /* working_counter:工作计数器,表示域中成功传输的数据帧数量。 每次主站发送数据帧并收到从站的响应后,工作计数器会增加。 如果工作计数器没有增加,可能表示通信出现问题(如从站未响应)。 domain1_state.working_counter:上一次保存的工作计数器值。 作用:检查工作计数器是否发生变化。如果发生变化,打印新的工作计数器值。 */ 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; } /****************************************************************************/ /** 这段代码定义了一个函数 rt_check_master_state,用于检查 EtherCAT 主站(Master)的状态, 并在状态发生变化时打印相关信息。 rt_check_master_state 的主要功能是: 获取 EtherCAT 主站的当前状态。 检查主站状态是否发生变化(特别是从站响应数量、AL 状态和链路状态)。 如果状态发生变化,打印相关信息。 */ void rt_check_master_state(void) { ec_master_state_t ms; ecrt_master_state(master, &ms); /* slaves_responding:当前响应的从站数量。 表示主站成功通信的从站数量。 如果从站数量减少,可能表示某些从站掉线或通信中断。 master_state.slaves_responding:上一次保存的从站响应数量。 作用:检查从站响应数量是否发生变化。如果发生变化,打印新的从站响应数量。 */ if (ms.slaves_responding != master_state.slaves_responding) { rt_printf("%u slave(s).\n", ms.slaves_responding); } /* al_states:EtherCAT 从站的 Application Layer(AL)状态。 表示从站的状态机状态,常见的状态包括: 0x01:Init。 0x02:Pre-Operational。 0x04:Safe-Operational。 0x08:Operational。 如果 AL 状态异常,可能表示从站未正确初始化或配置错误。 master_state.al_states:上一次保存的 AL 状态。 */ if (ms.al_states != master_state.al_states) { rt_printf("AL states: 0x%02X.\n", ms.al_states); } /* link_up:链路状态,表示 EtherCAT 主站与从站之间的物理连接状态。 1:链路正常(up)。 0:链路断开(down)。 master_state.link_up:上一次保存的链路状态。 */ if (ms.link_up != master_state.link_up) { rt_printf("Link is %s.\n", ms.link_up ? "up" : "down"); } master_state = ms; } /****************************************************************************/ /* my_thread 的主要功能是: 实现一个精确的周期性任务,周期时间为 cycle_us(默认为 1 ms)。 在每个周期内: 接收 EtherCAT 数据并处理域数据。 检查域状态。 定期检查主站状态。 更新过程数据(如控制数字输出)。 发送过程数据。 */ 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; ///< 计算下一个周期的时间点。 // cycle_us 是周期时间(默认为 1 ms),转换为纳秒后累加到 next_period.tv_nsec。 // 处理纳秒溢出。 // 如果 tv_nsec 超过 1 秒(NSEC_PER_SEC),将其减去 1 秒,并将 tv_sec 加 1。 while (next_period.tv_nsec >= NSEC_PER_SEC) { next_period.tv_nsec -= NSEC_PER_SEC; next_period.tv_sec++; } /* 使线程睡眠,直到达到 next_period 指定的时间点。 CLOCK_REALTIME:使用系统实时时钟。 TIMER_ABSTIME:表示 next_period 是绝对时间。 */ clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next_period, NULL); // cycle_counter:记录当前周期数,用于后续的条件判断。 cycle_counter++; // receive EtherCAT ecrt_master_receive(master); ///< 接收 EtherCAT 数据。从 EtherCAT 网络中读取从站发送的数据。 ecrt_domain_process(domain1); ///< 处理域数据。将接收到的数据更新到域的过程数据中。 rt_check_domain_state(); ///< 检查域状态。(如工作计数器和工作计数器状态)。如果状态发生变化,打印相关信息。 // 定期检查主站状态 // 每 1000 个周期(即 1 秒)检查一次主站状态。 // 调用 rt_check_master_state 函数,检查主站的状态(如从站响应数量、AL 状态和链路状态)。 // 如果状态发生变化,打印相关信息。 if (!(cycle_counter % 1000)) { rt_check_master_state(); } // 更新过程数据 if (!(cycle_counter % 200)) { blink = !blink; } // 将 blink 的值写入过程数据。 EC_WRITE_U8(domain1_pd + off_dig_out0, blink ? 0x0 : 0x0F); // send process data ecrt_domain_queue(domain1); ///< 将域的过程数据加入发送队列。 ecrt_master_send(master); ///< 发送 EtherCAT 数据。将更新后的过程数据发送到从站。 } 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; } /****************************************************************************/
欢迎加入“喵星计算机技术研究院”,原创技术文章第一时间推送。

- 作者:tangcuyu
- 链接:https://expoli.tech/articles/2025/03/06/Learning-the-IGH-xenomai_posix-Sample-Code
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章