【EtherCATBasics】- KRTS C++示例精讲(2)

EtherCATBasics示例讲解


目录

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


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

结构说明

在这里插入图片描述

  • EtherCATBasics:应用层程序,主要用于人机交互、数据显示、内核层数据交互等;

    1. EtherCATBasics.h : 数据定义
    2. EtherCATBasics.cpp:用户应用层源码
  • EtherCATBasics_64: 内核层程序(实时层程序),主要用于实时数据处理;

    1. EtherCATBasics.h : 数据定义
    2. EtherCATBasics_dll.cpp : 内核层源码
  • 其余文件说明请查看【BaseFunction精讲】中的结构说明。
    ps : 内核层中的数据、结构体需要一字节对齐,需要以MT方式构建
    在这里插入图片描述

代码讲解

EtherCATBasics.h :应用层与内核层共用一个头文件

/* Copyright (c) 2011-2024 by Kithara Software GmbH. All rights reserved. */

//##############################################################################################################
//
// 文件:         EtherCATBasics.h
//
// 使用模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块 
//
// 用户应用程序和内核DLL之间的共享定义,例如:如何通过EtherCAT实现基本进程数据交换的示例
//
// 开发者:      m.gru 2011-05-11
//
//##############################################################################################################

   /*=====================================================================*\
   |                    *** 免责声明 ***                     			   |
   |                                                                       |
   |       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		   |
   |																	   |
   \*=====================================================================*/

//##############################################################################################################
//
// 目的:
//
// 该示例说明了如何在不考虑同步读取进程数据的情况下实现基本的进程数据交换。
// 此外,该示例还展示了如何检测从站可用的分布式时钟功能以及如何利用这些功能。
// 首先,我们打开网络作为 EtherCAT 主站,并创建一个 EtherCAT 主站。
// 然后,我们让用户从连接的 EtherCAT 从站中选择一个,并将其映射到创建的数据集。
// 如果所选的 EtherCAT 从站支持分布式时钟 (DC) 功能,用户可以选择其中一种支持的模式。
// 然后,用户必须从从站的一个 PDO 中选择一个变量。
// 将创建一个定时器,触发拓扑(已连接的 EtherCAT从站)与本示例实例化的 EtherCAT 主站之间的数据交换。
// 所选变量每 100ms 显示一次(定时器每 1ms 接收一次)。
//
//##############################################################################################################

#ifndef __SMP_ETHERCATBASICS_H
#define __SMP_ETHERCATBASICS_H

#include "../_KitharaSmp/_KitharaSmp.h"

//--------------------------------------------------------------------------------------------------------------
// SharedData 是用户定义的参数结构,用于在内核 DLL 和用户应用程序之间使用共享内存交换信息。
//--------------------------------------------------------------------------------------------------------------

struct SharedData {
  KSHandle hKernel;                                     // 内核句柄
  KSHandle hAdapter;                                    // 网络适配器句柄
  KSHandle hMaster;                                     // EtherCAT主站句柄
  KSHandle hSlave;                                      // EtherCAT从站句柄
  KSEcatMasterState masterState;                        // 获取主站状态的结构体
  KSHandle hDataSet;                                    // 用于主站和拓扑结构之间交换的数据集的句柄
  KSHandle hDataSetCallBack;                            // 回调句柄,当数据集从拓扑返回时将被调用
  KSHandle hTimerCallBack;                              // 由定时器调用的回调的句柄
  KSHandle hTimer;                                      // 将调用 DataSet 的计时器的句柄
  int varIndex;                                         // 需要查看的变量的索引
  int varSubIndex;                                      // 需要查看的变量的子索引
  uint data;                                            // 从 DataSet 复制的数据,供应用程序访问
  KSError error;                                        // 用于从内核空间 dll 向用户空间应用程序传递错误信息
};

#endif // __SMP_ETHERCATBASICS_H

EtherCATBasics.cpp

/* Copyright (c) 2009-2024 by Kithara Software GmbH. All rights reserved. */

//##############################################################################################################
//
// 文件:         EtherCATBasics.cpp
//
// 使用模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块 
//
// 用户应用程序和内核DLL之间的共享定义,例如:如何通过EtherCAT实现基本进程数据交换的示例
//
// 开发者:      t.pet 2009-08-26
//
//##############################################################################################################

   /*=====================================================================*\
   |                    *** 免责声明 ***                     			   |
   |                                                                       |
   |       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		   |
   |																	   |
   \*=====================================================================*/

//##############################################################################################################
//
// 目的:
//
// 该示例说明了如何在不考虑同步读取进程数据的情况下实现基本的进程数据交换。
// 此外,该示例还展示了如何检测从站可用的分布式时钟功能以及如何利用这些功能。
// 首先,我们打开网络作为 EtherCAT 主站,并创建一个 EtherCAT 主站。
// 然后,我们让用户从连接的 EtherCAT 从站中选择一个,并将其映射到创建的数据集。
// 如果所选的 EtherCAT 从站支持分布式时钟 (DC) 功能,用户可以选择其中一种支持的模式。
// 然后,用户必须从从站的一个 PDO 中选择一个变量。
// 将创建一个定时器,触发拓扑(已连接的 EtherCAT从站)与本示例实例化的 EtherCAT 主站之间的数据交换。
// 所选变量每 100ms 显示一次(定时器每 1ms 接收一次)。
//
//##############################################################################################################


//--------------------------------------------------------------------------------------------------------------
// 为了在主程序(用户层)和内核 DLL 之间共享数据结构的定义,我们使用了一个通用的头文件。
//--------------------------------------------------------------------------------------------------------------

#include "EtherCATBasics.h"

//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 别忘了输入你的序列号(6位客户编号),这是打开驱动程序所必需的。
// 
// 如果你使用Demo版本,也可以使用“DEMO”代替。
// 如果你使用Beta版本,也可以使用“BETA”代替。
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

// 如上说所,定义的客户号 
const char _pCustomerNumber[] = "DEMO";

// 主程序入口
void runSample() {
  // 调用KitharaSmp.h 中的函数,输出文本
  outputTxt("***** Kithara example program 'EtherCATBasics' *****");

  // 错误码定义,KSError 是 Kithara API 所有函数的返回类型,通过 【KSError】 可以查询接口的返回错误信息。
  KSError ksError;

  //------------------------------------------------------------------------------------------------------------
  // 打开驱动程序的第一步,所有KRTS程序必须进行的操作。
  // 只要该函数调用成功后,我们可以使用其他函数。如果打开失败,则无法调用其他函数。
  // 此函数接受您的客户编号作为参数,其中包含 Kithara(如果适用可以为“DEMO”或“BETA”)。
  //------------------------------------------------------------------------------------------------------------
  ksError = KS_openDriver(
              _pCustomerNumber);                        // 客户编号
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_openDriver", "Unable to open the driver!");
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 创建共享内存
  // 为实时层中的DLL和此用户层应用程序之间的通信。
  //------------------------------------------------------------------------------------------------------------

  KSHandle hSharedMemory;
  ksError = KS_createSharedMemEx(
              &hSharedMemory,                           // 返回创建的共享内存句柄
              "",                                       // 共享内存的名称
              sizeof(SharedData),                       // 共享内存的大小
              KSF_NO_FLAGS);                            // 无标记,此选项可以进行一些特殊设定
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_createSharedMemEx", "Unable to create shared memory!");
    KS_closeDriver();
    return;
  }


  // 要访问共享内存,应用程序需要使用刚创建的共享内存的句柄来获取指向分配的共享内存的指针。
  SharedData* pApp = NULL;								// 自定义的共享内存结构体
  ksError = KS_getSharedMemEx(
              hSharedMemory,                            // 共享内存的句柄
              (void**)&pApp,                            // 指向共享内存的结构的指针
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_getSharedMemEx", "Unable to map shared memory!");
    KS_closeDriver();
    return;
  }

  // 确定操作系统的位数大小以决定是加载32位还是64位内核DLL。
  KSSystemInformation systemInfo;                       // 获取系统信息的结构体
  systemInfo.structSize = sizeof(KSSystemInformation);  // 不要忘记设备结构体大小
  ksError = KS_getSystemInformation(
              &systemInfo,                              // 结构体指针用于获取结构体数据
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_getSystemInformation", "Unable to get system information to distinguish bitsize!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 想要在内核级别上使用DLL中的函数,必须加载dll,在调用里面的函数!
  // 注意!加载程序必须找到DLL,因此它应该放在搜索路径的目录中!
  // 因为我们想要在共享内存中传递加载的内核的句柄,所以我们不使用加载内核时的初始化函数,
  // 而是在填充内核句柄和初始化内核所需的所有信息之后,显式调用初始化函数。
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_loadKernel(
              &pApp->hKernel,                           // 返回内核操作句柄
              systemInfo.isSys64Bit ?                   // 根据系统位数加载内核Dll
                "EtherCATBasics_64.dll" :               
                "EtherCATBasics_32.dll",                
              NULL,                                     // 需要支持的函数名称(未使用)
              NULL,                                     // 函数参数 (未使用)
              KSF_KERNEL_EXEC);                         // 内核空间中加载(实时层运行)
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_loadKernel", "Unable to load DLL! Is the DLL in the search path?");
    KS_closeDriver();
    return;
  }

  // 查询并展示所有受支持的网络适配器
  char pDeviceName[256];			// 用于保存设备名称

  outputTxt(" ");
  outputTxt("Following network adapters found:");

  for (int i = 0;; ++i) {
	// KS_enumDevices()可以用于查询分配给Kithara驱动程序的所有网络适配器的名称。
    ksError = KS_enumDevices(
                "NET",                                  // 'NET' 代表搜索网络设备
                i,                                      // 从0开始的枚举索引号
                pDeviceName,                            // 返回设备名称
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK) {
      if (KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND)
        outputErr(ksError, "KS_enumDevices", "Unable to query network device name!");
      if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND && !i) {
        outputTxt("No network adapters found!");
        outputTxt(" ");
        KS_closeDriver();
        return;
      }
      break;
    }
    // 输出索引号对应的设备名称
    outputDec(i, "", ": ", false);
    outputTxt(pDeviceName);
  }
  outputTxt(" ");

  // 输入想要打开的适配器索引号
  outputTxt("Attention!");
  outputTxt("By selecting a device its Windows driver gets removed and replaced by the");
  outputTxt("appropriate Kithara driver. This will render the network device for the duration");
  outputTxt("of this sample invisible to Windows.");
  outputTxt("Be sure that all other Applications using that device are closed right now!");


  // 输入并保存索引号并根据索引和再次获取设备名称
  int deviceIndex = inputDec("Device number: ", 0);
  ksError = KS_enumDevices(
              "NET",                                    // 'NET' 代表搜索网络设备
              deviceIndex,                              // 选择的设备索引号
              pDeviceName,                              // 返回设备名称
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_enumDevices", "Unable to query selected network device name!");
    KS_closeDriver();
    return;
  }

  outputTxt("Selected device: ", false);
  outputTxt(pDeviceName);
  outputTxt(" ");


  // 根据设备名称,打开以太网适配器。
  ksError = KS_openNetworkAdapter(
              &pApp->hAdapter,                          // 获取适配器句柄
              pDeviceName,                              // 输入适配器的硬件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;
  }

  bool dcLicensed = true;			// 是否支持DC模式
  // 输出从站在线个数
  outputDec(pApp->masterState.slavesOnline, "slavesOnline = ");	
  outputTxt("Choose a slave: ");
  KSEcatSlaveState slaveState;		// 从站状态的结构体
  slaveState.structSize = sizeof(KSEcatSlaveState);     // 初始化结构体大小

  // 遍历所有已连接的从设备,显示它们的基本信息,让用户选择其中一个。
  for (int i = 0; i < pApp->masterState.slavesOnline; ++i) {

    //----------------------------------------------------------------------------------------------------------
    // 使用KS_enumEcatSlaves()函数来迭代所有在线的从站设备。
    // 如果它的第二个参数大于在线从设备的数量,它将返回KSERROR_DEVICE_NOT_FOUND。
    //----------------------------------------------------------------------------------------------------------

    ksError = KS_enumEcatSlaves(
                pApp->hMaster,                          // EtherCAT 主站句柄
                i,                                      // 从0开始的枚举索引号
                &slaveState,                            // 从站状态结构体
                KSF_NO_FLAGS);                          // 无标记
    if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND)
      break;
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_enumEcatSlaves", "Failed to enumerate EtherCAT slaves!");
      KS_closeDriver();
      return;
    }


    //----------------------------------------------------------------------------------------------------------
    // 创建从站
    // KS_enumEcatSlaves()函数填充了KSEcatSlaveState结构体,其中包含了从设备的基本信息。
    // 为了获取更多的详细信息,需要为该从站设备创建一个句柄。
    // 可以直接将接收到的KSEcatSlaveState结构体传递给KS_createEcatSlaveIndirect()函数。
    //----------------------------------------------------------------------------------------------------------

    KSHandle hSlave;
    ksError = KS_createEcatSlaveIndirect(
                pApp->hMaster,                          // 主站句柄
                &hSlave,                                // 返回新从站句柄
                &slaveState,                            // 返回从站信息
                KSF_FORCE_OVERRIDE);                    // 忽略缺少的XML信息
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_createEcatSlaveIndirect", "Failed to create EtherCAT slave!");
      KS_closeDriver();
      return;
    }

    // 修改从站状态,只有在等于或高于的KS_ECAT_STATE_PREOP状态下,才能查询在线从机信息。
    ksError = KS_changeEcatState(
                hSlave,                                 // 从站句柄
                KS_ECAT_STATE_PREOP,                    // 需要切换的状态
                KSF_NO_FLAGS);                          // 无标志
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_changeEcatState", "Failed to change EtherCAT slave state to PREOP!");
      KS_closeDriver();
      return;
    }

    //----------------------------------------------------------------------------------------------------------
    // 查询从站信息
	// KS_queryEcatSlaveInfo()函数将KSEcatSlaveInfo结构填充了所有可用的信息。
	// 通过使用KSF_PDO和KSF_SDO标志,您可以决定是否想要有关过程数据对象、服务数据对象或两者的信息。
	// 因为我们只对从设备的名称感兴趣,而不需要对象信息,所以我们使用flags == KSF_NO_FLAGS。
    //----------------------------------------------------------------------------------------------------------

    KSEcatSlaveInfo* pSlaveInfo;
    ksError = KS_queryEcatSlaveInfo(
                hSlave,                                 // 从站句柄
                &pSlaveInfo,                            // 返回从站信息
                KSF_NO_FLAGS);                          // 无标记
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_queryEcatSlaveInfo", "Unable to read slave information!");
      KS_closeDriver();
      return;
    }

    //----------------------------------------------------------------------------------------------------------
	// 为了测试一个从节点是否支持分布式时钟,我们只需要枚举分布式时钟的操作模式。
	// 如果索引等于0,则该从节点支持分布式时钟。
    //----------------------------------------------------------------------------------------------------------

    char pDcOpMode[256];
    ksError = KS_enumEcatDcOpModes(
                hSlave,                                 // 从站句柄
                0,                                      // OP模式
                pDcOpMode,                              // 模式名称
                NULL,                                   // 模式描述
                KSF_NO_FLAGS);                          // 无标记
    if (KSERROR_CODE(ksError) == KSERROR_FEATURE_NOT_LICENSED) {
      ksError = KS_OK;
      dcLicensed = false;
    }

    if (ksError != KS_OK && KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND) {
      outputErr(ksError, "KS_enumEcatDcOpModes", "Failed to query EtherCAT DC op mode");
      KS_closeDriver();
      return;
    }

    // 输出从站索引以及从站是否支持分布式时钟和从站名称。
    if (KSERROR_CODE(ksError) == KSERROR_DEVICE_NOT_FOUND || !dcLicensed)
      outputDec(i, "", ": [noDC] ", false);
    else
      outputDec(i, "", ": [DC]   ", false);
    outputTxt(pSlaveInfo->name);

    // 删除用于查询从属信息以供显示从站对象。
    ksError = KS_deleteEcatSlave(
                hSlave);                                // 需要删除的从站句柄
    if (ksError != KS_OK) {
      outputErr(ksError, "KS_deleteEcatSlave", "Unable to delete slave!");
      KS_closeDriver();
      return;
    }
  }

   // 请输入选择的从站,已查询从站信息
  int slaveIndex = inputDec("Slave index: ", -1);

  if (slaveIndex < 0 || slaveIndex >= pApp->masterState.slavesOnline) {
    outputTxt("Invalid slave index!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 在指定的索引处创建一个 ECAT 从节点设备。
  // 只用索引,不需要 ID、厂商 ID、产品 ID 或修订号。
  //------------------------------------------------------------------------------------------------------------

  // 创建从站 
  ksError = KS_createEcatSlave(
              pApp->hMaster,                            // 主站句柄
              &pApp->hSlave,                            // 返回从站句柄
              0,                                        // 该位置相对于的标识符,0表示绝对位置
              slaveIndex,                               // 从站位置
              0,                                        // 供应商 ID(0 = any)
              0,                                        // 产品ID (0 = any)
              0,                                        // 版本(0 = any)
              KSF_FORCE_OVERRIDE);                      // 忽略缺少的XML信息
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_createEcatSlave", "Failed to create EtherCAT slave!");
    KS_closeDriver();
    return;
  }


  // 只有在KS_ECAT_STATE_PREOP等于或高于的状态下,才能查询在线从机信息。
  ksError = KS_changeEcatState(
              pApp->hSlave,                             // 从站句柄
              KS_ECAT_STATE_PREOP,                      // 需要修改的从站状态
              KSF_NO_FLAGS);                            // 无状态
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_changeEcatState", "Failed to change EtherCAT slave state to PREOP!");
    KS_closeDriver();
    return;
  }

  // 如果DC功能可用,则显示所有可用的分布式时钟操作模式。
  if (dcLicensed) {
    char pDcOpMode[256];
    char pDcOpModeDescription[256];

    outputTxt(" ");

    int dcModeCount = -1;
    for (int i = 0;; ++i) {


      //--------------------------------------------------------------------------------------------------------
	  // 使用 KS_enumEcatDcOpModes() 函数,您可以枚举所有可用的分布式时钟操作模式。
	  // 我们使用此函数来显示所有可用的分布式时钟操作模式,以便让用户选择其中一个。
      //--------------------------------------------------------------------------------------------------------

      ksError = KS_enumEcatDcOpModes(
                  pApp->hSlave,                         // 从站句柄
                  i,                                    // 从零开始枚举索引
                  pDcOpMode,                            // 获取DC模式名称
                  pDcOpModeDescription,                 // 获取DC模式描述信息
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK) {
        if (KSERROR_CODE(ksError) != KSERROR_DEVICE_NOT_FOUND) {
          outputErr(ksError, "KS_enumEcatDcOpModes", "Unable to query DC op mode!");
          KS_closeDriver();
          return;
        }

        break;
      }
      dcModeCount = i;
      if (i == 0)
          outputTxt("Following DC op modes where found:");

      outputDec(i, "", ": ", false);
      outputTxt(pDcOpMode, false);
      outputTxt(" - ", false);
      outputTxt(pDcOpModeDescription);
    }

    //----------------------------------------------------------------------------------------------------------
    // 如果有 DC 操作可用,让用户通过索引选择一个并验证选择。
    //----------------------------------------------------------------------------------------------------------

    if (dcModeCount >= 0) {
      int dcOpModeIndex = inputDec("Op mode number: ", -1);

      outputTxt(" ");

      ksError = KS_enumEcatDcOpModes(
                  pApp->hSlave,                         // 从站句柄
                  dcOpModeIndex ,                       // 索引
                  pDcOpMode,                            // 获取DC模式名称
                  pDcOpModeDescription,                 // 获取DC模式描述信息
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK) {
        outputErr(ksError, "KS_enumEcatDcOpModes", "Unable to query DC op mode!");
        KS_closeDriver();
        return;
      }

      outputTxt("Selected op mode: ", false);
      outputTxt(pDcOpMode, false);
      outputTxt(" - ", false);
      outputTxt(pDcOpModeDescription, false);
      outputTxt(" ");


      // 获取DC操作模式的参数。
      KSEcatDcParams dcParams;
      ksError = KS_lookupEcatDcOpMode(
                  pApp->hSlave,                         // 从站句柄
                  pDcOpMode,                            // 操作模式
                  &dcParams,                            // 返回DC模式参数
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK) {
        outputErr(ksError, "KS_lookupEcatDcOpMode", "Unable to lookup DC op mode!");
        KS_closeDriver();
        return;
      }

      // 根据需要调整参数。
	  // ...


      // 如果配置了从站节点以使用此DC操作模式(可能调整了dcParams)。
      ksError = KS_configEcatDcOpMode(
                  pApp->hSlave,                         // 从站句柄
                  pDcOpMode,                            // 模式名称
                  &dcParams,                            // DC参数设定
                  KSF_NO_FLAGS);                        // 无标记
      if (ksError != KS_OK) {
        outputErr(ksError, "KS_configEcatDcOpMode", "Unable to lookup DC op mode!");
        KS_closeDriver();
        return;
      }
    }
  }


  //------------------------------------------------------------------------------------------------------------
  // 现在将为所选的从属节点分配DataSet。
  // 只要KS_assignEcatDataSet()没有返回错误,我们就可以分配对象。
  // 简化同步对象的选择,使用提供的特殊常量:KS_ECAT_SYNC_INPUT、KS_ECAT_SYNC_OUTPUT和KS_ECAT_SYNC_ALL。
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_assignEcatDataSet(
              pApp->hDataSet,                           // DataSet 句柄
              pApp->hSlave,                             // 从站句柄
              KS_ECAT_SYNC_ALL,                         // 同步对象的类型
              0,                                        // 在DataSet中的特殊位置
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_assignEcatDataSet", "Failed to assign slave!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // KS_queryEcatSlaveInfo()用所有可用信息填充KSEcatSlaveInfo结构。
  // 使用Flags KSF_PDO和KSF_SDO可以决定是否需要有关PDO,SDO对象或两者。
  //------------------------------------------------------------------------------------------------------------

  KSEcatSlaveInfo* pSlaveInfo;
  ksError = KS_queryEcatSlaveInfo(
              pApp->hSlave,                             // 从站句柄
              &pSlaveInfo,                              // 从站信息的结构体
              KSF_PDO);                                 // 查询PDO数据
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_queryEcatSlaveInfo", "Unable to read slave information!");
    KS_closeDriver();
    return;
  }

  //------------------------------------------------------------------------------------------------------------
  // 让用户选择一个PDO对象。
  //------------------------------------------------------------------------------------------------------------

  // 遍历并输出PDO信息
  outputTxt(" ");
  outputTxt("Choose a PDO: (only active and readable PDOs are displayed)");

  for (int i = 0; i < pSlaveInfo->objCount; ++i) {
    KSEcatDataObjInfo* pObjInfo = pSlaveInfo->objs[i];
    if ((pObjInfo->objType & KS_DATAOBJ_PDO_TYPE) &&
        (pObjInfo->objType & KS_DATAOBJ_ACTIVE) &&
        (pObjInfo->objType & KS_DATAOBJ_READABLE)) {
      outputDec(i, "", ": ", false);
      outputTxt(pObjInfo->name);
    }
  }

  int pdoIndex = inputDec("PDO index: ", -1);
  if (pdoIndex < 0 || pdoIndex >= pSlaveInfo->objCount) {
    outputTxt("Invalid PDO index!");
    KS_closeDriver();
    return;
  }

  KSEcatDataObjInfo* pObjInfo = pSlaveInfo->objs[pdoIndex];
    if (!(pObjInfo->objType & KS_DATAOBJ_PDO_TYPE) ||
      !(pObjInfo->objType & KS_DATAOBJ_ACTIVE) ||
      !(pObjInfo->objType & KS_DATAOBJ_READABLE)) {
    outputTxt("Invalid PDO!");
    KS_closeDriver();
    return;
  }

  outputTxt(" ");
  outputTxt("Choose a variable: ");
  for (int i = 0; i < pObjInfo->varCount; ++i) {
    KSEcatDataVarInfo* pVarInfo = pObjInfo->vars[i];
    outputDec(i, "", ": ", false);
    outputTxt(pVarInfo->name);
  }
  int varIndex = inputDec("Variable index: ", -1);

  if (varIndex < 0 || varIndex >= pObjInfo->varCount) {
    outputTxt("Invalid variable index!");
    KS_closeDriver();
    return;
  }

  pApp->varIndex    = pObjInfo->index;
  pApp->varSubIndex = pObjInfo->vars[varIndex]->subIndex;


  //------------------------------------------------------------------------------------------------------------
  // 开始进行数据交换
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_execKernelFunctionEx(
              pApp->hKernel,                            // 内核句柄
              "_startDataExchange",                     // 调用内核层函数
              KS_INVALID_HANDLE,                        // 传参
              KS_INVALID_HANDLE,                        // 上下文
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_execKernelFunctionEx", "Error while executing kernel functions!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 这是主循环。它将每100 ms更新所选变量的显示,直到用户按下“q”键或数据交换错误将被检测到时退出。
  //------------------------------------------------------------------------------------------------------------

  outputTxt(" ");
  outputTxt("Press [Q] to finish the process data exchange...");
  outputTxt(" ");

  for (;;) {
    waitTime(100 * ms);
    outputHex08(pApp->data, "Value: ", "\r", false);

    if (pApp->error != KS_OK)
      break;

    if (myKbhit() != 0) {
      int key = myGetch();
      if (key == 'q' || key == 'Q')
        break;
    }
  }

  if (pApp->error != KS_OK)
    outputErr(ksError, "Error while receiving data!");

  //  清理内核层DLL中分配的资源
  ksError = KS_execKernelFunctionEx(
              pApp->hKernel,                            // 内核句柄
              "_exitFunction",                          // 内核层退出函数
              KS_INVALID_HANDLE,                        // 传参
              KS_INVALID_HANDLE,                        // 上下文
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    outputErr(ksError, "KS_execKernelFunctionEx", "Error while deallocating resources on kernel level!");
    KS_closeDriver();
    return;
  }


  //------------------------------------------------------------------------------------------------------------
  // 使用共享句柄卸载内核DLL。
  // 尽管KS_closeDriver()释放了所有分配的资源(如共享内存和加载的内核),
  // 明确释放您分配的资源是很好的习惯。
  //------------------------------------------------------------------------------------------------------------
  
  // 释放内核
  ksError = KS_freeKernel(
              pApp->hKernel);                           // 内核句柄
  if (ksError != KS_OK)
    outputErr(ksError, "KS_freeKernel", "Unable to unload the kernel!");


  // 清理共享内存
  ksError = KS_freeSharedMemEx(
              hSharedMemory,                            // 共享内存句柄
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK)
    outputErr(ksError, "KS_freeSharedMemEx", "Unable to remove shared memory!");

  // 关闭设备,清理所有资源
  ksError = KS_closeDriver();
  if (ksError != KS_OK)
    outputErr(ksError, "KS_closeDriver", "Unable to close the driver!");

  waitTime(500 * ms);
  outputTxt(" ");
  outputTxt("End of program 'EtherCATBasics'.");
}

EtherCATBasics_dll.cpp

/* Copyright (c) 2011-2024 by Kithara Software GmbH. All rights reserved. */

//##############################################################################################################
//
// 文件:         EtherCATBasics_dll.cpp
//
// 使用模块: EtherCAT模块, 时钟模块, 网络模块, 实时模块 
//
// 用户应用程序和内核DLL之间的共享定义,例如:如何通过EtherCAT实现基本进程数据交换的示例
//
// 开发者:      m.gru 2011-05-11
//
//##############################################################################################################

   /*=====================================================================*\
   |                    *** 免责声明 ***                     			   |
   |                                                                       |
   |       本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任!		   |
   |																	   |
   \*=====================================================================*

//##############################################################################################################
//
// 目的:
//
// 该示例说明了如何在不考虑同步读取进程数据的情况下实现基本的进程数据交换。
// 此外,该示例还展示了如何检测从站可用的分布式时钟功能以及如何利用这些功能。
// 首先,我们打开网络作为 EtherCAT 主站,并创建一个 EtherCAT 主站。
// 然后,我们让用户从连接的 EtherCAT 从站中选择一个,并将其映射到创建的数据集。
// 如果所选的 EtherCAT 从站支持分布式时钟 (DC) 功能,用户可以选择其中一种支持的模式。
// 然后,用户必须从从站的一个 PDO 中选择一个变量。
// 将创建一个定时器,触发拓扑(已连接的 EtherCAT从站)与本示例实例化的 EtherCAT 主站之间的数据交换。
// 所选变量每 100ms 显示一次(定时器每 1ms 接收一次)。
//
//##############################################################################################################

//--------------------------------------------------------------------------------------------------------------
// 为了在主程序和内核 DLL 之间共享数据结构定义,我们使用一个公共头文件。
//--------------------------------------------------------------------------------------------------------------

#include "EtherCATBasics.h"

// 共享内存用于在内核层 DLL 和用户层的应用程序之间共享数据。
SharedData* _pSys = NULL;


// 指向从站选定 ECAT 设备变量接收到的数据。
void* _pSlaveData;

// 在数据集中要查看的变量的位置和长度。
int _bitOffset;
int _bitLength;

// 前置声明:定时器回调、数据集回调,声明在文件末尾定义的回调函数。
KSError __stdcall timerCallBack  (void* /*pArgs*/, void* /*pContext*/);
KSError __stdcall dataSetCallBack(void* /*pArgs*/, void* /*pContext*/);

//--------------------------------------------------------------------------------------------------------------
// 这是初始化函数。
// 它在加载内核后被调用,并将共享内存的句柄作为参数传递。
//
// 注意!请记住,所有函数都应声明为 'extern "C"'!
// 否则它们的名字可能无法被加载器找到。
// 必须通过 '__declspec(dllexport)' 导出它们。
//--------------------------------------------------------------------------------------------------------------

extern "C" KSError __declspec(dllexport) __stdcall _initFunction(void* pArgs, void* /*pContext*/) {
  KSError ksError;


  // 共享内存的指针通过 KS_execKernelFunctionEx() 作为 'pArgs' 参数传递。
  _pSys = (SharedData*)pArgs;


  //------------------------------------------------------------------------------------------------------------
  // 在这里我们等待主站连接到拓扑。
  // 代替轮询主站状态,您也可以注册一个回调来处理此事件。
  // 详情请参阅手册中的 KS_installEcatHandler()。
  //------------------------------------------------------------------------------------------------------------

  _pSys->masterState.structSize = sizeof(KSEcatMasterState); // 不要忘记初始化 structSize!

  for (int i = 0; i < 50; ++i) {
    ksError = KS_queryEcatMasterState(
                _pSys->hMaster,                         // 主站句柄
                &_pSys->masterState,                    // 返回KSEcatMasterState 结构体
                KSF_NO_FLAGS);                          // 无标志
    if (ksError != KS_OK)
      return ksError;

    if (_pSys->masterState.connected)
      break;

    KS_microDelay(100 * ms);
  }

  if (_pSys->masterState.connected == 0)
    return KSERROR_CATEGORY_ETHERCAT;

  //------------------------------------------------------------------------------------------------------------
  // 为了准备过程数据交换,我们需要创建一个数据集并分配一个同步对象给它。
  // 因为我们稍后会使用 KS_getEcatDataObjAddress() 来获取内存位置。
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_createEcatDataSet(
              _pSys->hMaster,                           // 主站句柄
              &_pSys->hDataSet,                         // 写入新数据集句柄的地址
              NULL,                                     // 数据集数据的应用程序空间指针(未使用)
              NULL,                                     // 数据集数据的内核空间指针(未使用)
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK)
    return ksError;


  //------------------------------------------------------------------------------------------------------------
  // 创建一个回调,当数据集从从站返回数据时调用。
  // 安装数据集处理器允许我们在从从站接收到数据时作出反应。
  //------------------------------------------------------------------------------------------------------------
  ksError = KS_createCallBack(
              &_pSys->hDataSetCallBack,                 // 写入新回调句柄的地址
              dataSetCallBack,                          // 回调函数
              NULL,                                     // 回调参数(未使用)
              KSF_DIRECT_EXEC,                          // 标志,这里内核级别
              0);                                       // 优先级(内核级别未使用)
  if (ksError != KS_OK)
    return ksError;

  // 安装创建的回调作为数据集处理器。
  ksError = KS_installEcatHandler(
              _pSys->hDataSet,                          // 数据集句柄
              KS_DATASET_SIGNAL,                        // 事件代码
              _pSys->hDataSetCallBack,                  // 回调句柄
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

 // 创建一个定时器回调,该回调将定期执行并将数据集发送到拓扑。
  ksError = KS_createCallBack(
              &_pSys->hTimerCallBack,                   // 写入新回调句柄的地址
              timerCallBack,                            // 回调函数
              NULL,                                     // 回调参数(未使用)
              KSF_DIRECT_EXEC,                          // 标志,这里内核级别
              0);                                       // 优先级(内核级别未使用)
  if (ksError != KS_OK)
    return ksError;

  // 创建一个周期为 1 毫秒的定时器,并分配回调给它。
  ksError = KS_createTimer(
              &_pSys->hTimer,                           // 写入新定时器句柄的地址
              1 * ms,                                   // 定时器周期(100纳秒单位)
              _pSys->hTimerCallBack,                    // 回调句柄
              KSF_REALTIME_EXEC |                       // 精确的高分辨率实时定时器
                KSF_DONT_START);                        // 不立即启动
  if (ksError != KS_OK)
    return ksError;
    
  return KS_OK;
}

//--------------------------------------------------------------------------------------------------------------
// 这是清理函数,关闭 EtherCAT 主站和网络设备,并移除定时器、其回调、数据集、数据集处理器、数据集回调和 EtherCAT 从站。
//--------------------------------------------------------------------------------------------------------------

extern "C" KSError __declspec(dllexport) __stdcall _exitFunction(void* /*pArgs*/, void* /*pContext*/) {
  if (_pSys == NULL)                                    // 共享内存未映射!
    return KSERROR_FUNCTION_NOT_AVAILABLE;              // _initFunction 未调用?

  KSError ksError;

  // 关闭
  ksError = KS_changeEcatState(
              _pSys->hDataSet,                          // 数据集句柄
              KS_ECAT_STATE_SAFEOP,                     // 状态
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

  // 停止定时器。
  ksError = KS_stopTimer(
              _pSys->hTimer);                           // 定时器句柄
  if (ksError != KS_OK)
    return ksError;

  // 切换 SAFEOP
  ksError = KS_changeEcatState(
              _pSys->hDataSet,                          // DataSet 句柄
              KS_ECAT_STATE_INIT,                       // 状态
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

  // 移除定时器
  ksError = KS_removeTimer(
              _pSys->hTimer);                           // 定时器句柄
  if (ksError != KS_OK)
    return ksError;

  // 移除定时器回调。
  ksError = KS_removeCallBack(
              _pSys->hTimerCallBack);                   // 定时器回调句柄
  if (ksError != KS_OK)
    return ksError;

  // 卸载数据集处理器
  ksError = KS_installEcatHandler(
              _pSys->hDataSet,                          // DataSet 句柄
              KS_DATASET_SIGNAL,                        // 回调事件类型
              KS_INVALID_HANDLE,                        // 使事件无效
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

  // 移除数据集回调
  ksError = KS_removeCallBack(
              _pSys->hDataSetCallBack);                 // DataSet 回调句柄
  if (ksError != KS_OK)
    return ksError;


  // 删除数据集
  ksError = KS_deleteEcatDataSet(
              _pSys->hDataSet);                         // DataSet 句柄
  if (ksError != KS_OK)
    return ksError;


  // 切换 OP 状态与 EtherCAT 从站
  ksError = KS_changeEcatState(
              _pSys->hSlave,                            // 从站句柄
              KS_ECAT_STATE_INIT,                       // 状态类型
              KSF_NO_FLAGS);                            // DataSet 
  if (ksError != KS_OK)
    return ksError;

  // EtherCAT 主站的状态应在结束时更改为 'init'
  ksError = KS_changeEcatState(
              _pSys->hMaster,                           // 主站句柄
              KS_ECAT_STATE_INIT,                       // 状态类型
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

  //------------------------------------------------------------------------------------------------------------
  // 删除EtherCAT从站
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_deleteEcatSlave(
              _pSys->hSlave);                           // 从站句柄
  if (ksError != KS_OK)
    return ksError;

  //------------------------------------------------------------------------------------------------------------
  // 关闭EtherCAT主站
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_closeEcatMaster(
              _pSys->hMaster);                          // 主站句柄
  if (ksError != KS_OK)
    return ksError;

  //------------------------------------------------------------------------------------------------------------
  // 关闭网络适配器
  //------------------------------------------------------------------------------------------------------------

  ksError = KS_closeNetwork(
              _pSys->hAdapter,                          // 网络适配器句柄
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

  return KS_OK;
}

//--------------------------------------------------------------------------------------------------------------
// _startDataExchange()函数将被用户调用,以开始数据交换。
//--------------------------------------------------------------------------------------------------------------

extern "C" __declspec(dllexport) KSError __stdcall _startDataExchange(void* /*pArgs*/, void* /*pContext*/) {
  KSError ksError;

  _pSys->error = KS_OK;

  // 查询所选变量的地址、位偏移量和位长度。
  ksError = KS_getEcatDataObjAddress(
              _pSys->hDataSet,                          // DataSet句柄
              _pSys->hSlave,                            // 从站句柄
              _pSys->varIndex,                          // 索引号
              _pSys->varSubIndex,                       // 子索引号
              NULL,                                     // 应用程序空间数据指针(未使用)
              &_pSlaveData,                             // 返回从站数据
              &_bitOffset,                              // 返回位偏移量
              &_bitLength,                              // 返回位长度
              KSF_NO_FLAGS);                            // 无标志
  if (ksError != KS_OK)
    return ksError;

  //------------------------------------------------------------------------------------------------------------
  // 如果 EtherCAT 从站具有分布式时钟(Distributed Clocks)功能,我们就需要启用该功能,以确保该示例能按预期运行。
  // 在进入 SAFEOP 之前必须调用 KS_activateEcatDcMode(),它将启动相关的定时器。
  //------------------------------------------------------------------------------------------------------------

  int64ref dcStartTime = 0;
  ksError = KS_activateEcatDcMode(
              _pSys->hDataSet,                          // 主站, 从站或DataSet 句柄
              dcStartTime,                              // startTime,在不久的将来的一段时间为0
              1000000,                                  // 循环时间(ns)->1 ms
              0,                                        // 偏移时间(ns)
              _pSys->hTimer,                            // 定时器句柄
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK && KSERROR_CODE(ksError) != KSERROR_FEATURE_NOT_LICENSED)
    return ksError;

  // 为了读取过程数据,必须将EtherCAT从站的状态更改为“KS_ECAT_STATE_SAFEOP”。
  ksError = KS_changeEcatState(
              _pSys->hDataSet,                          // DataSet 句柄
              KS_ECAT_STATE_SAFEOP,                     // 切换状态
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK)
    return ksError;

  // 现在将Slave切换到OP。
  ksError = KS_changeEcatState(
              _pSys->hDataSet,                          // DataSet 句柄
              KS_ECAT_STATE_OP,                         // 切换状态
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK)
    return ksError;

  return KS_OK;
}

// 该回调由定时器定期调用,并通过 KS_postEcatDataSet()启动进程数据交换。
KSError __stdcall timerCallBack(void* /*pArgs*/, void* /*pContext*/) {
  KSError ksError;

  if (_pSys->error != KS_OK)
    return _pSys->error;

  // 下发数据
  ksError = KS_postEcatDataSet(
              _pSys->hDataSet,                          // DataSet 句柄
              KSF_NO_FLAGS);                            // 无标记

  return _pSys->error = ksError;
}

//--------------------------------------------------------------------------------------------------------------
// 当 DataSet 从从站拓扑返回主站时,将调用此回调。
// 从站将向其中写入进程数据。
// KS_readEcatDataSet() 用于检索 DataSet 以访问数据。
//--------------------------------------------------------------------------------------------------------------

KSError __stdcall dataSetCallBack(void* /*pArgs*/, void* /*pContext*/) {
  KSError ksError;
  static bool running = false;

  ksError = KS_readEcatDataSet(
              _pSys->hDataSet,                          // DataSet 句柄
              KSF_NO_FLAGS);                            // 无标记
  if (ksError != KS_OK) {
    if ((running == 0) && (KSERROR_CODE(ksError) == KSERROR_NO_RESPONSE))

      // 该错误表示从站设备没有回答 KS_postEcatDataSet()。
      // 有些从站设备需要更多时间才能完全进入 SAFEOP 并应答 DataSet。
      // 因此,我们在此忽略这个错误。
      // 实际应用中的应用程序应该在启动和运行阶段以不同方式处理这个错误。
      return KS_OK;
    _pSys->error = ksError;
    return ksError;
  }

  //------------------------------------------------------------------------------------------------------------
  // 一旦 KS_readEcatDataSet() 成功返回数据,就说明从站程序已进入 SAFEOP 或更高版本。
  // 从现在起,我们将认真处理每一个错误。
  //------------------------------------------------------------------------------------------------------------

  running = true;

  //------------------------------------------------------------------------------------------------------------
  // 根据bitLength获取数据,然后通过bitOffset向右移位。
  //------------------------------------------------------------------------------------------------------------

  if (_bitLength > 0 && _bitLength <= 8)
    _pSys->data = *reinterpret_cast<byte*>(_pSlaveData) >> _bitOffset;

  if (_bitLength > 8 && _bitLength <= 16)
    _pSys->data = *reinterpret_cast<ushort*>(_pSlaveData) >> _bitOffset;

  if (_bitLength > 24 && _bitLength <= 32)
    _pSys->data = *reinterpret_cast<uint*>(_pSlaveData) >> _bitOffset;


  //------------------------------------------------------------------------------------------------------------
  // 获取想要的数据,并将结果存储在共享内存中,供应用程序访问。
  //------------------------------------------------------------------------------------------------------------

  if (_bitLength < 32)
    _pSys->data &= (1 << _bitLength) - 1;

    _pSys->error = ksError;
    return ksError;
}

//--------------------------------------------------------------------------------------------------------------
// 需要实现 DllMain 函数,该函数在 DLL 加载时不会被执行。
//
// 对于初始化,请定义一个特殊的 init 函数,并在调用 KS_loadKernel()时将其名称作为参数传递给它,
// 或者在加载内核的句柄以后在加载的 DLL 调用函数(如本例所示)时使用,
// 请不要在加载内核时执行的 init 函数,而是在加载内核后自己明确地调用它,并根据需要传递参数,如本例所示。
//--------------------------------------------------------------------------------------------------------------

#define WIN32_LEAN_AND_MEAN
#pragma pack(push, 8)
#include <windows.h>
#pragma pack(pop)

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID pReserved) {
  return TRUE;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/942535.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端初学基础

一.Web开发 前端三件 HTML &#xff0c;页面展现 CSS&#xff0c;样式 JS(JavaScript),动起来 二&#xff0c;HTML 1.HTML概念 网页&#xff0c;网站中的一个页面&#xff0c;网页是构成网站的基本元素&#xff0c;是承载各种网站应用的平台。通俗的说&#xff0c;网站就…

C语言结构体位定义(位段)的实际作用深入分析

1、结构体位段格式 struct struct_name {type [member_name] : width; };一般定义结构体&#xff0c;成员都是int、char等类型&#xff0c;占用的空间大小是固定的在成员名称后用冒号来指定位宽&#xff0c;可以指定每个成员所占用空间&#xff0c;并且也不用受结构体成员起始…

机器学习之PCA降维

主成分分析&#xff08;PCA&#xff0c;Principal Component Analysis&#xff09; 主成分分析&#xff08;PCA&#xff09;是一种常见的无监督学习技术&#xff0c;广泛应用于数据降维、数据可视化以及特征提取等任务。PCA的目标是通过线性变换将数据从高维空间映射到低维空间…

x86_64 Ubuntu 编译安装英伟达GPU版本的OpenCV

手把手带你在Linux上安装带GPU加速的opencv库&#xff08;C版本&#xff09;_opencv linux-CSDN博客 cmake \-D CMAKE_BUILD_TYPERELEASE \-D OPENCV_GENERATE_PKGCONFIGON \-D CMAKE_INSTALL_PREFIX/usr/local \-D OPENCV_EXTRA_MODULES_PATH/home/hwj/opencv/opencv_contrib…

Bert各种变体——RoBERTA/ALBERT/DistillBert

RoBERTa 会重复一个语句10次&#xff0c;然后每次都mask不同的15%token。丢弃了NSP任务&#xff0c;论文指出NSP任务有时甚至会损害性能。使用了BPE ALBERT 1. 跨层参数共享 可以共享多头注意力层的参数&#xff0c;或者前馈网络层的参数&#xff0c;或者全部共享。 实验结果…

ReMoE: Fully Differentiable Mixture-of-Experts with ReLU Routing

基本信息 &#x1f4dd; 原文链接: https://arxiv.org/abs/2412.14711&#x1f465; 作者: Ziteng Wang, Jianfei Chen, Jun Zhu&#x1f3f7;️ 关键词: Mixture-of-Experts, ReLU routing&#x1f4da; 分类: 机器学习 摘要 中文摘要 稀疏激活的专家混合模型&#xff08;…

【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1、输入数值 2、选择结构语句 3、计算结果并输出 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;编写一个程序&#xff0c;该程序需输入个人数据&#xff0c;进而预测其成年后的身高。 相关知识 为了完成本…

(Arxiv-2024)SwiftEdit:通过一步扩散实现闪电般快速的文本引导图像编辑

SwiftEdit&#xff1a;通过一步扩散实现闪电般快速的文本引导图像编辑 Paper是VinAI Research发表在Arxiv2024的工作 Paper Title:SwiftEdit: Lightning Fast Text-Guided Image Editing via One-Step Diffusion Code地址 Abstract 文本引导的图像编辑方面的最新进展利用了基于…

python langid识别一段字符串是哪国语言

分析&#xff1a; 在利用爬虫抓取亚马逊网站的数据时&#xff0c;有时会出现所抓页面的语言类型发生错误的情况&#xff08;如抓取沙特站数据时想要英文页面&#xff0c;抓到的确是阿拉伯语页面&#xff09;。在数据量大的时候人工排查这类异常情况是非常麻烦的&#xff0c;这时…

英特尔的创新困局与未来的转机:重塑还是消亡?

英特尔&#xff0c;这家曾引领全球半导体行业的巨头&#xff0c;如今正面临前所未有的挑战。从技术创新的停滞&#xff0c;到错失人工智能领域的制高点&#xff0c;再到被AMD和英伟达等竞争对手赶超&#xff0c;英特尔的创新之路似乎正走向尽头。但这是否意味着它的未来注定黯淡…

软考:系统架构设计师教材笔记(持续更新中)

教材中的知识点都会在。其实就是将教材中的废话删除&#xff0c;语言精练一下&#xff0c;内容比较多&#xff0c;没有标注重点 系统架构概述 定义 系统是指完成某一特定功能或一组功能所需要的组件集&#xff0c;而系统架构则是对所有组件的高层次结构表示&#xff0c;包括各…

No.1免费开源ERP:Odoo自定义字段添加到配置页中的技术分享

文 / 开源智造&#xff08;OSCG&#xff09; Odoo亚太金牌服务 在Odoo18之中&#xff0c;配置设定于管控各类系统配置层面发挥着关键之效用&#xff0c;使您能够对软件予以定制&#xff0c;以契合您特定的业务需求。尽管 Odoo 提供了一组强劲的默认配置选项&#xff0c;然而有…

YOLO11全解析:从原理到实战,全流程体验下一代目标检测

前言 一、模型介绍 二、网络结构 1.主干网络&#xff08;Backbone&#xff09; 2.颈部网络&#xff08;Neck&#xff09; 3.头部网络&#xff08;Head&#xff09; 三、算法改进 1.增强的特征提取 2.优化的效率和速度 3.更高的准确性与更少的参数 4.环境适应性强 5.…

虚幻引擎结构之ULevel

在虚幻引擎中&#xff0c;场景的组织和管理是通过子关卡&#xff08;Sublevel&#xff09;来实现的。这种设计不仅提高了资源管理的灵活性&#xff0c;还优化了游戏性能&#xff0c;特别是在处理大型复杂场景时。 1. 场景划分模式 虚幻引擎采用基于子关卡的场景划分模式。每个…

自动驾驶---Parking端到端架构

​​​​​​1 背景 自动泊车也是智能驾驶低速功能中比较重要的一部分&#xff0c;低速功能其中还包括记忆泊车&#xff0c;代客泊车等。传统的泊车算法通常使用基于规则或者搜索优化的方案来实现。然而&#xff0c;由于算法的复杂设计&#xff0c;这些方法在复杂的泊车场景中效…

[ffmpeg]编译 libx264

步骤 下载 libx264 git clone https://code.videolan.org/videolan/x264.git cd x264环境搭建 然后在开始菜单中找到并打开 x64 Native Tools Command Prompt for VS 2019 &#xff1a; 打开 msys2_shell.cmd -use-full-path 这时会打开 MSYS 的新窗口&#xff0c;先把一些汇…

华为管理变革之道:管理制度创新

目录 华为崛起两大因素&#xff1a;管理制度创新和组织文化。 管理是科学&#xff0c;150年来管理史上最伟大的创新是流程 为什么要变革&#xff1f; 向世界标杆学习&#xff0c;是变革第一方法论 体系之一&#xff1a;华为的DSTE战略管理体系&#xff08;解决&#xff1a…

【自留】Unity VR入门

帮老师写的&#xff0c;自留&#xff0c;不保证是很好的教程。 1.PICO开发指南&#xff08;官方&#xff09; 在该页面&#xff0c;能找到大部分能实现的功能&#xff0c;以及实现方式。非常推荐&#xff01;PICO Unity Integration SDK | PICO 开发者平台 2.如何快速入门&…

uniapp 项目基础搭建(vue2)

一 .创建项目 创建项目可以通过工具创建&#xff0c;也可以通过脚手架下载 1.通过工具创建 2.通过脚手架下载 安装脚手架 ​​npm install -g vue/cli 下载项目模板 vue create -p dcloudio/uni-preset-vue 项目名称 二. 下载相关依赖 1. 项目默认是没有package.json文件的&…

使用vcpkg安装opencv>=4.9后#include<opencv2/opencv.hpp>#include<opencv2/core.hpp>无效

使用vcpkg安装opencv>4.9后#include<opencv2/opencv.hpp>#include<opencv2/core.hpp>无效\无法查找或打开 至少从2024年开始&#xff0c;发布的vcpkg默认安装的opencv版本都是4.x版。4.8版本及以前&#xff0c;vcpkg编译后的opencv头文件目录是*/vcpkg/x64-win…