Home > IEC 61131-3 > IEC 61131-3: Arrays mit variabler Länge

IEC 61131-3: Arrays mit variabler Länge

Bei der Deklaration von Arrays musste bisher immer eine konstante Größe angegeben werden. Ab der 3rd Edition der IEC 61131-3 können Arrays mit einer variablen Länge deklariert werden. Funktionen lassen sich dadurch deutlich generischer anlegen als bisher.

Zwar können für die Arraygrenzen auch Variablen benutzt werden, diese Variablen müssen aber als Konstanten deklariert werden. Eine Anpassung der Arraygrenzen zur Laufzeit ist somit nicht möglich.

PROGRAM MAIN
VAR
  arrData             : ARRAY[1..ARRAY_UPPER_BOUND] OF INT;
END_VAR
VAR CONSTANT
  ARRAY_UPPER_BOUND   : INT := 10;	
END_VAR

Gerade wenn Arrays als Parameter an Funktionen oder Funktionsblöcken übergeben werden, stellen feste Arraygrenzen eine unangenehme Limitierung dar. Ist diese nicht hinnehmbar, musste bisher auf Pointerarithmetik gewechselt werden, mit allen Nachteilen. Hier ein einfaches Beispiel, welches die Summe eines eindimensionalen Arrays von LREAL-Variablen berechnet.

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

Die Funktion kann für die Addition beliebiger LREAL-Arrays genutzt werden. Sie ist unabhängig von der Anzahl der Elemente und von der oberen und unteren Arraygrenze.

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

Beispiel 1 (TwinCAT 3.1.4020)

Allerdings hat diese Lösung einige Nachteile. Zum einen eben die Tatsache das Pointerarithmetik benutzt werden muss. Der Quellcode der Funktion wird schon bei relativ einfachen Aufgaben recht komplex. Zum anderen muss an die Funktion auch eine Größen- bzw. Längenangabe übergeben werden. Bei dem Aufruf muss also sichergestellt werden, das der Pointer auf das Array und die Längenangabe übereinstimmen.

Seit der 3rd Edition der IEC 61131-3 können Arrays auch mit variabler Arraygrenze definiert werden. Statt der Arraygrenze, wird ein ’*’ angegeben:

arrData   : ARRAY[*] OF LREAL;

Wird die Funktion aufgerufen, so muss das übergebene Array konstante Arraygrenzen besitzen. In der Funktion kann über die Funktionen LOWER_BOUND und UPPER_BOUND die jeweilige obere- und untere Arraygrenze abgefragt werden.

Derzeit können Arrays mit variabler Länge nur an VAR_IN_OUT Variablen von Funktionen, Funktionsblöcken und Methoden übergeben werden (bleibt zu hoffen, dass in Zukunft auch VAR_INPUT und VAR_OUTPUT Variablen unterstützt werden).

Hier das angepasste Beispiele:

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

Die Funktion erwartet als Eingangsparameter nur noch ein Array von LREAL-Werten. Die Anzahl der Array-Elemente ist variabel. Mit LOWER_BOUND und UPPER_BOUND kann eine Iteration über das gesamte Array durchgeführt werden. Der Quellcode ist deutlich lesbarer als im ersten Beispiel.

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

Beispiel 2 (TwinCAT 3.1.4020)

Auch werden mehrdimensionale Arrays unterstützt. Bei der Deklaration müssen alle Dimensionen als variabel angelegt werden:

arrData    : ARRAY[*, *, *] OF LREAL;

Der zweite Parameter von UPPER_BOUND und LOWER_BOUND gibt die Dimension an, von der die jeweilige Arraygrenze ermittelt werden soll.

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

Bei dem Aufruf kann ein beliebiges dreidimensionales Array von LREAL-Werten an die Funktion übergeben werden.

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

Beispiel 3 (TwinCAT 3.1.4020)

Somit lassen sich auch komplexere Aufgaben flexibel umsetzen ohne das auf Pointerarithmetik zurückgegriffen werden muss.

Dieses soll zum Schluss an einem Funktionsblock gezeigt werden, der zwei Matrizen miteinander multipliziert. Die Größen der Matrizen sind variabel:

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;

Die Methode kann mit unterschiedlich großen Arrays aufgerufen werden.

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

Beispiel 4 (TwinCAT 3.1.4020)

Advertisements
  1. Jochen
    September 29, 2016 at 10:36 am

    Scheint nicht in CoDeSys 3.5 SP6 zu funktionieren. Ist das vielleicht etwas Beckhoff-Spezifisches?

    • September 29, 2016 at 12:26 pm

      Nein. In der Norm IEC 61131-3 Ed 3.0 wird diese Möglichkeit ab Kapitel 6.5.3 beschrieben.
      Es ist durchaus möglich, das nicht alle Punkte der Norm zu 100 % von einem Hersteller umgesetzt werden.
      Gruß
      Stefan henneken

  2. Norman
    June 19, 2017 at 10:16 pm

    Hallo Stefan.
    Danke für diesen tollen Blog.
    Mit dem __New Operator lassen sich auch zur Laufzeit Arrays variabler Länge erstellen. Gibt es auch eine Möglichkeit ein solches Array mit der TcAds-Bibliothek zu lesen?
    Norman

    • June 22, 2017 at 8:02 am

      Hallo Norman,
      ja, mit einer aktuellen TC3 Version ist dieses möglich. Pointer, die sich in der SPS befinden, lassen sich per ADS dereferenzieren. Man kommt also an die Werte, auf die der Pointer zeigt. Hier ein kleines Beispiel:
      Das SPS-Programm:

      {attribute ‘enable_dynamic_creation’}
      TYPE ST_Test :
      STRUCT
      fA : LREAL;
      nB : INT;
      bC : BOOL;
      END_STRUCT
      END_TYPE

      PROGRAM MAIN
      VAR
      pData : POINTER TO ST_Test;
      END_VAR
      IF (pData = 0) THEN
      pData := __NEW(ST_Test);
      pData^.fA := 3.1415;
      pData^.nB := 1234;
      pData^.bC := TRUE;
      END_IF

      Zum Testen habe ich ein C# Programm genutzt.

      TcAdsClient tcClient = new TcAdsClient();
      tcClient.Connect(“192.168.0.100.1.1”, 851);

      int hVar = tcClient.CreateVariableHandle(“MAIN.pData^”);

      AdsStream dataStream = new AdsStream(16);
      BinaryReader binRead = new BinaryReader(dataStream);

      tcClient.Read(hVar, dataStream);

      dataStream.Position = 0;
      Debug.WriteLine(binRead.ReadDouble().ToString());
      Debug.WriteLine(binRead.ReadInt16().ToString());
      Debug.WriteLine(binRead.ReadBoolean().ToString());

      tcClient.DeleteVariableHandle(hVar);

      Wichtig ist hierbei, das der Zugriff auf die Werte, auf die der Pointer zeigt, nur per Symbolname m;glich ist.
      Als Symbolname muss unbedingt das ^ mit angegeben werden, ansonsten erhält man die Adresse zurück, auf die der Pointer zeigt.
      Bei Arrays können nur einzelne Elemente gelesen werden. Also als Symbolname kann nicht nicht das ganze Array angegeben werden, sondern nur ein Element aus dem Array: “MAIN.pData[5]^”

      Ich hoffe dir damit weitergeholfen zu haben.
      Stefan

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: