EtherCAT 伺服控制功能块实现

news2024/10/7 18:25:25

        EtherCAT 是运动控制领域主要的通信协议,开源EtherCAT 主站协议栈 IgH 和SOEM 两个项目,IgH 相对更普及一些,但是它是基于Linux 内核的方式,比SOEM更复杂一些。使用IgH 协议栈编写一个应用程序,控制EtherCAT 伺服电机驱动器是比较简单的。但是要实现一个通用的EtherCAT 组件库(例如IEC61131-3 ,或者IEC61499功能块)就复杂一些了,例如动态地加入一个从站驱动器,通过组件控制某一个从站。

本博文研究基于组件的EtherCAT 程序架构及其实现方法。

背景技术

CiA402 运动控制的CANopen 驱动器规范

        EtherCAT 的运动控制器是基于CANopen 的CiA402规范。这套配置文件规范标准化了伺服驱动器、变频器和步进电机控制器的功能行为。它定义了状态机,控制字,状态字,参数值,它们映射到过程数据对象(PDO)配置文件已在 IEC 61800-7 系列中部分实现国际标准化。

COE 协议

CANopen Over EtherCAT 协议被称为COE,它的架构为:

正是由于如此,基于EtherCAT 的运动控制器的控制方式,PDO 定义,控制方式都是类似的。

主要的一些数据对象

 

PLCopen 运动控制库

 最著名的运动控制的标准应当数PLCopen 运动控制库,它是PLC 上的一个功能块集。PLC 的应用程序通过这些功能块能够方便地实现运动控制。但是这些功能块如何实现,如何与硬件驱动结合。内部实现应该是比较复杂的。笔者看来,应该有两种方式:

  •    PLC 内嵌运动控制模型
  •    通过Ethercat 总线外接运动控制模块

两种结构的实现方法应该各不相同。是否有支持etherCAT 的PLCopen 功能块库?

EtherCAT 主站程序

        EtherCAT 协议是倍福公司提出的,从站通常使用专用ASIC 芯片,FPGA 实现,而主站使用通用Ethernet接口和软件实现。EtherCAT 主站协议有专业公司开发的商业化产品,也有开源代码,下面是两个比较流行的EtherCAT Master

  • IgH
  • SOEM

感觉IgH  更普及一点,于是我们选择IgH 协议栈。

EtherCAT 组件设计

IgH 主要实现Ethercat 协议数据帧的映射,以及通过Ethernet 发送和接收。如果设计成为组件库,许多参数需要可编程,比如:

  •     多少从站
  •    每个从站的位置
  •    每个从站的操作模型,操作算法
  •    每个从机的状态

        本项目的基本思路是构建一个从站类,每个物理从站对应一个虚拟从站,应用程序通过虚拟从站控制从站,将虚拟从站的参数映射到物理从站参数,通过Ethercat 网络发送和接收。

从站类(SevoController Class)与主站类(Master Class)

        为了实现动态的建立和控制从站,采用虚拟从站类。为每个物理的从站创建一个从站类(SevoController). 该类型中包含了物理伺服驱动控制器的参数和状态。应用程序可以通过修改SevoController 的参数,实现对物理伺服的驱动。

        为了相对于,我们同时设立一个Master 类(Master Class)。存放主站的参数。

系统架构

        从上图可见,使用Slaver 类作为应用程序和EtherCAT 底层的接口。EtherCAT 底层程序读取Slave 的参数,对EtherCAT 初始化,并且建立一个EtherCAT 线程,周期扫描各个从站。

从站类(slave class)

#ifndef _SEVOCONTROLLER_H
#define _SEVOCONTROLLER_H

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
#include "ecrt.h"
#define PROFILE_POSITION 1
#define VEOLOCITY 2
#define PROFILE_VELOCITY 3
#define PROFILE_TORQUE 4
#define HOMING 6
#define CYCLICE_SYNC_POSITION 8
using namespace std;
struct pdo_offset
{
    unsigned int ctrl_word;
    unsigned int operation_mode;
    unsigned int target_velocity;
    unsigned int target_position;
    unsigned int profile_velocity;
    unsigned int status_word;
    unsigned int mode_display;
    unsigned int current_velocity;
};
class SevoController
{
public:
    pdo_offset offset;
    uint16_t position;
    uint32_t vendor_id;
    uint32_t product_code;
    uint32_t position_actual;
    uint32_t velocity_actual;
    uint32_t operation_modes;
    uint32_t target_velocity;
    uint32_t target_position;
    uint32_t profile_velocity;
    ec_slave_config_t *slave_config;
    void eventAction(string EventName);
    SevoController(uint32_t Position, uint32_t Vendor_id, uint32_t Product_cdode, uint32_t Modes_operation);
};
#endif

控制代码

#include "ecrt.h"
#include "stdio.h"
#include <errno.h>
#include <sys/resource.h>
#include <list>
#include "SevoController.hpp"
#include <pthread.h>
void check_domain_state(void);
void check_slave_config_states(void);
pthread_t cycle_thread;
int cycles;
int Run = 1;
ec_master_t *master = NULL;
static ec_master_state_t master_state = {};

static ec_domain_t *domainServo = NULL;
static ec_domain_state_t domainServo_state = {};
static uint8_t *domain_pd = NULL;
std::list<SevoController *> SevoList;
ec_pdo_entry_reg_t *domainServo_regs;
static ec_pdo_entry_info_t pdo_entries[] = {
   /*RxPdo 0x1600*/
    {0x6040, 0x00, 16},
    {0x6060, 0x00, 8 }, 
    {0x60FF, 0x00, 32},
    {0x607A, 0x00, 32},
    {0x6081, 0x00, 32},
    /*TxPdo 0x1A00*/
    {0x6041, 0x00, 16},
    {0x6061, 0x00, 8},
    {0x606C, 0x00, 32}
};

static ec_pdo_info_t Slave_pdos[] = {
    // RxPdo
    {0x1600, 5, pdo_entries + 0},
    // TxPdo
    {0x1A00, 3, pdo_entries + 5}};

static ec_sync_info_t Slave_syncs[] = {
    {0, EC_DIR_OUTPUT, 0, NULL, EC_WD_DISABLE},
    {1, EC_DIR_INPUT, 0, NULL, EC_WD_DISABLE},
    {2, EC_DIR_OUTPUT, 1, Slave_pdos + 0, EC_WD_DISABLE},
    {3, EC_DIR_INPUT, 1, Slave_pdos + 1, EC_WD_DISABLE},
    {0xFF}};
int ConfigPDO()
{
    domainServo = ecrt_master_create_domain(master);
    if (!domainServo)
    {
        return -1;
    }

    //
    domainServo_regs = new ec_pdo_entry_reg_t[9];
 
    std::list<SevoController *>::iterator it;
    int index = 0;

     for (it = SevoList.begin(); it != SevoList.end(); it++)
    {
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x6040, 0x00, &((**it).offset.ctrl_word)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x6060, 0x00, &((**it).offset.operation_mode)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x60FF, 0x00, &((**it).offset.target_velocity)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x607A, 0x00, &((**it).offset.target_position)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x6081, 0x00, &((**it).offset.profile_velocity)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x6041, 0x00, &((**it).offset.status_word)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x6061, 0x00, &((**it).offset.mode_display)};
        domainServo_regs[index++] = {0, (**it).position, (**it).vendor_id, (**it).product_code, 0x606C, 0x00, &((**it).offset.current_velocity)};
    printf("product_code:%x\n", (**it).product_code);
    }
    domainServo_regs[index++] = {}; 
    //
    

    //
    for (it = SevoList.begin(); it != SevoList.end(); it++)
    {
        (**it).slave_config = ecrt_master_slave_config(master, 0, (**it).position, (**it).vendor_id, (**it).product_code);
        ecrt_slave_config_pdos((**it).slave_config, EC_END, Slave_syncs);
    }
    //
    if (ecrt_domain_reg_pdo_entry_list(domainServo, domainServo_regs))
    {
        printf("PDO entry registration failed!\n");
        return -1;
    }

    return 0;
}
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;
}
void *cyclic_task(void *arg)
{
    uint16_t status;
    //  int8_t      opmode;
    static uint16_t command = 0x004F;
    printf("Cycles Task Start\n");
    while (Run)
    {
        ecrt_master_receive(master);
        ecrt_domain_process(domainServo);
        check_domain_state();
        check_master_state();
        check_slave_config_states();
        std::list<SevoController *>::iterator it;

        for (it = SevoList.begin(); it != SevoList.end(); it++)
        {

            status = EC_READ_U16(domain_pd + (**it).offset.status_word);

            if ((status & command) == 0x0040)
            {
                printf("Switch On disabled\n");
                EC_WRITE_U16(domain_pd + (**it).offset.ctrl_word, 0x0006);
                EC_WRITE_S8(domain_pd + (**it).offset.operation_mode, (**it).operation_modes);
                command = 0x006F;
            }
            /*Ready to switch On*/
            else if ((status & command) == 0x0021)
            {
                EC_WRITE_U16(domain_pd + (**it).offset.ctrl_word, 0x0007);
                command = 0x006F;
            }
            /* Switched On*/
            else if ((status & command) == 0x0023)
            {
                printf("Switched On\n");
                EC_WRITE_U16(domain_pd + (**it).offset.ctrl_word, 0x000f);
                if ((**it).operation_modes == PROFILE_VELOCITY)
                {
                    EC_WRITE_S32(domain_pd + (**it).offset.target_velocity, (**it).target_velocity);
                }
                else
                {
                    EC_WRITE_S32(domain_pd + (**it).offset.target_position, (**it).target_position);
                    EC_WRITE_S32(domain_pd + (**it).offset.profile_velocity, (**it).profile_velocity);
                }

                command = 0x006F;
            }
            // operation enabled

            else if ((status & command) == 0x0027)
            {
                printf("operation enabled:%d\n", cycles);
                if (cycles == 0)
                    EC_WRITE_U16(domain_pd + (**it).offset.ctrl_word, 0x001f);
                if ((status & 0x400) == 0x400)
                {
                    printf("target reachedd\n");
                    Run = 0;
                    EC_WRITE_U16(domain_pd + (**it).offset.ctrl_word, 0x0180); // halt
                }
                cycles = cycles + 1;
            }
        }
        ecrt_domain_queue(domainServo);
        ecrt_master_send(master);
        usleep(10000);
    }
    return ((void *)0);
}
void ethercat_initialize()
{
    master = ecrt_request_master(0);
    ConfigPDO();
    if (ecrt_master_activate(master))
    {
        printf("Activating master...failed\n");
        return;
    }

    if (!(domain_pd = ecrt_domain_data(domainServo)))
    {
        fprintf(stderr, "Failed to get domain data pointer.\n");
        return;
    }

    // 启动master Cycles Thread
    pthread_create(&cycle_thread, NULL, cyclic_task, NULL);
}
void check_domain_state(void)
{
    ec_domain_state_t ds = {};
    // ec_domain_state_t ds1 = {};
    // domainServoInput
    ecrt_domain_state(domainServo, &ds);
    if (ds.working_counter != domainServo_state.working_counter)
    {
        printf("domainServoInput: WC %u.\n", ds.working_counter);
    }
    if (ds.wc_state != domainServo_state.wc_state)
    {
        printf("domainServoInput: State %u.\n", ds.wc_state);
    }
    domainServo_state = ds;
}
void check_slave_config_states(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;
}

主程序

/*****************************************************************************
sudo /etc/init.d/ethercat start
gcc testbyesm.c -Wall -I /opt/etherlab/include -l ethercat -L /opt/etherlab/lib -o testbyesm

****************************************************************************/
#include  "time.h"
#include  "SevoController.hpp"
#include "ethercat.hpp"
#define Panasonic           0x0000066F,0x60380004
#define TASK_FREQUENCY          100 /*Hz*/
#define TIMOUT_CLEAR_ERROR  (1*TASK_FREQUENCY)  /*clearing error timeout*/
#define TARGET_VELOCITY         8388608 /*target velocity*/
#define PROFILE_VELOCITY            3   /*Operation mode for 0x6060:0*/
#define PROFILE_POSITION            1  
int main(){
    printf("EtherCAT Component Test\n");
    SevoController *Sevo1=new SevoController(0,Panasonic,PROFILE_POSITION);
    Sevo1->profile_velocity=TARGET_VELOCITY*100;
    Sevo1->target_velocity=TARGET_VELOCITY*10;
    Sevo1->target_position=TARGET_VELOCITY/2;
 SevoList.push_back(Sevo1);

 ethercat_initialize();
 while(1){
     sleep(10);
 }
}

小结

上面的程序基于松下A6 EtherCAT 伺服电机

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1221207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MIKE水动力笔记19_统计平均潮差

本文目录 前言Step 1 ArcGIS中创建渔网点Step 2 将dfsu数据提取到渔网点Step 3 Python统计平均潮差 前言 日平均潮差&#xff08;average daily tidal range&#xff09;&#xff1a;日高潮潮高合计之和除以实有高潮个数为日平均高潮潮高&#xff0c;日低潮潮高合计之和除以实…

【漏洞复现】NUUO摄像头存在远程命令执行漏洞

漏洞描述 NUUO摄像头是中国台湾NUUO公司旗下的一款网络视频记录器&#xff0c;该设备存在远程命令执行漏洞&#xff0c;攻击者可利用该漏洞执行任意命令&#xff0c;进而获取服务器的权限。 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&…

【C语法学习】25 - strncpy()函数

文章目录 1 函数原型2 参数3 返回值4 使用说明5 示例5.1 示例15.2 示例2 1 函数原型 strncpy()&#xff1a;将str指向的字符串的前n个字符拷贝至dest&#xff0c;函数原型如下&#xff1a; char *strncpy(char *dest, const char *src, size_t n);2 参数 strncpy()函数有三个…

linux进程间通信之共享内存(mmap,shm_open)

共享内存&#xff0c;顾名思义就是允许两个不相关的进程访问同一个逻辑内存&#xff0c;共享内存是两个正在运行的进 程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中&#xff0c…

信号的机制——信号处理函数的注册

在 Linux 操作系统中&#xff0c;为了响应各种各样的事件&#xff0c;也是定义了非常多的信号。我们可以通过 kill -l 命令&#xff0c;查看所有的信号。 # kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS …

【Spring】加载properties文件

文章目录 在Spring Context中加载properties文件测试总结 在Spring Context中加载properties文件 分为三步&#xff0c;如下图所示&#xff1a; 完整代码&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.…

【Linux】U盘安装的cfg引导文件配置

isolinux.cfg文件 default vesamenu.c32 timeout 600display boot.msg# Clear the screen when exiting the menu, instead of leaving the menu displayed. # For vesamenu, this means the graphical background is still displayed without # the menu itself for as long …

计算机是如何工作的(简单介绍)

目录 一、冯诺依曼体系 二、CPU基本流程工作 逻辑⻔ 电⼦开关——机械继电器(Mechanical Relay) ⻔电路(Gate Circuit) 算术逻辑单元 ALU&#xff08;Arithmetic & Logic Unit&#xff09; 算术单元(ArithmeticUnit) 逻辑单元(Logic Unit) ALU 符号 寄存器(Regis…

java:IDEA中的Scratches and Consoles

背景 IntelliJ IDEA中的Scratches and Consoles是一种临时的文件编辑环境&#xff0c;用于写一些文本内容或者代码片段。 其中&#xff0c;Scratch files拥有完整的运行和debug功能&#xff0c;这些文件需要指定编程语言类型并且指定后缀。 举例&#xff1a;调接口 可以看到…

Unity——URP相机详解

2021版本URP项目下的相机&#xff0c;一般新建一个相机有如下组件 1:Render Type(渲染类型) 有Base和Overlay两种选项&#xff0c;默认是Base选项 Base:主相机使用该种渲染方式&#xff0c;负责渲染场景中的主要图形元素 Overlay&#xff08;叠加&#xff09;:使用了Oveylay的…

Python大数据之linux学习总结——day09_hive函数

hive函数 函数分类标准[重点] 知识点: 原生分类标准: 内置函数 和 用户定义函数(UDF,UDAF,UDTF)分类标准扩大化: 本来&#xff0c;UDF 、UDAF、UDTF这3个标准是针对用户自定义函数分类的&#xff1b; 但是&#xff0c;现在可以将这个分类标准扩大到hive中所有的函数&#…

vue项目如何防范XSS攻击?

场景&#xff1a; 前后端交互的过程中&#xff0c;前端使用v-html或者{{}}渲染时&#xff0c;网页自动执行其恶意代码&#xff0c;如页面弹窗、跳转到钓鱼网站等 解决方案&#xff1a; 先说解决方式&#xff0c;其原理下文解释. 由于我是vue项目所以用的是vue-dompurify-html这…

大数据基础设施搭建 - Hadoop

文章目录 一、下载安装包二、上传压缩包三、解压压缩包四、配置环境变量五、测试Hadoop5.1 测试hadoop命令5.2 测试wordcount案例5.2.1 创建wordcount输入文本信息5.2.2 执行程序5.2.3 查看结果 六、分发压缩包到集群中其他机器6.1 分发压缩包6.2 解压压缩包6.3 配置环境变量 七…

行情分析——加密货币市场大盘走势(11.17)

大机构拉高出货&#xff0c;放心大胆干&#xff0c;笔者手上空单一直拿着&#xff0c;继续等待大饼下跌。 空单策略&#xff1a;入场37000附近 止盈34000-32500 止损39000 以太按照预期回调&#xff0c;继续盈利中&#xff0c;等待继续下跌。没有入场的可以入场&#xff0c;重…

你知道什么是Oracle嘛

文章目录 Oracle数据简介环境准备安装配置安装Oracle设置Oracle开机自启Oracle核心概念创建用户修改用户密码用户授权查看用户 数据备份总结 Oracle数据简介 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它…

DDD落地:从腾讯视频DDD重构之路,看DDD极大价值

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#x…

Python 如何实现 Mediator 中介者设计模式?什么是中介者设计模式?Python 设计模式示例代码

什么是中介者设计模式&#xff1f; 中介者&#xff08;Mediator&#xff09;设计模式是一种行为型设计模式&#xff0c;其主要目的是通过将对象之间的直接交互转变为通过中介者对象进行的间接交互&#xff0c;从而减少对象之间的耦合度。中介者模式通过集中控制对象之间的通信…

【基础算法】筛质数

文章目录 问题描述解决方法朴素筛法线性筛法 问题描述 给定一个正整数 n n n&#xff0c;请你求出 1 ∼ n 1∼n 1∼n 中质数的个数。 输入格式 共一行&#xff0c;包含整数 n。 输出格式 共一行&#xff0c;包含一个整数&#xff0c;表示 1∼n 中质数的个数。 数据范围 …

关于python中内存分配的问题,运行一些操作可能会导致为新结果分配内存,用Python的id()函数演示

一、考虑背景&#xff1a; 一般在python中不会考虑像C中的内存问题&#xff0c;但是在一些高级应用中会考虑&#xff0c;例如有一个特别特别大的矩阵&#xff0c;最好不要不断的赋值&#xff0c;导致内存问题产生。 二、python中的id&#xff1a; 在python中有个id&#xff…

Postman:API测试之Postman使用完全指南

Postman是一个可扩展的API开发和测试协同平台工具&#xff0c;可以快速集成到CI/CD管道中。旨在简化测试和开发中的API工作流。 Postman工具有Chrome扩展和独立客户端&#xff0c;推荐安装独立客户端。 Postman有个workspace的概念&#xff0c;workspace分personal和team类型…