Использование универсального типа в Struct & Function Block

Я хотел бы создать универсальный тип STRUCT и парный Function Block, которые принимают и возвращают переменные универсального типа (предположительно ANY_NUM).

Это необходимо для объединения многих существующих пар STRUCT и FB в одном формате с использованием общих типов чисел вероятно принадлежащих к типу ANY_NUM в одну общую пару.

В C++ общая структура может быть выполнена с помощью Template Class, но я не могу найти подобную структуру в структурированном тексте.

Я попробовал общий функциональный блок на Страница Beckhoff ANY/ANY_(TYPE), однако она быстро не convert type 'LREAL' to type '__SYSTEM.AnyType'.

Вопрос:

В какой степени я могу достичь этой цели в структурированном тексте?

ИЗМЕНИТЬ:

Я ошибочно предположил, что ANY является единственным подходящим родовым языком ST. Меня перенаправили на введите T_Arg в качестве потенциально жизнеспособного кандидата.

Пример формата попытки:

Структура:

TYPE Bounded_Value:
STRUCT
    Value   : ANY_NUM;
    Min_    : ANY_NUM;
    Max_    : ANY_NUM;
END_STRUCT
END_TYPE

Функциональный блок:

FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
    value_struct: Bounded_Value;
END_VAR

(Реализация свяжет value_struct.Value между value_struct.min_ и value_struct.max_)


person Graeme Rock    schedule 27.04.2018    source источник
comment
REAL и LREAL не являются ANY_NUM. Те должны быть преобразованы. С точки зрения математики, какой мы ее знаем, мы считаем ее числом, но с точки зрения типов данных INT и REAL — это разные типы.   -  person Sergey Romanov    schedule 29.04.2018
comment
@SergeyRomanov Я не понимаю, что вы имеете в виду. Согласно странице Beckhoff на ЛЮБОМ, ANY_NUM включает ANY_REAL и ANY_INT.   -  person Graeme Rock    schedule 30.04.2018
comment
@SergeyRomanov О, вы хотите сказать, что ANY_NUM и LREAL не являются эквивалентными типами, поскольку ANY_NUM — это структура, содержащая тип, указатель и размер числа, а REAL/LREAL/INT — это сами числа. Правильный?   -  person Graeme Rock    schedule 30.04.2018
comment
ты прав. Я пропустил это с ANY_INT. Извиняюсь.   -  person Sergey Romanov    schedule 01.05.2018


Ответы (3)


Я недавно исследовал это (ЛЮБОГО типа) в TwinCAT. Что вам в основном нужно сделать, так это преобразовать каждый байт, на который указывает ANY-указатель, в LREAL (который, как вы знаете, согласно IEC61131-3, всегда будет 8 байтов). Тип ANY содержит информацию о том, на какой тип он указывает, поэтому вы узнаете, когда это LREAL, проанализировав структуру данных, на которую указывает указатель ANY. Пожалуйста, прочитайте мое полное расследование в моем блоге: Чудеса ЛЮБОГО

person Jakob    schedule 27.04.2018
comment
Я полагаю, что мог бы попытаться использовать функциональный блок или программу для загрузки значений в каждую STRUCT, но это только создает больше работы и занимает гораздо больше места, чем просто иметь STRUCT для каждой переменной. - person Graeme Rock; 30.04.2018

(Решение моей проблемы я нашел на сайте запись в блоге Стефана Хеннекена о T_Arg.)

Цель может быть достигнута, но не особенно чисто. Есть два общих типа (которые я пока нашел), которые применимы: ANY_NUM и T_Arg.

(Я использую ANY_NUM, потому что это наиболее подходит для этого примера. ANY, ANY_REAL или ANY_INT также были бы разумными вариантами)

Оба варианта имеют схожую структуру и работают одинаково. Каждая из них представляет собой структуру, содержащую информацию о хранимой переменной: ее тип, указатель на нее и ее размер.

Тем не менее, есть плюсы и минусы для каждого. Для наиболее точного решения этой задачи мы будем использовать T_Arg.

Вот разница:

ЛЮБОЙ / ANY_NUM / ETC

Преимущество: преобразование переменной в ANY_NUM выполняется неявно, когда присваивается переменная. Входная переменная не требует предварительного преобразования перед вводом в функцию, что сокращает размер кода.

Кроме того, он принимает только переменные, принадлежащие его домену, поэтому строки не будут использоваться случайно.

Недостаток: ANY_NUM нельзя объявить за пределами блока VAR_INPUT, и фактически выдает это сообщение об ошибке при попытке:

Variables of type 'ANY_NUM' only allowed as input of functions.

Поэтому ANY_NUM нельзя использовать в качестве переменной STRUCT, даже если эта STRUCT объявлена ​​как входная функция. Вот почему он не может быть использован для решения этой конкретной проблемы.

T_Arg

Преимущество: T_Arg можно объявить и использовать где угодно.

Недостаток: T_Arg требуются функции преобразования для любого ожидаемого типа переменной, например: F_INT(),F_REAL(),F_DINT() и т. д.

Поэтому проверку типов необходимо выполнять до и после ввода.

Пример решения

К сожалению, переменная, хранящаяся в T_Arg, не может быть изменена напрямую. Чтобы использовать сохраненную переменную, необходимо переместить ее во временную переменную. Таким образом, Value, Min_ и Max_ потребуется преобразование из типа T_Arg в тип REAL/INT/и т. д.

Поскольку мы пытаемся использовать только один STRUCT, Value нужно будет снова преобразовать в T_Arg, как только Bind_Value закончит манипулировать им.

В общей сложности Value будет преобразовано три раза при создании экземпляра и дважды за каждый последующий вызов.

Структура:

TYPE Bounded_Value:
STRUCT
    Value   : T_Arg;
    Min_    : T_Arg;
    Max_    : T_Arg;
END_STRUCT
END_TYPE

Функциональный блок:

FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
    value_struct: Bounded_Value;
    // Other variable type declarations
END_VAR
VAR
    val_int     :   INT;
    max_int     :   INT;
    min_int     :   INT;
END_VAR  

CASE (value_struct.Value.eType) OF
    E_ArgType.ARGTYPE_INT: // If the struct's Value's type is INT
        // Copy generic pointer information into typed pointer 
        MEMCPY(ADR(val_int), value_struct.Value.pData, value_struct.Value.cbLen);
        MEMCPY(ADR(max_int), value_struct.Max_.pData,  value_struct.Max_.cbLen);
        MEMCPY(ADR(min_int), value_struct.Min_.pData,  value_struct.Min_.cbLen);

        IF val_int > max_int THEN
            value_struct.Value.pData := value_struct.Max_.pData;
        ELSIF val_int < min_int THEN
            value_struct.Value.pData := value_struct.Min_.pData;
        END_IF
    // Other variable type handlings
END_CASE

ГЛАВНОЕ:

PROGRAM MAIN
VAR
    val     : INT := -1; //Change this to test
    minim   : INT :=  0;
    maxim   : INT :=  5;
    newVal  : INT;
    bv      : Bounded_Value;
    bind    : Bind_Value;
END_VAR

// Convert INT variables to T_Arg in structure
bv.Value:= F_INT(val);
bv.Max_ := F_INT(maxim);
bv.Min_ := F_INT(minim);
// Bind_Value.value_struct := bv;
bind(value_struct := bv);
// Copy result to newVal
MEMCPY(ADR(newVal), bv.Value.pData, bv.Value.cbLen);
person Graeme Rock    schedule 30.04.2018
comment
Вы пытались использовать псевдонимы? TYPE MyType: T_Arg; END_TYPE, а затем VAR in: MyType; END_VAR? - person Sergey Romanov; 01.05.2018
comment
Я не делал, почему ты спрашиваешь? (Я не вижу, как замена T_Arg приведет к сокращению размера кода, поскольку мне все равно придется выполнять преобразование типов.) - person Graeme Rock; 01.05.2018

Вы также можете создать общий тип с помощью функционального блока и объединения. Допустим, вы определяете Union со всеми вашими DUT и POU:

TYPE GenericType :
UNION
    generic : PVOID;
    bBool   : REFERENCE TO BOOL;
    nInt    : REFERENCE TO INT;
    nUint   : REFERENCE TO UINT;
    nUdint  : REFERENCE TO UDINT;
    fReal   : REFERENCE TO REAL;
    fLreal  : REFERENCE TO LREAL;
    fbTest  : REFERENCE TO FB_Test;
END_UNION
END_TYPE

Затем вы создаете специальный функциональный блок:

FUNCTION_BLOCK Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    uGenericType : GenericType;
END_VAR

И свойство, которое получает и устанавливает PVOID:

PROPERTY PUBLIC generic : PVOID

Добытчик:

generic := uGenericType.generic;

Сеттер:

uGenericType.generic := generic;

Вам также нужны Свойства, чтобы позже получить ваш тип:

Изображение FB

Примером установщика getBool может быть:

IF uGenericType.generic = 0 
THEN
    RETURN;
ELSE
    getBool := uGenericType.bBool;
END_IF

Теперь вы создаете FB, который будет использовать общий тип:

FUNCTION_BLOCK FB_Container
VAR_INPUT

    myGenericType       : Generic;
    nContainerOption    : INT;

END_VAR
VAR_OUTPUT
END_VAR
VAR

    testInt             : INT;
    testBool            : BOOL;
    testFB              : FB_Test;

END_VAR

CASE nContainerOption OF

    1:
        testInt := myGenericType.getInt;

    2:
        testFB := myGenericType.getFbTest;

    3:
        testBool := myGenericType.getBool;

END_CASE

Звонок может быть таким:

fbContainer.myGenericType.generic := ADR(testInteger);
...
fbContainer(nContainerOption := 1);

Другой подход заключается в расширении наших FB с помощью Generic FB. Однако нам нужно внести некоторые изменения в Generic FB и в GenericType Union:

FUNCTION_BLOCK Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    uGenericType    : GenericType;
    bInit           : BOOL;
END_VAR

IF NOT bInit THEN
 uGenericType.generic := ADR(THIS^);
 bInit := TRUE;
END_IF

TYPE GenericType :
UNION
    generic : PVOID;

    //Add the pointer of the FB you want to extend
    pAxis   : POINTER TO FB_Axis;

    bBool   : REFERENCE TO BOOL;
    nInt    : REFERENCE TO INT;
    nUint   : REFERENCE TO UINT;
    nUdint  : REFERENCE TO UDINT;
    fReal   : REFERENCE TO REAL;
    fLreal  : REFERENCE TO LREAL;
    fbTest  : REFERENCE TO FB_Test;

END_UNION
END_TYPE

Расширенный ФБ:

FUNCTION_BLOCK FB_Axis EXTENDS Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
    fPosition : LREAL;
END_VAR

А теперь вызов нашего контейнера как раньше:

fbContainer.myGenericType := fbAxis;

в FB_Container вы можете вызвать ось следующим образом:

IF myGenericType.getPointerFbAxis <> 0 
THEN
    position := myGenericType.getPointerFbAxis^.fPosition;
END_IF
person Filippo Boido    schedule 05.07.2019