当前位置: 首页 > news >正文

【EtherCATBridge】- KRTS C++示例精讲(9)

EtherCATBridge示例讲解


文章目录

    • EtherCATBridge示例讲解
      • 结构说明
      • 代码说明


项目打开请查看【BaseFunction精讲】。

结构说明

在这里插入图片描述

EtherCATBridge.h :数据定义
EtherCATBridge.cpp :应用层源码
EtherCATBridge_dll.cpp :内核层源码

  • 其余文件说明请查看【BaseFunction精讲】中的结构说明。
    ps : 内核层中的数据、结构体需要一字节对齐,需要以MT方式构建

在这里插入图片描述

代码说明

EtherCATBridge.h :内核层和应用层共用此文件方便通过共享内存进行数据交换

/* Copyright (c) 2009-2024 by Kithara Software GmbH. All rights reserved. *///##############################################################################################################
//
// 文件:         EtherCATBridge.h
//
// 模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块
//
// 描述:   用户空间应用程序和内核空间 DLL 之间的共享定义,例如 通过直接访问 EtherCAT 进行简单的桥接(重映射)流程和数据交换
//
// 创建人:      t.pet 2009-08-26
//
//##############################################################################################################/*=====================================================================*\|                    *** 免责声明 ***                     			   ||                                                                       ||       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		  ||																	   |\*=====================================================================*///##############################################################################################################
//
// 目的:
//
// 本示例介绍如何配置和使用 EL6692 型 EtherCAT 网桥。
//
// 在此示例中, EL6692 的主站设备和从站设备处于相同的拓扑结构中。
// 为此,请将 EL6692 连接到耦合器上,并将耦合器的输出插头与 EL6692 的输入插头连接起来。如下图所示:
//
//                                    +--------+------+
//   +-------------------+            |  EK1100|EL6692|
//   |                   |            | In     |      |
//   |  PC 运行着Kithara |            |+--+    |      |
//   |                   |<---------->||  |    |  In  |
//   |  EtherCAT主站     |            |+--+    | +--+ |
//   |                   |            |        | |  | |<---+
//   +-------------------+            |+--+    | +--+ |    |
//                               +--->||  |    | +--+ |    |
//                               |    |+--+    | |  | |    |
//                               |    | Out    | +--+ |    |
//                               |    |        |  Out |    |
//                               |    |        |      |    |
//                               |    +--------+------+    |
//                               |                         |
//                               +-------------------------+
//                                      网络拓扑结构
//
// 在成功枚举出从属设备后,一级从站 EL6692 被配置为具有两个输出和一个输入,二级从站 EL6692 被配置为具有一个输出和两个输入。
// 请注意,一级从站从站和二级从站从站的配置输入和输出必须首尾相连。
//
// 二级从站从站的两个输出将作为二级从站从站的两个输入。
// 二级从站从站将输出其两个输入端的乘积,而这两个输入端将成为一级从站的单个输入端。
// 用户可以更改一级从站两个输出端(= 二级从站的输入端)的输出值,这些值将与一级从站的输入端(= 二级从站输入端1 x 二级从站输入端2 = 二级从站输出端)一起显示。
//
//###############################################################################################################ifndef __SMP_ETHERCATBRIDGE_H
#define __SMP_ETHERCATBRIDGE_H#include "../_KitharaSmp/_KitharaSmp.h"//--------------------------------------------------------------------------------------------------------------
// SharedData 是一个用户定义的参数结构,用于在内核 DLL 和用户应用程序之间使用共享内存进行信息交换。
// 您可以自由定义任何与系统位大小无关的参数。
//--------------------------------------------------------------------------------------------------------------struct SharedData {KSHandle hKernel;                                     // 内核的句柄KSHandle hAdapter;                                    // 网络适配器的句柄KSHandle hMaster;                                     // EtherCAT 主站的句柄KSHandle hPrimarySlave;                               // 一级从站的句柄KSHandle hPrimaryDataSet;                             // 用于与包含一级从站的拓扑结构进行数据交换的 DataSet 的句柄KSHandle hPrimarySendCallBack;                        // 由 hPrimaryTimer 调用的回调函数的句柄KSHandle hPrimaryReceiveCallBack;                     // 当 DataSet 从一级从站返回时将被调用的回调函数的句柄KSHandle hPrimaryTimer;                               // 用于触发从主站到一级从站的 DataSet 传输的定时器的句柄KSError  primarySlaveError;                           // 一级从站用于从内核空间 DLL 向用户空间应用程序传递错误信息KSHandle hSecondarySlave;                             // 二级从站的句柄KSHandle hSecondaryDataSet;                           // 用于与包含二级从站的拓扑结构进行数据交换的 DataSet 的句柄KSHandle hSecondarySendCallBack;                      // 由 hSecondaryTimer 调用的回调函数的句柄KSHandle hSecondaryReceiveCallBack;                   // 当 DataSet 从二级从站返回时将被调用的回调函数的句柄KSHandle hSecondaryTimer;                             // 用于触发从主站到二级从站的 DataSet 传输的定时器的句柄KSError  secondarySlaveError;                         // 二级从站用于从内核空间 DLL 向用户空间应用程序传递错误信息KSHandle hPrimaryDataSentEvent;                       // 用于通知应用程序 DataSet 已发送到一级从站的事件ushort primaryOutput1;                                // 将被复制到一级从站的过程数据中ushort primaryOutput2;                                // 将被复制到一级从站的过程数据中uint primaryInput;                                    // 将从一级从站的过程数据中复制过来
};
#endif // __SMP_ETHERCATBRIDGE_H

EtherCATBridge.cpp :应用层源码

/* Copyright (c) 2009-2024 by Kithara Software GmbH. All rights reserved. *///##############################################################################################################
//
// 文件:         EtherCATBridge.cpp
//
// 模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块
//
// 描述:   用户空间应用程序和内核空间 DLL 之间的共享定义,例如 通过直接访问 EtherCAT 进行简单的桥接(重映射)流程和数据交换
//
// 创建人:      t.pet 2009-08-26
//
//##############################################################################################################/*=====================================================================*\|                    *** 免责声明 ***                     			   ||                                                                       ||       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		  ||																	   |\*=====================================================================*///##############################################################################################################
//
// 目的:
//
// 本示例介绍如何配置和使用 EL6692 型 EtherCAT 网桥。
//
// 在此示例中, EL6692 的主站设备和从站设备处于相同的拓扑结构中。
// 为此,请将 EL6692 连接到耦合器上,并将耦合器的输出插头与 EL6692 的输入插头连接起来。如下图所示:
//
//                                    +--------+------+
//   +-------------------+            |  EK1100|EL6692|
//   |                   |            | In     |      |
//   |  PC 运行着Kithara |            |+--+    |      |
//   |                   |<---------->||  |    |  In  |
//   |  EtherCAT主站     |            |+--+    | +--+ |
//   |                   |            |        | |  | |<---+
//   +-------------------+            |+--+    | +--+ |    |
//                               +--->||  |    | +--+ |    |
//                               |    |+--+    | |  | |    |
//                               |    | Out    | +--+ |    |
//                               |    |        |  Out |    |
//                               |    |        |      |    |
//                               |    +--------+------+    |
//                               |                         |
//                               +-------------------------+
//                                      网络拓扑结构
//
// 在成功枚举出从属设备后,一级从站 EL6692 被配置为具有两个输出和一个输入,二级从站 EL6692 被配置为具有一个输出和两个输入。
// 请注意,一级从站从站和二级从站从站的配置输入和输出必须首尾相连。
//
// 二级从站从站的两个输出将作为二级从站从站的两个输入。
// 二级从站从站将输出其两个输入端的乘积,而这两个输入端将成为一级从站的单个输入端。
// 用户可以更改一级从站两个输出端(= 二级从站的输入端)的输出值,这些值将与一级从站的输入端(= 二级从站输入端1 x 二级从站输入端2 = 二级从站输出端)一起显示。
//
//##############################################################################################################//--------------------------------------------------------------------------------------------------------------
// 为了在主程序(用户层)和内核 DLL 之间共享数据结构的定义,我们使用了一个通用的头文件。
//--------------------------------------------------------------------------------------------------------------#include "EtherCATBridge.h"//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 别忘了输入你的序列号(6位客户编号),这是打开驱动程序所必需的。
//
// 如果你使用Demo版本,也可以使用“DEMO”代替。
// 如果你使用Beta版本,也可以使用“BETA”代替。
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!// 如上说所,定义的客户号 
const char _pCustomerNumber[] = "DEMO";// 主程序入口
void runSample() {outputTxt("***** Kithara example program 'EtherCATBridge' *****");KSError ksError;// 错误码定义,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"EtherCATBridge_64.dll" :                  "EtherCATBridge_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;}//------------------------------------------------------------------------------------------------------------// 主程序循环。//------------------------------------------------------------------------------------------------------------outputTxt(" ");outputTxt("Press '1' to change value1, '2' for value2, 'x' for exit.");outputTxt(" ");bool exitSample = false;while (!exitSample) {ushort lastPrimaryOutput1 = 0;ushort lastPrimaryOutput2 = 0;uint lastPrimaryInput = 1;do {if (lastPrimaryOutput1 != pApp->primaryOutput1 ||lastPrimaryOutput2 != pApp->primaryOutput2 ||lastPrimaryInput != pApp->primaryInput) {outputDec(pApp->primaryOutput1, "value1 = ", " | ", false);outputDec(pApp->primaryOutput2, "value2 = ", " | ", false);outputDec(pApp->primaryInput, "value1 * value2 = ", "          \r", false);lastPrimaryOutput1 = pApp->primaryOutput1;lastPrimaryOutput2 = pApp->primaryOutput2;lastPrimaryInput   = pApp->primaryInput;}} while ((pApp->primarySlaveError == KS_OK) &&(pApp->secondarySlaveError == KS_OK) &&!myKbhit());if (pApp->primarySlaveError != KS_OK)ksError = pApp->primarySlaveError;else if (pApp->secondarySlaveError != KS_OK)ksError = pApp->secondarySlaveError;if (ksError != KS_OK) {outputErr(ksError, "", "An error occurred during data exchange!");break;}outputTxt("\r                                                                \r", false);switch(myGetch()) {case 'x':case 'X':exitSample = true;break;case '1':pApp->primaryOutput1 = inputHex("Enter a new hexadecimal value for value1: ");break;case '2':pApp->primaryOutput2 = inputHex("Enter a new hexadecimal value for value2: ");break;}//----------------------------------------------------------------------------------------------------------// 等待我们的数据被发送。//----------------------------------------------------------------------------------------------------------ksError = KS_waitForEvent(pApp->hPrimaryDataSentEvent,            // 事件句柄KSF_NO_FLAGS,                           // 无标记10 * ms);                               // 超时,以100纳秒为单位。0 = 无穷大if (ksError != KS_OK) {outputErr(ksError, "KS_waitForEvent", "Failed to wait for the data-sent-event!");break;}}outputTxt(" ");//------------------------------------------------------------------------------------------------------------//  清理内核层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 'EtherCATBridge'.");
}

EtherCATBridge_dll.cpp


/* Copyright (c) 2009-2024 by Kithara Software GmbH. All rights reserved. *///##############################################################################################################
//
// 文件:         EtherCATBridge_dll.cpp
//
// 模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块
//
// 描述:   用户空间应用程序和内核空间 DLL 之间的共享定义,例如 通过直接访问 EtherCAT 进行简单的桥接(重映射)流程和数据交换
//
// 创建人:      t.pet 2009-08-26
//
//##############################################################################################################/*=====================================================================*\|                    *** 免责声明 ***                     			   ||                                                                       ||       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		  ||																	   |\*=====================================================================*///##############################################################################################################
//
// 目的:
//
// 本示例介绍如何配置和使用 EL6692 型 EtherCAT 网桥。
//
// 在此示例中, EL6692 的主站设备和从站设备处于相同的拓扑结构中。
// 为此,请将 EL6692 连接到耦合器上,并将耦合器的输出插头与 EL6692 的输入插头连接起来。如下图所示:
//
//                                    +--------+------+
//   +-------------------+            |  EK1100|EL6692|
//   |                   |            | In     |      |
//   |  PC 运行着Kithara |            |+--+    |      |
//   |                   |<---------->||  |    |  In  |
//   |  EtherCAT主站     |            |+--+    | +--+ |
//   |                   |            |        | |  | |<---+
//   +-------------------+            |+--+    | +--+ |    |
//                               +--->||  |    | +--+ |    |
//                               |    |+--+    | |  | |    |
//                               |    | Out    | +--+ |    |
//                               |    |        |  Out |    |
//                               |    |        |      |    |
//                               |    +--------+------+    |
//                               |                         |
//                               +-------------------------+
//                                      网络拓扑结构
//
// 在成功枚举出从属设备后,一级从站 EL6692 被配置为具有两个输出和一个输入,二级从站 EL6692 被配置为具有一个输出和两个输入。
// 请注意,一级从站从站和二级从站从站的配置输入和输出必须首尾相连。
//
// 二级从站从站的两个输出将作为二级从站从站的两个输入。
// 二级从站从站将输出其两个输入端的乘积,而这两个输入端将成为一级从站的单个输入端。
// 用户可以更改一级从站两个输出端(= 二级从站的输入端)的输出值,这些值将与一级从站的输入端(= 二级从站输入端1 x 二级从站输入端2 = 二级从站输出端)一起显示。
//
//##############################################################################################################//--------------------------------------------------------------------------------------------------------------
// 为了在主程序(用户层)和内核 DLL 之间共享数据结构的定义,我们使用了一个通用的头文件。
//--------------------------------------------------------------------------------------------------------------#include "EtherCATBridge.h"// 共享内存用于在内核层 DLL 和用户层的应用程序之间共享数据。
SharedData* _pSys = NULL;//--------------------------------------------------------------------------------------------------------------
// 用于映射一级从站设备过程数据的变量。
//--------------------------------------------------------------------------------------------------------------ushort *_primaryOutput1;
ushort *_primaryOutput2;
uint *_primaryInput;//--------------------------------------------------------------------------------------------------------------
// 用于映射二级从站过程数据的变量。
//--------------------------------------------------------------------------------------------------------------uint *_secondaryOutput;
ushort *_secondaryInput1;
ushort *_secondaryInput2;//--------------------------------------------------------------------------------------------------------------
// 前置声明:定时器回调、数据集回调,声明在文件末尾定义的回调函数。
//--------------------------------------------------------------------------------------------------------------KSError __stdcall primarySendCallBack(void* /*pArgs*/, void* /*pContext*/);
KSError __stdcall primaryReceiveCallBack(void* /*pArgs*/, void* /*pContext*/);
KSError __stdcall secondarySendCallBack(void* /*pArgs*/, void* /*pContext*/);
KSError __stdcall secondaryReceiveCallBack(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()。//------------------------------------------------------------------------------------------------------------KSEcatMasterState masterState;masterState.structSize = sizeof(KSEcatMasterState);   // 不要忘记初始化 structSize!for (int i = 0; i < 50; ++i) {ksError = KS_queryEcatMasterState(_pSys->hMaster,                         // 主站句柄&masterState,                           // 返回KSEcatMasterState 结构体KSF_NO_FLAGS);                          // 无标志if (ksError != KS_OK)return ksError;if (masterState.connected)break;KS_microDelay(100 * ms);}if (masterState.connected == 0)return KSERROR_CATEGORY_ETHERCAT;//------------------------------------------------------------------------------------------------------------// 通过指定供应商和产品 ID 创建主从站。// 只有在所附拓扑中找到从属设备,函数才会成功执行。//------------------------------------------------------------------------------------------------------------ksError = KS_createEcatSlave(_pSys->hMaster,                           // 主站句柄&_pSys->hPrimarySlave,                    // 返回从站句柄0,                                        // ID (0 = 任意)0,                                        // 从站位置 (0 = 任意)0x00000002,                               // 厂商 ID (2 = Beckhoff)0x1a243052,                               // 产品 ID (0x1a243052 = EL6692)0,                                        // 版本 (0 = 任意)KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 通过指定供应商和产品 ID 创建二级从站设备。// 一级从机在此阶段已经连接,因此此调用将不再处理它。//------------------------------------------------------------------------------------------------------------ksError = KS_createEcatSlave(_pSys->hMaster,                           // 主站句柄&_pSys->hSecondarySlave,                  // 返回从站句柄0,                                        // ID (0 = 任意)0,                                        // 从站位置 (0 = 任意)0x00000002,                               // 厂商 ID (2 = Beckhoff)0x1a243052,                               // 产品 ID (0x1a243052 = EL6692)0,                                        // 版本 (0 = 任意)KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 为了配置从站,我们需要让它们进入 PREOP 状态。通过使用主站句柄调用 KS_changeEcatState(),// 拓扑中创建的所有从站也都会进入 PREOP 状态。//------------------------------------------------------------------------------------------------------------ksError = KS_changeEcatState(_pSys->hMaster,                           // 主站句柄KS_ECAT_STATE_PREOP,                      // 切换状态类型KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 通过调用 KS_setEcatPdoMapping(),我们可以配置从站的输入和输出。// 对于一级从机,我们希望有两个输出和一个输入。// 第一步是重置输出和输入的 PDO。//------------------------------------------------------------------------------------------------------------ksError = KS_setEcatPdoMapping(_pSys->hPrimarySlave,                     // 从站句柄0x1600,                                   // 输出PDO的索引-1,                                       // 要映射的条目,-1表示重置此PDO0,                                        // 子索引,未使用0,                                        // 位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_setEcatPdoMapping(_pSys->hPrimarySlave,                     // 从站句柄0x1A00,                                   // 输入PDO的索引-1,                                       // 要映射的条目,-1表示重置此PDO0,                                        // 子索引,未使用0,                                        // 位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 现在为两个输出和一个输入配置给一级从站。// KS_setEcatPdoMapping() 以增量方式工作,每次调用都会向指定的 PDO 添加一个变量。// 输出配置为 16 位宽,输入配置为 32 位宽。//------------------------------------------------------------------------------------------------------------ksError = KS_setEcatPdoMapping(_pSys->hPrimarySlave,                     // 从站句柄0x1600,                                   // 输出PDO的索引0x7000,                                   // 要映射的条目1,                                        // 子索引16,                                       // 位长度KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_setEcatPdoMapping(_pSys->hPrimarySlave,                     // 从站句柄0x1600,                                   // 输出PDO的索引0x7000,                                   // 要映射的条目2,                                        // 子索引16,                                       // 位长度KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_setEcatPdoMapping(_pSys->hPrimarySlave,                     // 从站句柄0x1A00,                                   // 输入PDO的索引0x6000,                                   // 要映射的条目1,                                        // 子索引32,                                       // 位长度KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 现在,通过重置= PDO,将二级从站设备配置为一个输出端和两个输入端。//------------------------------------------------------------------------------------------------------------ksError = KS_setEcatPdoMapping(_pSys->hSecondarySlave,                   // 从站句柄(次级从站)0x1600,                                   // 输出PDO的索引-1,                                       // 要映射的条目,-1表示重置此PDO0,                                        // 子索引,未使用0,                                        // 位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_setEcatPdoMapping(_pSys->hSecondarySlave,                   // 从站句柄(次级从站)0x1A00,                                   // 输入PDO的索引-1,                                       // 要映射的条目,-1表示重置此PDO0,                                        // 子索引,未使用0,                                        // 位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 现在将二级从站配置为一个输出和两个输入。// 输出配置为 32 位宽,输入配置为 16 位宽。//------------------------------------------------------------------------------------------------------------ksError = KS_setEcatPdoMapping(_pSys->hSecondarySlave,                   // 从站句柄(二级从站)0x1600,                                   // 输出PDO的索引0x7000,                                   // 要映射的条目1,                                        // 子索引32,                                       // 位长度KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_setEcatPdoMapping(_pSys->hSecondarySlave,                   // 从站句柄(二级从站)0x1A00,                                   // 输入PDO的索引0x6000,                                   // 要映射的条目1,                                        // 子索引16,                                       // 位长度KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_setEcatPdoMapping(_pSys->hSecondarySlave,                   // 从站句柄(二级从站)0x1A00,                                   // 输入PDO的索引0x6000,                                   // 要映射的条目2,                                        // 子索引16,                                       // 位长度KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 为了显示一级从站和二级从站是独立的,我们创建了两个数据集,每个从站一个。//------------------------------------------------------------------------------------------------------------ksError = KS_createEcatDataSet(_pSys->hMaster,                           // 主站句柄&_pSys->hPrimaryDataSet,                  // 用于写入新数据集句柄的地址NULL,                                     // 数据集数据的应用层指针(未使用)NULL,                                     // 数据集数据的内核层指针(未使用)KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;ksError = KS_createEcatDataSet(_pSys->hMaster,                           // 主站句柄&_pSys->hSecondaryDataSet,                // 用于写入新数据集句柄的地址NULL,                                     // 数据集数据的应用层指针(未使用)NULL,                                     // 数据集数据的内核层指针(未使用)KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 创建数据集后,我们为每个数据集分配一个从数据集,以便能够访问它们的流程数据。//------------------------------------------------------------------------------------------------------------ksError = KS_assignEcatDataSet(_pSys->hPrimaryDataSet,                   // 数据集句柄_pSys->hPrimarySlave,                     // 从站句柄KS_ECAT_SYNC_ALL,                         // 同步对象0,                                        // 数据集中的特殊位置KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;ksError = KS_assignEcatDataSet(_pSys->hSecondaryDataSet,                 // 数据集句柄_pSys->hSecondarySlave,                   // 从站句柄KS_ECAT_SYNC_ALL,                         // 同步对象0,                                        // 数据集中的特殊位置KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 映射一级从机的过程数据。//------------------------------------------------------------------------------------------------------------ksError = KS_getEcatDataObjAddress(_pSys->hPrimaryDataSet,                   // 数据集句柄_pSys->hPrimarySlave,                     // 从站句柄0x1600, 0,                                // 目标对象的索引和子索引NULL,                                     // 数据应用层指针的地址,未使用(void**)&_primaryOutput1,                 // 数据内核层指针的地址NULL, NULL,                               // 位偏移和位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_getEcatDataObjAddress(_pSys->hPrimaryDataSet,                   // 数据集句柄_pSys->hPrimarySlave,                     // 从站句柄0x1600, 1,                                // 目标对象的索引和子索引NULL,                                     // 数据应用层指针的地址,未使用(void**)&_primaryOutput2,                 // 数据内核层指针的地址NULL, NULL,                               // 位偏移和位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_getEcatDataObjAddress(_pSys->hPrimaryDataSet,                   // 数据集句柄_pSys->hPrimarySlave,                     // 从站句柄0x1A00, 0,                                // 目标对象的索引和子索引NULL,                                     // 数据应用层指针的地址,未使用(void**)&_primaryInput,                   // 数据内核层指针的地址NULL, NULL,                               // 位偏移和位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 初始化一级从站在共享内存中的过程数据。//------------------------------------------------------------------------------------------------------------_pSys->primarySlaveError   = KS_OK;_pSys->secondarySlaveError = KS_OK;*_primaryOutput1      = 0;_pSys->primaryOutput1 = 0;*_primaryOutput2      = 0;_pSys->primaryOutput2 = 0;//------------------------------------------------------------------------------------------------------------// 映射二级从站的过程数据。//------------------------------------------------------------------------------------------------------------ksError = KS_getEcatDataObjAddress(_pSys->hSecondaryDataSet,                 // 数据集句柄_pSys->hSecondarySlave,                   // 从站句柄0x1A00, 0,                                // 目标对象的索引和子索引NULL,                                     // 数据应用层指针的地址,未使用(void**)&_secondaryInput1,                // 数据内核层指针的地址NULL, NULL,                               // 位偏移和位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_getEcatDataObjAddress(_pSys->hSecondaryDataSet,                 // 数据集句柄_pSys->hSecondarySlave,                   // 从站句柄0x1A00, 1,                                // 目标对象的索引和子索引NULL,                                     // 数据应用层指针的地址,未使用(void**)&_secondaryInput2,                // 数据内核层指针的地址NULL, NULL,                               // 位偏移和位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_getEcatDataObjAddress(_pSys->hSecondaryDataSet,                 // 数据集句柄_pSys->hSecondarySlave,                   // 从站句柄0x1600, 0,                                // 目标对象的索引和子索引NULL,                                     // 数据应用层指针的地址,未使用(void**)&_secondaryOutput,                // 数据内核层指针的地址NULL, NULL,                               // 位偏移和位长度,未使用KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 实际上开始通信的第一步是将主站状态切换至SAFEOP。//------------------------------------------------------------------------------------------------------------ksError = KS_changeEcatState(_pSys->hMaster,                           // 主站句柄KS_ECAT_STATE_SAFEOP,                     // 切换状态类型KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 我们将使用一个事件来通知应用程序一级从站数据集的传输情况。//------------------------------------------------------------------------------------------------------------ksError = KS_createEvent(&_pSys->hPrimaryDataSentEvent,            // 返回一个事件句柄NULL,                                     // 事件名称,可忽略KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------
// 在本示例中,我们需要使用四个回调函数。
// 第一个回调函数将由 hPrimaryTimer 触发,用于发送包含一级从站过程数据的 hPrimaryDataSet 到其拓扑。
//------------------------------------------------------------------------------------------------------------ksError = KS_createCallBack(&_pSys->hPrimarySendCallBack,             // 用于写入新回调句柄的地址primarySendCallBack,                      // 回调函数NULL,                                     // 回调函数的参数(未使用)KSF_DIRECT_EXEC,                          // 标志,此处为内核级别0);                                       // 优先级(在内核级别未使用)
if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------
// 第二个回调函数将在接收包含一级从站过程数据的 hPrimaryDataSet 从拓扑返回时触发。
//------------------------------------------------------------------------------------------------------------ksError = KS_createCallBack(&_pSys->hPrimaryReceiveCallBack,          // 用于写入新回调句柄的地址primaryReceiveCallBack,                   // 回调函数NULL,                                     // 回调函数的参数(未使用)KSF_DIRECT_EXEC,                          // 标志,此处为内核级别0);                                       // 优先级(在内核级别未使用)
if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------
// 第三个回调函数将由 hSecondaryTimer 触发,用于发送包含二级从站过程数据的 hSecondaryDataSet 到其拓扑。
//------------------------------------------------------------------------------------------------------------ksError = KS_createCallBack(&_pSys->hSecondarySendCallBack,           // 用于写入新回调句柄的地址secondarySendCallBack,                    // 回调函数NULL,                                     // 回调函数的参数(未使用)KSF_DIRECT_EXEC,                          // 标志,此处为内核级别0);                                       // 优先级(在内核级别未使用)
if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------
// 第四个回调函数将在接收包含二级从站过程数据的 hSecondaryDataSet 从拓扑返回时触发。
//------------------------------------------------------------------------------------------------------------ksError = KS_createCallBack(&_pSys->hSecondaryReceiveCallBack,        // 用于写入新回调句柄的地址secondaryReceiveCallBack,                 // 回调函数NULL,                                     // 回调函数的参数(未使用)KSF_DIRECT_EXEC,                          // 标志,此处为内核级别0);                                       // 优先级(在内核级别未使用)
if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------
// 将回调函数附加到它们的信号上。
//------------------------------------------------------------------------------------------------------------ksError = KS_createTimer(&_pSys->hPrimaryTimer,                    // 用于写入新定时器句柄的地址1 * ms,                                   // 定时器周期,单位为 100 纳秒_pSys->hPrimarySendCallBack,              // 回调句柄KSF_REALTIME_EXEC);                       // 标志,此处为高精度实时定时器
if (ksError != KS_OK)return ksError;ksError = KS_createTimer(&_pSys->hSecondaryTimer,                  // 用于写入新定时器句柄的地址1 * ms,                                   // 定时器周期,单位为 100 纳秒_pSys->hSecondarySendCallBack,            // 回调句柄KSF_REALTIME_EXEC);                       // 标志,此处为高精度实时定时器
if (ksError != KS_OK)return ksError;ksError = KS_installEcatHandler(_pSys->hPrimaryDataSet,                   // 数据集句柄KS_DATASET_SIGNAL,                        // 事件代码_pSys->hPrimaryReceiveCallBack,           // 回调句柄KSF_NO_FLAGS);                            // 标志(未使用)
if (ksError != KS_OK)return ksError;ksError = KS_installEcatHandler(_pSys->hSecondaryDataSet,                 // 数据集句柄KS_DATASET_SIGNAL,                        // 事件代码_pSys->hSecondaryReceiveCallBack,         // 回调句柄KSF_NO_FLAGS);                            // 标志(未使用)
if (ksError != KS_OK)return ksError;//------------------------------------------------------------------------------------------------------------// 一切都设置好了。现在将总线状态切入OP。//------------------------------------------------------------------------------------------------------------ksError = KS_changeEcatState(_pSys->hMaster,                           // 主站句柄KS_ECAT_STATE_OP,                         // 状态KSF_NO_FLAGS);                            // 无标记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->hPrimaryTimer);                    // 定时器句柄if (ksError != KS_OK)return ksError;ksError = KS_stopTimer(_pSys->hSecondaryTimer);                  // 定时器句柄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->hPrimaryTimer);                    // 定时器句柄if (ksError != KS_OK)return ksError;ksError = KS_removeTimer(_pSys->hSecondaryTimer);                  // 定时器句柄if (ksError != KS_OK)return ksError;// 卸载数据集处理器ksError = KS_installEcatHandler(_pSys->hPrimaryDataSet,                   // 数据集句柄KS_DATASET_SIGNAL,                        // 事件代码KS_INVALID_HANDLE,                        // 无效句柄,用于卸载KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;ksError = KS_installEcatHandler(_pSys->hSecondaryDataSet,                 // 数据集句柄KS_DATASET_SIGNAL,                        // 事件代码KS_INVALID_HANDLE,                        // 无效句柄,用于卸载KSF_NO_FLAGS);                            // 标志(未使用)if (ksError != KS_OK)return ksError;// 移除回调ksError = KS_removeCallBack(_pSys->hPrimarySendCallBack);             // 回调句柄if (ksError != KS_OK)return ksError;ksError = KS_removeCallBack(_pSys->hPrimaryReceiveCallBack);          // 回调句柄if (ksError != KS_OK)return ksError;ksError = KS_removeCallBack(_pSys->hSecondarySendCallBack);           // 回调句柄if (ksError != KS_OK)return ksError;ksError = KS_removeCallBack(_pSys->hSecondaryReceiveCallBack);        // 回调句柄if (ksError != KS_OK)return ksError;// 删除数据集ksError = KS_deleteEcatDataSet(_pSys->hPrimaryDataSet);                  // 数据集句柄if (ksError != KS_OK)return ksError;ksError = KS_deleteEcatDataSet(_pSys->hSecondaryDataSet);                // 数据集句柄if (ksError != KS_OK)return ksError;// 删除EtherCAT从站ksError = KS_deleteEcatSlave(_pSys->hPrimarySlave);                    // 从站句柄if (ksError != KS_OK)return ksError;ksError = KS_deleteEcatSlave(_pSys->hSecondarySlave);                  // 从站句柄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;
}//--------------------------------------------------------------------------------------------------------------
// 该回调触发主从机数据集的数据交换。
// 它将一级从站设备的两个输出值从共享内存复制到进程数据中,并在每次被定时器触发时将 DataSet 发送到拓扑中。
// 之后,它会通过设置事件通知应用程序发送成功。
//--------------------------------------------------------------------------------------------------------------KSError __stdcall primarySendCallBack(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;if (_pSys->primarySlaveError != KS_OK)return _pSys->primarySlaveError;//------------------------------------------------------------------------------------------------------------// 在发送 DataSet 之前,将所需的输出值从共享内存复制出来。//------------------------------------------------------------------------------------------------------------*_primaryOutput1 = _pSys->primaryOutput1;*_primaryOutput2 = _pSys->primaryOutput2;ksError = KS_postEcatDataSet(_pSys->hPrimaryDataSet,                   // 数据集句柄KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK) {_pSys->primarySlaveError = ksError;return ksError;}ksError = KS_setEvent(_pSys->hPrimaryDataSentEvent);            // 事件句柄if (ksError != KS_OK)_pSys->primarySlaveError = ksError;return ksError;
}//--------------------------------------------------------------------------------------------------------------
// 每次收到一级从站数据集时,都会调用该回调。
// 它将把输入值复制到共享内存中,以便从应用程序中访问。
//--------------------------------------------------------------------------------------------------------------KSError __stdcall primaryReceiveCallBack(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;if (_pSys->primarySlaveError != KS_OK)return _pSys->primarySlaveError;ksError = KS_readEcatDataSet(_pSys->hPrimaryDataSet,                   // 数据集句柄KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK) {_pSys->primarySlaveError = ksError;return ksError;}//------------------------------------------------------------------------------------------------------------// 将输入的接收值存储在共享内存中。//------------------------------------------------------------------------------------------------------------_pSys->primaryInput = *_primaryInput;return KS_OK;
}//--------------------------------------------------------------------------------------------------------------
// 该回调将触发二级从站设备 DataSet 的数据交换。
// 它将把数据集发布到包含二级从站设备的拓扑中。
//--------------------------------------------------------------------------------------------------------------KSError __stdcall secondarySendCallBack(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;if (_pSys->secondarySlaveError != KS_OK)return _pSys->secondarySlaveError;ksError = KS_postEcatDataSet(_pSys->hSecondaryDataSet,                 // 数据集句柄KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK)_pSys->secondarySlaveError = ksError;return ksError;
}//--------------------------------------------------------------------------------------------------------------
// 每次收到二级从站 DataSet 时,都会调用该回调。
// 在这里,我们通过将输出设置为输入 1 * 输入 2 来完成二级从站设备的工作。
// 当 secondarySlave.input1 = primarySlave.output1 和 secondarySlave.input2 = primarySlave.output2 时
//--------------------------------------------------------------------------------------------------------------KSError __stdcall secondaryReceiveCallBack(void* /*pArgs*/, void* /*pContext*/) {KSError ksError;if (_pSys->secondarySlaveError != KS_OK)return _pSys->secondarySlaveError;ksError = KS_readEcatDataSet(_pSys->hSecondaryDataSet,                 // 数据集句柄KSF_NO_FLAGS);                            // 无标记if (ksError != KS_OK) {_pSys->secondarySlaveError = ksError;return ksError;}//------------------------------------------------------------------------------------------------------------// 根据输入计算输出。// 想要的输出值将在下一次迭代 secondarySendCallBack 时设置。//------------------------------------------------------------------------------------------------------------*_secondaryOutput = (*_secondaryInput1) * (*_secondaryInput2);return KS_OK;
}//--------------------------------------------------------------------------------------------------------------
// 需要实现 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;
}

相关文章:

【EtherCATBridge】- KRTS C++示例精讲(9)

EtherCATBridge示例讲解 文章目录 EtherCATBridge示例讲解结构说明代码说明 项目打开请查看【BaseFunction精讲】。 结构说明 EtherCATBridge.h &#xff1a;数据定义 EtherCATBridge.cpp &#xff1a;应用层源码 EtherCATBridge_dll.cpp &#xff1a;内核层源码 其余文件说明…...

C++实现设计模式--- 观察者模式 (Observer)

观察者模式 (Observer) 观察者模式 是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;使得当一个对象的状态发生改变时&#xff0c;其依赖者&#xff08;观察者&#xff09;会收到通知并自动更新。 意图 定义对象之间的一对多依赖关系。当一个对象状…...

iOS 解决两个tableView.嵌套滚动手势冲突

我们有这样一个场景&#xff0c;就是页面上有一个大的tableView&#xff0c; 每一个cell都是和屏幕一样高的&#xff0c;然后cell中还有一个可以 tableView&#xff0c;比如直播间的情形&#xff0c;这个时候如果我们拖动 cell里面的tableView滚动的话&#xff0c;如果滚动到内…...

Lianwei 安全周报|2025.1.13

新的一周又开始了&#xff0c;以下是本周「Lianwei周报」&#xff0c;我们总结推荐了本周的政策/标准/指南最新动态、热点资讯和安全事件&#xff0c;保证大家不错过本周的每一个重点&#xff01; 政策/标准/指南最新动态 01 美国国土安全部发布《公共部门生成式人工智能部署手…...

rtthread学习笔记系列(2) -- 宏

文章目录 2.链接文件2.0. 参考链接2.1._stext 和 _etext2.2. "."与"*符号作用2.3..linkonce 段2.4. KEEP2.5 ENTRY2.6 PROVIDE2.7 AT2.8 SORT2.9 NOLOAD 源文件路径:https://github.com/wdfk-prog/RT-Thread-Study 2.链接文件 2.0. 参考链接 https://home.cs…...

美摄科技PC端视频编辑解决方案,为企业打造专属的高效创作平台

在当今这个信息爆炸的时代&#xff0c;视频已成为不可或缺的重要内容形式&#xff0c;美摄科技推出了PC端视频编辑解决方案的私有化部署服务&#xff0c;旨在为企业提供一款量身定制的高效创作平台。 一、全面功能&#xff0c;满足企业多样化需求 美摄科技的PC端视频编辑解决…...

服务端开发模式-thinkphp-重新整理workman

一、登录接口 <?php /*** 登录退出操作* User: 龙哥三年风水* Date: 2024/10/29* Time: 15:53*/ namespace app\controller\common; use app\controller\Emptys; use app\model\permission\Admin; use app\model\param\System as SystemModel; use Email\EmailSender; use…...

HTB:Access[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 尝试匿名连接至…...

【论文笔记】SmileSplat:稀疏视角+pose-free+泛化

还是一篇基于dust3r的稀疏视角重建工作&#xff0c;作者联合优化了相机内外参与GS模型&#xff0c;实验结果表明优于noposplat。 abstract 在本文中&#xff0c;提出了一种新颖的可泛化高斯方法 SmileSplat&#xff0c;可以对无约束&#xff08;未标定相机的&#xff09;稀疏多…...

电机控制的数字化升级:基于DSP和FPGA的仿真与实现

数字信号处理器&#xff08;DSP&#xff0c;Digital Signal Processor&#xff09;在工业自动化领域的应用日益广泛。DSP是一种专门用于将模拟信号转换成数字信号并进行处理的技术&#xff0c;能够实现信号的数字滤波、重构、调制和解调等多项功能&#xff0c;确保信号处理的精…...

1/14 C++

练习&#xff1a;将图形类的获取周长和获取面积函数设置成虚函数&#xff0c;完成多态 再定义一个全局函数&#xff0c;能够在该函数中实现&#xff1a;无论传递任何图形&#xff0c;都可以输出传递的图形的周长和面积 #include <iostream>using namespace std; class Sh…...

java springboot3.x jwt+spring security6.x实现用户登录认证

springboot3.x jwtspring security6.x实现用户登录认证 什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它用于在网络应用环境中传递声明。通常&#xff0c;JWT用于身份验证和信息交换。JWT的一个典型用法是…...

YOLOv5训练长方形图像详解

文章目录 YOLOv5训练长方形图像详解一、引言二、数据集准备1、创建文件夹结构2、标注图像3、生成标注文件 三、配置文件1、创建数据集配置文件2、选择模型配置文件 四、训练模型1、修改训练参数2、开始训练 五、使用示例1、测试模型2、评估模型 六、总结 YOLOv5训练长方形图像详…...

【2025最新】Poe保姆级订阅指南,Poe订阅看这一篇就够了!最方便使用各类AI!

1.Poe是什么&#xff1f; Poe, 全称Platform for Open Exploration。 Poe本身并不提供基础的大语言模型&#xff0c;而是整合多个来自不同科技巨头的基于不同模型的AI聊天机器人&#xff0c;其中包括来自OpenAI的ChatGPT&#xff0c;Anthropic的Claude、Google的PaLM&#xf…...

type1-100,2 words

dish n.餐具、碟&#xff0c;盘子&#xff1b;菜肴、饭菜&#xff08;指一顿餐食中的一道菜&#xff09; kind of 稍微&#xff1b;有点 sort of 稍微&#xff1b;有点儿 smallish adj.有点小的 crack 敲碎/裂&#xff0c;敲开&#xff0c;砸开&#xff0c;砸碎&#xff1b;裂开…...

Leetcode 377. 组合总和 Ⅳ 动态规划

原题链接&#xff1a;Leetcode 377. 组合总和 Ⅳ 可参考官解 class Solution { public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target 1);dp[0] 1;// 总和为 i 的元素组合的个数for (int i 1; i < target; i) {// 每次都…...

计算机网络(五)——传输层

一、功能 传输层的主要功能是向两台主机进程之间的通信提供通用的数据传输服务。功能包括实现端到端的通信、多路复用和多路分用、差错控制、流量控制等。 复用&#xff1a;多个应用进程可以通过同一个传输层发送数据。 分用&#xff1a;传输层在接收数据后可以将这些数据正确分…...

【SQL】进阶知识 -- 删除表的几种方法(包含表内单个字段的删除方法)

大家好&#xff01;欢迎来到本篇SQL进阶博客。如果你已经掌握了基础的SQL操作&#xff0c;接下来就让我们一起探索删除表的几种方法。删除表可能听起来有点危险&#xff0c;事实也是如此&#xff0c;所以在我们实际开发过程中&#xff0c;大多数时候我们都有数据的使用权限&…...

【搭建JavaEE】(3)前后端交互,请求响应机制,JDBC数据库连接

前后端交互 Apache Tomat B/S目前主流。 tomat包含2部分&#xff1a; apache容器 再认识servlet 抽象出的开发模式 项目创建配置 maven javaeetomcat 忽略一些不用的文件 webapp文件夹 HiServlet 这里面出现了webinfo&#xff0c;这个别删因为这个呢&#xff0c;是这这个这…...

项目概述、开发环境搭建(day01)

软件开发整体介绍 软件开发流程 第1阶段: 需求分析 需求规格说明书&#xff0c; 一般来说就是使用 Word 文档来描述当前项目的各个组成部分&#xff0c;如&#xff1a;系统定义、应用环境、功能规格、性能需求等&#xff0c;都会在文档中描述。产品原型&#xff0c;一般是通过…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...