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) auf GitHub
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) auf GitHub
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) auf GitHub
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);
Scheint nicht in CoDeSys 3.5 SP6 zu funktionieren. Ist das vielleicht etwas Beckhoff-Spezifisches?
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
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
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
Sehr geehrter Herr Henneken,
ist es möglich Arrays mit variabler Länge mit Hilfe des FB_Inits zu übergeben?
Hallo Timo,
das ist leider nicht möglich. Die Paramater, die man per FB_init() an den FB übergeben möchte, müssen im VAR_INPUT-Bereich stehen. Arrays mit variabler Länge müssen per VAR_IN_OUT übergeben werden. Das schließt sich leider gegenseitig aus.
Gruß
Stefan
Hallo Herr Henneken,
sehen/ kennen Sie eine Möglichkeit ein variables Array als Typ während der Laufzeit mit dem __NEW Befehl oder ähnlichem zu erzeugen?
var:
pArrayBytes : POINTER TO ARRAY[*] OF INT;
end_var
pArrayBytes := __NEW(INT, 25);
–> Übergabe an Funktion als variables Array, welches jetzt die Länge 25 hat
Ich danke
Hannes
OK, ich habe es über die „alte“ Pointervariante gelöst. Geht auch. Grüße, Hannes
Hallo Hannes,
bei deinem Ansatz wird der Compiler die Zeile anmerken wo der Pointer deklariert wird: pArrayBytes : POINTER TO ARRAY[*] OF INT.
‘ARRAY[*]’ erlaubt der Compiler nur im VAR_IN_OUT Bereich. ‘POINTER TO INT’ und ‘ARRAY[*] OF INT’ sind für den Compiler zwei unterschiedliche Datentypen. Deshalb kann mit dem POINTER OF INT keine Funktion aufgerufen werden die als Datentyp ein ARRAY[*] OF INT erwartet.
Hier würde ich ebenfalls den klassischen Weg nehmen und auch bei Funktion einen Pointer als Datentyp für den Parameter nehmen.
Aber für deine Idee, ARRAY[*] zu benutzen und diese Arrays dynamisch zu erzeugen, gibt es sicher ausreichend Anwendungsfälle.
Stefan
Ich habe festgestellt, dass LOWER_BOUND(arrData, 1) TO UPPER_BOUND(arrData, 1)
nur bei Arrays deren Array Grenzen mit Konstanten definiert wurden funktionieren.
Die Übergabe per [*] funktioniert nicht bei einem
VAR
arrData : ARRAY[1..10] OF INT;
END_VAR
nicht, sondern nur wie oben von Dir erklärt :
PROGRAM MAIN
VAR
arrData : ARRAY[1..ARRAY_UPPER_BOUND] OF INT;
END_VAR
VAR CONSTANT
ARRAY_UPPER_BOUND : INT := 10;
END_VAR
Liebe Grüße Darius