EtherCATBasics示例讲解
目录
- EtherCATBasics示例讲解
- 结构说明
- 代码讲解
项目打开请查看【BaseFunction精讲】。
结构说明
-
EtherCATBasics:应用层程序,主要用于人机交互、数据显示、内核层数据交互等;
- EtherCATBasics.h : 数据定义
- EtherCATBasics.cpp:用户应用层源码
-
EtherCATBasics_64: 内核层程序(实时层程序),主要用于实时数据处理;
- EtherCATBasics.h : 数据定义
- EtherCATBasics_dll.cpp : 内核层源码
-
其余文件说明请查看【BaseFunction精讲】中的结构说明。
ps : 内核层中的数据、结构体需要一字节对齐,需要以MT方式构建
代码讲解
EtherCATBasics.h :应用层与内核层共用一个头文件
/* Copyright (c) 2011-2024 by Kithara Software GmbH. All rights reserved. *///##############################################################################################################
//
// 文件: EtherCATBasics.h
//
// 使用模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块
//
// 用户应用程序和内核DLL之间的共享定义,例如:如何通过EtherCAT实现基本进程数据交换的示例
//
// 开发者: m.gru 2011-05-11
//
//##############################################################################################################/*=====================================================================*\| *** 免责声明 *** || || 本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任! || |\*=====================================================================*///##############################################################################################################
//
// 目的:
//
// 该示例说明了如何在不考虑同步读取进程数据的情况下实现基本的进程数据交换。
// 此外,该示例还展示了如何检测从站可用的分布式时钟功能以及如何利用这些功能。
// 首先,我们打开网络作为 EtherCAT 主站,并创建一个 EtherCAT 主站。
// 然后,我们让用户从连接的 EtherCAT 从站中选择一个,并将其映射到创建的数据集。
// 如果所选的 EtherCAT 从站支持分布式时钟 (DC) 功能,用户可以选择其中一种支持的模式。
// 然后,用户必须从从站的一个 PDO 中选择一个变量。
// 将创建一个定时器,触发拓扑(已连接的 EtherCAT从站)与本示例实例化的 EtherCAT 主站之间的数据交换。
// 所选变量每 100ms 显示一次(定时器每 1ms 接收一次)。
//
//###############################################################################################################ifndef __SMP_ETHERCATBASICS_H
#define __SMP_ETHERCATBASICS_H#include "../_KitharaSmp/_KitharaSmp.h"//--------------------------------------------------------------------------------------------------------------
// SharedData 是用户定义的参数结构,用于在内核 DLL 和用户应用程序之间使用共享内存交换信息。
//--------------------------------------------------------------------------------------------------------------struct SharedData {KSHandle hKernel; // 内核句柄KSHandle hAdapter; // 网络适配器句柄KSHandle hMaster; // EtherCAT主站句柄KSHandle hSlave; // EtherCAT从站句柄KSEcatMasterState masterState; // 获取主站状态的结构体KSHandle hDataSet; // 用于主站和拓扑结构之间交换的数据集的句柄KSHandle hDataSetCallBack; // 回调句柄,当数据集从拓扑返回时将被调用KSHandle hTimerCallBack; // 由定时器调用的回调的句柄KSHandle hTimer; // 将调用 DataSet 的计时器的句柄int varIndex; // 需要查看的变量的索引int varSubIndex; // 需要查看的变量的子索引uint data; // 从 DataSet 复制的数据,供应用程序访问KSError error; // 用于从内核空间 dll 向用户空间应用程序传递错误信息
};#endif // __SMP_ETHERCATBASICS_H
EtherCATBasics.cpp
/* Copyright (c) 2009-2024 by Kithara Software GmbH. All rights reserved. *///##############################################################################################################
//
// 文件: EtherCATBasics.cpp
//
// 使用模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块
//
// 用户应用程序和内核DLL之间的共享定义,例如:如何通过EtherCAT实现基本进程数据交换的示例
//
// 开发者: t.pet 2009-08-26
//
//##############################################################################################################/*=====================================================================*\| *** 免责声明 *** || || 本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任! || |\*=====================================================================*///##############################################################################################################
//
// 目的:
//
// 该示例说明了如何在不考虑同步读取进程数据的情况下实现基本的进程数据交换。
// 此外,该示例还展示了如何检测从站可用的分布式时钟功能以及如何利用这些功能。
// 首先,我们打开网络作为 EtherCAT 主站,并创建一个 EtherCAT 主站。
// 然后,我们让用户从连接的 EtherCAT 从站中选择一个,并将其映射到创建的数据集。
// 如果所选的 EtherCAT 从站支持分布式时钟 (DC) 功能,用户可以选择其中一种支持的模式。
// 然后,用户必须从从站的一个 PDO 中选择一个变量。
// 将创建一个定时器,触发拓扑(已连接的 EtherCAT从站)与本示例实例化的 EtherCAT 主站之间的数据交换。
// 所选变量每 100ms 显示一次(定时器每 1ms 接收一次)。
//
//##############################################################################################################//--------------------------------------------------------------------------------------------------------------
// 为了在主程序(用户层)和内核 DLL 之间共享数据结构的定义,我们使用了一个通用的头文件。
//--------------------------------------------------------------------------------------------------------------#include "EtherCATBasics.h"//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 别忘了输入你的序列号(6位客户编号),这是打开驱动程序所必需的。
//
// 如果你使用Demo版本,也可以使用“DEMO”代替。
// 如果你使用Beta版本,也可以使用“BETA”代替。
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!// 如上说所,定义的客户号
const char _pCustomerNumber[] = "DEMO";// 主程序入口
void runSample() {// 调用KitharaSmp.h 中的函数,输出文本outputTxt("***** Kithara example program 'EtherCATBasics' *****");// 错误码定义,KSError 是 Kithara API 所有函数的返回类型,通过 【KSError】 可以查询接口的返回错误信息。KSError ksError;//------------------------------------------------------------------------------------------------------------// 打开驱动程序的第一步,所有KRTS程序必须进行的操作。// 只要该函数调用成功后,我们可以使用其他函数。如果打开失败,则无法调用其他函数。// 此函数接受您的客户编号作为参数,其中包含 Kithara(如果适用可以为“DEMO”或“BETA”)。//------------------------------------------------------------------------------------------------------------ksError = KS_openDriver(_pCustomerNumber); // 客户编号if (ksError != KS_OK) {outputErr(ksError, "KS_openDriver", "Unable to open the driver!");return;}//------------------------------------------------------------------------------------------------------------// 创建共享内存// 为实时层中的DLL和此用户层应用程序之间的通信。//------------------------------------------------------------------------------------------------------------KSHandle hSharedMemory;ksError = KS_createSharedMemEx(&hSharedMemory, // 返回创建的共享内存句柄"", // 共享内存的名称sizeof(SharedData), // 共享内存的大小KSF_NO_FLAGS); // 无标记,此选项可以进行一些特殊设定if (ksError != KS_OK) {outputErr(ksError, "KS_createSharedMemEx", "Unable to create shared memory!");KS_closeDriver();return;}// 要访问共享内存,应用程序需要使用刚创建的共享内存的句柄来获取指向分配的共享内存的指针。SharedData* pApp = NULL; // 自定义的共享内存结构体ksError = KS_getSharedMemEx(hSharedMemory, // 共享内存的句柄(void**)&pApp, // 指向共享内存的结构的指针KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_getSharedMemEx", "Unable to map shared memory!");KS_closeDriver();return;}// 确定操作系统的位数大小以决定是加载32位还是64位内核DLL。KSSystemInformation systemInfo; // 获取系统信息的结构体systemInfo.structSize = sizeof(KSSystemInformation); // 不要忘记设备结构体大小ksError = KS_getSystemInformation(&systemInfo, // 结构体指针用于获取结构体数据KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_getSystemInformation", "Unable to get system information to distinguish bitsize!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// 想要在内核级别上使用DLL中的函数,必须加载dll,在调用里面的函数!// 注意!加载程序必须找到DLL,因此它应该放在搜索路径的目录中!// 因为我们想要在共享内存中传递加载的内核的句柄,所以我们不使用加载内核时的初始化函数,// 而是在填充内核句柄和初始化内核所需的所有信息之后,显式调用初始化函数。//------------------------------------------------------------------------------------------------------------ksError = KS_loadKernel(&pApp->hKernel, // 返回内核操作句柄systemInfo.isSys64Bit ? // 根据系统位数加载内核Dll"EtherCATBasics_64.dll" : "EtherCATBasics_32.dll", NULL, // 需要支持的函数名称(未使用)NULL, // 函数参数 (未使用)KSF_KERNEL_EXEC); // 内核空间中加载(实时层运行)if (ksError != KS_OK) {outputErr(ksError, "KS_loadKernel", "Unable to load DLL! Is the DLL in the search path?");KS_closeDriver();return;}// 查询并展示所有受支持的网络适配器char pDeviceName[256]; // 用于保存设备名称outputTxt(" ");outputTxt("Following network adapters found:");for (int i = 0;; ++i) {// KS_enumDevices()可以用于查询分配给Kithara驱动程序的所有网络适配器的名称。ksError = KS_enumDevices("NET", // 'NET' 代表搜索网络设备i, // 从0开始的枚举索引号pDeviceName, // 返回设备名称KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {if (KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND)outputErr(ksError, "KS_enumDevices", "Unable to query network device name!");if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND && !i) {outputTxt("No network adapters found!");outputTxt(" ");KS_closeDriver();return;}break;}// 输出索引号对应的设备名称outputDec(i, "", ": ", false);outputTxt(pDeviceName);}outputTxt(" ");// 输入想要打开的适配器索引号outputTxt("Attention!");outputTxt("By selecting a device its Windows driver gets removed and replaced by the");outputTxt("appropriate Kithara driver. This will render the network device for the duration");outputTxt("of this sample invisible to Windows.");outputTxt("Be sure that all other Applications using that device are closed right now!");// 输入并保存索引号并根据索引和再次获取设备名称int deviceIndex = inputDec("Device number: ", 0);ksError = KS_enumDevices("NET", // 'NET' 代表搜索网络设备deviceIndex, // 选择的设备索引号pDeviceName, // 返回设备名称KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_enumDevices", "Unable to query selected network device name!");KS_closeDriver();return;}outputTxt("Selected device: ", false);outputTxt(pDeviceName);outputTxt(" ");// 根据设备名称,打开以太网适配器。ksError = KS_openNetworkAdapter(&pApp->hAdapter, // 获取适配器句柄pDeviceName, // 输入适配器的硬件IDNULL, // 设配器配置选项KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_openNetworkAdapter", "Failed to open network adapter!");KS_closeDriver();return;}// 创建主站// 输入ESI文件,文件夹路径,ESI文件可以在大多数设备官网中获取。 char* pXmlPath = inputTxt("Please enter config path to XML files: ", "C:\\Program Files\\Kithara\\RealTime Suite Demo\\xml");ksError = KS_createEcatMaster(&pApp->hMaster, // 返回主站句柄pApp->hAdapter, // 适配器句柄pXmlPath, // ESI文件夹路径"", // 拓扑文件KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_createEcatMaster", "Failed to create EtherCAT master!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// 显式调用初始化函数。// 调用内核的【_initFunction】函数,并传递共享内存的句柄,这样内核就可以从句柄中检索到共享内存的指针,// 并根据共享内存中存储的信息进行所有必要的资源分配。// 更为详细的内核初始化操作可以查看内核层【_initFunction】函数//------------------------------------------------------------------------------------------------------------ksError = KS_execKernelFunctionEx(pApp->hKernel, // 内核句柄"_initFunction", // 函数名称hSharedMemory, // 共享内存的句柄KS_INVALID_HANDLE, // 上下文KSF_NO_FLAGS); // 未使用if (ksError != KS_OK) {outputErr(ksError, "KS_execKernelFunctionEx", "Unable to initialize the kernel DLL!");KS_closeDriver();return;}bool dcLicensed = true; // 是否支持DC模式// 输出从站在线个数outputDec(pApp->masterState.slavesOnline, "slavesOnline = "); outputTxt("Choose a slave: ");KSEcatSlaveState slaveState; // 从站状态的结构体slaveState.structSize = sizeof(KSEcatSlaveState); // 初始化结构体大小// 遍历所有已连接的从设备,显示它们的基本信息,让用户选择其中一个。for (int i = 0; i < pApp->masterState.slavesOnline; ++i) {//----------------------------------------------------------------------------------------------------------// 使用KS_enumEcatSlaves()函数来迭代所有在线的从站设备。// 如果它的第二个参数大于在线从设备的数量,它将返回KSERROR_DEVICE_NOT_FOUND。//----------------------------------------------------------------------------------------------------------ksError = KS_enumEcatSlaves(pApp->hMaster, // EtherCAT 主站句柄i, // 从0开始的枚举索引号&slaveState, // 从站状态结构体KSF_NO_FLAGS); // 无标记if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND)break;if (ksError != KS_OK) {outputErr(ksError, "KS_enumEcatSlaves", "Failed to enumerate EtherCAT slaves!");KS_closeDriver();return;}//----------------------------------------------------------------------------------------------------------// 创建从站// KS_enumEcatSlaves()函数填充了KSEcatSlaveState结构体,其中包含了从设备的基本信息。// 为了获取更多的详细信息,需要为该从站设备创建一个句柄。// 可以直接将接收到的KSEcatSlaveState结构体传递给KS_createEcatSlaveIndirect()函数。//----------------------------------------------------------------------------------------------------------KSHandle hSlave;ksError = KS_createEcatSlaveIndirect(pApp->hMaster, // 主站句柄&hSlave, // 返回新从站句柄&slaveState, // 返回从站信息KSF_FORCE_OVERRIDE); // 忽略缺少的XML信息if (ksError != KS_OK) {outputErr(ksError, "KS_createEcatSlaveIndirect", "Failed to create EtherCAT slave!");KS_closeDriver();return;}// 修改从站状态,只有在等于或高于的KS_ECAT_STATE_PREOP状态下,才能查询在线从机信息。ksError = KS_changeEcatState(hSlave, // 从站句柄KS_ECAT_STATE_PREOP, // 需要切换的状态KSF_NO_FLAGS); // 无标志if (ksError != KS_OK) {outputErr(ksError, "KS_changeEcatState", "Failed to change EtherCAT slave state to PREOP!");KS_closeDriver();return;}//----------------------------------------------------------------------------------------------------------// 查询从站信息// KS_queryEcatSlaveInfo()函数将KSEcatSlaveInfo结构填充了所有可用的信息。// 通过使用KSF_PDO和KSF_SDO标志,您可以决定是否想要有关过程数据对象、服务数据对象或两者的信息。// 因为我们只对从设备的名称感兴趣,而不需要对象信息,所以我们使用flags == KSF_NO_FLAGS。//----------------------------------------------------------------------------------------------------------KSEcatSlaveInfo* pSlaveInfo;ksError = KS_queryEcatSlaveInfo(hSlave, // 从站句柄&pSlaveInfo, // 返回从站信息KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_queryEcatSlaveInfo", "Unable to read slave information!");KS_closeDriver();return;}//----------------------------------------------------------------------------------------------------------// 为了测试一个从节点是否支持分布式时钟,我们只需要枚举分布式时钟的操作模式。// 如果索引等于0,则该从节点支持分布式时钟。//----------------------------------------------------------------------------------------------------------char pDcOpMode[256];ksError = KS_enumEcatDcOpModes(hSlave, // 从站句柄0, // OP模式pDcOpMode, // 模式名称NULL, // 模式描述KSF_NO_FLAGS); // 无标记if (KSERROR_CODE(ksError) == KSERROR_FEATURE_NOT_LICENSED) {ksError = KS_OK;dcLicensed = false;}if (ksError != KS_OK && KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND) {outputErr(ksError, "KS_enumEcatDcOpModes", "Failed to query EtherCAT DC op mode");KS_closeDriver();return;}// 输出从站索引以及从站是否支持分布式时钟和从站名称。if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND || !dcLicensed)outputDec(i, "", ": [noDC] ", false);elseoutputDec(i, "", ": [DC] ", false);outputTxt(pSlaveInfo->name);// 删除用于查询从属信息以供显示从站对象。ksError = KS_deleteEcatSlave(hSlave); // 需要删除的从站句柄if (ksError != KS_OK) {outputErr(ksError, "KS_deleteEcatSlave", "Unable to delete slave!");KS_closeDriver();return;}}// 请输入选择的从站,已查询从站信息int slaveIndex = inputDec("Slave index: ", -1);if (slaveIndex < 0 || slaveIndex >= pApp->masterState.slavesOnline) {outputTxt("Invalid slave index!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// 在指定的索引处创建一个 ECAT 从节点设备。// 只用索引,不需要 ID、厂商 ID、产品 ID 或修订号。//------------------------------------------------------------------------------------------------------------// 创建从站 ksError = KS_createEcatSlave(pApp->hMaster, // 主站句柄&pApp->hSlave, // 返回从站句柄0, // 该位置相对于的标识符,0表示绝对位置slaveIndex, // 从站位置0, // 供应商 ID(0 = any)0, // 产品ID (0 = any)0, // 版本(0 = any)KSF_FORCE_OVERRIDE); // 忽略缺少的XML信息if (ksError != KS_OK) {outputErr(ksError, "KS_createEcatSlave", "Failed to create EtherCAT slave!");KS_closeDriver();return;}// 只有在KS_ECAT_STATE_PREOP等于或高于的状态下,才能查询在线从机信息。ksError = KS_changeEcatState(pApp->hSlave, // 从站句柄KS_ECAT_STATE_PREOP, // 需要修改的从站状态KSF_NO_FLAGS); // 无状态if (ksError != KS_OK) {outputErr(ksError, "KS_changeEcatState", "Failed to change EtherCAT slave state to PREOP!");KS_closeDriver();return;}// 如果DC功能可用,则显示所有可用的分布式时钟操作模式。if (dcLicensed) {char pDcOpMode[256];char pDcOpModeDescription[256];outputTxt(" ");int dcModeCount = -1;for (int i = 0;; ++i) {//--------------------------------------------------------------------------------------------------------// 使用 KS_enumEcatDcOpModes() 函数,您可以枚举所有可用的分布式时钟操作模式。// 我们使用此函数来显示所有可用的分布式时钟操作模式,以便让用户选择其中一个。//--------------------------------------------------------------------------------------------------------ksError = KS_enumEcatDcOpModes(pApp->hSlave, // 从站句柄i, // 从零开始枚举索引pDcOpMode, // 获取DC模式名称pDcOpModeDescription, // 获取DC模式描述信息KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {if (KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND) {outputErr(ksError, "KS_enumEcatDcOpModes", "Unable to query DC op mode!");KS_closeDriver();return;}break;}dcModeCount = i;if (i == 0)outputTxt("Following DC op modes where found:");outputDec(i, "", ": ", false);outputTxt(pDcOpMode, false);outputTxt(" - ", false);outputTxt(pDcOpModeDescription);}//----------------------------------------------------------------------------------------------------------// 如果有 DC 操作可用,让用户通过索引选择一个并验证选择。//----------------------------------------------------------------------------------------------------------if (dcModeCount >= 0) {int dcOpModeIndex = inputDec("Op mode number: ", -1);outputTxt(" ");ksError = KS_enumEcatDcOpModes(pApp->hSlave, // 从站句柄dcOpModeIndex , // 索引pDcOpMode, // 获取DC模式名称pDcOpModeDescription, // 获取DC模式描述信息KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_enumEcatDcOpModes", "Unable to query DC op mode!");KS_closeDriver();return;}outputTxt("Selected op mode: ", false);outputTxt(pDcOpMode, false);outputTxt(" - ", false);outputTxt(pDcOpModeDescription, false);outputTxt(" ");// 获取DC操作模式的参数。KSEcatDcParams dcParams;ksError = KS_lookupEcatDcOpMode(pApp->hSlave, // 从站句柄pDcOpMode, // 操作模式&dcParams, // 返回DC模式参数KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_lookupEcatDcOpMode", "Unable to lookup DC op mode!");KS_closeDriver();return;}// 根据需要调整参数。// ...// 如果配置了从站节点以使用此DC操作模式(可能调整了dcParams)。ksError = KS_configEcatDcOpMode(pApp->hSlave, // 从站句柄pDcOpMode, // 模式名称&dcParams, // DC参数设定KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_configEcatDcOpMode", "Unable to lookup DC op mode!");KS_closeDriver();return;}}}//------------------------------------------------------------------------------------------------------------// 现在将为所选的从属节点分配DataSet。// 只要KS_assignEcatDataSet()没有返回错误,我们就可以分配对象。// 简化同步对象的选择,使用提供的特殊常量:KS_ECAT_SYNC_INPUT、KS_ECAT_SYNC_OUTPUT和KS_ECAT_SYNC_ALL。//------------------------------------------------------------------------------------------------------------ksError = KS_assignEcatDataSet(pApp->hDataSet, // DataSet 句柄pApp->hSlave, // 从站句柄KS_ECAT_SYNC_ALL, // 同步对象的类型0, // 在DataSet中的特殊位置KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_assignEcatDataSet", "Failed to assign slave!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// KS_queryEcatSlaveInfo()用所有可用信息填充KSEcatSlaveInfo结构。// 使用Flags KSF_PDO和KSF_SDO可以决定是否需要有关PDO,SDO对象或两者。//------------------------------------------------------------------------------------------------------------KSEcatSlaveInfo* pSlaveInfo;ksError = KS_queryEcatSlaveInfo(pApp->hSlave, // 从站句柄&pSlaveInfo, // 从站信息的结构体KSF_PDO); // 查询PDO数据if (ksError != KS_OK) {outputErr(ksError, "KS_queryEcatSlaveInfo", "Unable to read slave information!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// 让用户选择一个PDO对象。//------------------------------------------------------------------------------------------------------------// 遍历并输出PDO信息outputTxt(" ");outputTxt("Choose a PDO: (only active and readable PDOs are displayed)");for (int i = 0; i < pSlaveInfo->objCount; ++i) {KSEcatDataObjInfo* pObjInfo = pSlaveInfo->objs[i];if ((pObjInfo->objType & KS_DATAOBJ_PDO_TYPE) &&(pObjInfo->objType & KS_DATAOBJ_ACTIVE) &&(pObjInfo->objType & KS_DATAOBJ_READABLE)) {outputDec(i, "", ": ", false);outputTxt(pObjInfo->name);}}int pdoIndex = inputDec("PDO index: ", -1);if (pdoIndex < 0 || pdoIndex >= pSlaveInfo->objCount) {outputTxt("Invalid PDO index!");KS_closeDriver();return;}KSEcatDataObjInfo* pObjInfo = pSlaveInfo->objs[pdoIndex];if (!(pObjInfo->objType & KS_DATAOBJ_PDO_TYPE) ||!(pObjInfo->objType & KS_DATAOBJ_ACTIVE) ||!(pObjInfo->objType & KS_DATAOBJ_READABLE)) {outputTxt("Invalid PDO!");KS_closeDriver();return;}outputTxt(" ");outputTxt("Choose a variable: ");for (int i = 0; i < pObjInfo->varCount; ++i) {KSEcatDataVarInfo* pVarInfo = pObjInfo->vars[i];outputDec(i, "", ": ", false);outputTxt(pVarInfo->name);}int varIndex = inputDec("Variable index: ", -1);if (varIndex < 0 || varIndex >= pObjInfo->varCount) {outputTxt("Invalid variable index!");KS_closeDriver();return;}pApp->varIndex = pObjInfo->index;pApp->varSubIndex = pObjInfo->vars[varIndex]->subIndex;//------------------------------------------------------------------------------------------------------------// 开始进行数据交换//------------------------------------------------------------------------------------------------------------ksError = KS_execKernelFunctionEx(pApp->hKernel, // 内核句柄"_startDataExchange", // 调用内核层函数KS_INVALID_HANDLE, // 传参KS_INVALID_HANDLE, // 上下文KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_execKernelFunctionEx", "Error while executing kernel functions!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// 这是主循环。它将每100 ms更新所选变量的显示,直到用户按下“q”键或数据交换错误将被检测到时退出。//------------------------------------------------------------------------------------------------------------outputTxt(" ");outputTxt("Press [Q] to finish the process data exchange...");outputTxt(" ");for (;;) {waitTime(100 * ms);outputHex08(pApp->data, "Value: ", "\r", false);if (pApp->error != KS_OK)break;if (myKbhit() != 0) {int key = myGetch();if (key == 'q' || key == 'Q')break;}}if (pApp->error != KS_OK)outputErr(ksError, "Error while receiving data!");// 清理内核层DLL中分配的资源ksError = KS_execKernelFunctionEx(pApp->hKernel, // 内核句柄"_exitFunction", // 内核层退出函数KS_INVALID_HANDLE, // 传参KS_INVALID_HANDLE, // 上下文KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {outputErr(ksError, "KS_execKernelFunctionEx", "Error while deallocating resources on kernel level!");KS_closeDriver();return;}//------------------------------------------------------------------------------------------------------------// 使用共享句柄卸载内核DLL。// 尽管KS_closeDriver()释放了所有分配的资源(如共享内存和加载的内核),// 明确释放您分配的资源是很好的习惯。//------------------------------------------------------------------------------------------------------------// 释放内核ksError = KS_freeKernel(pApp->hKernel); // 内核句柄if (ksError != KS_OK)outputErr(ksError, "KS_freeKernel", "Unable to unload the kernel!");// 清理共享内存ksError = KS_freeSharedMemEx(hSharedMemory, // 共享内存句柄KSF_NO_FLAGS); // 无标记if (ksError != KS_OK)outputErr(ksError, "KS_freeSharedMemEx", "Unable to remove shared memory!");// 关闭设备,清理所有资源ksError = KS_closeDriver();if (ksError != KS_OK)outputErr(ksError, "KS_closeDriver", "Unable to close the driver!");waitTime(500 * ms);outputTxt(" ");outputTxt("End of program 'EtherCATBasics'.");
}
EtherCATBasics_dll.cpp
/* Copyright (c) 2011-2024 by Kithara Software GmbH. All rights reserved. *///##############################################################################################################
//
// 文件: EtherCATBasics_dll.cpp
//
// 使用模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块
//
// 用户应用程序和内核DLL之间的共享定义,例如:如何通过EtherCAT实现基本进程数据交换的示例
//
// 开发者: m.gru 2011-05-11
//
//##############################################################################################################/*=====================================================================*\| *** 免责声明 *** || || 本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任! || |\*=====================================================================*//##############################################################################################################
//
// 目的:
//
// 该示例说明了如何在不考虑同步读取进程数据的情况下实现基本的进程数据交换。
// 此外,该示例还展示了如何检测从站可用的分布式时钟功能以及如何利用这些功能。
// 首先,我们打开网络作为 EtherCAT 主站,并创建一个 EtherCAT 主站。
// 然后,我们让用户从连接的 EtherCAT 从站中选择一个,并将其映射到创建的数据集。
// 如果所选的 EtherCAT 从站支持分布式时钟 (DC) 功能,用户可以选择其中一种支持的模式。
// 然后,用户必须从从站的一个 PDO 中选择一个变量。
// 将创建一个定时器,触发拓扑(已连接的 EtherCAT从站)与本示例实例化的 EtherCAT 主站之间的数据交换。
// 所选变量每 100ms 显示一次(定时器每 1ms 接收一次)。
//
//##############################################################################################################//--------------------------------------------------------------------------------------------------------------
// 为了在主程序和内核 DLL 之间共享数据结构定义,我们使用一个公共头文件。
//--------------------------------------------------------------------------------------------------------------#include "EtherCATBasics.h"// 共享内存用于在内核层 DLL 和用户层的应用程序之间共享数据。
SharedData* _pSys = NULL;// 指向从站选定 ECAT 设备变量接收到的数据。
void* _pSlaveData;// 在数据集中要查看的变量的位置和长度。
int _bitOffset;
int _bitLength;// 前置声明:定时器回调、数据集回调,声明在文件末尾定义的回调函数。
KSError __stdcall timerCallBack (void* /*pArgs*/, void* /*pContext*/);
KSError __stdcall dataSetCallBack(void* /*pArgs*/, void* /*pContext*/);//--------------------------------------------------------------------------------------------------------------
// 这是初始化函数。
// 它在加载内核后被调用,并将共享内存的句柄作为参数传递。
//
// 注意!请记住,所有函数都应声明为 'extern "C"'!
// 否则它们的名字可能无法被加载器找到。
// 必须通过 '__declspec(dllexport)' 导出它们。
//--------------------------------------------------------------------------------------------------------------extern "C" KSError __declspec(dllexport) __stdcall _initFunction(void* pArgs, void* /*pContext*/) {KSError ksError;// 共享内存的指针通过 KS_execKernelFunctionEx() 作为 'pArgs' 参数传递。_pSys = (SharedData*)pArgs;//------------------------------------------------------------------------------------------------------------// 在这里我们等待主站连接到拓扑。// 代替轮询主站状态,您也可以注册一个回调来处理此事件。// 详情请参阅手册中的 KS_installEcatHandler()。//------------------------------------------------------------------------------------------------------------_pSys->masterState.structSize = sizeof(KSEcatMasterState); // 不要忘记初始化 structSize!for (int i = 0; i < 50; ++i) {ksError = KS_queryEcatMasterState(_pSys->hMaster, // 主站句柄&_pSys->masterState, // 返回KSEcatMasterState 结构体KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;if (_pSys->masterState.connected)break;KS_microDelay(100 * ms);}if (_pSys->masterState.connected == 0)return KSERROR_CATEGORY_ETHERCAT;//------------------------------------------------------------------------------------------------------------// 为了准备过程数据交换,我们需要创建一个数据集并分配一个同步对象给它。// 因为我们稍后会使用 KS_getEcatDataObjAddress() 来获取内存位置。//------------------------------------------------------------------------------------------------------------ksError = KS_createEcatDataSet(_pSys->hMaster, // 主站句柄&_pSys->hDataSet, // 写入新数据集句柄的地址NULL, // 数据集数据的应用程序空间指针(未使用)NULL, // 数据集数据的内核空间指针(未使用)KSF_NO_FLAGS); // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 创建一个回调,当数据集从从站返回数据时调用。// 安装数据集处理器允许我们在从从站接收到数据时作出反应。//------------------------------------------------------------------------------------------------------------ksError = KS_createCallBack(&_pSys->hDataSetCallBack, // 写入新回调句柄的地址dataSetCallBack, // 回调函数NULL, // 回调参数(未使用)KSF_DIRECT_EXEC, // 标志,这里内核级别0); // 优先级(内核级别未使用)if (ksError != KS_OK)return ksError;// 安装创建的回调作为数据集处理器。ksError = KS_installEcatHandler(_pSys->hDataSet, // 数据集句柄KS_DATASET_SIGNAL, // 事件代码_pSys->hDataSetCallBack, // 回调句柄KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;// 创建一个定时器回调,该回调将定期执行并将数据集发送到拓扑。ksError = KS_createCallBack(&_pSys->hTimerCallBack, // 写入新回调句柄的地址timerCallBack, // 回调函数NULL, // 回调参数(未使用)KSF_DIRECT_EXEC, // 标志,这里内核级别0); // 优先级(内核级别未使用)if (ksError != KS_OK)return ksError;// 创建一个周期为 1 毫秒的定时器,并分配回调给它。ksError = KS_createTimer(&_pSys->hTimer, // 写入新定时器句柄的地址1 * ms, // 定时器周期(100纳秒单位)_pSys->hTimerCallBack, // 回调句柄KSF_REALTIME_EXEC | // 精确的高分辨率实时定时器KSF_DONT_START); // 不立即启动if (ksError != KS_OK)return ksError;return KS_OK;
}//--------------------------------------------------------------------------------------------------------------
// 这是清理函数,关闭 EtherCAT 主站和网络设备,并移除定时器、其回调、数据集、数据集处理器、数据集回调和 EtherCAT 从站。
//--------------------------------------------------------------------------------------------------------------extern "C" KSError __declspec(dllexport) __stdcall _exitFunction(void* /*pArgs*/, void* /*pContext*/) {if (_pSys == NULL) // 共享内存未映射!return KSERROR_FUNCTION_NOT_AVAILABLE; // _initFunction 未调用?KSError ksError;// 关闭ksError = KS_changeEcatState(_pSys->hDataSet, // 数据集句柄KS_ECAT_STATE_SAFEOP, // 状态KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;// 停止定时器。ksError = KS_stopTimer(_pSys->hTimer); // 定时器句柄if (ksError != KS_OK)return ksError;// 切换 SAFEOPksError = KS_changeEcatState(_pSys->hDataSet, // DataSet 句柄KS_ECAT_STATE_INIT, // 状态KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;// 移除定时器ksError = KS_removeTimer(_pSys->hTimer); // 定时器句柄if (ksError != KS_OK)return ksError;// 移除定时器回调。ksError = KS_removeCallBack(_pSys->hTimerCallBack); // 定时器回调句柄if (ksError != KS_OK)return ksError;// 卸载数据集处理器ksError = KS_installEcatHandler(_pSys->hDataSet, // DataSet 句柄KS_DATASET_SIGNAL, // 回调事件类型KS_INVALID_HANDLE, // 使事件无效KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;// 移除数据集回调ksError = KS_removeCallBack(_pSys->hDataSetCallBack); // DataSet 回调句柄if (ksError != KS_OK)return ksError;// 删除数据集ksError = KS_deleteEcatDataSet(_pSys->hDataSet); // DataSet 句柄if (ksError != KS_OK)return ksError;// 切换 OP 状态与 EtherCAT 从站ksError = KS_changeEcatState(_pSys->hSlave, // 从站句柄KS_ECAT_STATE_INIT, // 状态类型KSF_NO_FLAGS); // DataSet if (ksError != KS_OK)return ksError;// EtherCAT 主站的状态应在结束时更改为 'init'ksError = KS_changeEcatState(_pSys->hMaster, // 主站句柄KS_ECAT_STATE_INIT, // 状态类型KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 删除EtherCAT从站//------------------------------------------------------------------------------------------------------------ksError = KS_deleteEcatSlave(_pSys->hSlave); // 从站句柄if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 关闭EtherCAT主站//------------------------------------------------------------------------------------------------------------ksError = KS_closeEcatMaster(_pSys->hMaster); // 主站句柄if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 关闭网络适配器//------------------------------------------------------------------------------------------------------------ksError = KS_closeNetwork(_pSys->hAdapter, // 网络适配器句柄KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;return KS_OK;
}//--------------------------------------------------------------------------------------------------------------
// _startDataExchange()函数将被用户调用,以开始数据交换。
//--------------------------------------------------------------------------------------------------------------extern "C" __declspec(dllexport) KSError __stdcall _startDataExchange(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;_pSys->error = KS_OK;// 查询所选变量的地址、位偏移量和位长度。ksError = KS_getEcatDataObjAddress(_pSys->hDataSet, // DataSet句柄_pSys->hSlave, // 从站句柄_pSys->varIndex, // 索引号_pSys->varSubIndex, // 子索引号NULL, // 应用程序空间数据指针(未使用)&_pSlaveData, // 返回从站数据&_bitOffset, // 返回位偏移量&_bitLength, // 返回位长度KSF_NO_FLAGS); // 无标志if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 如果 EtherCAT 从站具有分布式时钟(Distributed Clocks)功能,我们就需要启用该功能,以确保该示例能按预期运行。// 在进入 SAFEOP 之前必须调用 KS_activateEcatDcMode(),它将启动相关的定时器。//------------------------------------------------------------------------------------------------------------int64ref dcStartTime = 0;ksError = KS_activateEcatDcMode(_pSys->hDataSet, // 主站, 从站或DataSet 句柄dcStartTime, // startTime,在不久的将来的一段时间为01000000, // 循环时间(ns)->1 ms0, // 偏移时间(ns)_pSys->hTimer, // 定时器句柄KSF_NO_FLAGS); // 无标记if (ksError != KS_OK && KSERROR_CODE(ksError) != KSERROR_FEATURE_NOT_LICENSED)return ksError;// 为了读取过程数据,必须将EtherCAT从站的状态更改为“KS_ECAT_STATE_SAFEOP”。ksError = KS_changeEcatState(_pSys->hDataSet, // DataSet 句柄KS_ECAT_STATE_SAFEOP, // 切换状态KSF_NO_FLAGS); // 无标记if (ksError != KS_OK)return ksError;// 现在将Slave切换到OP。ksError = KS_changeEcatState(_pSys->hDataSet, // DataSet 句柄KS_ECAT_STATE_OP, // 切换状态KSF_NO_FLAGS); // 无标记if (ksError != KS_OK)return ksError;return KS_OK;
}// 该回调由定时器定期调用,并通过 KS_postEcatDataSet()启动进程数据交换。
KSError __stdcall timerCallBack(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;if (_pSys->error != KS_OK)return _pSys->error;// 下发数据ksError = KS_postEcatDataSet(_pSys->hDataSet, // DataSet 句柄KSF_NO_FLAGS); // 无标记return _pSys->error = ksError;
}//--------------------------------------------------------------------------------------------------------------
// 当 DataSet 从从站拓扑返回主站时,将调用此回调。
// 从站将向其中写入进程数据。
// KS_readEcatDataSet() 用于检索 DataSet 以访问数据。
//--------------------------------------------------------------------------------------------------------------KSError __stdcall dataSetCallBack(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;static bool running = false;ksError = KS_readEcatDataSet(_pSys->hDataSet, // DataSet 句柄KSF_NO_FLAGS); // 无标记if (ksError != KS_OK) {if ((running == 0) && (KSERROR_CODE(ksError) == KSERROR_NO_RESPONSE))// 该错误表示从站设备没有回答 KS_postEcatDataSet()。// 有些从站设备需要更多时间才能完全进入 SAFEOP 并应答 DataSet。// 因此,我们在此忽略这个错误。// 实际应用中的应用程序应该在启动和运行阶段以不同方式处理这个错误。return KS_OK;_pSys->error = ksError;return ksError;}//------------------------------------------------------------------------------------------------------------// 一旦 KS_readEcatDataSet() 成功返回数据,就说明从站程序已进入 SAFEOP 或更高版本。// 从现在起,我们将认真处理每一个错误。//------------------------------------------------------------------------------------------------------------running = true;//------------------------------------------------------------------------------------------------------------// 根据bitLength获取数据,然后通过bitOffset向右移位。//------------------------------------------------------------------------------------------------------------if (_bitLength > 0 && _bitLength <= 8)_pSys->data = *reinterpret_cast<byte*>(_pSlaveData) >> _bitOffset;if (_bitLength > 8 && _bitLength <= 16)_pSys->data = *reinterpret_cast<ushort*>(_pSlaveData) >> _bitOffset;if (_bitLength > 24 && _bitLength <= 32)_pSys->data = *reinterpret_cast<uint*>(_pSlaveData) >> _bitOffset;//------------------------------------------------------------------------------------------------------------// 获取想要的数据,并将结果存储在共享内存中,供应用程序访问。//------------------------------------------------------------------------------------------------------------if (_bitLength < 32)_pSys->data &= (1 << _bitLength) - 1;_pSys->error = ksError;return ksError;
}//--------------------------------------------------------------------------------------------------------------
// 需要实现 DllMain 函数,该函数在 DLL 加载时不会被执行。
//
// 对于初始化,请定义一个特殊的 init 函数,并在调用 KS_loadKernel()时将其名称作为参数传递给它,
// 或者在加载内核的句柄以后在加载的 DLL 调用函数(如本例所示)时使用,
// 请不要在加载内核时执行的 init 函数,而是在加载内核后自己明确地调用它,并根据需要传递参数,如本例所示。
//--------------------------------------------------------------------------------------------------------------#define WIN32_LEAN_AND_MEAN
#pragma pack(push, 8)
#include <windows.h>
#pragma pack(pop)BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID pReserved) {return TRUE;
}