How to determine the type of a pointer indexed member - codesys

How to determine the type of a pointer indexed member.
I intend to useļ¼š
TYPE DUT_DemoStruct :
STRUCT
Member_1: BOOL;
Member_2: INT;
END_STRUCT
END_TYPE
PROGRAM Prg_Main
VAR
DemoStructPointer:
Obj_DemoStruct1: DUT_DemoStruct ;
Obj_DemoStruct2: DUT_DemoStruct ;
Obj_DemoStruct3: DUT_DemoStruct ;
Demo_INT: INT;
Index: INT;
_pointer: POINTER TO DUT_DemoStruct;
END_VAR
FOR Index:=0 TO 5 DO
IF _pointer[Index] THEN //whether the _pointer[Index] (pointer index member) is of type DUT_DemoStruct
_pointer[Index].Member_1:=TRUE;
END_IF;
END_FOR;
In addition, Do you have any application cases for CheckPointer?
EDIT:2022-06-05
This is to better implement the contracted part of the HMSD(Hierarchical Master-Slave Distribution) framework. For example, batch initialization of states. For example: if you want to implement batch initialization of objects of class Step, you only need to pass the pointer of the first Step to the initialization function, and the function will automatically search down according to the first pointer until it finds an object that is not of the Step class and stops searching. This process needs to be automated. To achieve such a function, you need the index of the pointer, and determine the type of the pointer index member. Ideal code:
FUNCTION_BLOCK Exe_PalletShuttle EXTENDS Frame.Base_LocalScope
VAR
{attribute 'hide'}pointer_Step: POINTER TO Frame.Base_Step;
{attribute 'hide'}step_Start: Frame.Base_Step;
{attribute 'hide'}step_StartRecharg: Frame.Base_Step;
{attribute 'hide'}step_LengthwaysMove_Start: Frame.Base_Step;
{attribute 'hide'}step_LiftShuttleCyd_PutDown: Frame.Base_Step;
{attribute 'hide'}step_LengthwaysMove_GoTarget: Frame.Base_Step;
{attribute 'hide'}step_LengthwaysMove_End: Frame.Base_Step;
{attribute 'hide'}step_WidthwaysMove_Start: Frame.Base_Step;
{attribute 'hide'}step_LiftShuttleCyd_Uplift: Frame.Base_Step;
{attribute 'hide'}step_WidthwaysMove_GoTarget: Frame.Base_Step;
{attribute 'hide'}step_WidthwaysMove_End: Frame.Base_Step;
{attribute 'hide'}step_UpliftPallet_Start: Frame.Base_Step;
{attribute 'hide'}step_LiftPalletCyd_Uplift: Frame.Base_Step;
{attribute 'hide'}step_UpliftPallet_End: Frame.Base_Step;
{attribute 'hide'}step_PutdownPallet_Start: Frame.Base_Step;
{attribute 'hide'}step_LiftPalletCyd_Putdown: Frame.Base_Step;
{attribute 'hide'}step_PutdownPallet_End: Frame.Base_Step;
{attribute 'hide'}step_End: Frame.Base_Step;
{attribute 'hide'}VividerMark: BOOL;
{attribute 'hide'}obj_PalletLiftCyd: Frame.Device_Actuator;
{attribute 'hide'}obj_DiverterLiftCyd: Frame.Device_Actuator;
END_VAR
pointer_Step:= ADR(step_Start);
InitialStepScope(FirstAdr:=pointer_Step );
Feeling the current mechanism of pointer implementation, such a function seems to be difficult to achieve. Looking forward to the launch of the collection function.

You can't know the type of the data the pointer points to. When you create the pointer, you are explicitly telling the compiler to assume the data is of the specified type.
You could store the address of the first and last elements and iterate between them (haven't tested):
elem1: MY_STRUC;
elem2: MY_STRUC;
...
elemN: MY_STRUC;
pbegin: POINTER TO MY_STRUCT := ADR(elem1);
pend: POINTER TO MY_STRUCT := ADR(elemN);
WHILE pbegin <> pend DO
// use pbegin^
pbegin := pbegin + SIZEOF(pbegin^); // or pbegin := ADR(pbegin[1]);
END_FOR
However, the above assumes that:
All of elemX are of the same size
There are no other type of variables in between the elements
Sequentially defined variables in CODESYS must also be placed in memory sequentially in the same order (don't know if this is true)
As such, the above is very error prone in my opinion. A better approach might be to "bundle" the elements in a dedicated structure (tested on a simulator):
TYPE MY_STRUC_BUNDLE:
STRUCT
elem1: MY_STRUC;
elem2: MY_STRUC;
...
elemN: MY_STRUC;
END_STRUCT
END_TYPE
bundle: MY_STRUC_BUNDLE;
ptr: POINTER TO MY_STRUCT := ADR(bundle);
len: UDINT := SIZEOF(bundle) / SIZEOF(ptr^) - 1;
index: UDINT;
FOR index := 0 TO len DO
// use ptr[Index]
END_FOR
Assuming that the bunle structure only contains the desired structs of the same type (and size), this should work as structures should have a continuous memory segment.
I still think that if you want to access normal variables with an index, the best, and least error prone, solutions is to bind them into an array:
elem1: MY_STRUC;
elem2: MY_STRUC;
...
elemN: MY_STRUC;
arr: ARRAY [1..N] OF POINTER TO MY_STRUC := [ADR(elem1), ADR(elem2), ..., ADR(elemN)];
FOR Index := 1 TO N DO
// use arr[Index]^
END_FOR

Related

How to assign variable length to an array in twincat3

I need to change the length of the array dynamically.Right now the code looks like this:
VAR
arrData : ARRAY[1..200] OF INT;
length : INT := 200;
END_VAR
The size of the array depends on the length variable.Here the value of length variable can be changed during runtime using the VISU(gui). So if I change the value of the length = 180 then 20 bytes of arrData are unused.Is there a way to declare the array as variable length similar to vectors in c++ such that the memory is not assigned during declaration but during runtime.
Edit:
How to deallocate the memory safely?
PROGRAM MAIN
VAR
arrData : POINTER TO INT;
length : INT := 200; // can be changed at runtime
bNew : BOOL := TRUE;
oldLength : INT; // to hold the old length variable
isInit : BOOL := FALSE;
END_VAR
IF NOT isInit THEN
oldLength := length; // initialise only once
isInit := TRUE;
END_IF
// if length is changed during runtime then delete the array
IF oldLength <> length THEN
IF arrData <> 0 THEN
__DELETE(arrData);
bNew := TRUE;
END_IF
oldLength := length;
END_IF
// during the first run or when the length is changed
IF bNew THEN
arrData := __NEW(INT,length);
bNew := FALSE;
END_IF
// deallocate the memory when the MAIN program goes out of scope
// as we are not deleting the array when the length variable is not
// changed during runtime
END_CASE
The way to do it is to use __NEW __NEW in Infosys
pMyPointer := __NEW(INT, length);
__NEW will return a pointer to first element of an array. You can access latter elements by offsetting this pointer.
You can check if length was changed by comparing value from this and previous cycle. If so, __DELETE the old array and initialize a new one.
Edit:
I think, that you get your error the moment TwinCAT runtime is stopped, as the momory allocated by __NEW is not freed at that point.
Your code should be placed not in a Program (PRG) but in a Function Block (FB). The reason for that is that you need to implement FB_exit method (This method is called implicitly when FB instance is destroyed, i.e. when stopping TwinCAT runtime like you do by activating configuration). There is no equivalent method for a PRG as far as I know.
To do this:
Create a new FB, instantiate it and call it in your MAIN and move your code from MAIN to the FB
Add a FB_exit method to this FB. Exact naming is crucial
In your FB_exit method write the following code:
IF __ISVALIDREF(arrData) THEN
__DELETE(arrData);
END_IF
This method will be called every time you stop your runtime and free the memory.
Links to Infosys:
__ISVALIDREF - equal to pMyPointer <> 0 but more readable
FB_exit

Declare values of array of structures in POU using PLC ST

I have structure declared like this:
TYPE board:
STRUCT
number: INT;
color: DWORD;
END_STRUCT
END_TYPE
And i want to declare array of these structures with starting values in POU. I do it like this:
Program PLC_PRG
VAR
arr1: ARRAY[1..61] OF board;
board: board;
arr1[1].color := 16#FF0000;
END_VAR
But i get an error which say: "Error 4024: PLC_PRG(10): Expecting ':' before '['".
Do anybody know how to solve this problem?
You didn't specify a platform but I'll assume TwinCAT. To initialize an array of structures, you have to do it all in the variable declaration line.
arr1: ARRAY[1..61] OF board := [(number:=7, color:=16#FF0000), (number:=5, color:=16#FF0001)];
This example will initialize elements 1 and 2 of the array. To my knowledge you can't selectively initialize individual array elements as you are maybe wanting to do. You could use statements like arr1[17].color := 16#FF0000 of course but it would have to be outside of your variable declaration block.

User defined functions with params

In codesys some functions support what in other languages is usually called 'params', i.e. a function that can take a varied amount of similarly typed variables. For example the ADD Operator (function in ladder).
My question is, if there's any way to do the same in user defined functions?
The only idea that I have so far is to take an ARRAY [*] OF SOMETHING and use LOWER_BOUND and UPPER_BOUND to do the computations. This does work, but requires the user to create an additional array variable every time they want to call my function. For example, we have the CONCAT function that concatenates 2 strings. Suppose I want a CONCAT_ALL function that takes n strings and concatenates them all:
STRS: ARRAY [0..9] OF STRING := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10];
// This works, but I want to avoid creating an array variable!
CONALL1: STRING := CONCAT_ALL(STRINGS := STRS);
// This doesn't work!
CONALL2: STRING := CONCAT_ALL(STRINGS := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10]);
(EDIT: As I was asked, I am using Schneider Electric Machine Expert 1.2, or CODESYS compiler 3.5.12.80)
There is hope in the future!
In Codesys V3.5 SP16 it seems to be finally possible to use FUNCTIONs and METHODs with optional arguments. Of course this will be in non-codesys products, like TwinCAT and Schneider, in later versions.
This means you can finally create a CONCAT with 100 arguments and call it with just for example 3! Awesome.
https://www.codesys.com/fileadmin/data/Images/Download/features-and-improvements-V35SP16-en.pdf
Here is an object oriented example of a string concatenator Function Block:
First we define an Interface with 2 methods:
INTERFACE I_MultipleConcat
METHOD concatString : I_MultipleConcat
VAR_INPUT
sTarget : STRING;
END_VAR
METHOD getResult
VAR_IN_OUT
sRetrieveResult : STRING(1000);
END_VAR
Then the Function Block which implements the Interface:
FUNCTION_BLOCK FB_MultipleConcat IMPLEMENTS I_MultipleConcat
VAR_OUTPUT
uiLen : UINT;
END_VAR
VAR
sResult : STRING(1000);
END_VAR
//-------------------------------------------------------------------
METHOD concatString : I_MultipleConcat
VAR_INPUT
sTarget : STRING;
END_VAR
//make sure that the length of sResult is not exceeded
IF uiLen + INT_TO_UINT(LEN(sTarget)) <= (SIZEOF(sResult)-1)
THEN
//add uiLen as offset to sResult memory access
memcpy(ADR(sResult) + uiLen,ADR(sTarget),LEN(sTarget));
uiLen := uiLen + INT_TO_UINT(LEN(sTarget));
END_IF
//return the instance of this FuncBlock in order to concat new strings
//with concatString() or pass the result to another STRING with getResult()
concatString := THIS^;
//-------------------------------------------------------------------
METHOD getResult
VAR_IN_OUT
sRetrieveResult : STRING(1000);
END_VAR
sRetrieveResult := sResult;
sResult := '';
uiLen := 0;
You can call it like this:
IF NOT bInit
THEN
bInit := TRUE;
//s1 must be a STRING(1000) otherwise compile error
fbMultipleConcat
.concatString('Test1 ')
.concatString('Test2 ')
.concatString('Test3 ')
.getResult(s1);
END_IF
Short answer: There is no way to pass n arguments to a function.
Structured text is a strongly and statically typed language designed for hard real time requirements and it is not a scripting language like Python.
If you have a lot of string manipulations in your code that you don't want to do in python but in your real time loop (and you should assess if it's really necessary depending on your requirements) and still want to make it in a comfortable way, then you have to put some effort in it and build a string manipulation library yourself.
After that you could have a very comfortable function call like this:
sResult := F_Concat6(str1,str2,str3,str4,str5,str6);
I understand that it is tempting to adopt thought and programming patterns learned from other programming languages, but structured text and real time industrial control programming is really another kind of beast compared to common user land programming.
With that I mean, that there are specific reasons why the language is designed as it is and when those principles are correctly understood and applied, rock solid architectures derive from them.
To sum it up, my two cents of advice on this:
Think and write software as expected by your domain and do not port incompatible working methods from other domains.
No you cannot pass n arguments to function.
But you can pass an array, with none fixed number of elements. Syntaxyx for Codesys 2.3.
FUNCTION CONCAT_ALL : STRING(250)
VAR_INPUT
asParts: POINTER TO ARRAY[0..10000] OF STRING(20); (* Array of strings *)
iNum: INT; (* Number of elements *)
END_VAR
VAR
iCount: INT; (* For cycle *)
END_VAR
FOR iCount := 0 TO 10000 DO
IF iCount > iNum THEN
EXIT;
END_IF;
CONCAT_ALL := CONCAT(CONCAT_ALL, asParts^[iCount]);
END_FOR;
END_FUNCTION
PROGRAM PLC_PRG
VAR
(* Array 1 to test *)
asTest1: ARRAY[1..2] OF STRING(20) := 'String 1', 'String 2';
(* Array 2 to test *)
asTest2: ARRAY[1..3] OF STRING(20) := 'String 1', 'String 2', 'String 3';
s1: STRING(250);
s2: STRING(250);
END_VAR
s1 := CONCAT_ALL(ADR(asTest1), 2);
s1 := CONCAT_ALL(ADR(asTest2), 3);
END_PROGRAM

Store variable value in each PLC cycle

Is it possible to store a variable value in each PLC cycle? I need the first 10 values each time to perform some calculations. I am using the OpenPCS platform and ST for programming.
You can create an array of values and then store as array values
VAR
aBuffer : ARRAY[1..32] OF WORD;
init:BOOL; (* Init array *)
rest:BOOL; (* Reset *)
val:WORD; (* Value *)
iCount:INT; (* Array index *)
END_VAR
VAR_TEMP
iTmp : INT;
END_VAR
iTmp := UINT_TO_INT(N) - 1;
IF NOT init OR rest THEN
init := TRUE;
FOR iCount := 1 TO iTmp DO
aBuffer[iCount] := val;
END_FOR;
END_IF
iCount := INC1(iCount, 32);
aBuffer[iCount] := val;
This is a code example that created 32 elements array and every new PLC cycle assign new element and rotates.
After that, you can calculate the average or min and max.
INC1 increments given value by one until it reaches 32 and then reset to 1.

Integer to real conversion function

Is there a common conversion function to convert a integer type object to a real type in VHDL?
This is for a testbench so synthesizability is a non-issue.
You can convert integer to real as follows:
signal i: integer;
signal R: Real;
...
R <= Real(i);