工作需要,测试板卡的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作为子目录包含在项目目录下
配置VS包含目录 – C/C++常規配置添加附加包含目錄
配置VS附加依赖项 – 鏈接器輸入中添加附加依賴項
SOEM 开发控制从站的流程
以下是我画的操控流程,也是代码编写时对从站操控的过程,产品的各IO读写都是按照这样的方式,如遇到特殊情况,再评论区交流。
其中:
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(安全运行状态)可执行
PDO就是用来进行过程数据的读写,就是我们通常认知的Output和Input,在Operational(运行状态)可执行


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;
}
写在最后
因为也是初次接触,为了完成工作任务,只是学了个皮毛,能实现自动化测试需要就行了,大家有不明白再评论探讨,也请大神给予指导!