## IEC 61131-3: Arrays with variable length

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).