__TRY, __CATCH, __FINALLY, __ENDTRY

The operators are an extension of the IEC 61131-3 standard and are used for a targeted exception handling in the IEC code.

__TRY, __CATCH, __FINALLY, __ENDTRY 1:

Available from TC3.1 Build 4024 for 32-bit runtime systems

Available from TC3.1 Build 4026 for 64-bit runtime systems

Syntax:

__TRY
    <try_statements>
 
__CATCH(exc)
    <catch_statements>
 
__FINALLY
    <finally_statements>
 
__ENDTRY
 
<further_statements>

If an instruction that appears under the operator __Try generates an exception, the PLC program does not stop. Instead, it executes the instructions under _CATCH and thus starts the exception handling. The instructions under __FINALLY are then executed. The exception handling ends with __ENDTRY. The PLC program then executes the subsequent instructions (instructions after __ENDTRY).

The instructions of the _TRY block, which are located under the instruction that triggers the exception, are no longer executed. This means that as soon as the exception is discarded, the further execution of the _TRY block is aborted and the instructions under _CATCH are executed.

The instructions under _FINALLY are always executed, i.e. even if the instructions under _TRY do not throw any exception.

An IEC variable for an exception has the Data type __SYSTEM.ExceptionCode.

Notice

Machine downtime due to intercepted floating point exceptions on x86 target systems

Due to a technical limitation of the x86 platform, intercepted exceptions caused by a floating point operation can have unintended, serious consequences:

The system may be put into an unrecoverable state or a machine downtime may occur due to a stack overflow. It can also happen that the machine does not come to a standstill immediately, but later during further operations that are also carried out on the stack.

  • Use implicit check functions when floating point operations are used within try-catch blocks on x86 targets.

Sample

The _TRY block in the following sample contains a pointer access and a division. In the first cycle an "Access Violation" exception is thrown inside this block, because the pointer pSample is still a NULL-pointer at this point in time. The use of a NULL-pointer leads to an exception. In the second cycle a further exception is thrown inside the _TRY block - this time it is an exception due to a division by 0.

Both exception causes are resolved within the __CATCH statements: the first exception by a value correction of the pointer pSample and the second exception by a value correction of the divisor nDivisor.

The fact that the exception handling works and how it works can be comprehended at runtime as follows:

Global variable list "GVL_Exc":

{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
    cMaxExc            : UINT := 10;
END_VAR
VAR_GLOBAL
    nExcIndex          : UINT;
    aExceptionHistory  : ARRAY[0..cMaxExc] OF __SYSTEM.ExceptionCode;
END_VAR

Function "F_SaveExceptionCode":

FUNCTION F_SaveExceptionCode
VAR_INPUT
    excInput           : __SYSTEM.ExceptionCode;
END_VAR
// Log the thrown exception into the global exception history array
IF GVL_Exc.nExcIndex <= GVL_Exc.cMaxExc THEN
    GVL_Exc.aExceptionHistory[GVL_Exc.nExcIndex] := excInput;
    GVL_Exc.nExcIndex := GVL_Exc.nExcIndex + 1;
END_IF

Program "MAIN":

PROGRAM MAIN
VAR
    nCounter1          : INT;
    nCounter2          : INT;
    nCounter_TRY1      : INT;
    nCounter_TRY2      : INT;
    nCounter_CATCH     : INT;
    nCounter_FINALLY   : INT;
 
    exc                : __SYSTEM.ExceptionCode;
    lastExc            : __SYSTEM.ExceptionCode;
 
    pSample            : POINTER TO BOOL;
    bVar               : BOOL;
    nSample            : INT := 100;
    nDivisor           : INT;
END_VAR
// Counter 1
nCounter1 := nCounter1 + 1;
 
// TRY-CATCH block
__TRY
    nCounter_TRY1 := nCounter_TRY1 + 1;
    pSample^      := TRUE;                // 1. cycle: null pointer access leads to "access violation" exception
    nSample       := nSample/nDivisor;    // 2. cycle: division by zero leads to "divide by zero" exception
    nCounter_TRY2 := nCounter_TRY2 + 1;
 
__CATCH(exc)
    nCounter_CATCH := nCounter_CATCH + 1;
 
    // Exception logging
    lastExc := exc;
    F_SaveExceptionCode(excInput := exc);
 
    // Correct the faulty variable values
    IF (exc = __SYSTEM.ExceptionCode.RTSEXCPT_ACCESS_VIOLATION) AND (pSample = 0) THEN
        pSample  := ADR(bVar);
    ELSIF ((exc = __SYSTEM.ExceptionCode.RTSEXCPT_DIVIDEBYZERO) OR (exc = __SYSTEM.ExceptionCode.RTSEXCPT_FPU_DIVIDEBYZERO)) AND (nDivisor = 0) THEN
        nDivisor := 1;
    END_IF
 
__FINALLY
    nCounter_FINALLY := nCounter_FINALLY + 1;
 
__ENDTRY
 
// Counter 2
nCounter2 := nCounter2 + 1;