基于SOEM的Ethercat通讯开发

工作需要,测试板卡的DIO、AIO、PWM、温度侦测等功能,于是浅学了一下SOEM,并开发了一支完整的板卡功能测试程序,以下仅针对Windows环境,Linux类似。

目录

SOEM下载

项目环境配置

SOEM 开发控制从站的流程

SDO和PDO的解析

🔎 深入理解两者区别

💡 实际应用场景

举例说明

EtherCAT通讯状态

5个状态说明

1. ​Init(初始化状态)​​

2. ​Pre-Operational(预运行状态)​​

3. ​Safe-Operational(安全运行状态)​​

4. ​Operational(运行状态)​​

5. ​Bootstrap(引导状态,可选)​​

状态转换流程

启动顺序

重要规则

状态转换操作

实例代码

DIO读写完整代码

AO测试完整代码

写在最后


SOEM下载

下载地址:https://github.com/OpenEtherCATsociety/SOEM

这是个开源库,后续的功能面开发都依赖此库,可以Bulid成VS项目,开发语言使用C++,本想包成动态链接库供C#开发用,尝试后失败了,最后用C++开发了。

Build方式参考地址:https://docs.rt-labs.com/soem

基于SOEM的Ethercat通讯开发

基于SOEM的Ethercat通讯开发

基于SOEM的Ethercat通讯开发

基于SOEM的Ethercat通讯开发

项目环境配置

将SOEM作为子目录包含在项目目录下基于SOEM的Ethercat通讯开发配置VS包含目录 – C/C++常規配置添加附加包含目錄基于SOEM的Ethercat通讯开发配置VS附加依赖项 – 鏈接器輸入中添加附加依賴項基于SOEM的Ethercat通讯开发

SOEM 开发控制从站的流程

以下是我画的操控流程,也是代码编写时对从站操控的过程,产品的各IO读写都是按照这样的方式,如遇到特殊情况,再评论区交流。基于SOEM的Ethercat通讯开发

其中:

1. SDO和PDO,引用deepseek回答,想深入了解的可以自己去查,网上很多资料

SDO和PDO的解析

在工业现场总线协议(如CANopen、EtherCAT)中,SDO(服务数据对象)和PDO(过程数据对象)是两种核心的通信机制,它们分别承担着不同的数据交换任务,以满足系统对实时性和可靠性的不同需求。

下面的表格清晰地对比了它们的主要特性。

特性维度

PDO (过程数据对象)

SDO (服务数据对象)

核心用途

传输实时过程数据

传输配置参数与服务数据

通信模型

生产/消费模型 (单向,无需应答)

客户端/服务器模型 (请求-响应,需确认)

实时性

,优先级高,延迟低

较低,优先级相对较低

数据长度

≤ 8字节

理论上无限制 (快速SDO≤4字节,普通SDO可分帧)

触发方式

事件触发(如数据变化)、定时触发或同步信号触发

按需请求触发

数据完整性

不保证(可能因覆盖而丢失)

保证​(通过握手机制确保传输可靠)

类比

实时广播(如现场直播)

挂号信(有回执,确保送达)

🔎 深入理解两者区别

PDO:为速度与效率而生

PDO的设计目标是最大限度地降低传输延迟,适用于需要周期性快速交换的控制数据,例如传感器的实时读数、发送给执行器的控制命令等。它采用“一厢情愿”的传输方式,发送方直接发出数据,不等待接收方的确认,这大大提高了效率并降低了总线负载

。为了保证实时性,一个PDO报文最多只能携带8字节数据。其传输可由特定事件(如数据变化、定时器到期)或同步报文立即触发。正因如此,PDO数据传输可能因新的数据覆盖旧数据而丢失,不适合传输关键配置信息。

SDO:为可靠与精确而设计

SDO的核心任务是可靠地读写设备参数,例如在设备启动时配置工作参数,或是在运行时查询设备状态。它采用严谨的“客户端/服务器”模型:客户端(通常是主站)发送一个包含目标参数地址(索引和子索引)的请求,服务器(从站)必须返回一个响应以确认操作成功

。这种机制确保了数据传输的100%可靠。SDO分为两种:​快速SDO用于传输4字节以内的数据,一次通信即可完成;普通SDO则通过分帧方式传输大量数据(如固件升级文件)。

💡 实际应用场景

在实际系统中,PDO和SDO通常协同工作:

系统启动阶段​:主站使用SDO访问从站的对象字典,配置其通信参数(如设置PDO的映射关系)和工作参数

系统运行阶段​:设备间通过PDO进行高速、周期性的实时数据交换(如电机位置反馈、阀门控制信号)。当需要临时修改某个参数或诊断故障时,再使用SDO进行非周期性的访问

简单来说,可以把PDO看作传输数据的“高速公路”,追求速度;而SDO则是确保关键数据准确送达的“特种运输车”,追求可靠。

希望这些解释能帮助你清晰地理解SDO和PDO。如果你对某个特定的应用场景(如CANopen或EtherCAT)更感兴趣,我可以提供更具体的说明。

举例说明

就以我开发的项目来看,SDO读写主要用来做一些配置,比如AIO range,AO State等,在​Safe-Operational(安全运行状态)​可执行基于SOEM的Ethercat通讯开发

PDO就是用来进行过程数据的读写,就是我们通常认知的Output和Input,在Operational(运行状态)​可执行

基于SOEM的Ethercat通讯开发基于SOEM的Ethercat通讯开发

2. EtherCAT通讯协议定义了五个主要状态,用于协调主站与从站之间的通信和操作。这些状态按照特定的顺序转换,确保系统能够安全、可靠地启动和运行。

EtherCAT通讯状态

5个状态说明

1. ​Init(初始化状态)​

功能​:应用层无任何通信,主站只能访问数据链路层的寄存器以获取设备信息

特点​:没有邮箱通信,也没有过程数据通信

操作​:主站验证设备是否存在,检查型号是否正确,并初始化从站控制器的配置寄存器

2. ​Pre-Operational(预运行状态)​

功能​:邮箱通信被激活,可以进行非周期性数据传输

特点​:支持SDO(服务数据对象)通信,用于参数配置

操作​:主站配置同步管理器通道、FMMU通道,以及PDO映射参数

3. ​Safe-Operational(安全运行状态)​

功能​:开始过程数据通信,但只允许读取输入数据

特点​:输出数据被设置为”安全状态”,不产生实际输出信号

操作​:从站应用程序传输实际输入数据,主站不对从站输出进行操作

4. ​Operational(运行状态)​

功能​:系统全面启动运行,输入和输出数据都有效

特点​:按任务周期刷新过程数据,所有SDO和PDO都可用

操作​:主站应用程序发出输出数据,从站设备产生输出信号

5. ​Bootstrap(引导状态,可选)​

功能​:用于固件更新和文件传输

特点​:仅支持File-Access-Over-EtherCAT(FoE)协议

限制​:只能从Init状态进入,且只能返回到Init状态

状态转换流程

启动顺序

Init → Pre-Operational

主站配置从站地址和同步管理器通道

启动邮箱通信

配置分布式时钟同步

Pre-Operational → Safe-Operational

主站配置过程数据映射

设置FMMU通道

从站检查同步管理器配置是否正确

Safe-Operational → Operational

主站发送有效的输出数据

请求进入运行状态

重要规则

顺序要求​:从Init到Op状态必须按顺序转换,不能越级

状态同步​:从站的状态不能高于主站状态

回退机制​:从运行状态返回时可以越级转换

状态转换操作

状态转换

操作描述

Init → Pre-Operational

启动邮箱通信,配置同步管理器通道

Pre-Operational → Safe-Operational

配置过程数据映射,设置FMMU通道

Safe-Operational → Operational

发送有效输出数据,请求运行状态

任何状态 → Init

停止所有通信,返回初始化状态

EtherCAT状态机通过这种严格的状态管理机制,确保了工业自动化系统的可靠性和安全性,特别是在需要高实时性和同步精度的运动控制应用中。

实例代码

DIO读写完整代码

代码的功能是对所有DO写入1,然后从DI读取,Check是否也是1,以此判断DIO功能是否OK,读写过程中因电平拉高,所以同步点亮LED灯。



#include "pch.h"
#include "DIO.h"
#include <windows.h>  // 用于控制台颜色设置
#include <conio.h>    // 用于_kbhit和_getch
#include <iostream>
 
extern ecx_contextt ctx;
extern uint8 IOmap[4096];
extern volatile int wkc;
extern int expectedWKC;
extern boolean needlf;
extern boolean inOP;
 
static const int inputsMax = 32;
 
// 设置控制台文本颜色(高亮)
void SetHighlightColor(int color) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, color);
}
 
// 重置为默认颜色(白色前景+黑色背景)
void ResetColor() {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
 
void slavetop(int i)
{
    ctx.slavelist[i].state = EC_STATE_OPERATIONAL;
    ecx_send_processdata(&ctx);
    ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
    ecx_writestate(&ctx, i);
}
 
bool set_do_state(char* ifname, int outPutsAddr[], int outPutsAddrSize, int inPutsAddr[], int inPutsAddrSize, StringBuilder^ stringBuilder)
{
    HighlightOutput("测试过程中请确认所有DIO LED以及RUN灯是否绿色亮?
(持续5s)");
    bool flag = true;
    int i;
    int k = 100;
    uint8 input[inputsMax];
    int IoMap_Size = 0;
    boolean DC_MODE = FALSE;
    int chk;
 
    needlf = FALSE;
    inOP = FALSE;
 
    // init ecat checker
    ecatchecker_init();
 
    // 初始化SOEM,绑定套接字到ifname
    if (ecx_init(&ctx, ifname))
    {
        printf("SOEM init success.
");
    }
    else
    {
        printf("No slaves found!
");
        stringBuilder->Append("No slaves found!
");
        flag = false;
    }
 
    if (flag)
    {
        // scan slave and config it.
        if (ecx_config_init(&ctx) > 0)
        {
            printf("found %d slaves.
", ctx.slavecount);
            // slave index from 1, not 0
            for (i = 1; i <= ctx.slavecount; i++)
            {
                printf("slave %d : %s
", i, ctx.slavelist[i].name);
            }
        }
        else
        {
            printf("No slaves found!
");
            stringBuilder->Append("No slaves found!
");
            flag = false;
        }
    }
 
    if (flag)
    {
        // mapping process data
        IoMap_Size = ecx_config_map_group(&ctx, IOmap, 0);
        printf("ecx_config_map_group success, IoMap_Size : %d
", IoMap_Size);
 
        // config dc mode.
        DC_MODE = ecx_configdc(&ctx);
        if (DC_MODE)
            printf("DC_MODE
");
        else
        {
            printf("DC_MODE FALSE
");
            stringBuilder->Append("DC_MODE FALSE
");
            flag = false;
        }
    }
 
    if (flag)
    {
        printf("Slaves mapped, state to SAFE_OP.
");
        // wait for all slaves to reach SAFE_OP state
        ecx_statecheck(&ctx, 0, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE * 4); // run to safe op state.
 
        // config dc mode to master slave sync.
        for (i = 1; i <= ctx.slavecount; i++)
        {
            uint32_t dc_control = 0x0001; // master slave sync mode.
            if (ecx_SDOwrite(&ctx, i, 0x000F, 0x01, FALSE, 4, (uint8_t*)&dc_control, EC_TIMEOUTRXM) != 0)
            {
                printf("slave %d DC config failed.
", i);
                stringBuilder->Append(String::Format("slave {0} DC config failed.
", i));
                flag = false;
            }
        }
    }
 
    if (flag)
    {
        // work counter.
        // printf("Request operational state for all slaves
");
        expectedWKC = (ctx.grouplist[0].outputsWKC * 2) + ctx.grouplist[0].inputsWKC;
        printf("Calculated workcounter %d
", expectedWKC);
        ctx.slavelist[0].state = EC_STATE_OPERATIONAL;
 
        /* send one valid process data to make outputs in slaves happy*/
        ecx_send_processdata(&ctx);
        // receive process data.
        ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
        /* request OP state for all slaves */
        // write state to all slaves.
        ecx_writestate(&ctx, 0);
 
        chk = 200;
        /* wait for all slaves to reach OP state */
        do
        {
            ecx_send_processdata(&ctx);
            ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
            ecx_statecheck(&ctx, 0, EC_STATE_OPERATIONAL, 50000);
        } while (chk-- && (ctx.slavelist[0].state != EC_STATE_OPERATIONAL));
 
        if (ctx.slavelist[0].state == EC_STATE_OPERATIONAL)
        {
            printf("Operational state reached for all slaves.
");
			inOP = TRUE;
            ecx_send_processdata(&ctx);
            wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
 
            if (wkc >= expectedWKC)
            {
                flag = false;
                while (k--)
                {
                    ecx_send_processdata(&ctx);
                    wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
                    if (wkc >= expectedWKC)
                    {
                        String^ outPutsStr = "Output values: ";
                        for (int m = 0; m < outPutsAddrSize; m++)
                        {
                            //0x04~0x07
                            ctx.slavelist[1].outputs[outPutsAddr[m]] = 0xff;
                            outPutsStr = outPutsStr + "0x" + outPutsAddr[m].ToString("X2") + "->0xFF ";
                        }
                        printf("%s
", outPutsStr);
                        stringBuilder->Append(outPutsStr + "
");
                        //ecx_send_processdata(&ctx);
                        //wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
                        //osal_usleep(50000);
                        String^ inPutsStr = "Input values: ";
                        int ffCount = 0;
                        for (int m = 0; m < inPutsAddrSize; m++)
                        {
                            //0x1C~0x1F
                            input[m] = ctx.slavelist[1].inputs[inPutsAddr[m]];
                            inPutsStr = inPutsStr + "0x" + inPutsAddr[m].ToString("X2") + "->0x" + input[m].ToString("X2") + " ";
                            if (input[m] == 0xff)
                                ffCount++;
                        }
                        if (ffCount == inPutsAddrSize)
                            flag = true;
                        printf("%s
", inPutsStr);
                        if (flag)
                        {
                            stringBuilder->Append(inPutsStr + "
");
                        }
                        else
                        {
                            stringBuilder->Append(inPutsStr + "
");
                        }
                        osal_usleep(50000);
                        needlf = TRUE;
                    }
                }
 
                if (!HighlightOutputYesNo("待测的LED灯是否全亮?是输入Y,否则输入N"))
                {
                    flag = false;
                    stringBuilder->Append("Check LED, The user inputted "N" to confirm that the LED lights were not all on.
");
                }
                else
                {
                    stringBuilder->Append("Check LED, The user input "Y" to confirm that all the LED lights are on.
");
                }
 
                ctx.slavelist[0].state = EC_STATE_INIT;
                ecx_writestate(&ctx, 0);
                ecx_statecheck(&ctx, 0, EC_STATE_INIT, EC_TIMEOUTSTATE);
            }
            else
            {
                printf("[Error] Unexpected WKC: %d, expectedWKC: %d
", wkc, expectedWKC);
                stringBuilder->Append(String::Format("Unexpected WKC: {0}, expectedWKC: {1}
", wkc, expectedWKC));
                flag = false;
            }
			inOP = FALSE;
        }
        else  // do not enter operational state.
        {
            printf("Not all slaves reached operational state.
");
            stringBuilder->Append("Not all slaves reached operational state.
");
            ecx_readstate(&ctx); // read state
            for (i = 1; i <= ctx.slavecount; i++)
            {
                if (ctx.slavelist[i].state != EC_STATE_OPERATIONAL)
                {
                    printf("Slave %d State=0x%2.2x StatusCode=0x%4.4x : %s
",
                        i, ctx.slavelist[i].state, ctx.slavelist[i].ALstatuscode, ec_ALstatuscode2string(ctx.slavelist[i].ALstatuscode));
                    stringBuilder->Append(String::Format("Slave {0} State=0x{1:x2} StatusCode=0x{2:x4} : {3}
",
                        i, ctx.slavelist[i].state, ctx.slavelist[i].ALstatuscode, gcnew String(ec_ALstatuscode2string(ctx.slavelist[i].ALstatuscode))));
                }
            }
            flag = false;
        }
    }
 
    try
    {
        printf("
Request init state for all slaves
");
        ctx.slavelist[0].state = EC_STATE_INIT;
        // request INIT state for all slaves
        ecx_writestate(&ctx, 0);
        // close soem.
        ecx_close(&ctx);
    }
    catch (Exception^ msg) {}
 
    printf("Digital Test end.
");
 
    return flag;
}
 
void HighlightOutput(std::string showMessage)
{
    // 设置红色高亮并输出信息
    SetHighlightColor(FOREGROUND_RED);
    std::cout << showMessage << std::endl;
 
    // 重置为默认颜色
    ResetColor();
 
    // 暂停等待用户按任意键
    std::cout << "按任意键继续...";
    while (!_kbhit());  // 检测是否有按键按下(不阻塞)
    _getch();          // 读取按键(清除输入缓冲区)
    //std::cout << "
程序继续执行..." << std::endl;
}
 
bool HighlightOutputYesNo(std::string showMessage)
{
    // 设置红色高亮并输出信息
    SetHighlightColor(FOREGROUND_RED);
    std::cout << showMessage << std::endl;
    // 重置为默认颜色
    ResetColor();
    // 暂停等待用户按Y或N键
    std::cout << "请输入Y(是)或N(否): ";
    char ch;
    while (true)
    {
        ch = _getch();  // 读取按键(不阻塞)
        if (ch == 'Y' || ch == 'y')
        {
            std::cout << ch << std::endl; // 显示用户输入的字符
            return true; // 用户选择了"是"
        }
        else if (ch == 'N' || ch == 'n')
        {
            std::cout << ch << std::endl; // 显示用户输入的字符
            return false; // 用户选择了"否"
        }
    }
    return false; // 默认返回false,实际不会执行到这里
}

AO测试完整代码

功能是,AO写入10mA,AI读取,Check是否正确。



#include "pch.h"
#include "AOut.h"
#include "DIO.h"
 
extern ecx_contextt ctx;
extern uint8 IOmap[4096];
extern volatile int wkc;
extern int expectedWKC;
extern boolean needlf;
extern boolean inOP;
 
AO_RANGE_CONF_t ao_range_conf;
 
static double electricValue = 0;
 
void Init_AO_RANGE_CONF(std::string basePath)
{
    String^ cfgFile = gcnew String((basePath + "\setup.ini").c_str());
 
	ao_range_conf.AO_Group_Count = Convert::ToInt16(KernelFunction::ReadValue("AnalogOut", "AO_Group_Count", cfgFile, "2")->Trim());	
    printf("AO_Group_Count: %d
", ao_range_conf.AO_Group_Count);
	ao_range_conf.Message_Before_Run = new std::string[ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_Count = new int[ao_range_conf.AO_Group_Count];
 
    ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE = new int* [ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX = new int* [ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX = new int* [ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_REG_ADDR = new int* [ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_AI_RANGE_BASE = new int* [ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX = new int* [ao_range_conf.AO_Group_Count];
    ao_range_conf.AO_Group_Channel_AI_REG_ADDR = new int* [ao_range_conf.AO_Group_Count];
 
    for (int i = 0; i < ao_range_conf.AO_Group_Count; i++)
    {
        char buff[256] = { 0 };
        DWORD BytesCount = 0;
 
        memset(buff, 0, sizeof(buff));
        BytesCount = GetPrivateProfileStringA("AnalogOut", ("Message_Before_Run_" + std::to_string(i + 1)).c_str(), "", buff, sizeof(buff), (basePath + "\setup.ini").c_str());
        // Ensure null termination
        ao_range_conf.Message_Before_Run[i] = trim(buff);
		printf("Message_Before_Run_%d: %s
", i + 1, buff);
 
        ao_range_conf.AO_Group_Channel_Count[i] = Convert::ToInt16(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_Count", cfgFile, "4")->Trim());
		printf("AO_Group_%d_Channel_Count: %d
", i + 1, ao_range_conf.AO_Group_Channel_Count[i]);
		
        ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        ao_range_conf.AO_Group_Channel_REG_ADDR[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        ao_range_conf.AO_Group_Channel_AI_REG_ADDR[i] = new int[ao_range_conf.AO_Group_Channel_Count[i]];
        for (int j = 0; j < ao_range_conf.AO_Group_Channel_Count[i]; j++)
        {
            ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_CONF_RANGE_BASE", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_CONF_RANGE_BASE: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[i][j]);
            ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_CONF_RANGE_INDEX", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_CONF_RANGE_INDEX: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[i][j]);
            ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_CONF_ENABLE_INDEX", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_CONF_ENABLE_INDEX: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[i][j]);
            ao_range_conf.AO_Group_Channel_REG_ADDR[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_REG_ADDR", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_REG_ADDR: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_REG_ADDR[i][j]);
            ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_AI_RANGE_BASE", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_AI_RANGE_BASE: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[i][j]);
            ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_AI_RANGE_INDEX", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_AI_RANGE_INDEX: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[i][j]);
			ao_range_conf.AO_Group_Channel_AI_REG_ADDR[i][j] = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Group_" + (i + 1).ToString() + "_Channel_" + (j + 1).ToString() + "_AI_REG_ADDR", cfgFile, "0x00")->Trim(), 16);
			printf("AO_Group_%d_Channel_%d_AI_REG_ADDR: 0x%X
", i + 1, j + 1, ao_range_conf.AO_Group_Channel_AI_REG_ADDR[i][j]);
        }
    }
    ao_range_conf.AO_Write_Value = Convert::ToInt32(KernelFunction::ReadValue("AnalogOut", "AO_Write_Value", cfgFile, "0xC000")->Trim(), 16);
    printf("AO_Write_Value: 0x%X
", ao_range_conf.AO_Write_Value);
    // 使用位运算计算2^16(1左移16位等于65536)
    int denominator = 1 << 16; // 结果为65536(int类型)
    // 计算(65535 / 2^16) * 40 - 20,需将denominator转为double避免整数除法
    electricValue = (static_cast<double>(ao_range_conf.AO_Write_Value) / denominator) * 20;
}
 
std::string trim(const std::string& str) {
    // 定义空白字符集合(可扩展为" 	

fv"等)
    const std::string whitespace = " 	

";
    size_t start = str.find_first_not_of(whitespace); // 第一个非空白字符位置
    if (start == std::string::npos) return ""; // 全空白字符串返回空
 
    size_t end = str.find_last_not_of(whitespace); // 最后一个非空白字符位置
    return str.substr(start, end - start + 1); // 截取非空白部分
}
 
std::string GBKToUTF8(const char* gbkStr) {
    int len = MultiByteToWideChar(CP_ACP, 0, gbkStr, -1, NULL, 0); // GBK转宽字符
    wchar_t* wstr = new wchar_t[len + 1];
    MultiByteToWideChar(CP_ACP, 0, gbkStr, -1, wstr, len);
 
    len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); // 宽字符转UTF-8
    char* utf8Str = new char[len + 1];
    WideCharToMultiByte(CP_UTF8, 0, wstr, -1, utf8Str, len, NULL, NULL);
 
    std::string result(utf8Str);
    delete[] wstr;
    delete[] utf8Str;
    return result;
}
 
void release_AO_RANGE_CONF()
{
    for (int i = 0; i < ao_range_conf.AO_Group_Count; i++)
    {
        if (ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[i];
            ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[i] = nullptr;
        }
        if (ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[i];
            ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[i] = nullptr;
        }
        if (ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[i];
            ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[i] = nullptr;
        }
        if (ao_range_conf.AO_Group_Channel_REG_ADDR[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_REG_ADDR[i];
            ao_range_conf.AO_Group_Channel_REG_ADDR[i] = nullptr;
        }
        if (ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[i];
            ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[i] = nullptr;
        }
        if (ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[i];
            ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[i] = nullptr;
        }
        if (ao_range_conf.AO_Group_Channel_AI_REG_ADDR[i] != nullptr)
        {
            delete[] ao_range_conf.AO_Group_Channel_AI_REG_ADDR[i];
            ao_range_conf.AO_Group_Channel_AI_REG_ADDR[i] = nullptr;
        }
    }
    if (ao_range_conf.AO_Group_Channel_Count != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_Count;
        ao_range_conf.AO_Group_Channel_Count = nullptr;
    }
    if (ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE;
        ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE = nullptr;
    }
	if (ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX;
        ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX = nullptr;
	}
    if (ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX;
        ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX = nullptr;
    }
    if (ao_range_conf.AO_Group_Channel_REG_ADDR != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_REG_ADDR;
        ao_range_conf.AO_Group_Channel_REG_ADDR = nullptr;
    }
    if (ao_range_conf.AO_Group_Channel_AI_RANGE_BASE != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_AI_RANGE_BASE;
        ao_range_conf.AO_Group_Channel_AI_RANGE_BASE = nullptr;
    }
    if (ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX;
        ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX = nullptr;
    }
    if (ao_range_conf.AO_Group_Channel_AI_REG_ADDR != nullptr)
    {
        delete[] ao_range_conf.AO_Group_Channel_AI_REG_ADDR;
        ao_range_conf.AO_Group_Channel_AI_REG_ADDR = nullptr;
    }
	ao_range_conf.AO_Group_Count = 0;
    ao_range_conf.Message_Before_Run = nullptr;
}
 
// input
// range_flag 
//      0 - 0~20mA
//      1 - 4~20mA
//      2 - +-10V
int set_ao_range(uint16_t slave, int AO_CONF_RANGE_BASE, int AO_CONF_RANGE_INDEX, uint16_t range_flag)
{
    uint32_t ao_offset;
    uint16_t range_set = range_flag;
 
    ao_offset = AO_CONF_RANGE_BASE;
    if (ctx.slavelist[0].state == EC_STATE_SAFE_OP)
    {
        ecx_SDOwrite(&ctx, slave, ao_offset, AO_CONF_RANGE_INDEX, FALSE, 1, &range_set, EC_TIMEOUTSAFE);
    }
    else {
        printf("Not in SAFE mode.
");
        return -1;
    }
 
    return 0;
}
 
// enable or disable ao channel
// input
// enable_flag 
//          0 - disable
//          1 - enable
int set_ao_state(uint16_t slave, int AO_CONF_RANGE_BASE, int AO_CONF_ENABLE_INDEX, uint16_t enable_flag)
{
    uint32_t ao_offset;
    uint16_t ao_enable = enable_flag;
 
    ao_offset = AO_CONF_RANGE_BASE;
    if (ctx.slavelist[0].state == EC_STATE_SAFE_OP)
    {
        ecx_SDOwrite(&ctx, slave, ao_offset, AO_CONF_ENABLE_INDEX, FALSE, 1, &ao_enable, EC_TIMEOUTSAFE);
    }
    else {
        printf("Not in SAFE mode.
");
        return -1;
    }
    // ao_enable = ecx_SDOread(&ctx, slave, PDOassign, 0x00, FALSE, &rdl, &rdat, EC_TIMEOUTSAFE);
 
    return 0;
}
 
int set_ao_value(uint16_t slave, int AO_L_REG_ADDR, uint16_t value)
{
    uint32_t ao_offset;
 
    ao_offset = AO_L_REG_ADDR;
 
    ctx.slavelist[slave].outputs[ao_offset] = value;
    ctx.slavelist[slave].outputs[ao_offset + 1] = (value >> 8) & 0xFF;
 
    return 0;
}
 
int set_ai_range(uint16_t slave, int AI_RANGE_BASE, int AI_RANGE_INDEX, uint16_t range_flag)
{
    uint32_t ai_offset;
    uint16_t range_set = range_flag;
 
    ai_offset = AI_RANGE_BASE;
    if (ctx.slavelist[0].state == EC_STATE_SAFE_OP)
    {
        ecx_SDOwrite(&ctx, slave, ai_offset, AI_RANGE_INDEX, FALSE, 1, &range_set, EC_TIMEOUTSAFE);
    }
    else {
        printf("Not in SAFE mode.
");
        return -1;
    }
 
    return 0;
}
 
uint16_t get_ai(uint16_t slave, int AI_L_REG_ADDR)
{
    uint32_t ai_offset;
    uint16_t input = 0;
 
    ai_offset = AI_L_REG_ADDR;
 
    input = ctx.slavelist[slave].inputs[ai_offset];
    input |= ctx.slavelist[slave].inputs[ai_offset + 1] << 8;
 
    return input;
}
 
bool AnalogOutTest(char* ifname, double standardValue, double tolValue, StringBuilder^ stringBuilder)
{
    bool flag = true;
    for (int gourpCount = 0; gourpCount < ao_range_conf.AO_Group_Count; gourpCount++)
    {
        if (ao_range_conf.Message_Before_Run[gourpCount] != "")
        {
            HighlightOutput(ao_range_conf.Message_Before_Run[gourpCount]);
        }
 
        int i, j;
        boolean DC_MODE = FALSE;
        int IoMap_Size = 0;
        int chk;
        int32_t count = 0;
 
        needlf = FALSE;
        inOP = FALSE;
 
        // init ecat checker
        ecatchecker_init();
 
        // 初始化SOEM,绑定套接字到ifname
        if (ecx_init(&ctx, ifname))
        {
            printf("SOEM init success.
");
        }
        else
        {
            printf("No slaves found!
");
            stringBuilder->Append("No slaves found!
");
            flag = false;
            break;
        }
 
        if (flag)
        {
            // scan slave and config it.
            if (ecx_config_init(&ctx) > 0)
            {
                printf("found %d slaves.
", ctx.slavecount);
                // slave index from 1, not 0
                for (i = 1; i <= ctx.slavecount; i++)
                {
                    printf("slave %d : %s
", i, ctx.slavelist[i].name);
                }
            }
            else
            {
                printf("No slaves found!
");
                stringBuilder->Append("No slaves found!
");
                flag = false;
                break;
            }
        }
 
        if (flag)
        {
            // mapping process data
            IoMap_Size = ecx_config_map_group(&ctx, IOmap, 0);
            printf("ecx_config_map_group success, IoMap_Size : %d
", IoMap_Size);
 
            // config dc mode.
            DC_MODE = ecx_configdc(&ctx);
            if (DC_MODE)
                printf("DC_MODE
");
            else
            {
                printf("DC_MODE FALSE
");
                stringBuilder->Append("DC_MODE FALSE
");
                flag = false;
                break;
            }
        }
 
        if (flag)
        {
            // config dc mode to master slave sync.
            for (i = 1; i <= ctx.slavecount; i++)
            {
                uint32_t dc_control = 0x0001; // master slave sync mode.
                if (ecx_SDOwrite(&ctx, i, 0x000F, 0x01, FALSE, 4, (uint8_t*)&dc_control, EC_TIMEOUTRXM) != 0)
                {
                    printf("slave %d DC config failed.
", i);
                    stringBuilder->Append(String::Format("slave {0} DC config failed.
", i));
                    flag = false;
                    break;
                }
            }
        }
 
 
        if (flag)
        {
            // work counter.
            expectedWKC = (ctx.grouplist[0].outputsWKC * 2) + ctx.grouplist[0].inputsWKC;
            printf("Calculated workcounter %d
", expectedWKC);
            // Test AO function for each group and channel.
 
            printf("AO Group %d Test Start...
", gourpCount + 1);
            stringBuilder->Append(String::Format("AO Group {0} Test Start...
", gourpCount + 1));
            printf("Slaves mapped, state to SAFE_OP.
");
            // wait for all slaves to reach SAFE_OP state
            ecx_statecheck(&ctx, 0, EC_STATE_SAFE_OP, EC_TIMEOUTSTATE * 4); // run to safe op state.
            if (ctx.slavelist[0].state != EC_STATE_SAFE_OP)
            {
                printf("Not All slaves in SAFE mode.
");
                stringBuilder->Append("Not All slaves in SAFE mode.
");
                flag = false;
                break;
            }
 
            for (int channelCount = 0; channelCount < ao_range_conf.AO_Group_Channel_Count[gourpCount]; channelCount++)
            {
                //AO range set and enable
                set_ao_range(1, ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[gourpCount][channelCount], ao_range_conf.AO_Group_Channel_CONF_RANGE_INDEX[gourpCount][channelCount], 1);
                set_ao_state(1, ao_range_conf.AO_Group_Channel_CONF_RANGE_BASE[gourpCount][channelCount], ao_range_conf.AO_Group_Channel_CONF_ENABLE_INDEX[gourpCount][channelCount], 1);
                // AI range set
                set_ai_range(((ctx.slavecount > 1) ? 2 : 1), ao_range_conf.AO_Group_Channel_AI_RANGE_BASE[gourpCount][channelCount], ao_range_conf.AO_Group_Channel_AI_RANGE_INDEX[gourpCount][channelCount], 1);
            }
 
            // printf("Request operational state for all slaves
");
            ctx.slavelist[0].state = EC_STATE_OPERATIONAL;
            /* send one valid process data to make outputs in slaves happy*/
            ecx_send_processdata(&ctx);
            // receive process data.
            ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
            /* request OP state for all slaves */
            // write state to all slaves.
            ecx_writestate(&ctx, 0);
 
            chk = 200;
            /* wait for all slaves to reach OP state */
            do
            {
                ecx_send_processdata(&ctx);
                ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
                ecx_statecheck(&ctx, 0, EC_STATE_OPERATIONAL, 50000);
            } while (chk-- && (ctx.slavelist[0].state != EC_STATE_OPERATIONAL));
 
            if (ctx.slavelist[0].state == EC_STATE_OPERATIONAL)
            {
                printf("Operational state reached for all slaves.
");
                inOP = TRUE;
                ecx_send_processdata(&ctx);
                wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
                if (wkc >= expectedWKC)
                {
                    int passChannelCount = 0;
                    for (int channelCount = 0; channelCount < ao_range_conf.AO_Group_Channel_Count[gourpCount]; channelCount++)
                    {
                        //set ao value to max 0xffff, write 10 times for each channel
                        //read ai value. read 10 times for each channel
                        uint16_t ai_value = 0;
                        for (int cycle = 1; cycle <= 200; cycle++)
                        {
                            ecx_send_processdata(&ctx);
                            wkc = ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
                            //write ao value
                            set_ao_value(1, ao_range_conf.AO_Group_Channel_REG_ADDR[gourpCount][channelCount], ao_range_conf.AO_Write_Value);
                            if (wkc >= expectedWKC)
                            {
                                //read ai value
                                ai_value = get_ai(((ctx.slavecount > 1) ? 2 : 1), ao_range_conf.AO_Group_Channel_AI_REG_ADDR[gourpCount][channelCount]);
								double ai_voltage = (double)ai_value / 1000.0;
                                if ((ai_voltage > (standardValue + tolValue)) || (ai_voltage < (standardValue - tolValue)))
                                {
                                    printf("AO Group %d Channel %d : AO Value = %1fmA, AI Value = %1fmA, Cycle %d: NG!
", gourpCount + 1, channelCount + 1, electricValue, ai_voltage, cycle);
                                    stringBuilder->Append(String::Format("AO Group {0} Channel {1} : AO Value = {3:f}mA, AI Value = {3:f}mA, Cycle {4}: NG!
", gourpCount + 1, channelCount + 1, electricValue, ai_voltage, cycle));
                                }
                                else
                                {
                                    printf("AO Group %d Channel %d : AO Value = %1fmA, AI Value = %1fmA, Cycle %d: OK!
", gourpCount + 1, channelCount + 1,electricValue, ai_voltage, cycle);
                                    stringBuilder->Append(String::Format("AO Group {0} Channel {1} : AO Value = {3:f}mA, AI Value = {3:f}mA, Cycle {3}: OK!
", gourpCount + 1, channelCount + 1, electricValue, ai_voltage, cycle));
                                    passChannelCount++;
                                    break;
								}
                                osal_usleep(5000);
                                needlf = TRUE;
                            }
                            else
                            {
                                printf("AO Group %d Channel %d : wkc = %d, expectedWKC = %d
", gourpCount + 1, channelCount + 1, wkc, expectedWKC);
                                stringBuilder->Append(String::Format("AO Group {0} Channel {1} : wkc = {2}, expectedWKC = {3}
", gourpCount + 1, channelCount + 1, wkc, expectedWKC));
                            }
                        }
 
						//set ao value to min 0x0000, write 100 times for each channel
                        for (int cycle = 0; cycle < 100; cycle++)
                        {
                            ecx_send_processdata(&ctx);
                            ecx_receive_processdata(&ctx, EC_TIMEOUTRET);
                            //write ao value
                            set_ao_value(1, ao_range_conf.AO_Group_Channel_REG_ADDR[gourpCount][channelCount], 0x0000);
                            osal_usleep(5000);
                        }
                    }
 
                    if (passChannelCount == ao_range_conf.AO_Group_Channel_Count[gourpCount])
                    {
                        printf("AO Group %d Test OK! 
", gourpCount + 1);
						stringBuilder->Append(String::Format("AO Group {0} Test OK! 
", gourpCount + 1));
                    }
                    else
                    {
                        printf("AO Group %d Test NG! 
", gourpCount + 1);
                        stringBuilder->Append(String::Format("AO Group {0} Test NG! 
", gourpCount + 1));
						flag = false;
                    }
                }
                else
                {
                    printf("[Error] Unexpected WKC: %d, expectedWKC: %d
", wkc, expectedWKC);
                    stringBuilder->Append(String::Format("Unexpected WKC: {0}, expectedWKC: {1}
", wkc, expectedWKC));
                    flag = false;
                    inOP = FALSE;
                    break;
                }
                inOP = FALSE;
            }
            else
            {
                printf("Not all slaves reached operational state.
");
                stringBuilder->Append("Not all slaves reached operational state.
");
                ecx_readstate(&ctx); // read state
                for (i = 1; i <= ctx.slavecount; i++)
                {
                    if (ctx.slavelist[i].state != EC_STATE_OPERATIONAL)
                    {
                        printf("Slave %d State=0x%2.2x StatusCode=0x%4.4x : %s
",
                            i, ctx.slavelist[i].state, ctx.slavelist[i].ALstatuscode, ec_ALstatuscode2string(ctx.slavelist[i].ALstatuscode));
                        stringBuilder->Append(String::Format("Slave {0} State=0x{1:x2} StatusCode=0x{2:x4} : {3}
",
                            i, ctx.slavelist[i].state, ctx.slavelist[i].ALstatuscode, gcnew String(ec_ALstatuscode2string(ctx.slavelist[i].ALstatuscode))));
                    }
                }
                flag = false;
                break;
            }
        }
 
        try
        {
            printf("
Request init state for all slaves
");
            ctx.slavelist[0].state = EC_STATE_INIT;
            // request INIT state for all slaves
            ecx_writestate(&ctx, 0);
            // close soem.
            ecx_close(&ctx);
        }
        catch (Exception^ msg) {}
    }
 
    release_AO_RANGE_CONF();
 
    printf("
");
    printf("Get counter end.
");
 
    return flag;
}

写在最后

因为也是初次接触,为了完成工作任务,只是学了个皮毛,能实现自动化测试需要就行了,大家有不明白再评论探讨,也请大神给予指导!

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...