当前位置: 首页 > news >正文

EtherCAT主站教程4--IGH主站代码详解

代码是一个基于EtherCAT实时通信协议的C语言程序,用于控制支持CiA 402协议的从站设备(具体为InoSV660N伺服驱动器)。程序通过EtherCAT主站与从站进行实时数据交换,管理伺服驱动器的状态机,并通过共享内存与外部程序通信。以下是对代码各部分的详细说明:

1. 头文件包含

#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 <fcntl.h>    /* shm_open */
#include <stdbool.h>
#include "sv660n_ethercat/shared_memory.h"
#include "ecrt.h" // EtherCAT 实时接口库
  • 作用:包含程序所需的系统头文件和EtherCAT实时接口库(ecrt.h)。这些头文件提供错误处理、信号处理、时间操作、内存管理、调度策略、文件操作和共享内存功能。
  • 自定义头文件:sv660n_ethercat/shared_memory.h定义了共享内存的结构和信号量,用于与外部程序通信。

 2. 宏定义和常数

#define NSEC_PER_SEC (1000000000L) // 每秒的纳秒数
#define FREQUENCY 1000 // 任务频率(Hz),1000 Hz = 1 kHz
#define PERIOD_NS (NSEC_PER_SEC / FREQUENCY) // 任务周期(纳秒),1 毫秒
#define SHIFT_NS (NSEC_PER_SEC / FREQUENCY / 4) // 分布式时钟偏移(250 微秒)
#define CLOCK_TO_USE CLOCK_MONOTONIC // 使用单调时钟以确保时间稳定性
#define DIFF_NS(A, B) (((B).tv_sec - (A).tv_sec) * NSEC_PER_SEC + (B).tv_nsec - (A).tv_nsec)
#define TIMESPEC2NS(T) ((uint64_t)(T).tv_sec * NSEC_PER_SEC + (T).tv_nsec)
#define MAX_SAFE_STACK (8 * 1024) // 保证安全访问的最大栈大小(8 KB)
#define TASK_FREQUENCY 10 /*Hz*/ // 未使用的任务频率定义
// #define MEASURE_TIMING // 取消注释以启用时间测量(默认禁用)

作用

  • 定义时间相关的常量,例如每秒纳秒数(NSEC_PER_SEC)、任务频率(FREQUENCY)、周期时间(PERIOD_NS)和分布式时钟偏移(SHIFT_NS)。
  • CLOCK_TO_USE指定使用单调时钟(CLOCK_MONOTONIC),避免系统时间调整对实时任务的影响。
  • DIFF_NS和TIMESPEC2NS宏用于计算时间差和将timespec结构转换为纳秒。
  • MAX_SAFE_STACK定义栈大小,用于预分配内存以避免页面错误。
  • TASK_FREQUENCY定义未使用,可能为早期调试或占位符。
  • MEASURE_TIMING宏用于启用/禁用时间性能测量,当前被注释(禁用)。

 3. 全局变量和EtherCAT相关定义

static ec_master_t *master = NULL; // EtherCAT 主站实例
static ec_master_state_t master_state = {0}; // 主站状态
static ec_domain_t *domain1 = NULL; // 过程数据域
static ec_domain_state_t domain1_state = {0}; // 域状态
static ec_slave_config_t *sc_ana_in = NULL; // 从站配置
static ec_slave_config_state_t sc_ana_in_state = {0}; // 从站配置状态
static uint8_t *domain_pd = NULL; // 过程数据指针
#define BusCouplerPos 0, 0 // 从站位置(别名,位置)
#define InoSV660N 0x00100000, 0x000c010d // InoSV660N 的 Vendor ID 和 Product Code

 作用

  • 定义EtherCAT主站(master)、域(domain1)和从站配置(sc_ana_in)的全局指针,用于管理EtherCAT通信。
  • 定义状态变量(master_state、domain1_state、sc_ana_in_state)用于监控主站、域和从站的状态。
  • domain_pd指向域的过程数据,用于直接读写PDO(过程数据对象)数据。
  • BusCouplerPos定义从站在EtherCAT网络中的位置(主站0,从站0)。
  • InoSV660N定义从站的Vendor ID和Product Code,用于标识InoSV660N伺服驱动器。

4. PDO条目偏移量定义

static unsigned int Status_Word; // 状态字(0x6041:0x00)的偏移量
static unsigned int Control_Word; // 控制字(0x6040:0x00)的偏移量
static unsigned int Target_Position; // 目标位置(0x607a:0x00)的偏移量
static unsigned int Position_Actual_Value; // 实际位置值(0x6064:0x00)的偏移量
static unsigned int Target_Velocity; // 目标速度(0x60FF:0x00)的偏移量
static unsigned int Mode_of_Operation; // 操作模式(0x6060:0x00)的偏移量
static unsigned int Error_Code; // 错误代码(0x603f:0x00)的偏移量
static unsigned int Max_Vel; // 最大速度(0x607f:0x00)的偏移量
static unsigned int Profile_Acceleration; // 最大加速度(0x6083:0x00)的偏移量
static unsigned int Profile_Deceleration; // 最大减速度(0x6084:0x00)的偏移量  
static unsigned int Profile_Velocity; // 轮廓速度(0x6081:0x00)的偏移量  
static unsigned int Homing_procedure; // 原点设置(0x6098:0x00)的偏移量

作用

  • 定义从站PDO条目在域数据中的偏移量。这些变量用于在domain_pd中直接访问和操作特定PDO数据(如状态字、控制字、目标位置等),符合CiA 402协议标准。

5. 其他全局变量 

static unsigned int counter = 0; // 用于周期性状态检查的计数器
const struct timespec cycletime = {0, PERIOD_NS}; // 任务周期时间(1 毫秒)
static int shm_fd = -1; // 共享内存文件描述符
static shared_data_t *shared_data = NULL; // 共享内存数据指针
static sem_t *sem = NULL; // 信号量指针

 作用

  • counter用于控制状态检查的频率(每FREQUENCY个周期检查一次)。
  • cycletime定义任务周期(1毫秒),用于定时执行实时任务。
  • shm_fd、shared_data和sem用于共享内存和信号量操作,实现与外部程序的同步通信。

 6. PDO条目注册

const static ec_pdo_entry_reg_t domain1_regs[] = {{BusCouplerPos, InoSV660N, 0x607f, 0x00, &Max_Vel, NULL}, // 最大速度{BusCouplerPos, InoSV660N, 0x6083, 0x00, &Profile_Acceleration, NULL}, // 最大加速度...{} // 列表结束
};

 作用

  • 定义域中注册的PDO条目,指定每个条目对应的从站位置、Vendor ID、Product Code、索引、子索引和偏移量变量。这些条目用于映射从站的输入输出数据(如状态字、控制字等)到域中。

 7. 从站PDO和同步管理器配置

ec_pdo_entry_info_t slave_0_pdo_entries[] = {{0x607F, 0x00, 32}, /* 最大速度 */{0x6040, 0x00, 16}, /* 控制字 */...
};
ec_pdo_info_t slave_0_pdos[] = {{0x1600, 10, slave_0_pdo_entries + 0}, /* 接收 PDO */{0x1A00, 3, slave_0_pdo_entries + 10}, /* 发送 PDO */
};
ec_sync_info_t slave_0_syncs[] = {{0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE}, // SM0:邮箱输出{1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},  // SM1:邮箱输入{2, EC_DIR_OUTPUT, 1, slave_0_pdos + 0, EC_WD_ENABLE}, // SM2:过程数据输出{3, EC_DIR_INPUT, 1, slave_0_pdos + 1, EC_WD_DISABLE}, // SM3:过程数据输入{0xff, EC_DIR_INVALID, 0, NULL, EC_WD_DISABLE} // 列表结束
};

作用

  • slave_0_pdo_entries定义从站的PDO条目,包括索引、子索引和数据长度(如32位或16位)。
  • slave_0_pdos定义接收PDO(0x1600,输出数据)和发送PDO(0x1A00,输入数据)。
  • slave_0_syncs定义同步管理器(SM),用于管理邮箱通信(SM0、SM1,未使用)和过程数据通信(SM2、SM3)。

8. 控制字定义 

uint16_t ctrl_word[] = {0x06, 0x07, 0x0f}; // 关机、开机、启用操作
uint16_t clear_word = 0x80; // 清除故障命令

作用

  • 定义CiA 402状态机转换所需的控制字(ctrl_word)和清除故障的控制字(clear_word)。这些值用于驱动伺服驱动器进入不同状态(如关机、开机、启用操作)。

9. 状态检查函数 

check_domain1_state
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;
}

作用

  • 检查过程数据域的状态,记录工作计数器(WC)和状态变化,更新全局状态变量domain1_state。
 check_master_state
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;
}

作用

  • 检查EtherCAT主站的状态,记录响应从站数量、应用层状态(AL states)和链路状态的变化,更新全局状态变量master_state。
check_slave_config_states 
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;
}

作用

  • 检查从站配置状态,记录AL状态、在线状态和运行状态的变化,更新全局状态变量sc_ana_in_state。

10. 实时循环任务 cyclic_task 

void cyclic_task(void) {struct timespec time;// 步骤 1:接收从站的过程数据ecrt_master_receive(master);ecrt_domain_process(domain1);// 步骤 2:读取状态字和错误代码raw_value = EC_READ_U16(domain_pd + Status_Word);int flag = raw_value & 0x1000;raw_value = raw_value & 0x03ff;error_code = EC_READ_U16(domain_pd + Error_Code);current_position = EC_READ_S32(domain_pd + Position_Actual_Value);// 步骤 3:共享内存操作sem_wait(sem);if (shared_data->target_ready) {new_target = shared_data->new_target;shared_data->target_ready = 0;printf("Updated new_target from shared memory: %d\n", new_target);}shared_data->current_position = current_position;shared_data->position_ready = 1;sem_post(sem);// 步骤 4:CiA 402状态机处理if (raw_value != 0) {if (raw_value == 0x0250) {EC_WRITE_U16(domain_pd + Control_Word, ctrl_word[0]); // 关机}if (raw_value == 0x0231) {EC_WRITE_U16(domain_pd + Control_Word, ctrl_word[1]); // 开机}if (raw_value == 0x0233) {EC_WRITE_U16(domain_pd + Control_Word, ctrl_word[2]); // 启用操作}if ((raw_value == 0x0237) || (raw_value == 0x0238)) {bool set_zero = false;if (!set_zero) {EC_WRITE_U16(domain_pd + Mode_of_Operation, 0x01); // 速度模式EC_WRITE_U16(domain_pd + Homing_procedure, 35); // 原点模式EC_WRITE_S32(domain_pd + Max_Vel, 50*8388608);EC_WRITE_S32(domain_pd + Profile_Velocity, 50*8388608);EC_WRITE_S32(domain_pd + Profile_Acceleration, 10*8388608);EC_WRITE_S32(domain_pd + Profile_Deceleration, 10*8388608);EC_WRITE_S32(domain_pd + Target_Velocity, 50*8388608);if (flag == 0x1000) {EC_WRITE_U16(domain_pd + Control_Word, 0x2f);}EC_WRITE_S32(domain_pd + Target_Position, new_target);if (flag == 0) {EC_WRITE_U16(domain_pd + Control_Word, 0x3f);}} else if (set_zero) {EC_WRITE_U16(domain_pd + Mode_of_Operation, 0x06); // 原点模式EC_WRITE_U16(domain_pd + Homing_procedure, 35);static int i = 0;if (i++ % 2 == 0) {EC_WRITE_U16(domain_pd + Control_Word, 0x0f);}if (i++ % 2 == 1) {默写EC_WRITE_U16(domain_pd + Control_Word, 0x1f);}}}if (raw_value == 0x0218 || error_code != 0) {EC_WRITE_U16(domain_pd + Control_Word, clear_word); // 清除故障}}// 步骤 5:检查域状态check_domain1_state();// 步骤 6:周期性检查主站和从站状态if (counter) {counter--;} else {counter = FREQUENCY;check_master_state();check_slave_config_states();}// 步骤 7:同步时钟clock_gettime(CLOCK_TO_USE, &time);ecrt_master_application_time(master, TIMESPEC2NS(time));ecrt_master_sync_reference_clock(master);ecrt_master_sync_slave_clocks(master);// 步骤 8:发送过程数据ecrt_domain_queue(domain1);ecrt_master_send(master);
}

作用:实时循环任务,负责EtherCAT通信和伺服驱动器控制:

  1. 接收数据:通过ecrt_master_receive和ecrt_domain_process接收从站的PDO数据。
  2. 读取状态:从domain_pd读取状态字(Status_Word)、错误代码(Error_Code)和实际位置(Position_Actual_Value)。
  3. 共享内存操作:通过信号量同步,读取外部程序写入的目标位置(new_target),并将当前位置(current_position)写入共享内存。
  4. CiA 402状态机:根据状态字(raw_value)执行状态转换(如关机、开机、启用操作),设置操作模式(速度模式或原点模式)、速度、加速度、目标位置等参数。
  5. 状态检查:调用check_domain1_state检查域状态,定期调用check_master_state和check_slave_config_states检查主站和从站状态。
  6. 时钟同步:使用分布式时钟(DC)同步主站和从站时间。
  7. 发送数据:通过ecrt_domain_queue和ecrt_master_send发送更新后的PDO数据到从站。

11. 栈预分配函数 stack_prefault 

void stack_prefault(void) {unsigned char dummy[MAX_SAFE_STACK];memset(dummy, 0, MAX_SAFE_STACK);
}

作用:通过分配并初始化一个8KB的数组,确保栈内存页面在实时任务开始前已分配,避免运行时页面错误。

12. 时间相加函数 timespec_add

struct timespec timespec_add(struct timespec time1, struct timespec time2) {struct timespec result;if ((time1.tv_nsec + time2.tv_nsec) >= NSEC_PER_SEC) {result.tv_sec = time1.tv_sec + time2.tv_sec + 1;result.tv_nsec = time1.tv_nsec + time2.tv_nsec - NSEC_PER_SEC;} else {result.tv_sec = time1.tv_sec + time2.tv_sec;result.tv_nsec = time1.tv_nsec + time2.tv_nsec;}return result;
}

作用:将两个timespec结构的时间相加,处理纳秒溢出,确保时间计算的正确性。

13. 主函数 main

int main(void) {// 步骤 1:初始化共享内存shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);...// 步骤 2:初始化 EtherCAT 主站master = ecrt_request_master(0);...// 步骤 3:创建过程数据域domain1 = ecrt_master_create_domain(master);...// 步骤 4:配置从站sc_ana_in = ecrt_master_slave_config(master, BusCouplerPos, InoSV660N);...// 步骤 5:配置 PDOecrt_slave_config_pdos(sc_ana_in, EC_END, slave_0_syncs);...// 步骤 6:注册 PDO 条目ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs);...// 步骤 7:配置分布式时钟ecrt_slave_config_dc(sc_ana_in, 0x0300, PERIOD_NS, SHIFT_NS, 0, 0);...// 步骤 8:激活主站ecrt_master_activate(master);...// 步骤 9:获取域数据指针domain_pd = ecrt_domain_data(domain1);...// 步骤 10:设置实时调度优先级struct sched_param param = {0};param.sched_priority = sched_get_priority_max(SCHED_FIFO);...// 步骤 11:锁定内存mlockall(MCL_CURRENT | MCL_FUTURE);...// 步骤 12:预分配栈stack_prefault();...// 步骤 13:启动实时任务clock_gettime(CLOCK_TO_USE, &wakeup_time);...// 步骤 14:主实时循环while (1) {wakeup_time = timespec_add(wakeup_time, cycletime);ret = clock_nanosleep(CLOCK_TO_USE, TIMER_ABSTIME, &wakeup_time, NULL);...cyclic_task();...}// 步骤 15:清理资源munmap(shared_data, SHM_SIZE);close(shm_fd);sem_close(sem);munlockall();return ret;
}

作用

  1. 初始化共享内存:打开并映射共享内存,初始化信号量。
  2. 初始化主站:请求EtherCAT主站实例。
  3. 创建域:创建用于过程数据交换的域。
  4. 配置从站:配置InoSV660N从站。
  5. 配置PDO:设置从站的PDO和同步管理器。
  6. 注册PDO条目:将PDO条目映射到域。
  7. 配置分布式时钟:设置同步周期和偏移。
  8. 激活主站:启动EtherCAT通信。
  9. 获取域数据指针:获取用于读写PDO数据的指针。
  10. 设置调度优先级:将进程设置为最高优先级的FIFO调度。
  11. 锁定内存:锁定当前和未来的内存页面,避免页面错误。
  12. 预分配栈:调用stack_prefault预分配栈。
  13. 启动实时任务:初始化唤醒时间,进入实时循环。
  14. 实时循环:以1毫秒周期执行cyclic_task,并在启用MEASURE_TIMING时记录时间性能。
  15. 清理:释放共享内存、信号量和锁定内存,返回状态。 

总结

该程序实现了一个实时EtherCAT主站应用,用于控制InoSV660N伺服驱动器。它通过CiA 402协议管理伺服驱动器的状态机,支持速度模式和原点模式,通过共享内存与外部程序交换目标位置和当前位置数据。以下是对sv660n_ethercat.c代码中EtherCAT配置过程的总结,清晰概述初始化和配置的关键步骤:

  1. 初始化共享内存和信号量
    • 打开共享内存(shm_open),映射到进程地址空间(mmap),用于与外部程序交换目标位置和当前位置。
    • 初始化信号量(sem_open),确保共享内存访问的线程安全。
  2. 初始化EtherCAT主站
    • 调用ecrt_request_master(0)请求主站实例(索引0),建立EtherCAT通信核心。
  3. 创建过程数据域
    • 使用ecrt_master_create_domain创建域(domain1),用于管理过程数据(PDO)的交换。
  4. 配置从站(InoSV660N)
    • 调用ecrt_master_slave_config配置从站,指定从站位置(BusCouplerPos: 0, 0)和标识(Vendor ID: 0x00100000, Product Code: 0x000c010d)。
  5. 配置PDO和同步管理器
    • 定义PDO条目(slave_0_pdo_entries),包括控制字(0x6040)、状态字(0x6041)、目标位置(0x607a)等,指定索引、子索引和数据长度。
    • 定义接收PDO(0x1600)和发送PDO(0x1A00),分别包含输出和输入数据。
    • 配置同步管理器(slave_0_syncs),包括SM2(输出,过程数据)和SM3(输入,过程数据),并启用SM2的看门狗。
    • 使用ecrt_slave_config_pdos应用PDO配置到从站。
  6. 注册PDO条目到域
    • 使用ecrt_domain_reg_pdo_entry_list将PDO条目(domain1_regs)注册到域,映射从站数据到域的内存偏移量(如Status_Word、Control_Word等)。
  7. 配置分布式时钟(DC)
    • 调用ecrt_slave_config_dc,设置同步周期(PERIOD_NS: 1ms)、偏移(SHIFT_NS: 250μs),确保从站时钟与主站同步。
  8. 激活主站
    • 调用ecrt_master_activate激活主站,启动EtherCAT通信。
  9. 获取域数据指针
    • 使用ecrt_domain_data获取域的过程数据指针(domain_pd),用于直接读写PDO数据。
  10. 设置实时环境
    • 设置最高优先级的FIFO调度(sched_setscheduler, SCHED_FIFO),确保实时任务优先执行。
    • 锁定内存(mlockall),避免页面错误。
    • stacks_prefault`预分配栈内存,确保实时操作稳定性。

配置过程通过初始化共享内存、主站、域和从站,设置PDO和同步管理器,注册PDO条目,配置分布式时钟,最终激活主站并准备实时环境,为实时EtherCAT通信和伺服驱动器控制奠定基础。这些步骤确保程序能够高效、稳定地与InoSV660N从站进行数据交换和状态管理。 

http://www.lqws.cn/news/577135.html

相关文章:

  • 云手机的用途都有哪些?
  • Deep Mean-Shift Priors for Image Restoration论文阅读
  • mysql mvcc
  • Hadoop WordCount 程序实现与执行指南
  • Java 案例 6 - 数组篇(基础)
  • 第 89 场周赛:山脉数组的峰值索引、车队、考场就坐、相似度为 K 的字符串
  • 大语言模型(LLM)笔记
  • UE5 一台电脑+双显示器 配置nDisplay裸眼3D效果
  • 东芝TC78S600FNG在打印机中的应用:静音、防卡纸与能效
  • Python 数据分析与机器学习入门 (八):用 Scikit-Learn 跑通第一个机器学习模型
  • 智慧畜牧-猪场猪只行为状态检测数据集VOC+YOLO格式3790张15类别
  • Java中for与foreach
  • python+uniapp基于微信小程序的生鲜订购系统nodejs+java
  • 基于uniapp的老年皮肤健康管理微信小程序平台(源码+论文+部署+安装+售后)
  • JAVA八股文:异常有哪些种类,可以举几个例子吗?Throwable类有哪些常见方法?
  • HTML5 实现的圣诞主题网站源码,使用了 HTML5 和 CSS3 技术,界面美观、节日氛围浓厚。
  • 湖北理元理律师事务所债务解法:从法律技术到生活重建
  • 车载Tier1 supplier梳理
  • VMware vSphere 9与ESXi 9正式发布:云原生与AI驱动的虚拟化平台革新
  • Nginx反向代理与缓存功能
  • 【软考高项论文】信息系统项目的资源管理
  • GitHub Actions配置python flake8和black
  • 企业流程知识:《企业再造:企业革命的宣言》
  • 大语言模型 API 进阶指南:DeepSeek 与 Qwen 的深度应用与封装实践
  • 【Linux】Vi编辑器保存和退出
  • AIGC检测系统升级后的AI内容识别机制与系统性降重策略研究(三阶段降重法)
  • Windows桌面上的「了解此图片」怎么弄掉?
  • Day2 音频基础知识
  • HarmonyOS NEXT仓颉开发语言实战案例:电影App
  • CAU数据挖掘 支持向量机