【问题标题】:Is it possible to add static parameters to function blocks?是否可以向功能块添加静态参数?
【发布时间】:2021-12-07 02:37:47
【问题描述】:

是否可以编写带有一些静态参数化的功能块?具体来说,我可以制作一个具有静态容量的缓冲区,但不同的实例可以有不同的容量吗?

理想情况下,我会想象一些常量参数,如下所示:

FUNCTION_BLOCK FB_Buffer
VAR_INPUT CONSTANT
    StorageSize : DINT;
END_VAR
VAR
    Storage : ARRAY [1..StorageSize] OF REAL;
END_VAR

然后实例化将是这样的:

FUNCTION_BLOCK FB_Usage
VAR
    SmallBuffer : FB_Buffer := (StorageSize := 10);
    LargeBuffer : FB_Buffer := (StorageSize := 1000);
END_VAR

假设这是不可能的,为不同功能块实例管理不同存储大小的最佳方法是什么?

我将发布我最不坏的解决方法作为回答。

【问题讨论】:

  • 我同意史蒂夫关于如何从理论上实现您所要求的回答。我想提出的一点是,编译器不允许您在功能块中动态声明数组是有原因的。原因与机器需要在一年中的每一天全天候工作有关,我们需要不惜一切代价尽量减少停机时间。机器程序应该遵循 KISS 原则,这就是为什么即使你可以像史蒂夫建议的那样进行构造,但你不应该这样做。相反,您应该静态声明您的数组,并拥有一个简洁明了的架构。
  • 与机器无关的程序部分应该转移到 plc 运行时之外,并用更高级别的语言(例如:Python 或 Java 或 C#)进行编程。因此,您大大减少了机器代码产生异常的机会。然后应该创建备份或紧急生产计划,以确保在过度结构不正常时生产仍然可以运行(例如连接到数据库)这样即使系统的一部分不工作,您的机器也始终可以继续生产.你明白我要指向哪里了吗?
  • @FilippoBoido 我不想动态声明数组,这就是重点。我理解为什么 PLC 和其他实时系统从不使用动态内存管理。我想要的是能够定义某种类型的多个变量,但具有不同的 static 容量。你已经可以用字符串做到这一点:声明一个STRING(10)类型的变量和另一个STRING(50)类型的变量。另一种方法是要么让所有缓冲区变量变得异常庞大,即使其中一个变量需要超过 10 个容量,或者具有复制粘贴类型,这是维护的噩梦。
  • @FilippoBoido 我同意你关于管理风险的观点:让所有必须运行的东西尽可能可靠,并与所有“奖励”功能分开。只是有时您还需要使实时事物更加复杂,以使它们变得可行或可重用。可重用性不仅可以节省时间,而且从长远来看,它甚至可以让事情变得更加可靠。
  • 通过坚持 KISS 原则,您可以使您的系统面向未来且可重复使用。我发布了一个带有其他建议的答案。

标签: plc twincat structured-text iec61131-3


【解决方案1】:

您对“静态”变量的引用让我有些吃惊,因为 VAR STAT 与您想要的不同,用于使 FB 的所有实例共享一个公共元素。

您真正想要的是FB_INIT__NEW 的奇迹


示例

您必须管理自己的数据访问权限,确保不会溢出和所有其他危险的事情,否则这应该按照您发布的答案工作。然后用几个不同的长度初始化这段代码很简单:

FUNCTION_BLOCK FB_Usage
VAR
  SmallBuffer : fb_DataArray( 100 );
  LargeBuffer : fb_DataArray( 10000 );
END_VAR
// Function block for handling a data array that is generated at boot time
FUNCTION_BLOCK fb_DataArray
VAR
  pZeroElem  : POINTER TO REAL;  // Pointer to the head of the array
  ArrayLength : UDINT;  // Length of the array in elements 
END_VAR

// Do something by indexing through ring
METHOD FB_init : BOOL
// Initialisation method for fb_DataArray, allocates memory space to array
VAR
    bInitRetains    :   BOOL;   // required
    bInCopyCode     :   BOOL;   // required 
    Length          :   UDINT;  //  Number of element in the array
END_VAR

pZeroElem := __NEW( REAL, Length ); 
// Generate a pointer to the first element of a dataspace that is precisely big enough for <Length> Real elements. 
Method FB_exit
// Needs to be called to de-allocate the memory space allocated in fb_init. 
VAR
 bInCopyCode : BOOL; // Required
END_VAR

IF pZeroElem <> 0 THEN
  // Checks if the pointer is valid, then deletes the allocation
  __DELETE( pZeroElem ); 
END_IF

【讨论】:

  • 很好的答案。如果您将{attribute 'enable_dynamic_creation'} 添加到功能块的顶部,则可以动态创建您的功能块。这意味着您可以在运行时使用__NEW 并根据例如用户输入来决定缓冲区的大小。在这里,您必须再次记住 __DELETE 您动态分配的所有内容,以防止可能的灾难性内存泄漏。 Beckhoff reference..
  • 是的,我的意思是静态“在编译时已知,不需要运行时信息”,而不是静态变量。很抱歉造成混乱。
  • @Mikkel 我想知道 __NEW 和 __DELETE 在运行时如何工作?我认为它需要动态内存管理,但这不是实时的,因此在 PLC 世界中是一个很大的禁忌。
  • 是的,不幸的是,Beckhoff Infosys 在这个主题上确实很差(就像在大多数主题上一样)。然而,他们给出了如何在运行时使用它的例子,我希望他们能提供系统仍然提供实时性能。文档说调用可能会失败。我怀疑这可能是因为路由器内存池中没有足够的未碎片化内存或其他措施使其具有实时性和确定性。
  • __NEW 和 __DELETE 的运行时应用程序工作得很好,我已经非常干净地使用它来处理生产和管理缩放器的工厂方法。话虽如此,考虑到大多数 PLC 所需的可靠性,通常建议不要在运行期间处理 PLC 中的内存操作。
【解决方案2】:

如果您不想创建具有动态内存的数组,同时又想减少项目中的类型数量,则可以使用条件编译指示。

示例:

//Declaration part of MAIN
PROGRAM MAIN
VAR
    {define variant_b}
        
    {IF defined(variant_a)}
        conveyor_buffer : ARRAY[1..10] OF INT;
        sensor_buffer : ARRAY[1..5] OF BOOL;
    {ELSIF defined(variant_b}
        conveyor_buffer : ARRAY[1..100] OF INT;
        sensor_buffer : ARRAY[1..20] OF BOOL;
    {END_IF}
    
    fbConveyor : FB_Conveyor;
END_VAR

//Implementation part of MAIN
fbConveyor(buffer:=conveyor_buffer);

//Declaration part of FB_Conveyor
FUNCTION_BLOCK FB_Conveyor
VAR_IN_OUT
    buffer      : ARRAY [*] OF INT;
END_VAR
VAR_OUTPUT

END_VAR
VAR
    length      : DINT;
END_VAR

//Implementation part of FB_Conveyor
length := UPPER_BOUND(buffer,1);

然后,您将缓冲区传递给实际使用它们作为参考的对象。在这些功能块中,您需要检查 UPPER 和 LOWER Bound 以免出现问题。

如果您不喜欢条件编译指示但仍希望您的项目简单明了,您可以在 GIT 存储库中将变体表示为 GIT 分支。 通过这种方式,您始终知道哪台机器具有哪些功能,并且可以保持干净的结构和架构。 另一种策略是利用 Beckhoff 自动化接口自动创建代码并按照您决定的结构构建项目。 链接在这里: https://infosys.beckhoff.com/index.php?content=../content/1031/tc3_automationinterface/242682763.html&id=

通过使用自动化界面自动生成代码,您再次减少了在机器操作中注入人为错误的可能性,并将复杂性导出到“更高级别”,从而使您的系统更加可靠。

因此,您可以使用多种方法来实现可重用的解决方案。 尽管我知道那里有许多复杂的机器,但如果您的 plc 架构变得复杂,可能是时候考虑哪些模块和功能可以“外包”到更高级别,以坚持 KISS 原则并确保生产安全在较低级别。

【讨论】:

  • 通过使用输入/输出变量从功能块外部获取缓冲区是一种解决方案,但我不明白编译指示与它有什么关系。您可以通过调用两个具有两个不同大小缓冲区的 FB_Conveyor 实例来回答我的问题。不幸的是,我无法阅读代码生成链接,因为它是德语的,而且谷歌翻译拒绝翻译 Infosys 页面(至少在移动设备上)。
  • 这种方案的缺点是缺乏封装,使用更复杂,这两者都使得代码更容易出现人为错误,而不是更少。传送器功能块本身可能不太复杂,但是在整个项目中每次使用它都需要开发人员为每个实例提供缓冲区,确保他们不会意外写入或使用一个缓冲区和两个传送器实例(例如通过复制粘贴并忘记重命名参数)。史蒂夫的回答我一次 会更复杂,但它尽可能使用 简单。这就是可重用库代码应该像 IMO 一样。
  • 顺便说一句,拥有单独的长时间运行的 git 分支似乎是个好主意,我们已经尝试了很长时间。不幸的是,这不是 git 的设计目的。维护多个分支是一场噩梦,因为您不能只对其中一个应用错误修复,然后您必须记住检查每个分支并挑选常见的修复。与合并不同,樱桃采摘通常不起作用,尤其是当您对在这些分支中存在差异的事物应用修复时。这意味着您必须复制粘贴,这又很费力且容易出错。
  • 这个答案只是 Steves 之上的一个,我也赞成他的答案,但是我的答案需要在更广泛的架构环境中看到,并且与你如何为机器系统设计软件的问题相关尽可能简单,以便 a) 错误很少见,并且大部分可以在低级别排除 b) 对系统知之甚少的工程师可以在必要时快速调试并恢复生产。
  • 如果您的机器变体需要不同的缓冲区,正如您的问题所指出的那样,该编译指示很有用。上面的示例只是一个基本示例,向您展示了如何感谢集中定义的变体(在示例 variant_b ) 你可以为不同的机器类型定义不同大小的缓冲区
【解决方案3】:

唯一想到的就是创建一个抽象的基 FB 并让不同的子类定义具体的存储。仍然有明显的重复和锅炉电镀,但至少比复制粘贴整个 FB 只是为了更改一个数字要好。

基础声明:

FUNCTION_BLOCK ABSTRACT FB_BufferBase
VAR
    Storage : POINTER TO REAL;
    StorageSize : DINT;
END_VAR

抽象方法声明:

METHOD ABSTRACT GetStorage
VAR_OUTPUT
    ZeroElement : POINTER TO REAL;
    ElementCount : DINT;
END_VAR

基体代码:

GetStorage (ZeroElement => Storage, ElementCount => StorageSize);

// Do stuff.

具体的小声明:

FUNCTION_BLOCK FB_BufferSmall EXTENDS FB_BufferBase
VAR
    ConcreteStorage : ARRAY [0..ConcreteStorageSize-1] OF REAL;
END_VAR
VAR CONSTANT
    ConcreteStorageSize : DINT := 10;
END_VAR

小具体方法实现:

ZeroElement := ADR(ConcreteStorage[0]);
ElementCount := ConcreteStorageSize;

FB_BufferLargeFB_BufferSmall相同,只是ConcreteStorageSize是1000而不是10。)

实例化:

FUNCTION_BLOCK FB_Usage
VAR
    SmallBuffer : FB_BufferSmall;
    LargeBuffer : FB_BufferLarge;
END_VAR

【讨论】:

    猜你喜欢
    • 2016-07-01
    • 2012-01-19
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多