IEC61499开源项目FORTE部分源码分析

一、IEC 61499简介

IEC 61499 作为工业自动化领域分布式控制系统级建模语言的标准,其第一(体系结构)、二(软件工具要求)、四(兼容文件的规则)部分的第一版于 2005 年正式发布,并在 2012 年发布第一部分的 2.0 版本,主要解决了旧版中诸如功能块执行控制表语法语义存在的歧义等一系列问题。

 图1 分布式应用的功能块连接

设备是多条资源的容器,并提供这些资源与通讯网络、传感器和执行器之间的接口。这些接口提供的服务由支持分布式应用的专用资源中的SIFB完成。通讯网络把各分散设备集成为一个完整的系统。这样,分布在不同物理设备中的功能块形成了一个真正的分布式应用。

分布式指的是上图的某个功能块可以部署到某一个硬件上,多个硬件设备共同实现一个工业流程,红线代表事件流,蓝线代表数据流,各个功能块协同工作,但对于每个功能块到底是在哪里并不关心,61499协议并不对底层的具体实现方式进行定义。

1. 4diac FORTE

Eclipse Foundation 4diac project 是一个支持IEC-61499协议的开源项目,

IDEFORTE、功能块库等部分组成。

 图2. 4diac project

2. 4diac IDE

IDE用于编辑PLC应用逻辑,下发PLC程序到硬件设备或导出PLC为.fboot文件,供PLC运行时使用。

下图是IEC-61499的DEMO,主要功能是一个实现一个opc ua服务器,对外提供10个数据点,10个数据由随机数生成器产生,随机数生成器每50ms更新一次。 demo内容下载链接在文章最后。

图3 IDE编辑IEC61499 demo

3. FORTE编译

下载FORTE源码并编译:

unzip forte_2.0.1.zip
cd forte_2.0.1
mkdir build
cd build
cmake -DFORTE_ARCHITECTURE=Posix -DFORTE_COM_ETH=ON \
-DFORTE_COM_FBDK=ON -DFORTE_COM_LOCAL=ON \
-DFORTE_TESTS=OFF -DFORTE_MODULE_CONVERT=ON \
-DFORTE_MODULE_IEC61131=ON -DFORTE_MODULE_UTILS=ON  ..

make –j

运行FORTE:

./forte -c 0.0.0.0:61500 -f server.fboot

-c设置PLC程序监听的地址。

-f指定启动文件,即从IDE中导出的fboot格式PLC程序。

4. server.fboot文件内容

以类似于XML文件的格式,定义PLC程序中用到的功能块和全部的连接信息。

图4 FB_RANDOM功能块到.fboot的转换

Action定义CREATE、WRITE、START、DELETE等操作。

fastWrite;<Request ID="5" Action="CREATE"><FB Name="FB_RANDOM" Type="FB_RANDOM" /></Request>

创建名为FB_RANDOM的功能块,ID为5,Type为FB_RANDOM。

fastWrite;<Request ID="6" Action="WRITE"><Connection Source="0" Destination="FB_RANDOM.SEED" /></Request>

为一个连接写入值,Source是0,Destination是FB_RANDOM.SEED。

fastWrite;<Request ID="12" Action="CREATE"><Connection Source="E_CYCLE.EO" Destination="FB_RANDOM.REQ" /></Request>

创建从E_CYCLE.EO到FB_RANDOM.REQ的连接。

fastWrite;<Request ID="19" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_1" /></Request>

创建从FB_RANDOM.VAL到PUBLISH_10.SD_1的连接。

完整的server.fboot的内容,列在本文最后。

二、PLC功能块到汇编代码的过程

区别于iec61131,比如OpenPLC的实现方式,OpenPLC是将PLC的逻辑变成中间语言的.st文件,再生成.cpp进行编译后生成 elf的可执行程序再进行执行的。

FORTE是将PLC程序导出成类似于XML格式的.fboot文件,在设备上运行的时候,由FORTE进行动态解析建立PLC逻辑。

下面分析.fboot文件在FORTE中的解析和处理流程。此处我们以FB_RANDOM功能块为例进行分析。

1.FB_RANDOM功能块

FB_RANDOM功能块的作用是生成介于0-1之间的float随机数。

 

图2.3 FB_RANDOM的对外接口

FB_RANDOM功能块的对外接口如下所示:

按61499的规范,功能块的左边是输入,右边是输出。

  1. INIT用于接收事件进行初始化。
  2. INITO用于对外输出初始化完毕事件,在INIT结束后发出。
  3. REQ用于接收外部请求事件,请求一次生成一个随机数。
  4. SEED即外部给的随机数种子。
  5. CNF是Execution Confirmation执行确认,REQ请求事件处理完毕后输出执行完毕事件。
  6. VAL是功能块的输出,输出生成的随机数。.fboot文件到具体逻辑的转换

2.FORTE_FB_RANDOM 的功能实现

IDE中的每一个功能块都与对应的cpp实现相关联。

如果要在IDE中自定义新的功能块,必须导出成对应的.cpp和.h文件,并加入到FORTE的源代码的src\modules目录中,重新编译FORTE才能启用此功能块。目前FORTE的这种实现方式添加新的功能块还不是特别方便。

自定义功能块功能支持ST语法或C语法,编写的程序会导出到.cpp文件中。

IEC 61499功能块按功能分为基本功能块、复合功能块、服务接口功能块(通讯功能块和管理功能块)和适配器(插件和插座)。

src\core\funcbloc.h中定义了CFunctionBlock,CFunctionBlock是所有功能块的基类。

继承此类的有4个类:

basicfb.h定义基本功能块类CBasicFB。

cfb.h定义复合功能块类CCompositeFB。

esfb.h定义服务接口功能块类CEventSourceFB。

adapter.h定义接口类CAdapter。

FB_RANDOM功能块对应的cpp类名称为:FORTE_FB_RANDOM,继承自CBasicFB。

class FORTE_FB_RANDOM: public CBasicFB{

3.FB_RANDOM.h

FB_RANDOM类继承了 CBasicFB,第一行DECLARE_FIRMWARE_FB(FORTE_FB_RANDOM)用于定义

宏展开后如下:

定义了一个static 静态函数createFB,用于生成此类的实例。

class FORTE_FB_RANDOM: public CBasicFB{
  DECLARE_FIRMWARE_FB(FORTE_FB_RANDOM)

private:
  static const CStringDictionary::TStringId scm_anDataInputNames[];
  static const CStringDictionary::TStringId scm_anDataInputTypeIds[];
  CIEC_UINT &SEED() {
    return *static_cast<CIEC_UINT*>(getDI(0));
  };

  static const CStringDictionary::TStringId scm_anDataOutputNames[];
  static const CStringDictionary::TStringId scm_anDataOutputTypeIds[];
  CIEC_REAL &VAL() {
    return *static_cast<CIEC_REAL*>(getDO(0));
  };

  static const TEventID scm_nEventINITID = 0;
  static const TEventID scm_nEventREQID = 1;
  static const TForteInt16 scm_anEIWithIndexes[];
  static const TDataIOID scm_anEIWith[];
  static const CStringDictionary::TStringId scm_anEventInputNames[];

  static const TEventID scm_nEventINITOID = 0;
  static const TEventID scm_nEventCNFID = 1;
  static const TForteInt16 scm_anEOWithIndexes[];
  static const TDataIOID scm_anEOWith[];
  static const CStringDictionary::TStringId scm_anEventOutputNames[];

  static const SFBInterfaceSpec scm_stFBInterfaceSpec;

   FORTE_BASIC_FB_DATA_ARRAY(2, 1, 1, 0, 0);

virtual void setInitialValues();
  void alg_INIT(void);
  void alg_REQ(void);
  static const TForteInt16 scm_nStateSTART = 0;
  static const TForteInt16 scm_nStateREQ = 1;
  static const TForteInt16 scm_nStateState = 2;

  void enterStateSTART(void);
  void enterStateREQ(void);
  void enterStateState(void);

  virtual void executeEvent(int pa_nEIID);

public:
  FORTE_FB_RANDOM(CStringDictionary::TStringId pa_nInstanceNameId, CResource *pa_poSrcRes) : 
       CBasicFB(pa_poSrcRes, &scm_stFBInterfaceSpec, pa_nInstanceNameId,
              0, m_anFBConnData, m_anFBVarsData){
  };

  virtual ~FORTE_FB_RANDOM(){};

};

4.FB_RANDOM.cpp

 在FB_RANDOM.cpp中是这样实现的:


#include "FB_RANDOM.h"
#ifdef FORTE_ENABLE_GENERATED_SOURCE_CPP
#include "FB_RANDOM_gen.cpp"
#endif
#include <time.h>
#include <stdlib.h>

DEFINE_FIRMWARE_FB(FORTE_FB_RANDOM, g_nStringIdFB_RANDOM)

const CStringDictionary::TStringId FORTE_FB_RANDOM::scm_anDataInputNames[] = {g_nStringIdSEED};

const CStringDictionary::TStringId FORTE_FB_RANDOM::scm_anDataInputTypeIds[] = {g_nStringIdUINT};

const CStringDictionary::TStringId FORTE_FB_RANDOM::scm_anDataOutputNames[] = {g_nStringIdVAL};

const CStringDictionary::TStringId FORTE_FB_RANDOM::scm_anDataOutputTypeIds[] = {g_nStringIdREAL};

const TForteInt16 FORTE_FB_RANDOM::scm_anEIWithIndexes[] = {0, -1};
const TDataIOID FORTE_FB_RANDOM::scm_anEIWith[] = {0, 255};
const CStringDictionary::TStringId FORTE_FB_RANDOM::scm_anEventInputNames[] = {g_nStringIdINIT, g_nStringIdREQ};

const TDataIOID FORTE_FB_RANDOM::scm_anEOWith[] = {0, 255};
const TForteInt16 FORTE_FB_RANDOM::scm_anEOWithIndexes[] = {-1, 0, -1};
const CStringDictionary::TStringId FORTE_FB_RANDOM::scm_anEventOutputNames[] = {g_nStringIdINITO, g_nStringIdCNF};

const SFBInterfaceSpec FORTE_FB_RANDOM::scm_stFBInterfaceSpec = {
  2,  scm_anEventInputNames,  scm_anEIWith,  scm_anEIWithIndexes,
  2,  scm_anEventOutputNames,  scm_anEOWith, scm_anEOWithIndexes,  1,  scm_anDataInputNames, scm_anDataInputTypeIds,
  1,  scm_anDataOutputNames, scm_anDataOutputTypeIds,
  0, 0
};


void FORTE_FB_RANDOM::setInitialValues(){
  SEED().fromString("0");
}

void FORTE_FB_RANDOM::alg_INIT(void){
// WARNING - Don't forget to add #include <time.h>
  if (SEED() == 0) {
    srand((unsigned int) time(NULL) );
  } else {
    srand( SEED() );
  }
}

void FORTE_FB_RANDOM::alg_REQ(void){
  VAL() = static_cast<TForteFloat>(rand())/static_cast<TForteFloat>(RAND_MAX);
}


void FORTE_FB_RANDOM::enterStateSTART(void){
  m_nECCState = scm_nStateSTART;
}

void FORTE_FB_RANDOM::enterStateREQ(void){
  m_nECCState = scm_nStateREQ;
  alg_REQ();
  sendOutputEvent( scm_nEventCNFID);
}

void FORTE_FB_RANDOM::enterStateState(void){
  m_nECCState = scm_nStateState;
  alg_INIT();
  sendOutputEvent( scm_nEventINITOID);
}

void FORTE_FB_RANDOM::executeEvent(int pa_nEIID){
  bool bTransitionCleared;
  do{
    bTransitionCleared = true;
    switch(m_nECCState){
      case scm_nStateSTART:
        if(scm_nEventREQID == pa_nEIID)
          enterStateREQ();
        else
        if(scm_nEventINITID == pa_nEIID)
          enterStateState();
        else
          bTransitionCleared  = false; //no transition cleared
        break;
      case scm_nStateREQ:
        if(1)
          enterStateSTART();
        else
          bTransitionCleared  = false; //no transition cleared
        break;
      case scm_nStateState:
        if(1)
          enterStateSTART();
        else
          bTransitionCleared  = false; //no transition cleared
        break;
      default:
      DEVLOG_ERROR("The state is not in the valid range! The state value is: %d. The max value can be: 2.", m_nECCState.operator TForteUInt16 ());
        m_nECCState = 0; //0 is always the initial state
        break;
    }
    pa_nEIID = cg_nInvalidEventID;  // we have to clear the event after the first check in order to ensure correct behavior
  }while(bTransitionCleared);
}

三、FB_RANDOM 功能块的动态加载

1. .fboot文件解析流程

.fboot文件中定义了PLC程序的功能块及相互的连接。

stdfblib\ita\DEV_MGR.cpp实现对.fboot文件的处理。通过解析.fboot文件建立各个功能块及输入和输出端口之间的连接。

DEV_MGR::parseAndExecuteMGMCommand(char *paDest, char *paCommand)

对.fboot进行文本解析,解析后的需要进行的处理保存在mCommand变量中,再执行executeMGMCommand函数进行相关资源的创建。

forte::core::SManagementCMD mCommand;

executeMGMCommand(mCommand);

在core\resource.cpp中实现资源创建。

可以看到FORTE支持动态创建,即用LUA语言进行动态创建,但暂时在.fboot文件中还没有看到lua的脚本,并且lua脚本可能也是类似的工作机制,并不能实现自定义功能块逻辑。

EMGMResponse CResource::executeMGMCommand(forte::core::SManagementCMD &paCommand){

最终执行到FORTE_FB_RANDOM的构造函数。

FB_RANDOM功能块初始化的调用堆栈如下所示:

#0  FORTE_FB_RANDOM::FORTE_FB_RANDOM (this=0x180, pa_nInstanceNameId=0, pa_poSrcRes=0x555555695119 <operator new(unsigned long)+28>) at /home/plc/forte_2.0.1/src/modules/utils/FB_RANDOM.h:66
#1  0x0000555555722445 in FORTE_FB_RANDOM::createFB (pa_nInstanceNameId=666, pa_poSrcRes=0x7ffff0004b60) at /home/plc/forte_2.0.1/src/modules/utils/FB_RANDOM.h:21
#2  0x00005555556b82cf in CTypeLib::CFBTypeEntry::createFBInstance (this=0x5555557b7020 <FORTE_FB_RANDOM::csm_oFirmwareFBEntry_FORTE_FB_RANDOM>, pa_nInstanceNameId=666, pa_poSrcRes=0x7ffff0004b60)
    at /home/plc/forte_2.0.1/src/core/./datatypes/../typelib.h:155
#3  0x00005555556b7e86 in CTypeLib::createFB (pa_nInstanceNameId=666, pa_nFBTypeId=666, pa_poRes=0x7ffff0004b60) at /home/plc/forte_2.0.1/src/core/typelib.cpp:118
#4  0x00005555556b33c0 in forte::core::CFBContainer::createFB (this=0x7ffff0004bd8, paNameListIt=..., paTypeName=666, paRes=0x7ffff0004b60) at /home/plc/forte_2.0.1/src/core/fbcontainer.cpp:68
#5  0x00005555556b476e in CResource::executeMGMCommand (this=0x7ffff0004b60, paCommand=...) at /home/plc/forte_2.0.1/src/core/resource.cpp:73
#6  0x00005555556b1418 in CDevice::executeMGMCommand (this=0x5555557cd870, paCommand=...) at /home/plc/forte_2.0.1/src/core/device.cpp:30
#7  0x000055555572a323 in DEV_MGR::parseAndExecuteMGMCommand (this=0x5555557cecc0, paDest=0x7ffff0005814 "fastWrite", paCommand=0x7ffff000581e "<Request ID=\"5") at /home/plc/forte_2.0.1/src/stdfblib/ita/DEV_MGR.cpp:637
#8  0x000055555572a6a3 in DEV_MGR::executeCommand (this=0x5555557cecc0, paDest=0x7ffff0005814 "fastWrite", paCommand=0x7ffff000581e "<Request ID=\"5") at /home/plc/forte_2.0.1/src/stdfblib/ita/DEV_MGR.cpp:696
#9  0x000055555572bad3 in ForteBootFileLoader::loadBootFile (this=0x7ffff724bde0) at /home/plc/forte_2.0.1/src/stdfblib/ita/ForteBootFileLoader.cpp:90
#10 0x0000555555728771 in DEV_MGR::executeEvent (this=0x5555557cecc0, paEIID=0) at /home/plc/forte_2.0.1/src/stdfblib/ita/DEV_MGR.cpp:68
#11 0x00005555556b2881 in CFunctionBlock::receiveInputEvent (this=0x5555557cecc0, paEIID=0, paExecEnv=0x5555557cdf60) at /home/plc/forte_2.0.1/src/core/funcbloc.cpp:314
#12 0x00005555556b8510 in CEventChainExecutionThread::mainRun (this=0x5555557cdf60) at /home/plc/forte_2.0.1/src/core/ecet.cpp:49
#13 0x00005555556b8454 in CEventChainExecutionThread::run (this=0x5555557cdf60) at /home/plc/forte_2.0.1/src/core/ecet.cpp:34
#14 0x0000555555695aa7 in forte::arch::CThreadBase<unsigned long, 0ul, forte::arch::EmptyThreadDeletePolicy>::runThread (paThread=0x5555557cdf60) at /home/plc/forte_2.0.1/src/arch/posix/../threadbase.tpp:69
#15 0x0000555555695324 in CPosixThread::threadFunction (paArguments=0x5555557cdf60) at /home/plc/forte_2.0.1/src/arch/posix/forte_thread.cpp:68
#16 0x00007ffff7f9a609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#17 0x00007ffff7b90293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

2.FB_RANDOM类是如何注册的?

从调用堆栈可以看到,.fboot文件解析后,FORTE根据.fboot文件的内容进行了动态创建功能块,但大家是不是有一个疑问,就是从Cpp语法角度,如何根据字符串创建一个类对象?

就像如下的代码,但下面的代码与FORTE的运行情况还不太一样,FORTE程序并不引用功能块的.h文件,main函数无法拿到FuncA的定义,那功能块又是如何动态创建的呢?

就是说,程序在运行时如何知道有这个类FuncA存在?

Class Base{
};

Class FuncA : public Base {
    FuncA(){
        printf("FuncA");
    }
};

Class FuncB : public Base {
    FuncB(){
        printf("FuncB");
    }
};


int main(){

    Base* ptr = nullptr;
    string str="FuncA";
    if(str== "FuncA")
        ptr =  new FuncA();
    else if(str== "FuncB")
        ptr =  new FuncB();

    return 0;
}

在FB_RANDOM.cpp中有如下宏定义

DEFINE_FIRMWARE_FB(FORTE_FB_RANDOM, g_nStringIdFB_RANDOM)

 宏展开后是这样的:

定义了一个const CTypeLib::CFBTypeEntry 类型的static变量 , static定义在FB_RANDOM.h中。

FORTE_FB_RANDOM::csm_oFirmwareFBEntry_FORTE_FB_RANDOM(( xxx), xxx,xxx);

CTypeLib::CFBTypeEntry 这个类在初始化的时候,在CFBTypeEntry 构造函数中执行 CTypeLib::addFBType函数,将自己的相关信息(包括id,接口相关函数)加入到静态变量中。

//! The base class for all function block types entries in the type lib.
  class CFBTypeEntry : public CSpecTypeEntry{
    public:
      CFBTypeEntry(CStringDictionary::TStringId pa_nTypeNameId, TFunctionBlockCreateFunc pa_pfuncCreateFB, const SFBInterfaceSpec* paSocketInterfaceSpec);
      virtual ~CFBTypeEntry(void);
      virtual CFunctionBlock *createFBInstance(CStringDictionary::TStringId pa_nInstanceNameId, CResource *pa_poSrcRes){
              return m_pfuncFBCreationFunc( pa_nInstanceNameId, pa_poSrcRes);
            }
    private:
       TFunctionBlockCreateFunc m_pfuncFBCreationFunc;
  };

CTypeLib::CFBTypeEntry::CFBTypeEntry(
CStringDictionary::TStringId pa_nTypeNameId,
 TFunctionBlockCreateFunc pa_pfuncCreateFB,
 const SFBInterfaceSpec* paSocketInterfaceSpec):
  CSpecTypeEntry(pa_nTypeNameId, paSocketInterfaceSpec),
  m_pfuncFBCreationFunc(pa_pfuncCreateFB)
{
     CTypeLib::addFBType(this); //这一行
}


void CTypeLib::addFBType(CFBTypeEntry *pa_poFBTypeEntry) 
{
  if (0 == findType(pa_poFBTypeEntry->getTypeNameId(), m_poFBLibStart)) 
{
    if(m_poFBLibStart == 0) {
      m_poFBLibStart = pa_poFBTypeEntry;
    } else {
      m_poFBLibEnd->m_poNext = pa_poFBTypeEntry;//pa_poFBTypeEntry加入静态变量m_poFBLibEnd中
    }
    m_poFBLibEnd = pa_poFBTypeEntry;
  }
}


CTypeLib::CFBTypeEntry *CTypeLib::m_poFBLibStart = 0;
CTypeLib::CFBTypeEntry *CTypeLib::m_poFBLibEnd = 0;


//.h头文件中的定义
class CTypeLib
{
  static CFBTypeEntry *m_poFBLibStart, *m_poFBLibEnd;
    //!< pointer to the begin of the firmware fb library list
    //!<pointer to the end of the firmware fb library list
    //静态变量的定义
}

在创建此类型的时候,首先执行findType()函数,进行查找,找到指定的TypeNameId,如果找不到,就报错,无法运行此程序。

void CTypeLib::addFBType(CFBTypeEntry *pa_poFBTypeEntry) {
  if (0 == findType(pa_poFBTypeEntry->getTypeNameId(), m_poFBLibStart)) {
    if(m_poFBLibStart == 0) {
      m_poFBLibStart = pa_poFBTypeEntry;
    } else {
      m_poFBLibEnd->m_poNext = pa_poFBTypeEntry;
    }
    m_poFBLibEnd = pa_poFBTypeEntry;
  }
}

c程序运行时,并不是从main开始运行,在main()运行前,会有一系列的函数进行一些初始化工作,static静态变量都是在这一时期初始化的。具体可参见下面的链接。

因此,在main函数运行前,会初始化此处的CTypeLib::CFBTypeEntry 类型的变量,将FB_RANDOM类的相关信息添加到m_poFBLibStart 开始的链表中。即可实现在解析.fboot文件时,根据ID查找对应的类。并通过调用对应的createFB()生成对应的对象。

linux编程之main()函数启动过程_gary_ygl的专栏-CSDN博客_linux启动main函数

附件

server.fboot文件内容:

;<Request ID="2" Action="CREATE"><FB Name="fastWrite" Type="EMB_RES" /></Request>
fastWrite;<Request ID="3" Action="CREATE"><FB Name="E_CYCLE" Type="E_CYCLE" /></Request>
fastWrite;<Request ID="4" Action="WRITE"><Connection Source="T#50ms" Destination="E_CYCLE.DT" /></Request>
fastWrite;<Request ID="5" Action="CREATE"><FB Name="FB_RANDOM" Type="FB_RANDOM" /></Request>
fastWrite;<Request ID="6" Action="WRITE"><Connection Source="0" Destination="FB_RANDOM.SEED" /></Request>
fastWrite;<Request ID="7" Action="CREATE"><FB Name="PUBLISH_10" Type="PUBLISH_10" /></Request>
fastWrite;<Request ID="8" Action="WRITE"><Connection Source="1" Destination="PUBLISH_10.QI" /></Request>
fastWrite;<Request ID="9" Action="WRITE"><Connection Source="opc_ua[WRITE;/Objects/folder1/var1;/Objects/folder1/var2;/Objects/folder1/var3;/Objects/folder1/var4;/Objects/folder1/var5;/Objects/folder1/var6;/Objects/folder1/var7;/Objects/folder1/var8;/Objects/folder1/var9;/Objects/folder1/var10]" Destination="PUBLISH_10.ID" /></Request>
fastWrite;<Request ID="10" Action="CREATE"><FB Name="E_DELAY_2" Type="E_DELAY" /></Request>
fastWrite;<Request ID="11" Action="WRITE"><Connection Source="T#2s" Destination="E_DELAY_2.DT" /></Request>
fastWrite;<Request ID="12" Action="CREATE"><Connection Source="E_CYCLE.EO" Destination="FB_RANDOM.REQ" /></Request>
fastWrite;<Request ID="13" Action="CREATE"><Connection Source="START.WARM" Destination="FB_RANDOM.INIT" /></Request>
fastWrite;<Request ID="14" Action="CREATE"><Connection Source="START.COLD" Destination="FB_RANDOM.INIT" /></Request>
fastWrite;<Request ID="15" Action="CREATE"><Connection Source="FB_RANDOM.INITO" Destination="PUBLISH_10.INIT" /></Request>
fastWrite;<Request ID="16" Action="CREATE"><Connection Source="FB_RANDOM.CNF" Destination="PUBLISH_10.REQ" /></Request>
fastWrite;<Request ID="17" Action="CREATE"><Connection Source="PUBLISH_10.INITO" Destination="E_DELAY_2.START" /></Request>
fastWrite;<Request ID="18" Action="CREATE"><Connection Source="E_DELAY_2.EO" Destination="E_CYCLE.START" /></Request>
fastWrite;<Request ID="19" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_1" /></Request>
fastWrite;<Request ID="20" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_2" /></Request>
fastWrite;<Request ID="21" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_3" /></Request>
fastWrite;<Request ID="22" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_4" /></Request>
fastWrite;<Request ID="23" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_5" /></Request>
fastWrite;<Request ID="24" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_6" /></Request>
fastWrite;<Request ID="25" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_7" /></Request>
fastWrite;<Request ID="26" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_8" /></Request>
fastWrite;<Request ID="27" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_9" /></Request>
fastWrite;<Request ID="28" Action="CREATE"><Connection Source="FB_RANDOM.VAL" Destination="PUBLISH_10.SD_10" /></Request>
fastWrite;<Request ID="28" Action="START"/>

所用demo下载

https://download.csdn.net/download/v6543210/80644938

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 复倒谱定义式和倒谱定义式都是用于从时域转换到频域的数学方法。它们的区别在于输入信号的类型和输出信号的性质。 复倒谱定义式是指将一个信号在时域上进行傅里叶变换,然后对其进行对数运算并再次进行傅里叶变换得到的结果。这个定义式的输入信号是一个复数序列,输出信号是一个复数序列。它常用于信号的频谱分析和滤波器设计。 倒谱定义式是指将一个信号在时域上进行自相关,然后对其进行对数运算并再次进行傅里叶变换得到的结果。这个定义式的输入信号是一个实数序列,输出信号是一个实数序列。它常用于信号的声音分析和识别。 ### 回答2: 复倒谱定义式是音乐理论中用于描述和分析乐谱中音程关系的一种方法。它是由德国音乐理论家奥托·爱德华·迈斯纳(Otto- Eduard Miesner)于20世纪初提出的。复倒谱定义式将音乐中的音程关系转换为数字关系,以便更方便地进行分析。 复倒谱定义式的基本原理是:将音程按照声部和方向进行编码,通过特定的编码规则将音程转换为数字。其中,声部的编码使用数字1-8来表示乐谱中的声部,1代表最低音部,8代表最高音部。方向的编码使用正负号来表示,正号表示上行音程,负号表示下行音程。 举例来说,如果在乐谱中,声部1和声部2之间的音程是一个纯五度,那么通过复倒谱定义式,可以将这个音程编码为12,其中1代表声部1,2代表声部2。 倒谱定义式是一种将音乐乐谱中的音程表示为相对音高区间的方法。它由美国音乐理论家Allen Forte于1964年提出。倒谱定义式的目的是通过对音程进行分类和编号,以便更方便地进行音乐分析和比较。 倒谱定义式通过对音程进行分类,将不同的音程归为同一类别,并为每个音程类别分配一个唯一的编号。例如,倒谱定义式将所有的纯五度音程归为同一类别,并分配编号为5。当在乐谱中遇到一个纯五度音程时,可以通过倒谱定义式将它表示为数字5,以便进行进一步的分析。 总结起来,复倒谱定义式和倒谱定义式都是音乐理论中用于描述和分析乐谱中音程关系的方法。复倒谱定义式通过将音程转换为数字关系来表示,而倒谱定义式通过对音程进行分类和编号来表示。它们都为音乐分析和比较提供了有用的工具。 ### 回答3: 复倒谱定义式和倒谱定义式是音乐理论中用于分析和描述音乐音高的两个概念。复倒谱定义式是指将音高表示为正数或负数的方式,以便在分析音乐时更方便地计算和比较音高差距。倒谱定义式是指将音高表示为正数和负数的组合形式,以便更直观地表示音高上升和下降的趋势。 在复倒谱定义式中,音高被表示为一个数值,其大小取决于与某个参考音高的相对差距。正数表示高于参考音高的音高,而负数表示低于参考音高的音高。该定义式强调了音高差距的绝对值,使得音高的计算和比较更加简单。例如,假设我们选择C4作为参考音高,那么A4将表示为+3(高三度),而F3将表示为-5(低五度)。 而在倒谱定义式中,音高被表示为一组正数和负数的组合形式,用来表示音高在演奏中的上升和下降趋势。每个正数或负数表示一个相对的音高差距。当音高逐渐升高时,正数增加;当音高逐渐降低时,负数增加。例如,假设我们选择C4作为参考音高,C5将表示为+1,而G3将表示为-1。这个定义式更加注重音高的变化趋势,使得分析音乐的音高组织更加直观和准确。 复倒谱定义式和倒谱定义式是两种常用的音高表示形式,它们在音乐的分析和理论研究中起到了重要的作用。它们使得我们能够更全面地认识和理解音乐音高结构的特点和规律。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路边闲人2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值