EtherCAT主站程序代码详解
简介
EtherCAT主站是用于控制EtherCAT网络的中央组件,负责协调和管理整个EtherCAT网络上的从站设备。主站与从站之间通过实时以太网通信,形成了一个高性能、低延迟的实时控制网络。
主要功能和责任:
-
实时通信: 主站负责在EtherCAT网络上实现实时通信。它与每个从站建立通信,通过EtherCAT协议实时传输控制信息和数据。
-
网络配置: 主站负责配置EtherCAT网络的拓扑结构。它需要了解整个网络上从站设备的位置、类型和通信参数。
-
同步控制: 主站确保网络中所有从站设备的时钟同步,以便实现高精度的同步控制。
-
数据处理: 主站负责处理从站发送的实时数据,并将控制指令传送到各个从站。
-
错误处理: 主站能够检测并处理网络中的错误。它可以监控通信故障、从站错误以及其他可能影响系统性能的问题。
本文介绍EtherLab 公司开发IgH(EtherLab IgH)主站程序案例:IgH主站程序案例
一、头文件
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h> /* clock_gettime() */
#include <sys/mman.h> /* mlockall() */
#include <sched.h> /* sched_setscheduler() */
#include "ecrt.h"
二、宏定义
// 任务周期(以 ns 为单位)
#define PERIOD_NS (1000000)
#define MAX_SAFE_STACK (8 * 1024) //保证安全访问而不会出现故障的最大堆栈大小
//常数
#define NSEC_PER_SEC (1000000000)
#define FREQUENCY (NSEC_PER_SEC / PERIOD_NS)
-
PERIOD_NS
: 定义了任务的周期,以纳秒(ns)为单位。在这里,周期被设置为 1000000 ns,即 1 毫秒。 -
MAX_SAFE_STACK
: 定义了最大的安全栈大小,单位是字节。在这里,最大安全栈大小被设置为 8 * 1024 字节,即 8 KB。这个值通常与实时系统中线程栈的大小相关,确保线程的栈不会溢出。 -
NSEC_PER_SEC
: 定义了一秒钟内的纳秒数,即 1000000000 ns。 -
FREQUENCY
: 定义了任务的执行频率,以 Hz 为单位。其用于配置实时任务的执行频率。
三、变量定义
?3.1?定义主站
static ec_master_t *master = NULL;
static ec_master_state_t master_state = {};
- 声明了一个指向
ec_master_t
类型的指针master
,并初始化为NULL
。 ec_master_t
通常代表 EtherCAT 主站的结构,用于表示主站的状态和配置信息。- 声明了一个
ec_master_state_t
类型的结构体变量master_state
,并初始化为零。 ec_master_state_t
通常包含了主站的当前状态信息,如主站是否启动、是否有错误等。
?3.2 定义域(domain)
static ec_domain_t *domain1 = NULL;
static ec_domain_state_t domain1_state = {};
- 声明了一个指向
ec_domain_t
类型的指针domain1
,并初始化为NULL
。 ec_domain_t
通常代表 EtherCAT 中的一个域,用于组织和管理从站的数据通信。- 声明了一个
ec_domain_state_t
类型的结构体变量domain1_state
,并初始化为零。 ec_domain_state_t
通常包含了域的当前状态信息,如域是否激活、是否有错误等。
?3.3 定义从站的配置
static ec_slave_config_t *sc_ana_in = NULL;
static ec_slave_config_state_t sc_ana_in_state = {};
- 声明了一个指向
ec_slave_config_t
类型的指针sc_ana_in
,并初始化为NULL
。 ec_slave_config_t
通常代表 EtherCAT 中的一个从站配置,包含了从站的地址、通信周期等信息。- 声明了一个
ec_slave_config_state_t
类型的结构体变量sc_ana_in_state
,并初始化为零。 ec_slave_config_state_t
通常包含了从站配置的当前状态信息,如从站是否准备好通信等。
3.4 定义过程数据
static uint8_t *domain1_pd = NULL;
domain1_pd
?是用于指向 EtherCAT 域(ec_domain_t
)的过程数据(process data)的缓冲区。在 EtherCAT 中,域是用于在主站和从站之间传输数据的逻辑单元。uint8_t
表示一个字节(8位),所以 domain1_pd
通常会被用来处理和存储域中的字节数据。
在 EtherCAT 应用程序中,会在初始化过程中动态分配内存给 domain1_pd
,以便存储域的过程数据。这个指针会在 EtherCAT 通信周期内用于读取或写入从站的数据。
3.5 定义从站信息变量
3.5.1 定义从站的别名
#define BusCouplerPos 0, 0
#define DigOutSlavePos 0, 2
#define AnaInSlavePos 0, 3
#define AnaOutSlavePos 0, 4
这些宏定义了不同从站的位置信息,其中 0,0表示从站在总线上的第一个位置,第一个地址。
3.5.2 定义从站的信息
#define Beckhoff_EK1100 0x00000002, 0x044c2c52
#define Beckhoff_EL2004 0x00000002, 0x07d43052
#define Beckhoff_EL2032 0x00000002, 0x07f03052
#define Beckhoff_EL3152 0x00000002, 0x0c503052
#define Beckhoff_EL3102 0x00000002, 0x0c1e3052
#define Beckhoff_EL4102 0x00000002, 0x10063052
用于标识不同的硬件设备,包含从站产品的供应商ID,产品代码(注:要与从站配置文件中的定义一致)
3.5.3 定义PDO条?的偏移量
static unsigned int off_ana_in_status;
static unsigned int off_ana_in_value;
static unsigned int off_ana_out;
static unsigned int off_dig_out;
-
off_ana_in_status
:表示模拟输入(Analog Input)状态的偏移量。 -
off_ana_in_value
:表示模拟输入值的偏移量。 -
off_ana_out
:表示模拟输出(Analog Output)的偏移量。 -
off_dig_out
:表示数字输出(Digital Output)的偏移量。
3.5.4 定义 EtherCAT 域(domain)中的对象
const static ec_pdo_entry_reg_t domain1_regs[] = {
{AnaInSlavePos, Beckhoff_EL3102, 0x3101, 1, &off_ana_in_status},
{AnaInSlavePos, Beckhoff_EL3102, 0x3101, 2, &off_ana_in_value},
{AnaOutSlavePos, Beckhoff_EL4102, 0x3001, 1, &off_ana_out},
{DigOutSlavePos, Beckhoff_EL2032, 0x3001, 1, &off_dig_out},
{}
};
以{AnaInSlavePos, Beckhoff_EL3102, 0x3101, 1, &off_ana_in_status}为例
:
- 对应于 EtherCAT 从站位置
AnaInSlavePos
上的一个 PDO 条目。 - 对应的硬件设备是 Beckhoff EL3102。
- 对象字典索引为
0x3101
。 - 子索引为
1
。 - 偏移量
&off_ana_in_status
指向变量,用于表示该 PDO 条目的状态。
3.5.4 定义 PDO对象字典、结构和同步周期
static ec_pdo_entry_info_t el3102_pdo_entries[] = {
{0x3101, 1, 8}, // channel 1 status
{0x3101, 2, 16}, // channel 1 value
{0x3102, 1, 8}, // channel 2 status
{0x3102, 2, 16}, // channel 2 value
{0x6401, 1, 16}, // channel 1 value (alt.)
{0x6401, 2, 16} // channel 2 value (alt.)
};
static ec_pdo_info_t el3102_pdos[] = {
{0x1A00, 2, el3102_pdo_entries},
{0x1A01, 2, el3102_pdo_entries + 2}
};
static ec_sync_info_t el3102_syncs[] = {
{2, EC_DIR_OUTPUT},
{3, EC_DIR_INPUT, 2, el3102_pdos},
{0xff}
};
-
el3102_pdo_entries[]
:- 这是一个包含
ec_pdo_entry_info_t
类型元素的数组,用于描述 PDO 条目的信息。 {0x3101, 1, 8}
表示第一个 PDO 条目,对象字典索引为0x3101
,子索引为1
,有 8 位(1 字节)的数据。{0x3101, 2, 16}
表示第二个 PDO 条目,对象字典索引为0x3101
,子索引为2
,有 16 位(2 字节)的数据。- 后续的条目类似,描述了 EL3102 模块的状态和值的不同通道。
- 这是一个包含
-
el3102_pdos[]
:- 这是一个包含
ec_pdo_info_t
类型元素的数组,用于描述 PDO 配置信息。 {0x1A00, 2, el3102_pdo_entries}
表示第一个 PDO,对象字典索引为0x1A00
,有 2 个 PDO 条目。{0x1A01, 2, el3102_pdo_entries + 2}
表示第二个 PDO,对象字典索引为0x1A01
,有 2 个 PDO 条目,其偏移量指向el3102_pdo_entries
数组的第三个元素,即第三个通道的状态和值。
- 这是一个包含
-
el3102_syncs[]
:- 这是一个包含
ec_sync_info_t
类型元素的数组,用于描述同步信息。 {2, EC_DIR_OUTPUT}
表示第一个同步,其周期为 2 个 DCycle(同步周期),方向为输出。{3, EC_DIR_INPUT, 2, el3102_pdos}
表示第二个同步,周期为 3 个 DCycle,方向为输入,包含了两个 PDO(通过el3102_pdos
指定)。{0xff}
表示同步信息数组的结束标志。
- 这是一个包含
四、函数
4.1?检查 EtherCAT 域 domain1
的状态
void check_domain1_state(void)
{
ec_domain_state_t ds;
ecrt_domain_state(domain1, &ds);
if (ds.working_counter != domain1_state.working_counter) {
printf("Domain1: WC %u.\n", ds.working_counter);
}
if (ds.wc_state != domain1_state.wc_state) {
printf("Domain1: State %u.\n", ds.wc_state);
}
domain1_state = ds;
}
此函数执行以下操作:?
-
获取域状态:
使用ecrt_domain_state
函数获取domain1
的当前状态,将状态信息存储在ec_domain_state_t
类型的结构体变量ds
中。 -
比较状态变化:
- 检查当前状态中的
working_counter
是否与之前存储的domain1_state.working_counter
不同,如果不同,则打印相关信息表示working_counter
的变化。 - 检查当前状态中的
wc_state
是否与之前存储的domain1_state.wc_state
不同,如果不同,则打印相关信息表示wc_state
的变化。
- 检查当前状态中的
-
更新域状态:
将当前的域状态ds
赋值给domain1_state
,以备下一次函数调用时进行比较。
? ? ? ? 此函数?来监测计数器WKC的状态,计数器?于数据帧的操作计数,放在帧内初始值为 0,成功写?+2,成功读取+1,所以会有0(发送、读取失败)、2(读取失败)、3(成功)三种情 况,频率过?会导致读取、写?失败,?般认为错误率低于0.1%即可
4.2 检查 EtherCAT 主站 master
的状态
void check_master_state(void)
{
ec_master_state_t ms;
ecrt_master_state(master, &ms);
if (ms.slaves_responding != master_state.slaves_responding) {
printf("%u slave(s).\n", ms.slaves_responding);
}
if (ms.al_states != master_state.al_states) {
printf("AL states: 0x%02X.\n", ms.al_states);
}
if (ms.link_up != master_state.link_up) {
printf("Link is %s.\n", ms.link_up ? "up" : "down");
}
master_state = ms;
}
这个函数执行以下操作:
-
获取主站状态:
使用ecrt_master_state
函数获取master
的当前状态,将状态信息存储在ec_master_state_t
类型的结构体变量ms
中。 -
比较状态变化:
- 检查当前状态中的
slaves_responding
是否与之前存储的master_state.slaves_responding
不同,如果不同,则打印相关信息表示连接的从站数量的变化。 - 检查当前状态中的
al_states
是否与之前存储的master_state.al_states
不同,如果不同,则打印相关信息表示主站的 AL(Application Layer) 状态的变化。 - 检查当前状态中的
link_up
是否与之前存储的master_state.link_up
不同,如果不同,则打印相关信息表示主站连接的物理链路状态的变化。
- 检查当前状态中的
-
更新主站状态:
将当前的主站状态ms
赋值给master_state
,以备下一次函数调用时进行比较。
4.3 检查与 EtherCAT 从站配置状态
void check_slave_config_states(void)
{
ec_slave_config_state_t s;
ecrt_slave_config_state(sc_ana_in, &s);
if (s.al_state != sc_ana_in_state.al_state) {
printf("AnaIn: State 0x%02X.\n", s.al_state);
}
if (s.online != sc_ana_in_state.online) {
printf("AnaIn: %s.\n", s.online ? "online" : "offline");
}
if (s.operational != sc_ana_in_state.operational) {
printf("AnaIn: %soperational.\n", s.operational ? "" : "Not ");
}
sc_ana_in_state = s;
}
这个函数执行以下操作:
-
获取从站配置状态:
使用ecrt_slave_config_state
函数获取与sc_ana_in
关联的从站配置的当前状态,将状态信息存储在ec_slave_config_state_t
类型的结构体变量s
中。 -
比较状态变化:
- 检查当前状态中的
al_state
是否与之前存储的sc_ana_in_state.al_state
不同,如果不同,则打印相关信息表示从站配置的 AL(Application Layer) 状态的变化。 - 检查当前状态中的
online
是否与之前存储的sc_ana_in_state.online
不同,如果不同,则打印相关信息表示从站配置的在线状态的变化。 - 检查当前状态中的
operational
是否与之前存储的sc_ana_in_state.operational
不同,如果不同,则打印相关信息表示从站配置的操作状态的变化。
- 检查当前状态中的
-
更新从站配置状态:
将当前的从站配置状态s
赋值给sc_ana_in_state
,以备下一次函数调用时进行比较。
4.4 周期性任务
void cyclic_task()
{
// receive process data
ecrt_master_receive(master);
ecrt_domain_process(domain1);
// check process data state
check_domain1_state();
if (counter) {
counter--;
} else { // do this at 1 Hz
counter = FREQUENCY;
// calculate new process data
blink = !blink;
// check for master state (optional)
check_master_state();
// check for slave configuration state(s) (optional)
check_slave_config_states();
}
#if 0
// read process data
printf("AnaIn: state %u value %u\n",
EC_READ_U8(domain1_pd + off_ana_in_status),
EC_READ_U16(domain1_pd + off_ana_in_value));
#endif
#if 1
// write process data
EC_WRITE_U8(domain1_pd + off_dig_out, blink ? 0x06 : 0x09);
#endif
// send process data
ecrt_domain_queue(domain1);
ecrt_master_send(master);
}
这个函数执行以下操作:
-
接收和处理过程数据:
- 使用
ecrt_master_receive
函数接收主站的过程数据。 - 使用
ecrt_domain_process
函数处理域domain1
中的过程数据。
- 使用
-
检查过程数据状态:
调用check_domain1_state
函数,检查并打印域domain1
的状态变化。 -
周期性任务执行:
- 使用
counter
计数器来确定何时执行每秒一次的任务。 - 在每秒执行一次的任务中,计算新的过程数据,然后检查主站和从站配置的状态(这两步是可选的)。
- 使用
-
读写过程数据:
在每个循环中,通过EC_WRITE_U8
函数写入一个字节的过程数据,根据blink
的值设置不同的值。 -
发送过程数据:
- 使用
ecrt_domain_queue
将域domain1
中的过程数据放入队列。 - 使用
ecrt_master_send
将过程数据发送到主站。
- 使用
这个函数通常在 EtherCAT 主循环中被调用,用于处理周期性的 EtherCAT 任务。在每个循环中,它接收、处理、检查状态,并在每秒执行一次的任务中计算新的过程数据。最后,它写入和发送过程数据到 EtherCAT 从站。
4.5 预先分配和初始化堆栈空间
void stack_prefault(void)
{
unsigned char dummy[MAX_SAFE_STACK];
memset(dummy, 0, MAX_SAFE_STACK);
}
这个函数的作用是在程序启动时,通过创建一个名为 dummy
的数组,其大小为 MAX_SAFE_STACK
,来分配一块固定大小的堆栈空间。然后,使用 memset
函数将整个数组初始化为零。
在实时控制系统或者需要确保稳定性和可预测性的应用中,预先分配和初始化堆栈空间是为了避免在程序运行过程中动态分配堆栈空间的不确定性。这可以帮助确保在程序执行的关键阶段,堆栈的空间分配是可靠且可控的。
4.6 主函数
int main(int argc, char **argv)
{
ec_slave_config_t *sc;
struct timespec wakeup_time;
int ret = 0;
master = ecrt_request_master(0);
if (!master) {
return -1;
}
domain1 = ecrt_master_create_domain(master);
if (!domain1) {
return -1;
}
if (!(sc_ana_in = ecrt_master_slave_config(
master, AnaInSlavePos, Beckhoff_EL3102))) {
fprintf(stderr, "Failed to get slave configuration.\n");
return -1;
}
printf("Configuring PDOs...\n");
if (ecrt_slave_config_pdos(sc_ana_in, EC_END, el3102_syncs)) {
fprintf(stderr, "Failed to configure PDOs.\n");
return -1;
}
if (!(sc = ecrt_master_slave_config(
master, AnaOutSlavePos, Beckhoff_EL4102))) {
fprintf(stderr, "Failed to get slave configuration.\n");
return -1;
}
if (ecrt_slave_config_pdos(sc, EC_END, el4102_syncs)) {
fprintf(stderr, "Failed to configure PDOs.\n");
return -1;
}
if (!(sc = ecrt_master_slave_config(
master, DigOutSlavePos, Beckhoff_EL2032))) {
fprintf(stderr, "Failed to get slave configuration.\n");
return -1;
}
if (ecrt_slave_config_pdos(sc, EC_END, el2004_syncs)) {
fprintf(stderr, "Failed to configure PDOs.\n");
return -1;
}
// Create configuration for bus coupler
sc = ecrt_master_slave_config(master, BusCouplerPos, Beckhoff_EK1100);
if (!sc) {
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))) {
return -1;
}
/* Set priority */
struct sched_param param = {};
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
printf("Using priority %i.", param.sched_priority);
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler failed");
}
/* Lock memory */
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
fprintf(stderr, "Warning: Failed to lock memory: %s\n",
strerror(errno));
}
stack_prefault();
printf("Starting RT task with dt=%u ns.\n", PERIOD_NS);
clock_gettime(CLOCK_MONOTONIC, &wakeup_time);
wakeup_time.tv_sec += 1; /* start in future */
wakeup_time.tv_nsec = 0;
while (1) {
ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
&wakeup_time, NULL);
if (ret) {
fprintf(stderr, "clock_nanosleep(): %s\n", strerror(ret));
break;
}
cyclic_task();
wakeup_time.tv_nsec += PERIOD_NS;
while (wakeup_time.tv_nsec >= NSEC_PER_SEC) {
wakeup_time.tv_nsec -= NSEC_PER_SEC;
wakeup_time.tv_sec++;
}
}
return ret;
}
这个主函数的关键部分包括:
-
EtherCAT 主站和域的创建:
- 使用
ecrt_request_master
请求 EtherCAT 主站。 - 使用
ecrt_master_create_domain
创建 EtherCAT 域。
- 使用
-
从站配置:
- 使用
ecrt_master_slave_config
配置各个从站,包括 AnaInSlavePos、AnaOutSlavePos、DigOutSlavePos 和 BusCouplerPos。 - 使用
ecrt_slave_config_pdos
配置每个从站的 PDO。
- 使用
-
PDO 条目的注册:
- 使用
ecrt_domain_reg_pdo_entry_list
注册域的 PDO 条目。
- 使用
-
EtherCAT 主站的激活:
- 使用
ecrt_master_activate
激活 EtherCAT 主站。
- 使用
-
实时任务的设置:
- 设置实时任务的优先级。
- 锁定内存以提高实时性能。
-
主循环:
- 进入主循环,其中通过
clock_nanosleep
在每个周期内休眠,等待下一个周期的开始。 - 在每个周期内,执行
cyclic_task
函数来处理 EtherCAT 主循环中的周期性任务。
- 进入主循环,其中通过
此程序实现了一个基本的 EtherCAT 主循环,用于实时控制应用。在主循环中,配置了 EtherCAT 主站、从站和域,执行周期性任务,同时使用实时任务的优先级和内存锁定来确保对实时性的要求。
?
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!