EtherCAT 伺服控制功能块实现

        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 伺服电机

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ZYNQ_project:uart(odd,even)

概念&#xff1a; UART&#xff08;Universal Asynchronous Receiver-Transmitter&#xff09;&#xff1a;即通用异步收发器&#xff0c;是一种通用串行数据总线&#xff0c;用于异步通信。一般UART接口常指串口。 UART在发送数据时将并行数据转换成串行数据来传输&#xff…

注册表单mvc 含源代码

总结 jsp给我们的ControllerServlet.java,ControllerServlet.java获取参数,信息封装给RegisterFormBean.java的对象看是否符合格式,符合格式再信息封装给UserBean对象,调用Dbutil插入方法查重.]]要创建一个user集合成功跳哪个界面,打印信息注意什么时候要加getsession失败跳哪…

react-router-dom 版本6.18.0中NavLink的api和属性介绍

React Router 是一个基于 React 的路由库&#xff0c;它可以帮助我们在 React 应用中实现页面的切换和路由的管理。而 NavLink 则是 React Router 中的一个组件&#xff0c;它可以帮助我们实现导航栏的样式设置和路由跳转。 在 React Router 版本6.18.0 中&#xff0c;NavLink…

【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏3(附项目源码)

文章目录 先看本次实现的最终效果前言绘制炮塔UI炮塔转向敌人生成炮弹旋转我们的子弹对敌人造成伤害&#xff0c;回收子弹自动发射子弹添加攻击间隔显示伤害字体设计通用泛型单例创建更多炮塔升级增加伤害升级缩短攻击间隔添加货币杀死敌人获取金币源码完结 先看本次实现的最终…

epoll协程简述

协程的由来 【协程第二话】协程和IO多路复用更配哦~_哔哩哔哩_bilibili 协程类别:有栈(静态)协程, 无栈(动态协程) 协程epoll 当有需要等待的时候,就切换出去,要用汇编保存这个栈rsp 运行时,要根据协程上下文恢复出这个栈

Beego之Bee工具使用

1、bee工具使用 bee 工具是一个为了协助快速开发 Beego 项目而创建的项目&#xff0c;通过 bee 你可以很容易的进行 Beego 项目的创 建、热编译、开发、测试、和部署。Bee工具可以使用的命令&#xff1a; [rootzsx ~]# bee 2023/02/18 18:17:26.196 [D] init global config…

Java基础笔记

1.数据类型在java语言中包括两种: 第一种:基本数据类型 基本数据类型又可以划分为4大类8小种: 第一类:整数型 byte , short,int, long(没有小数的&#xff09; 第二类:浮点型 float,aouble(带有小数的&#xff09; 第三类:布尔型 boole…

【Rust】快速教程——模块mod与跨文件

前言 道尊&#xff1a;没有办法&#xff0c;你的法力已经消失&#xff0c;我的法力所剩无几&#xff0c;除非咱们重新修行&#xff0c;在这个世界里取得更多法力之后&#xff0c;或许有办法下降。——《拔魔》 \;\\\;\\\; 目录 前言跨文件mod多文件mod 跨文件mod //my_mod.rs…

高能分享:软件测试十大必问面试题(附带答案)

1 介绍之前负责的项目 参考答案&#xff1a;先大概描述一下这个项目是做什么的&#xff08;主要功能&#xff09;&#xff0c;包括哪些模块&#xff0c;是什么架构的&#xff08;B/S、C/S、移动端&#xff1f;&#xff09;&#xff0c;你在其中负责哪些模块的测试。期间经历了几…

Java后端工程师有福啦,CSDN里找到宝藏

目录 一、说明 二、操作步骤 一、说明 CDSN也有系统的java学习资料&#xff0c;有事无事翻翻挺好。 二、操作步骤 1、在CSDN首页顶端左边&#xff0c;点【学习】 2、在【学习】的页面&#xff0c;往下滑&#xff0c;找到【职业路线】 3、java后端工程师【入门版】【进阶版】…

Python基础:输入输出详解-输出字符串格式化

Python中的输入和输出是编程中非常重要的方面。 1. 输入输出简单介绍 1.1 输入方式 Python中的输入可以通过input()函数从键盘键入&#xff0c;也可以通过命令行参数或读取文件的方式获得数据来源。 1&#xff09;input()示例 基本的input()函数&#xff0c;会将用户在终端&…

Unity中Shader纹理的环绕方式

文章目录 前言一、修改环绕方式前的设置准备二、在纹理的设置面板可以修改环绕方式三、在Shader中&#xff0c;实现纹理的环绕方式切换1、在属性面板定义一个和纹理面板一样的纹理环绕方式下拉框2、在Pass中&#xff0c;定义枚举对应的变体3、在片元着色器中&#xff0c;纹理采…

腾讯云服务器收费标准是多少?腾讯云服务器收费标准表

你是否曾被繁琐复杂的服务器租赁费用搞得头昏脑胀&#xff1f;看着一堆参数和计费方式却毫无头绪&#xff1f;别担心&#xff0c;这篇文章就来帮你解决这个问题&#xff01;我们今天就来揭秘一下腾讯云服务器的收费标准&#xff0c;让大家轻松明白地知道如何租用腾讯云服务器。…

2023.11.17-hive调优的常见方式

目录 0.设置hive参数 1.数据压缩 2.hive数据存储格式 3.fetch抓取策略 4.本地模式 5.join优化操作 6.SQL优化(列裁剪,分区裁剪,map端聚合,count(distinct),笛卡尔积) 6.1 列裁剪: 6.2 分区裁剪: 6.3 map端聚合(group by): 6.4 count(distinct): 6.5 笛卡尔积: 7…

Go 语言变量类型和声明详解

在Go中&#xff0c;有不同的变量类型&#xff0c;例如&#xff1a; int 存储整数&#xff08;整数&#xff09;&#xff0c;例如123或-123float32 存储浮点数字&#xff0c;带小数&#xff0c;例如19.99或-19.99string - 存储文本&#xff0c;例如“ Hello World”。字符串值用…

2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-C

2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-C 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A、B模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略设置 A-3 流量完整性保护 A-4 …

技术分享 | 如何写好测试用例?

对于软件测试工程师来说&#xff0c;设计测试用例和提交缺陷报告是最基本的职业技能。是非常重要的部分。一个好的测试用例能够指示测试人员如何对软件进行测试。在这篇文章中&#xff0c;我们将介绍测试用例设计常用的几种方法&#xff0c;以及如何编写高效的测试用例。 ## 一…

337. 打家劫舍 III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连…

学习指南:如何快速上手媒体生态一致体验开发

过去开发者们在使用多媒体能力时&#xff0c;往往会遇到这样的问题&#xff0c;比如&#xff1a;为什么我开发的相机不如系统相机的效果好&#xff1f;为什么我的应用和其他的音乐一起发声了&#xff0c;我要怎么处理&#xff1f;以及我应该怎么做才能在系统的播控中心里可以看…

talbay---贝叶斯网络分析工具产品介绍

一 简介 talbay是拥有独立知识产权的国产软件&#xff0c;主要功能是贝叶斯网络建模、决策网络建模、概率计算、决策支持、敏感性分析、网络模型验证、机器学习等。talbay以用户为中心&#xff0c;简单易用, 计算准确高效&#xff0c;分析全面多样&#xff0c;在应用成熟理论及…