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

news2024/12/26 7:29:01

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!");

  //  清理内核层资源
  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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

秒验简介与下载说明

秒验简介与下载说明 产品概述 秒验是一款帮助开发者实现一键验证功能的产品&#xff0c;从根源上降低企业验证成本&#xff0c; 有效提高拉新转化率&#xff0c;降低因验证带来的流失率&#xff0c;3秒完成手机号验证 SDK信息 下载SDK 下载地址 SDK提供Maven和pod引入两种方…

算法学习(17)—— FloodFill算法

目录 关于FloodFill算法 部分OJ题详解 733. 图像渲染 200. 岛屿数量 695. 岛屿的最大面积 130. 被围绕的区域 417. 太平洋大西洋水流问题 529. 扫雷问题 LCR130. 衣橱整理 关于FloodFill算法 爆搜&#xff0c;深搜&#xff0c;回溯的算法原理并不难&#xff0c;这类题…

美国辅料查询之FDA批准药用辅料数据库(IID数据库)

药用辅料的性质很大程度上决定了制剂的性质&#xff0c;每一种新的药用辅料的问世&#xff0c;都会为制剂技术的发展带来新的机遇&#xff0c;每一种药用辅料都可能让制剂研发员开发出新剂型药物&#xff0c;所以在药物制剂研发过程中&#xff0c;药用辅料的信息调研是不可或缺…

SpringAI人工智能开发框架006---SpringAI多模态接口_编程测试springai多模态接口支持

可以看到springai对多模态的支持. 同样去创建一个项目 也是跟之前的项目一样,修改版本1.0.0 这里 然后修改仓库地址,为springai的地址 然后开始写代码

【ELK】filebeat采集数据输出到kafka指定topic

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 背景filebeat主体配置filebeat.inputs部分filebeat.output部分 filebeat完整配置 背景 今天收到需求&#xff0c;生产环境中通需要优化filebeat的输出&#xff0c;…

知识图谱+大模型:打造全新智慧城市底层架构

在数字化时代&#xff0c;智慧城市的建设正迎来新一轮的变革。本文将探讨如何结合知识图谱和大模型技术&#xff0c;构建智慧城市的全新底层架构&#xff0c;以应对日益增长的数据量和复杂性&#xff0c;提升城市管理的智能化水平。 知识图谱&#xff1a;智慧城市的知识库 知识…

网络安全 | 云计算中的数据加密与访问控制

网络安全 | 云计算中的数据加密与访问控制 一、前言二、云计算概述2.1 云计算的定义与特点2.2 云计算的服务模式2.3 云计算的数据安全挑战 三、数据加密技术在云计算中的应用3.1 对称加密算法3.2 非对称加密算法3.3 混合加密算法 四、云计算中的访问控制模型4.1 基于角色的访问…

计算机毕业设计Python+卷积神经网络租房推荐系统 租房大屏可视化 租房爬虫 hadoop spark 58同城租房爬虫 房源推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

攻防世界 - Web - Level 1 unseping

关注这个靶场的其它相关笔记&#xff1a;攻防世界&#xff08;XCTF&#xff09; —— 靶场笔记合集-CSDN博客 0x01&#xff1a;Write UP 本关是一个 PHP 代码审计关卡&#xff0c;考察的是 PHP 反序列化漏洞以及命令执行的一些绕过手段&#xff0c;下面笔者将带你一步步过关。…

黑马程序员JavaWeb开发教程(前端部分) ---笔记分享

总结 此篇文章记录的内容是不全的&#xff0c;我觉得基础的部分没有记录&#xff0c;我想主要学的是此课程的后端部分&#xff0c;前端部分学校有学习过&#xff0c;我就开倍速一带而过啦&#xff0c;还有就是学校学的是Vue3和此视频讲的Vue2还是有一定区别的。希望能对大家有…

【统计的思想】统计抽样测试(二)

在统计抽样测试里&#xff0c;一旦我们选定了某个测试方案(n|Ac)&#xff0c;我们就可以算出任意不合格品率p对应的接收概率L(p)。把各种可能的p值对应的L(p)连成一条曲线&#xff0c;这就是测试方案(n|Ac)的操作特性曲线。比如&#xff0c;方案(80|1)的操作特性曲线长这个样子…

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用I-FGSSM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集I-FGSSM介绍I-FGSSM代码实现I-FGSSM算法实现攻击效果 代码汇总ifgssm.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器&#xff1a; Pytorch | 从零构建AlexNet对CIFAR10进行分类 Pytorch…

【多维DP】力扣576. 出界的路径数

给你一个大小为 m x n 的网格和一个球。球的起始坐标为 [startRow, startColumn] 。你可以将球移到在四个方向上相邻的单元格内&#xff08;可以穿过网格边界到达网格之外&#xff09;。你 最多 可以移动 maxMove 次球。 给你五个整数 m、n、maxMove、startRow 以及 startColu…

react防止页面崩溃

在 React 中&#xff0c;ErrorBoundary 组件是一种用于捕获并处理其子组件树中发生的 JavaScript 错误的机制。它可以帮助你在应用程序中实现优雅的错误处理&#xff0c;防止整个应用崩溃&#xff0c;并为用户提供友好的错误提示。ErrorBoundary 通过使用 React 的生命周期方法…

Python使用requests_html库爬取掌阅书籍(附完整源码及使用说明)

教程概述 本教程先是幽络源初步教学分析掌阅书籍的网络结构&#xff0c;最后提供完整的爬取源码与使用说明&#xff0c;并展示结果&#xff0c;切记勿将本教程内容肆意非法使用。 原文链接&#xff1a;Python使用requests_html库爬取掌阅书籍&#xff08;附完整源码及使用说明…

基于earthSDK三维地图组件开发

上效果 功能点 测量分析相机位置切换geojson数据加载地图打点&#xff0c;显示信息点击回传数据二三位切换 这里二三维切换通上篇openlayers分享&#xff0c;技术交流V:bloxed <template><div class"h100 w100"><div style"width:100%; heig…

基于JavaWeb的流动摊位管理系统

一、系统背景与意义 随着城市化进程的加速和市场经济的发展&#xff0c;流动摊位已经成为城市商业活动中不可或缺的一部分。然而&#xff0c;传统的流动摊位管理方式存在诸多弊端&#xff0c;如信息不透明、管理效率低下、租赁不公等。因此&#xff0c;开发一种高效、便捷、智…

自动驾驶3D目标检测综述(六)

停更了好久终于回来了&#xff08;其实是因为博主去备考期末了hh&#xff09; 这一篇接着&#xff08;五&#xff09;的第七章开始讲述第八章的内容。第八章主要介绍的是三维目标检测的高效标签。 目录 第八章 三维目标检测高效标签 一、域适应 &#xff08;一&#xff09;…

100V宽压输入反激隔离电源,适用于N道沟MOSFET或GaN或5V栅极驱动器,无需光耦合

说明: PC4411是一个隔离的反激式控制器在宽输入电压下具有高效率范围为2.7V至100V。它直接测量初级侧反激输出电压波形&#xff0c;不需要光耦合器或第三方用于调节的绕组。设置输出只需要一个电阻器电压。PC4411提供5V栅极驱动驱动外部N沟道MOSFET的电压或GaN。内部补偿和软启…

1.系统学习-线性回归

系统学习-线性回归 前言线性回归介绍误差函数梯度下降梯度下降示例 回归问题常见的评价函数1. MAE, mean absolutely error2. MSE, mean squared error3. R square &#xff08;决定系数或R方&#xff09; 机器学习建模流程模型正则化拓展阅读作业 链接: 2.系统学习-逻辑回归 …