When executing a program, there is always the possibility of an unexpected runtime error occurring. These occur when a program tries to perform an illegal operation. This kind of scenario can be triggered by events such as division by 0 or a pointer which tries to reference an invalid memory address. We can significantly improve the way these exceptions are handled by using the keywords __TRY and __CATCH.
The list of possible causes for runtime errors is endless. What all these errors have in common is that they cause the program to crash. Ideally, there should at least be an error message with details of the runtime error:
Because this leaves the program in an undefined state, runtime errors cause the system to halt. This is indicated by the yellow TwinCAT icon:
For an operational system, an uncontrolled stop is not always the optimal response. In addition, the error message does not provide enough information about where in the program the error occurred. This makes improving the software a tricky task.
To help track down errors more quickly, you can add check functions to your program.
Check functions are called whenever the relevant operation is executed. The best known is probably CheckBounds(). Each time an array element is accessed, this function is implicitly called beforehand. The parameters passed to this function are the array bounds and the index of the element being accessed. This function can be configured to automatically correct attempts to access elements which are out of bounds. This approach does, however, have some disadvantages.
- CheckBounds() is not able to determine which array is being accessed, so error correction has to be the same for all arrays.
- Because CheckBounds() is called whenever an array element is accessed, it can significantly slow down program execution.
It’s a similar story with other check functions.
It is not unusual for check functions to be used during development only. Check functions include breakpoints, which stop the program when an operation throws up an error. The call stack can then be used to determine where in the program the error has occurred.
The ‘try/catch’ statement
Runtime errors in general are also known as exceptions. IEC 61131-3 includes __TRY, __CATCH and __ENDTRY statements for detecting and handling these exceptions:
__TRY // statements __CATCH (exception type) // statements __ENDTRY // statements
The TRY block (the statements between __TRY and __CATCH) contains the code with the potential to throw up an exception. Assuming that no exception occurs, all of the statements in the TRY block will be executed as normal. The program will then continue from the line immediately following the __ENDTRY statement. If, however, one of the statements within the TRY block causes an exception, the program will jump straight to the CATCH block (the statements between __CATCH and __ENDTRY). All subsequent statements within the TRY block will be skipped.
The CATCH block is only executed if an exception occurs; it contains the error handling code. After processing the CATCH block, the program continues from the statement immediately following __ENDTRY.
The __CATCH statement takes the form of the keyword __CATCH followed, in brackets, by a variable of type __SYSTEM.ExceptionCode. The __SYSTEM.ExceptionCode data type contains a list of all possible exceptions. If an exception occurs, causing the CATCH block to be called, this variable can be used to query the cause of the exception.
The following example divides two elements of an array by each other. The array is passed to the function using a pointer. If the return value is negative, an error has occurred. The negative return value provides additional information on the cause of the 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
The ‘finally’ statement
The optional __FINALLY statement can be used to define a block of code that will always be called whether or not an exception has occurred. There’s only one condition: the program must step into the TRY block.
We’re going to extend our example so that a value of one is added to the result of the calculation. We’re going to do this whether or not an error has occurred.
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
The statement in the FINALLY block (line 24) will always be executed whether or not an exception has occurred.
If no exception occurs within the TRY block, the FINALLY block will be called straight after the TRY block.
If an exception does occur, the CATCH block will be executed first, followed by the FINALLY block. Only then will the program exit the function.
__FINALLY therefore enables you to perform various operations irrespective of whether or not an exception has occurred. This generally involves releasing resources, for example closing a file or dropping a network connection.
Extra care should be taken in implementing the CATCH and FINALLY blocks. If an exception occurs within these blocks, it will give rise to an unexpected runtime error, resulting in an immediate uncontrolled program stop.
The sample program runs under 32-bit TwinCAT 3.1.4024 or higher. 64-bit systems are not currently supported.