Home > IEC 61131-3 > IEC 61131-3: Weitere Spracherweiterungen

IEC 61131-3: Weitere Spracherweiterungen

Bisher lag der Schwerpunkt meiner Posts in den objektorientierten Erweiterungen. Es gibt aber noch einige allgemeine, meist nicht so tiefgreifende, Neuerungen innerhalb von TwinCAT 3. Im Folgenden sollen diese kurz vorgestellt werden.

Initialisieren von Arrays

Soll ein Array beim Anlegen initialisiert werden, so muss die Liste mit den Initialisierungswerten in eckige Klammern gesetzt werden.

VAR
  aTest1  : ARRAY [1..5] OF INT := [1, 2, 3, 4, 5];
  aTest2  : ARRAY [1..5] OF INT := [1, 3(5), 2];  (* kurz für [1, 5, 5, 5, 2] *)
END_VAR

Bisher waren die eckigen Klammern nicht notwendig.

Einzeilige Kommentare

C++, Java und C# lassen grüßen: Jetzt kann ein Kommentar mit // begonnen werden. Der Kommentar endet mit dem Zeilenumbruch.

VAR
  bVar   : BOOL;  // einzeiliger Kommentar
  nVar1  : INT;   (* mehrzeiliger
                     Kommentar *)
  nVar2  : INT;
END_VAR

Kommentare über mehrere Zeilen können weiterhin mit (**) angelegt werden.

CONTINUE in Schleifen

Bisher konnte innerhalb von FOR, WHILE und REPEAT-Schleifen mit der Anweisung EXIT die Schleife vorzeitig beendet werden. Jetzt ist es mit CONTINUE auch möglich, eine Schleife vorzeitig fortzusetzen.

PROGRAM MAIN
VAR
  nVar     : INT;
  nCounter : INT;
END_VAR

nCounter := 0;
FOR nVar := 1 TO 10 DO
  IF ((nVar MOD 2) = 0) THEN
    nCounter := nCounter + 1;
    CONTINUE;
  END_IF
  // weitere Befehle
  /// ...
END_FOR

Im obigen Beispiel wird die Variable nCounter nur dann erhöht, wenn nVar eine gerade Zahl ist. Alle Befehle, die unterhalb von CONTINUE stehen, werden nach dem Aufruf von CONTINUE nicht ausgeführt.

UNION in benutzerdefinierten Datentypen

Mit einer UNION (Verbund) ist es möglich, eigene Datentypen zu definieren, deren Elemente alle denselben Speicherplatz belegen. Die Speicherbereiche der einzelnen Elemente überlappen sich ganz oder zumindest teilweise. Eine UNION belegt dabei mindestens so viel Speicher, wie ihre größte Komponente.

TYPE U_Test :
UNION
  nVar1  : WORD;
  nVar2  : BYTE;
END_UNION
END_TYPE

Hier ein Beispiel, bei dem auf eine 16-Bit Variable auch einzeln auf das Low-Byte und auf das High-Byte zugegriffen werden kann. Dazu wird zuerst eine Struktur angelegt, die aus zwei Bytes besteht.

TYPE ST_Bytes :
STRUCT
  nVar1  : BYTE;
  nVar2  : BYTE;
END_STRUCT
END_TYPE

Diese Struktur, die eine Größe von 16-Bit belegt, wird in einer UNION mit einem WORD verbunden.

TYPE U_Test :
UNION
  nVar1   : WORD;
  stVar2  : ST_Bytes;
END_UNION
END_TYPE

Beide Variablen der UNION beginnen im Speicher ab der gleichen Adresse. Der Zugriff auf die einzelnen Elemente einer UNION erfolgt genauso, wie bei einer Struktur. Mit dem Unterschied, dass in diesem Fall ein Zugriff auf die Variable uVar.nVar1 auch die Variable uVar.stVar2 beeinflusst.

PROGRAM MAIN
VAR
  uVar     : U_Test;
  nA, nB   : BYTE;
END_VAR

uVar.nVar1 := 16#1234;
nA := uVar.stVar2.nVar1;    // Wert: 16#34 (LSB)
nB := uVar.stVar2.nVar2;    // Wert: 16#12 (MSB)

Nach dem Start des Programms ändert sich in der UNION auch der Wert der Variable stVar2. Dieses Beispiel zeigt, wie ohne Bit-Operationen aus einer Variablen vom Typ WORD das niedrigere Byte (LSB) und das höherwertige Byte (MSB) ermittelt werden.

Datentyp LTIME

Der Datentyp TIME erlaubt eine Auflösung nur im Millisekunden-Bereich. Da die Zykluszeiten von Steuerungen mittlerweile die 1 ms-Grenze unterschreiten, wurde es notwendig einen genaueren Datentyp für Zeitangaben zu definieren. LTIME hat eine Größe von 64 Bit (statt 32 Bit) und erlaubt eine Auflösung im Nanosekunden Bereich.

PROGRAM MAIN
VAR
  tTest  : LTIME;
END_VAR

tTest := LTIME#134D12H13M34S354MS2US74NS;

Datentyp WSTRING

Der Datentyp STRING kodiert die Zeichen nach ASCII. Jedes Zeichen wird durch ein Byte repräsentiert. Dadurch ist es zwar möglich, die meisten Buchstaben und Zeichen darzustellen, aber eben nicht alle. Um den Anforderungen anderer Sprachen gerecht zu werden, wurde der Datentyp WSTRING eingeführt. Dieser codiert die Zeichen nach Unicode. Unicode verwendet bis zu 4 Byte pro Zeichen. Bei TwinCAT 3 wird eine Unicode Variante benutzt, die immer 2 Byte pro Zeichen belegt. Über 65.000 verschiedene Zeichen können damit unterschieden werden. Damit sind die meisten, von Menschen verwendeten Schriftzeichen darstellbar, sofern sie in den Unicode-Standard aufgenommen wurden. Der Nachteil ist der höhere Speicherbedarf, wie folgendes Beispiel zeigt:

PROGRAM MAIN
VAR
  wsTest        : WSTRING(10) := "abcdefäüöß";
  sTest         : STRING(10) := 'abcdefäüöß';
  nSizeWString  : UDINT;
  nSizeString   : UDINT;
END_VAR

nSizeWString := SIZEOF(wsTest);  // Wert: 22
nSizeString := SIZEOF(sTest);    // Wert: 11

Unterschiede zwischen WSTRING und STRING gibt es auch bei der Initialisierung. Während eine STRING-Konstante mit dem Hochkomma definiert wird (Zeile 4), wird bei einer WSTRING-Konstante das Anführungszeichen verwendet (Zeile 3).

Datentyp REFERENCE

Dieser Datentyp ist vergleichbar mit den Datentyp POINTER. Auch eine Referenz enthält einen Verweis auf eine andere Variable. Im Gegensatz zu einem Zeiger wird der Wert, auf den gezeigt wird, direkt beeinflusst. Ein Dereferenzieren wie bei Pointern ist nicht notwendig. Zuweisungen erfolgen bei Referenz-Variablen mit dem speziellen Zuweisungsoperator REF=.

PROGRAM MAIN
VAR
  refSample  : REFERENCE TO INT;
  nA         : INT;
  pSample    : POINTER TO INT;
  nB         : INT;
END_VAR

refSample REF= nA;
refSample := 12;
pSample := ADR(nB);
pSample^ := 12;

Sowohl die Variable nA als auch nB haben nach dem Starten des Programms den Wert 12.

Referenzen können auch direkt bei der Deklaration initialisiert werden. Hierbei wird allerdings der übliche Zuweisungsoperator verwendet (Zeile 3).

PROGRAM MAIN
VAR
  refSample  : REFERENCE TO INT := nA;
  nA         : INT;
  pSample    : POINTER TO INT;
  nB         : INT;
END_VAR

refSample := 12;
pSample := ADR(nB);
pSample^ := 12;

Mit Hilfe des Operators __ISVALIDREF kann überprüft werden, ob eine Reference-Variable einen gültigen Wert enthält.

PROGRAM MAIN
VAR
  refSampleA  : REFERENCE TO INT := nA;
  nA          : INT;
  refSampleB  : REFERENCE TO INT;
  a, b        : BOOL;
END_VAR

a := __ISVALIDREF(refSampleA);   // TRUE
b := __ISVALIDREF(refSampleB);   // FALSE

Eine Reference-Variable wird ungültig, wenn ihr der Wert 0 zugewiesen wird.

refSample REF= 0;
a := __ISVALIDREF(refSample);   // FALSE

Zugriff auf Strings per [ ]

Auf die einzelnen Zeichen einer Variablen vom Typ STRING oder WSTRING kann mit dem Index-Operator zugegriffen werden. Bei einer Variablen vom Typ STRING liefert dieser den ASCII-Code als Byte zurück. Wird auf eine Variable vom Typ WSTRING zugegriffen, so wird der Unicode als WORD zurückgeliefert.

Es kann auch schreibend auf die Zeichen zugegriffen werden (Zeile 11). Das Array beginnt bei 0, d.h. das 1. Zeichen (von links beginnend) hat den Index 0.

PROGRAM MAIN
VAR
  wsTest          : WSTRING(10) := "abcdefgh";
  nLetterWString  : WORD;
  sTest           : STRING(10) := 'abcdefgh';
  nLetterString   : BYTE;
END_VAR

nLetterString := sTest[3];    // ASCII-Code von 'd': 100
nLetterWString := wsTest[5];  // Unicode von 'f': 102
wsTest[5] := 120;             // der 6. Buchstabe wird ein 'x'

bedingte Kompilierung

Bedingte Kompilierung ermöglicht dem Programmierer, das Kompilieren bestimmter Programmteile vom Zutreffen bestimmter Bedingungen abhängig zu machen. Bedingte Kompilierungsanweisungen sind so konzipiert, dass sie nicht zur Laufzeit, sondern während der Kompilierung durchgeführt werden.

Mit {IF} und {END_IF} kann ein Quelltextbereich eingrenzt werden, der nur dann kompiliert wird, wenn eine bestimmte Bedingung erfüllt ist. Ein {IF}/{END_IF} kann bei Bedarf auch mehrere {ELSIF}-Zweige und einen {ELSE}-Zweig aufweisen.

Die bedingte Kompilierung ist sehr hilfreich, wenn ein Programm für unterschiedliche Ausprägungen entwickelt werden soll. Bereiche, die nicht immer nötig sind, können somit vom Kompilieren ausgeschlossen werden. Dadurch wird nur der Code auf die Steuerung geladen, der tatsächlich benötigt wird.

Es können verschiedene Bedingungen abgefragt werden:

{IF defined (identifier)}

Die Abfrage ist TRUE, wenn identifier definiert wurde. Das Definieren kann mit der Anweisung {define identifier} im Quelltext erfolgen. Dieses bezieht sich aber nur auf den aktuellen Gültigkeitsbereich. Der Gültigkeitsbereich beginnt direkt nach der Anweisung von {define identifier} und endet spätestens mit dem Ende des jeweiligen Quelltextes oder mit der Anweisung {undefine identifier}.

Soll für den gesamten Gültigkeitsbereich des SPS-Projektes die Definition erfolgen, so kann dieses über den Eigenschaftsdialog des SPS-Projektes erfolgen. Mehrere Definitionen werden durch ein Komma voneinander getrennt.

Picture01

Eine globale Definition kann nicht durch ein {undefine identifier} aufgehoben werden.

Anweisungen für die bedingte Kompilierung sind nur im Programmbereich vom strukturierten Text und in globale Variablenlisten erlaubt. Im Deklarationsteil der Variablen oder in der Definition von Datentypen wie z.B. Strukturen sind diese (leider) nicht zulässig.

PROGRAM MAIN
VAR
  nScaling : INT;
END_VAR

{IF defined (MachineTypeA)}
  nScaling := 5;
{ELSIF defined (MachineTypeB)}
  nScaling := 10;
{ELSE}
  nScaling := 20;
{END_IF}
{IF defined (variable:variableName)}

Die Abfrage ist erfüllt, wenn die Variable variableName im aktuellen Gültigkeitsbereich definiert wurde.

PROGRAM MAIN
VAR
  nScaling     : INT := 1;
  // nOffset   : INT := 2;
  nX           : INT;
END_VAR

{IF defined (variable:nOffset)}
  nX := 100 * nScaling + nOffset;
{ELSE}
  nX := 100 * nScaling;
{END_IF}

Nach dem Starten des Programms, ist die Variable nX 100.

Es kann auch auf das Vorhandensein von Elementen innerhalb von Strukturen, Unions und Enums geprüft werden. Befindet sich z.B. eine entsprechende Struktur innerhalb einer SPS-Bibliothek, so kann die bedingte Kompilierung helfen, das eine Applikation sowohl mit der neueren Version, als auch mit der älteren Version der SPS-Bibliothek kompiliert werden kann.

PROGRAM MAIN
VAR
  stTest  : ST_Test;
END_VAR

stTest.nA := 1;
stTest.nB := 2;
{IF defined (variable:stTest.nC)}
  stTest.nC := 3;
{END_IF}
{IF defined (type:typeName)}

Die Abfrage ist TRUE, wenn eine Struktur, Aufzählung oder Verbund mit dem Namen typeName existiert.

{IF hastype (variable:variableName, dataType)}

Die Bedingung ist erfüllt, wenn die Variable variableName vom Type dataType ist.

PROGRAM MAIN
VAR
  varSample : LREAL;
END_VAR

{IF hastype (variable:varSample, LREAL)}
  varSample := 123.456;
{END_IF}
{IF hastype (variable:varSample, INT)}
  varSample := 123;
{END_IF}

Die Variable varSample bekommt nach dem Starten des Programms den Wert 123.456 zugewiesen.

{IF hasvalue (identifierName, identifierValue)}

Bei der Definition eines Identifiers kann diesem optional ein Wert vom Typ STRING zugewiesen werden. Dieser Wert kann abgefragt werden.

PROGRAM PLC_PRG
VAR
  nSample : INT;
END_VAR

{define identifier '1'}
{IF hasvalue (identifier, '1')}
  nSample := 100;
{END_IF}
{IF hasvalue (identifier, '2')}
  nSample := 200;
{END_IF}

Leider ist die Zuweisung eines Wertes zu einem Identifier nicht in den Eigenschaftendialog vom SPS-Projekt möglich.

Nach dem Starten des Programms hat die Variable nSample den Wert 100.

{IF defined (pou:pouName)}

Die Bedingung ist erfüllt, wenn ein POU (FUNCTION, FUNCTION BLOCK oder PROGRAM) mit den Namen pouName vorhanden ist. Es kann sich hierbei auch um einen POU aus einer SPS-Bibliothek handeln.

PROGRAM MAIN
VAR
  nSample : INT;
END_VAR

{IF defined (pou:F_CheckRange)}
  nSample := 100 * GVL.nValue;
{ELSE}
  nSample := 100 * F_CheckRange(GVL.nValue);
{END_IF}
{IF hasattribute (pou:pouName,‘attributeName‘)}

Die Bedingung ist erfüllt, wenn an dem POU pouName das Attribut attributeName vorhanden ist. POUs werden wie folgt mit einem Attribut versehen:

{attribute 'debug'}
FUNCTION TraceInfos : STRING
VAR_INPUT
  i : INT;
END_VAR
{IF hasattribute (variable:variableName,‘attributeName‘)}

Auch Variablen können mit Attributen deklariert werden, so wie es bei POUs ebenfalls möglich ist.

VAR
  {attribute 'debug'}
  nSample : INT;
END_VAR
Operatoren AND, OR, NOT und ()

Mehrere Abfragen können auch miteinander kombiniert werden.

PROGRAM MAIN
VAR
  nScaling : INT;
END_VAR

{IF hasvalue (identifier, '1') AND hastype (variable:nSample, WORD)}
  nScaling := 5;
{ELSIF defined (MachineTypeB)}
  nScaling := 10;
{ELSE}
  nScaling := 20;
{END_IF}
Advertisements
  1. Benjamin Berger
    July 9, 2016 at 9:32 am

    Danke für den tollen Beitrag. Hat mir wirklich sehr weitergeholfen!
    Ben

  1. August 16, 2017 at 6:05 pm

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: