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

Sample 4 (TwinCAT 3.1.4020) on GitHub

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

15 thoughts on “IEC 61131-3: Arrays with variable length”

  1. 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…

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

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

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

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

  5. 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…

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

  6. 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?

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

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

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

Leave a comment