__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.
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.
|
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:
- Since the erroneous accesses that normally lead to a runtime exception and to a corresponding stopping of the program execution are intercepted within the exception handling, the TC runtime remains in Run mode and the program execution continues.
- Since two exceptions are intercepted, the counter nCounter_CATCH is incremented twice and thus has the value 2.
- Since the instructions described below are executed in each cycle, the counters incremented there all have the same value. The value corresponds to the number of cycles so far.
- the instructions before the TRY-CATCH block = incrementation of the counter nCounter1
- the instructions at the start of the _TRY block = incrementation of the counter nCounter_TRY1
- the instructions under _FINALLY = incrementation of the counter nCounter_FINALLY
- the instructions after the TRY-CATCH block = incrementation of the counter nCounter2
- Since the execution of the _TRY block is aborted twice due to the two thrown exceptions, the end of the _TRY instructions is not reached in two cycles. The variable value of nCounter_TRY2 is thus smaller than that of nCounter_TRY1 by 2.
- Since the causes of the two exceptions are remedied inside the _CATCH instructions (assign a valid address to pSample and a value other than 0 to nDivisor), each of the two exceptions occurs only once.
- The two exceptions are saved in the sample in a global array for creating an exception history. Following the two exceptions, the corresponding index variable nExcIndex is thus 2 and the array aExceptionHistory has the following values:
- aExceptionHistory[0] = RTSEXCPT_ACCESS_VIOLATION
- aExceptionHistory[1] = RTSEXCPT_DIVIDEBYZERO
- aExceptionHistory[2] to aExceptionHistory[10] = RTSEXCPT_NOEXCEPTION
- Following the two exceptions the variable exc has returned to the default value RTSEXCPT_NOEXCEPTION and the variable lastExc indicates the last occurring exception code with RTSEXCPT_DIVIDEBYZERO.
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;