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

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 是 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,                              // 输入适配器的硬件ID
              NULL,                                     // 设配器配置选项
              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表示重置此PDO
              0,                                        // 子索引,未使用
              0,                                        // 位长度,未使用
              KSF_NO_FLAGS);                            // 标志(未使用)
  if (ksError != KS_OK)
    return ksError;

  ksError = KS_setEcatPdoMapping(
              _pSys->hPrimarySlave,                     // 从站句柄
              0x1A00,                                   // 输入PDO的索引
              -1,                                       // 要映射的条目,-1表示重置此PDO
              0,                                        // 子索引,未使用
              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表示重置此PDO
              0,                                        // 子索引,未使用
              0,                                        // 位长度,未使用
              KSF_NO_FLAGS);                            // 标志(未使用)
  if (ksError != KS_OK)
    return ksError;

  ksError = KS_setEcatPdoMapping(
              _pSys->hSecondarySlave,                   // 从站句柄(次级从站)
              0x1A00,                                   // 输入PDO的索引
              -1,                                       // 要映射的条目,-1表示重置此PDO
              0,                                        // 子索引,未使用
              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;


// 切换 SAFEOP
  ksError = 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值