4Diac 图形化编程的执行过程
一 4Diac 介绍
Eclipse 4diac为基于 IEC 61499 标准的分布式工业过程测量和控制系统提供开源基础架构。4diac 包括集成开发环境IDE、运行时Forte、Function Block Libray、Example Projects。IEC 61499 定义了用于开发分布式工业控制解决方案的特定领域建模语言。IEC61499 扩展了IEC61131-1,改进了软件组件的封装,提高了可重用性、 提供了独立于供应商的格式,并简化了对控制器间通信的支持。(官网:https://eclipse.dev/4diac/index.php )
4 Diac IDE基于Eclipse框架,可以集成其他插件到4Diac IDE当中。基于IEC61499的系统采用以应用为中心的设计,每个应用程序通过将所需功能块FB相互连接创建,将不同的FB分布部署在不同的设备上。
运行时Forte,采用C++实现,支持在线重新配置应用程序,并能实现执行IEC61499标准提供的所有功能块类型。其中使用了大量C++类来定义IEC61499的功能块,模型,通信接口,过程接口和运行环境。
4 Diac LIB包含可以在4Diac Forte上使用的FB,IEC61499将FB分为三类:基本功能块BFB、复合功能块CFB、服务接口功能块SIFB(也称SFB)。每个FB包含事件接口和数据接口,接口为数据传输和事件触发提供连接点,主体描述FB的功能。对于BFB来说,功能描述是以执行控制图ECC形式提供;对于CFB来说,功能描述是以FBN的形式提供;对于SIFB来说,这种描述是以服务序列图的形式提供。
图1 4Diac主要构成
二 4Diac IDE 图形化编程
IDE用于编写应用程序,将FB功能块建立连接,将应用程序下发到不同的硬件设备或导出为.fboot 文件,供硬件设备上的Forte调用。图2为一个简易的两数相加的应用程序。
图2 F_ADD两数相加应用程序
通过IDE导出.fboot文件,creat Forte boot-files,可以看出该文件为应用程序文件,类似于XML的描述性语言。根据图3,可以看出该文件描述了我们所使用的FB,包括了F_ADD、INT2INT、INT2INT_1和INT2INT_2。Connection Source = 12和Connection Source = 11为输入端口输入的数据,Connection Source 和 Destination 指明了FB之间的连接方式。如果FB分布式部署在不同的设备上,则导出不同硬件设备的.fboot文件,供每个设备上的Forte调用。
图3 .fboot文件内容
在官网处的documention处也有讲解:
当您使用的 FORTE 启用 FORTE_SUPPORT_BOOTFILE 选项时,FORTE 会在启动时尝试加载一个所谓的引导文件。该引导文件必须与 4diac FORTE 二进制文件位于同一目录,并命名为 forte.fboot。该文件包含设备的 FB 网络配置,并将在 4diac FORTE 启动时自动实例化。要创建此类引导文件,您需要在下载选择视图中选择一个或多个设备或资源,然后从上下文菜单中选择创建 FORTE 引导文件。在向导中,您可以选择要创建启动文件的设备和资源,以及放置这些文件的目录。按 "完成 "后,将为每个设备创建一个引导文件。该引导文件将包含所选资源和资源中的 FB 网络。由于引导文件中只包含所选的资源,因此在开发过程中可以使用更多的资源(如测试代码),而在引导文件中只使用主要资源。引导文件的名称是系统和设备名称的组合点 fboot。使用前需要将其重命名为 forte.fboot。
因此,我的理解是:IDE将我们的图形化编程,转化为.fboot文件,供不同设备上的Forte调用,其中Forte包含了许多FB的库,这些FB主要是以C++形式进行开发。在Forte中src可以找到FB的C++源码。Forte在接收到.fboot文件时,根据.fboot文件调用这些FB,实现具体功能。
Forte运行时源码主要包括:适配程序arch(与不同的操作系统适配)、com(一些扩展的通信协议,例如:HTTP、MODBUS、MQTT、OPC_UA等)、核心core(基本通信功能块cominfra、输入输出程序IO、数据类型datatypes、lua、fmi等)、模块module(IEC61161-3功能块、类型转换、各种特定的过程接口,例如树莓派的PiFace,raspberry_sps接口板的过程接口类程序)、stdfblib(标准功能库的实现)
在此,分析一下IEC61131-3当中的F_ADD模块,F_ADD收到输入事件,与输入事件相关的数据输入IN1和IN2被刷新,事件被传递到ECC,内部的加法功能触发执行,完成执行后提供新的输出数据,刷新与输出事件相关的输出数据OUT,发送输出事件。
F_ADD.h文件:
#ifndef _F_ADD_H_ #define _F_ADD_H_ #include <funcbloc.h> class FORTE_F_ADD: public CFunctionBlock{ DECLARE_FIRMWARE_FB(FORTE_F_ADD) private: static const CStringDictionary::TStringId scm_anDataInputNames[]; static const CStringDictionary::TStringId scm_anDataInputTypeIds[]; CIEC_ANY_MAGNITUDE &IN1() { return *static_cast<CIEC_ANY_MAGNITUDE*>(getDI(0)); }; CIEC_ANY_MAGNITUDE &IN2() { return *static_cast<CIEC_ANY_MAGNITUDE*>(getDI(1)); }; static const CStringDictionary::TStringId scm_anDataOutputNames[]; static const CStringDictionary::TStringId scm_anDataOutputTypeIds[]; CIEC_ANY_MAGNITUDE &st_OUT() { return *static_cast<CIEC_ANY_MAGNITUDE*>(getDO(0)); }; static const TEventID scm_nEventREQID = 0; static const TForteInt16 scm_anEIWithIndexes[]; static const TDataIOID scm_anEIWith[]; static const CStringDictionary::TStringId scm_anEventInputNames[]; static const TEventID scm_nEventCNFID = 0; static const TForteInt16 scm_anEOWithIndexes[]; static const TDataIOID scm_anEOWith[]; static const CStringDictionary::TStringId scm_anEventOutputNames[]; static const SFBInterfaceSpec scm_stFBInterfaceSpec; FORTE_FB_DATA_ARRAY(1, 2, 1, 0); void executeEvent(int pa_nEIID); public: FUNCTION_BLOCK_CTOR(FORTE_F_ADD){ }; template<typename T> void calculateValue(){ T &roIn1(static_cast<T&>(IN1())); T oIn2; oIn2.saveAssign(IN2()); st_OUT().saveAssign(ADD(roIn1,oIn2)); } virtual ~FORTE_F_ADD(){}; }; #endif //close the ifdef sequence from the beginning of the file
F_ADD.cpp文件
#include "F_ADD.h" #ifdef FORTE_ENABLE_GENERATED_SOURCE_CPP #include "F_ADD_gen.cpp" #endif #include <anyhelper.h> DEFINE_FIRMWARE_FB(FORTE_F_ADD, g_nStringIdF_ADD) const CStringDictionary::TStringId FORTE_F_ADD::scm_anDataInputNames[] = {g_nStringIdIN1, g_nStringIdIN2}; const CStringDictionary::TStringId FORTE_F_ADD::scm_anDataInputTypeIds[] = {g_nStringIdANY_MAGNITUDE, g_nStringIdANY_MAGNITUDE}; const CStringDictionary::TStringId FORTE_F_ADD::scm_anDataOutputNames[] = {g_nStringIdOUT}; const CStringDictionary::TStringId FORTE_F_ADD::scm_anDataOutputTypeIds[] = {g_nStringIdANY_MAGNITUDE}; const TForteInt16 FORTE_F_ADD::scm_anEIWithIndexes[] = {0}; const TDataIOID FORTE_F_ADD::scm_anEIWith[] = {0, 1, 255}; const CStringDictionary::TStringId FORTE_F_ADD::scm_anEventInputNames[] = {g_nStringIdREQ}; const TDataIOID FORTE_F_ADD::scm_anEOWith[] = {0, 255}; const TForteInt16 FORTE_F_ADD::scm_anEOWithIndexes[] = {0}; const CStringDictionary::TStringId FORTE_F_ADD::scm_anEventOutputNames[] = {g_nStringIdCNF}; const SFBInterfaceSpec FORTE_F_ADD::scm_stFBInterfaceSpec = { 1, scm_anEventInputNames, scm_anEIWith, scm_anEIWithIndexes, 1, scm_anEventOutputNames, scm_anEOWith, scm_anEOWithIndexes, 2, scm_anDataInputNames, scm_anDataInputTypeIds, 1, scm_anDataOutputNames, scm_anDataOutputTypeIds, 0, 0 }; void FORTE_F_ADD::executeEvent(int pa_nEIID){ if (scm_nEventREQID == pa_nEIID) { anyMagnitudeFBHelper<FORTE_F_ADD>(IN1().getDataTypeID(), *this); sendOutputEvent(scm_nEventCNFID); } }
anyhelper.h文件
template<class T> void anyMagnitudeFBHelper(CIEC_ANY::EDataTypeID pa_eDataTypeId, T &pa_roFB){ switch (pa_eDataTypeId){ case CIEC_ANY::e_REAL: #ifdef FORTE_USE_REAL_DATATYPE pa_roFB.template calculateValue<CIEC_REAL>(); #else //FORTE_USE_REAL_DATATYPE DEVLOG_ERROR("REAL is not compiled in this version of forte and you are still trying to use it. Exiting"); assert(0); #endif //FORTE_USE_REAL_DATATYPE break; case CIEC_ANY::e_LREAL: #ifdef FORTE_USE_LREAL_DATATYPE pa_roFB.template calculateValue<CIEC_LREAL>(); #else //FORTE_USE_LREAL_DATATYPE DEVLOG_ERROR("LREAL is not compiled in this version of forte and you are still trying to use it. Exiting"); assert(0); #endif //FORTE_USE_LREAL_DATATYPE break; default: if(pa_eDataTypeId <= CIEC_ANY::e_TIME){ #ifdef FORTE_USE_64BIT_DATATYPES pa_roFB.template calculateValue<CIEC_LINT>(); #else //FORTE_USE_64BIT_DATATYPES pa_roFB.template calculateValue<CIEC_DINT>(); #endif //FORTE_USE_64BIT_DATATYPES } break; } }
根据F_ADD.cpp文件可以看到,executeEvent函数传入参数为pa_nEIID(当前执行事件ID),判断当前执行事件ID和功能块输入事件ID是否相同,如果相同则执行代码。调用anyMagnitudeFBHelper函数,传入参数为IN1输入的数据类型,*this指向FORTE_F_ADD。
anyMagnitudFBHelper函数中,根据传递的第一个参数IN1数据输入的数据类型,利用分支语句决定调用计算函数。可以看出最后调用的都是Forte_F_ADD.template 中的calculateValue函数。
calculateValue函数,函数定义中的模板参数T表示数据类型,该函数可以根据不同的数据类型执行计算。
T &roIN1(static_cast<T&>(IN1())); T&roIN1 表示声明了一个类型为T的引用变量roIN1, static_cast<T&>(IN1())是一个静态类型转换操作。这行代码的作用就是将IN1数据输入转换为类型T的引用,并赋值给roIN1。
T oIN2;声明了一个oIN2的变量,类型模板参数为T,用于存储第二个输入数据IN2()的值。
oIn2.saveAssign(IN);将IN2()的数值保存在oIN2。
st_OUT().saveAssign(ADD(roIn1,oIn2));将roIN1与OIN2进行加法计算,并将结果保存在输出数据中。