IEC 61131-3: Ausnahmebehandlung mit __TRY/__CATCH

Bei der Ausführung eines SPS-Programms kann es zu unerwarteten Laufzeitfehlern kommen. Diese treten auf, sobald das SPS-Programm versucht eine unzulässige Operation auszuführen. Auslöser solcher Szenarien kann z.B. eine Division durch 0 sein oder ein Pointer verweist auf einen ungültigen Speicherbereich. Mit den Schlüsselwörtern __TRY und __CATCH kann auf diese Ausnahmen deutlich besser reagiert werden als bisher.

Die Liste der möglichen Ursachen für Laufzeitfehler kann endlos erweitert werden. Allen Fehlern ist aber gemeinsam: Sie führen zum Absturz des Programms. Bestenfalls wird durch eine Meldung auf den Laufzeitfehler hingewiesen:

Pic01

Da sich anschließend das SPS-Programm in einem nicht definierten Zustand befindet, wird das System gestoppt. Dies ist anhand des gelben TwinCAT Icon in der Windows Taskleiste zu erkennen:

Pic02

Für in Betrieb befindliche Anlagen ist das unkontrollierte Stoppen nicht immer die optimalste Reaktion. Außerdem gibt die Meldung nur unzureichend Auskunft darüber, wo genau im SPS-Programm der Fehler aufgetreten ist. Eine Optimierung der Software ist dadurch nur schwer möglich.

Um Fehler schneller ausfindig zu machen, können in dem SPS-Programm Überprüfungsfunktionen eingefügt werden.

Pic03

Überprüfungsfunktionen werden jedes Mal aufgerufen, wenn die entsprechende Operation ausgeführt wird. Am bekanntesten dürfte die Funktion CheckBounds() sein. Bei jedem Zugriff auf ein Arrayelement wird vorher diese Funktion implizit aufgerufen. Als Parameter erhält die Funktion die Arraygrenzen und den Index des Elements, auf das der Zugriff erfolgen soll. Die Funktion kann so angepasst werden, dass bei einem Zugriff außerhalb der Arraygrenzen eine Korrektur erfolgt. Dieser Ansatz hat allerdings einige Nachteile:

  1. In CheckBounds() kann nicht festgestellt werden auf welches Array zugegriffen wird. Somit kann nur für alle Arrays die gleiche Fehlerkorrektur implementiert werden.
  2. Da bei jedem Arrayzugriff die Überprüfungsfunktion aufgerufen wird, kann sich die Laufzeit des Programms erblich verschlechtern.

Ähnlich verhält es sich auch bei den anderen Überprüfungsfunktionen.

Nicht selten werden die Überprüfungsfunktionen nur während der Entwicklungsphase eingesetzt. In den Funktionen werden Breakpoints aktiviert, die, sobald eine fehlerhafte Operation ausgeführt wird, das SPS-Programm anhalten. Über den Callstack kann anschließend die entsprechende Stelle im SPS-Programm ermittelt werden.

Die ‚try/catch‘-Anweisung

Allgemein werden Laufzeitfehler als Ausnahmen (Exceptions) bezeichnet. Für das Erkennen und Bearbeiten von Exceptions gibt es in der IEC 61131-3 die Anweisungen __TRY, __CATCH und __ENDTRY:

__TRY
  // statements
__CATCH (exception type)
  // statements
__ENDTRY
// statements

Der TRY-Block (die Anweisungen zwischen __TRY und __CATCH) beinhaltet die Anweisungen, die potenziell eine Exception verursachen können. Tritt keine Exception auf, werden alle Anweisungen im TRY-Block ausgeführt. Anschließend setzt das SPS-Programm hinter __ENDTRY seine Arbeit fort. Verursacht eine der Anweisungen innerhalb des TRY-Blocks jedoch eine Exception, so wird der Programmablauf unmittelbar im CATCH-Block (die Anweisungen zwischen __CATCH und __ENTRY) fortgeführt. Alle übrigen Anweisungen innerhalb des TRY-Blocks werden dabei übersprungen.

Der CATCH-Block wird nur im Falle einer Exception ausgeführt und enthält die gewünschte Fehlerbehandlung. Nach der Abarbeitung des CATCH-Blocks wird das SPS-Programm mit den Anweisungen nach __ENDTRY fortgesetzt.

Hinter der __CATCH-Anweisung wird in runden Klammern eine Variable vom Typ __SYSTEM.ExceptionCode angegeben. Der Datentyp __SYSTEM.ExceptionCode enthält eine Auflistung aller möglichen Exceptions. Wird der CATCH-Block durch eine Exception aufgerufen, so kann über diese Variable die Ursache der Exception abgefragt werden.

In dem folgenden Beispiel werden zwei Elemente aus einem Array dividiert. Das Array wird hierbei durch einen Pointer an die Funktion übergeben. Ist der Rückgabewert der Funktion negativ, so ist bei der Ausführung ein Fehler aufgetreten. Der negative Rückgabewert der Funktion gibt genauere Informationen über die Ursache der Exception:

FUNCTION F_Calc : LREAL
VAR_INPUT
  pData     : POINTER TO ARRAY [0..9] OF LREAL;
  nElementA : INT;
  nElementB : INT;
END_VAR
VAR
  exc       : __SYSTEM.ExceptionCode;
END_VAR

__TRY
  F_Calc := pData^[nElementA] / pData^[nElementB];
__CATCH (exc)
  IF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ARRAYBOUNDS) THEN
    F_Calc := -1;
  ELSIF ((exc = __SYSTEM.ExceptionCode.RTSEXCPT_FPU_DIVIDEBYZERO) OR
         (exc = __SYSTEM.ExceptionCode.RTSEXCPT_DIVIDEBYZERO)) THEN
    F_Calc := -2;
  ELSIF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ACCESS_VIOLATION) THEN
    F_Calc := -3;
  ELSE
    F_Calc := -4;
  END_IF
__ENDTRY

Die ‚finally‘-Anweisung

Mit __FINALLY kann optional ein Codeblock definiert werden, der immer aufgerufen wird, unabhängig davon ob eine Exception aufgetreten ist oder nicht. Es gibt nur eine einzige Randbedingung: Das SPS-Programm muss zumindest in den TRY-Anweisungsblock eintreten.

Das Beispiel soll so erweitert werden, dass das Ergebnis der Berechnung zusätzlich um Eins erhöht wird. Dieses soll unabhängig davon erfolgen, ob ein Fehler aufgetreten ist oder nicht.

FUNCTION F_Calc : LREAL
VAR_INPUT
  pData     : POINTER TO ARRAY [0..9] OF LREAL;
  nElementA : INT;
  nElementB : INT;
END_VAR
VAR
  exc       : __SYSTEM.ExceptionCode;
END_VAR

__TRY
  F_Calc := pData^[nElementA] / pData^[nElementB];
__CATCH (exc)
  IF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ARRAYBOUNDS) THEN
    F_Calc := -1;
  ELSIF ((exc = __SYSTEM.ExceptionCode.RTSEXCPT_FPU_DIVIDEBYZERO) OR
         (exc = __SYSTEM.ExceptionCode.RTSEXCPT_DIVIDEBYZERO)) THEN
    F_Calc := -2;
  ELSIF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ACCESS_VIOLATION) THEN
    F_Calc := -3;
  ELSE
    F_Calc := -4;
  END_IF
__FINALLY
  F_Calc := F_Calc + 1;
__ENDTRY

Beispiel 1 (TwinCAT 3.1.4024 / 32 Bit) auf GitHub

Die Anweisung im FINALLY-Block (Zeile 24) wird immer aufgerufen, unabhängig davon ob eine Exception erzeugt wird oder nicht.

Wird im TRY-Block keine Exception ausgelöst, so wird der FINALLY-Block direkt nach dem TRY-Block ausgerufen.

Tritt eine Exception auf, so wird erst der CATCH-Block ausgeführt und anschließend auch der FINALLY-Block. Erst danach wird die Funktion verlassen.

__FINALLY gestattet es somit, diverse Operationen unabhängig davon auszuführen, ob eine Exception aufgetreten ist oder nicht. Dabei handelt es sich in der Regel um die Freigabe von Ressourcen, wie z.B. das Schließen einer Datei oder das Beenden einer Netzwerkverbindung.

Besonders sorgfältig sollte man die Implementierung der CATCH– und FINALLY-Blöcke vornehmen. Tritt in einem dieser Codeblöcke eine Exception auf, so löst dieses einen unerwarteten Laufzeitfehler aus. Mit dem Ergebnis, dass das SPS-Programm unmittelbar gestoppt wird.

An dieser Stelle möchte ich noch auf den Blog von Matthias Gehring hinweisen. In einem seiner Posts (https://www.codesys-blog.com/tipps/exceptionhandling-in-iec-applikationen-mit-codesys) wird das Thema Exception Handling ebenfalls behandelt.

Das Beispielprogramm ist unter 32-Bit Systemen ab TwinCAT 3.1.4024 lauffähig. 64-Bit Systeme werden derzeit noch nicht unterstützt.

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.

2 thoughts on “IEC 61131-3: Ausnahmebehandlung mit __TRY/__CATCH”

  1. Hallo,
    Gibt es einen Unterschied, ob man Code in einem Finally-Block ausführt oder ob dieser einfach nach dem Try-Catch-Block ausgeführt wird?

    1. Hallo Oswald,

      derzeit gibt es noch keinen Unterschied. So wie TRY/CATCH aktuell implementiert ist, kann die FINALLY-Anweisung auch entfallen.
      In C# wird z.B. finally immer angesprungen, auch dann, wenn im catch-Block oder try-Block ein return steht:

      try
      {
      Debug.WriteLine(“A1”);
      throw new ApplicationException();
      Debug.WriteLine(“A2”);
      return;
      }
      catch (Exception ex)
      {
      Debug.WriteLine(“B”);
      return;
      }
      finally
      {
      Debug.WriteLine(“C”);
      }
      Debug.WriteLine(“D”);

      Ausgabe mit Exception: A1, B, C
      Ausgabe ohne Exception: A1, A2, C

      Dadurch das im try-Block ein return steht, wird ‘D’ nicht ausgegeben. Was im finally-Block steht, wird immer vor dem Verlassen der Funktion aufgerufen. Auch dann, wenn die Funktion vorzeitig durch eine Exception oder return beendet wird. Alles was unterhalb von finally steht, wird in diesem Fall nicht ausgeführt.

      Derzeit verhält sich die Implementierung in der SPS etwas anders. Befindet sich im CATCH-Block eine RETRUN-Anweisung, so wird der FINALLY-Block nicht aufgerufen. Stattdessen wird mit den Anweisungen nach __ENDTRY vorgefahren. Allgemein ist die Implementierung in der SPS noch etwas unvollständig. Ich vermisse noch die Möglichkeit eine Exception auszulösen. Andere Programmiersprachen bieten hierzu entsprechende Anweisungen an (meistens throw).
      Ich hoffe, dass dieses in Zukunft noch umgesetzt wird. Exceptions halte ich grundsätzlich für eine gute Sache, da diese helfen Fehlerzustände innerhalb eines SPS-Programms einfacher abzufangen.

      Stefan

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 )

Facebook photo

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

Connecting to %s

%d bloggers like this: