While declaring arrays, one had always to define a constant value up to now. Since the 3rd edition of the IEC 61131-3, arrays can be declared with a variable length. Thus, you can create functions much more generically than previously.
Although, variables can be used for array bounds, they have to be declared as constants. An adaption of the array bounds is thus not possible at runtime.
PROGRAM MAIN VAR arrData : ARRAY[1..ARRAY_UPPER_BOUND] OF INT; END_VAR VAR CONSTANT ARRAY_UPPER_BOUND : INT := 10; END_VAR
Fixed array bounds represent an inconvenient limitation especially when arrays are passed to functions or function blocks as parameters. If this limitation is inacceptable, one had to switch to pointer arithmetic with all usual disadvantages. Below is a simple example, which calculates the sum of a one-dimensional array of LREAL variables.
FUNCTION F_CalcSum1DimArrayOldSchool : LREAL VAR_INPUT pData : POINTER TO LREAL; nSize : UDINT; END_VAR VAR pDataIndex : POINTER TO LREAL; nUpperIndex : UDINT; nIndex : UDINT; END_VAR F_CalcSum1DimArrayOldSchool := 0; nUpperIndex := nSize / SIZEOF(pDataIndex^); IF (nUpperIndex > 0) THEN FOR nIndex := 0 TO (nUpperIndex - 1) DO pDataIndex := pData + (nIndex * SIZEOF(pDataIndex^)); F_CalcSum1DimArrayOldSchool := F_CalcSum1DimArrayOldSchool + pDataIndex^; END_FOR END_IF
The function can be used for the addition of arbitrary LREAL arrays. It is independent of the number of elements and of the upper and lower array bounds.
PROGRAM MAIN VAR array01 : ARRAY[2..8] OF LREAL := [16.1, 34.1, 4.1, 43.1, 35.1, 2.1, 65.1]; lrSum01 : LREAL; array02 : ARRAY[-1..2] OF LREAL := [16.1, 34.1, 9.1, 13.1]; lrSum02 : LREAL; array03 : ARRAY[-3..-1] OF LREAL := [16.1, 34.1, 8.1]; lrSum03 : LREAL; END_VAR lrSum01 := F_CalcSum1DimArrayOldSchool(ADR(array01), SIZEOF(array01)); lrSum02 := F_CalcSum1DimArrayOldSchool(ADR(array02), SIZEOF(array02)); lrSum03 := F_CalcSum1DimArrayOldSchool(ADR(array03), SIZEOF(array03));
Sample 1 (TwinCAT 3.1.4020) on GitHub
However, this solution has several drawbacks. First of all, simply the fact that the pointer arithmetic has to be used. The source code of the function gets rather complex even with simple tasks. Secondly, size or length value has also to be passed to the function. When calling a function, it must be guaranteed that the array pointer and the length reference match.
Since the 3rd Edition of IEC 61131-3, array can be defined with a variable array bound. Instead of the array bound, a ā*ā is declared:
arrData : ARRAY[*] OF LREAL;
If the function is called, the passed array should have constant array bounds. By means of the functions LOWER_BOUND and UPPER_BOUND, the corresponding upper and lower array bounds can be queried in the function.
Currently, arrays with a variable length can be passed only to VAR_IN_OUT variables of functions, function blocks and methods. (One would hope that VAR_INPUT and VAR_OUTPUT variables will be supported in the future.)
Here is an adjusted example:
FUNCTION F_CalcSum1DimArray : LREAL VAR_IN_OUT arrData : ARRAY[*] OF LREAL; END_VAR VAR nIndex : DINT; END_VAR F_CalcSum1DimArray := 0; FOR nIndex := LOWER_BOUND(arrData, 1) TO UPPER_BOUND(arrData, 1) DO F_CalcSum1DimArray := F_CalcSum1DimArray + arrData[nIndex]; END_FOR
The function expects only one array of LREAL values as an input parameter. The number of array elements is variable. An iteration over the whole array can be performed with LOWER_BOUND and UPPER_BOUND. The source code is much more readable than in the first example.
PROGRAM MAIN VAR array01 : ARRAY[2..8] OF LREAL := [16.1, 34.1, 4.1, 43.1, 35.1, 2.1, 65.1]; lrSum01 : LREAL; array02 : ARRAY[-1..2] OF LREAL := [16.1, 34.1, 9.1, 13.1]; lrSum02 : LREAL; array03 : ARRAY[-3..-1] OF LREAL := [16.1, 34.1, 8.1]; lrSum03 : LREAL; END_VAR lrSum01 := F_CalcSum1DimArray(array01); lrSum02 := F_CalcSum1DimArray(array02); lrSum03 := F_CalcSum1DimArray(array03);
Sample 2 (TwinCAT 3.1.4020) on GitHub
Multidimensional arrays are also supported. All dimensions have to be declared as variable:
arrData : ARRAY[*, *, *] OF LREAL;
The second parameter of UPPER_BOUND and LOWER_BOUND specifies the dimension whose respective array bounds have to be identified.
FUNCTION F_CalcSum3DimArray : LREAL VAR_IN_OUT arrData : ARRAY[*, *, *] OF LREAL; END_VAR VAR nIndex1, nIndex2, nIndex3 : DINT; END_VAR F_CalcSum3DimArray := 0; FOR nIndex1 := LOWER_BOUND(arrData, 1) TO UPPER_BOUND(arrData, 1) DO FOR nIndex2 := LOWER_BOUND(arrData, 2) TO UPPER_BOUND(arrData, 2) DO FOR nIndex3 := LOWER_BOUND(arrData, 3) TO UPPER_BOUND(arrData, 3) DO F_CalcSum3DimArray := F_CalcSum3DimArray + arrData[nIndex1, nIndex2, nIndex3]; END_FOR END_FOR END_FOR
With the call, any three-dimensional array of LREAL values can be passed to a function.
ROGRAM MAIN VAR array01 : ARRAY[1..2, 3..4, 5..6] OF LREAL := [16.1, 34.1, 4.1, 43.1, 35.1, 2.1, 65.1, 16.1]; lrSum01 : LREAL; END_VAR lrSum01 := F_CalcSum3DimArray(array01);
Sample 3 (TwinCAT 3.1.4020) on GitHub
Thus, more complex tasks can be implemented flexibly without making use of pointer arithmetic.
Finally, it should be demonstrated with a function block which multiplies two matrices. The sizes of the matrices are variable:
METHOD PUBLIC Multiplication : BOOL VAR_IN_OUT arrayA : ARRAY[*, *] OF DINT; arrayB : ARRAY[*, *] OF DINT; arrayX : ARRAY[*, *] OF DINT; END_VAR VAR nIndex1, nIndex2, nIndex3, nIndex4 : DINT; END_VAR; FOR nIndex1 := LOWER_BOUND(arrayA, 1) TO UPPER_BOUND(arrayA, 1) DO FOR nIndex2 := LOWER_BOUND(arrayB, 2) TO UPPER_BOUND(arrayB, 2) DO nIndex4 := 0; FOR nIndex3 := LOWER_BOUND(arrayA, 2) TO UPPER_BOUND(arrayA, 2) DO nIndex4 := nIndex4 + arrayA[nIndex1, nIndex3] * arrayB[nIndex3, nIndex2]; END_FOR; arrayX[nIndex1, nIndex2] := nIndex4; END_FOR; END_FOR;
The method can be called by different-sized arrays.
PROGRAM MAIN VAR fbMatrix : FB_Matrix; arrayA1 : ARRAY[1..2, 1..2] OF DINT := [1, 2, 3, 4]; arrayB1 : ARRAY[1..2, 1..2] OF DINT := [5, 6, 7, 8]; arrayX1 : ARRAY[1..2, 1..2] OF DINT; arrayA2 : ARRAY[1..3, 1..3] OF DINT := [1, 2, 3, 4, 5, 6, 7, 8, 9]; arrayB2 : ARRAY[1..3, 1..3] OF DINT := [5, 6, 7, 8, 10, 11, 12, 13, 14]; arrayX2 : ARRAY[1..3, 1..3] OF DINT; END_VAR fbMatrix.Multiplication(arrayA1, arrayB1, arrayX1); fbMatrix.Multiplication(arrayA2, arrayB2, arrayX2);
The array’s are not really of variable length. VAR_IN_OUT are by defnition just references to variables. So you can pass the array into a FB, but the caller of this FB still needs to define a fixed length array…
This is also totally valid in ST
FUNCTION F_CalcSum1DimArrayOldSchool : LREAL
VAR_INPUT
pData : POINTER TO LREAL;
nSize : UDINT;
END_VAR
F_CalcSum1DimArrayOldSchool := 0;
FOR nIndex := 0 TO nSize-1 DO
F_CalcSum1DimArrayOldSchool := F_CalcSum1DimArrayOldSchool + pData[nIndex];
END_FOR
[] on a Pointer automatically does all the pointer arithmetic.
I find the following works well in CoDeSys V2.3:
datatable : POINTER TO ARRAY [0..0] OF Some_Struct;
datatable^[100].someelement := somevalue; (* index is referenced from 0, no bounds checking occurs *)
Its important to note that this lets me choose the reference origin in the function block receiving access to the array.
However this might be implementation specific. Its worked well on all the PLC Hardware I have used (STW and IFM).
I’ve run into difficulty when writing functions that receive an ARRAY[*,*] as a parameter and tries to pass that variable-length array to another function which also takes an ARRAY[*,*] parameter. In TwinCAT 4022.22 and 4022.30, the compiler throws an exception when it tries to compile the code. It doesn’t even return a nice syntax error! š¦
In my case, I’m writing functions for manipulating variable-size matrixes, and I frequently need to get the row & column count of the actual arrays that the function is working on, so I can validate that the input & output arrays are compatible sizes and shapes. So I defined a “GetMatrixSize” ARRAY[*,*] function, which returns the row and column counts for the passed-in array. GetMatrixSize works great when passed a concrete array of fixed size, but when I try to pass it an ARRAY[*,*] from another function, this is the exception message thrown by the compiler:
System.NullReferenceException: Object reference not set to an instance of an object.
at _3S.CoDeSys.LanguageModelManager.Compiler35100.TypeChecker.CheckInput(_ICallExpression call, ISignature sign, Int32 i, IVariable var, _IExpression exp)
at _3S.CoDeSys.LanguageModelManager.Compiler35100.TypeChecker.visit(_ICallExpression call)
at _3S.CoDeSys.LanguageModelManager.CallExpression.Accept(IExprementVisitor visitor)
at _3S.CoDeSys.LanguageModelManager.Compiler35100.TypeChecker.visit(_IAssignmentExpression assign)
at _3S.CoDeSys.LanguageModelManager.AssignmentExpression.Accept(IExprementVisitor visitor)
at _3S.CoDeSys.LanguageModelManager.Compiler35100.TypeChecker.visit(_IExpressionStatement expstat)
at _3S.CoDeSys.LanguageModelManager.ExpressionStatement.Accept(IExprementVisitor visitor)
at _3S.CoDeSys.LanguageModelManager.Compiler35100.TypeChecker.visit(_ISequenceStatement seq)
It looks like the CoDeSys compiler is unable to walk further up the call stack to find the actual concrete size of the array. Inability to compose functions that operate on ARRAY[*,*] seems to be a pretty severe limitation on the usefulness of the feature.
I’m not sure if the inability to pass ARRAY[*,*] between functions is a known language definition limitation or a compiler bug. Certainly, throwing exceptions rather than syntax errors is a compiler bug.
I’ll be contacting Beckhoff support about it, but it’s a public holiday today in the state where my local support contacts are, so I don’t have any information from them yet.
An update to this:
The compiler bug exists in TwinCAT 4022 up to and including 4022.32 (current release as of time of writing).
It is fixed in TwinCAT 4024.7 (and probably also earlier releases of TC 4024).
Thx for your feedback.
I’m trying to combine this Array[*] thing with polymorphism, but i’m getting confused / hit by compile errors (using codesys v3.5.15).
This is what I have:
1. A object
TYPE Object :
STRUCT
Position: INT := -1;
Width: INT;
END_STRUCT
END_TYPE
2. A bottle which extends my object
TYPE Bottle EXTENDS Object :
STRUCT
Cap: BOOL;
Label: BOOL;
Level: INT;
END_STRUCT
END_TYPE
3. A conveyor that has to transport objects
FUNCTION_BLOCK Conveyor
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
objects: ARRAY[*] OF Object;
END_VAR
Reference to is not allowed for VAR_IN_OUT’s and variable length arrays is not allowed for VAR_IN’s…
Are these variable arrays similar to dynamic arrays like in Python?
Hi Mark,
A dynamic array in Python is more like a list. This list can be expanded with new items at run time.
The ARRAY[*] data type is used to pass arrays of different lengths as parameters to POUs.
So they’re different things.
Stefan
I have a FB that accepts an custom structure array in VAR_IN_OUT
We have 2 arrays of different sizes that will utilize this FBs functionality
We’d like to display the array element data on an HMI.
⢠How can I write the FB variable length array to an external fixed length array defined in GVL (for HMI visibility)?
⢠How can I dereference the elements in the variable array?
Hi,
you can use MEMCPY:
MEMCPY(ADR(GVL_HMI.aGlobalValues), aValues, SIZEOF(GVL_HMI.aGlobalValues));
Where aValues is the array of you FB:
VAR_IN_OUT
aValues : ARRAY[*] OF ST_Foo;
END_VAR
aGlobalValues is a global array in GVL_HMI:
VAR_GLOBAL
aGlobalValues: ARRAY[1..5] OF ST_Foo;
END_VAR
and ST_Foo is a simple structure:
TYPE ST_Foo :
STRUCT
nValue : INT;
bValue : BOOL;
END_STRUCT
END_TYPE
Or you can work with a loop to copy each element to the global array:
FOR nIndex := LOWER_BOUND(aValues, 1) TO UPPER_BOUND(aValues, 1) DO
GVL_HMI.aGlobalValues[nIndex].bValue := aValues^.bValue;
GVL_HMI.aGlobalValues[nIndex].nValue := aValues^.nValue;
END_FOR;
Direct assignment to the global variable is not possible:
GVL_HMI.aGlobalValues := aValues^; // Error
Stefan
Hi,
Just for your information:
Passing a derived object into the dynamic array doesn’t seems to work.
Example:
TYPE Base :
STRUCT
x:BOOL;
END_STRUCT
END_TYPE
TYPE Derived EXTENDS Base :
STRUCT
y:BOOL;
END_STRUCT
END_TYPE
FUNCTION Func
VAR_IN_OUT
baseStruct : ARRAY[*] OF Base;
END_VAR
VAR
di : DINT;
END_VAR
FOR di := LOWER_BOUND(baseStruct, 1) TO UPPER_BOUND(baseStruct, 1) DO
baseStruct[di].x:=TRUE;
END_FOR
Now call the function:
derivedStruct:ARRAY[1..2] OF Derived;
Func(derivedStruct);
Compiler gives error:
C0032: Cannot convert type ‘ARRAY [1..2] OF Derived’ to type ‘ARRAY[*] OF Base’
I expected this to work š¦
Thanks!
Hi Stefan,
Thanks for the hint.
I would expect that too.
I will discuss your example internally.
Stefan
I’ve read this article dozens of times over the years, its quite the coincidence that a comment with my exact issue today (Passing an array of an extended type, variable length or no, into an input of its base type is invalid) would show up only a couple weeks ago.
Please let us know what you find. Currently my only work-around is giving the base type an Interface that works with __QueryPointer, and using __QueryPointer to cast each entry in the passed array when accessing them, with little to no protection against casting the wrong thing. Definitely not a solution for a library I’m trying to write! Very curious to find an answer, even have a question up on StackOverflow https://stackoverflow.com/questions/69319659/how-do-i-pass-an-array-of-an-extended-type-in-codesys-twincat3