Introduction:
The CODESYS runtime determines the execution order of instructions by the combined interaction of scan times and priorities. The term ‘scan time’ refers to the length of time required to read input data, execute program instructions, and update outputs. The ‘scan time’ in CODESYS is known as a task interval. Per CODESYS instructional documentation:
(1)“A task is a time-based flow unit of an IEC program. It is defined by a name, a priority, and a type, which determines which condition triggers the start of the task. You can define this condition either by time (cyclic-interval, freewheeling) or by the occurrence of an internal or external event to process the task. Examples of an event are the rising edge of a global project variable or an interrupt event of the controller.”
The CODESYS IDE allows users to choose the interval assigned to each “Task” within the application. This provides a higher level of control, but also introduces a risk of error. Within this post we will define the CODESYS settings that directly affect the scheduling of program instructions. In addition, we will also highlight some ‘risks’ related to poorly set task intervals and priorities and outline best practices related to their avoidance.
Disclaimer:
Weintek USA licenses CODESYS for use in its products but does not develop or co-develop CODESYS software or runtime files. The information provided in this post represents the recommendations and insights of engineers at Weintek USA. It is intended as a general guide to best practices for optimizing application performance, addressing inefficiencies, and minimizing CPU load. These recommendations align with widely accepted engineering principles but may not address all specific use cases. For official guidance on CODESYS software or runtime specifications, please refer to documentation and resources provided by CODESYS.
Definitions:
-
Task Configuration: In the task configuration, you define one or more tasks for controlling and executing the application program in the PLC. Each application has to include a Task Configuration object.
-
IEC-Cycle Count: The number of cycles, which have been executed since first starting the application, where the IEC code has actually been called.
-
Task: A task is a time-based flow unit of an IEC program. It is defined by a name, a priority, and a type, which determines which condition triggers the start of the task. You can define this condition either by time (cyclic-interval, freewheeling) or by the occurrence of an internal or external event to process the task. Examples of an event are the rising edge of a global project variable or an interrupt event of the controller.
-
Priority: The priority is used to determine execution order when multiple tasks exist. A priority level of ‘0’ is considered to be the highest priority and ‘31’ to be the lowest. Therefore, programs called by tasks with a ‘higher’ priority (e.g., 2) will execute before those with a ‘lower’ priority (e.g., 3).
-
Type: The “Type” dropdown list within a task object allows you to define the execution type or cycle characteristic of the task. The available options are as follows:
- Cyclic: The task is executed cyclically, with the cycle time defined in the “Interval” input field. You can specify the interval either numerically (e.g., 200) or as a time value (e.g., T#200ms).
- Freewheeling: The task executes automatically at program start and continues in a continuous loop, restarting at the end of each complete cycle.
- Event: The task is executed on the rising edge of the variable defined in the “Event” input field.
- Status: The task is executed when the variable defined in the “Event” input field is TRUE. While this variable remains TRUE the task will execute as if in Freewheeling.
-
Watch dog: If the task exceeds the currently set Time of the watchdog, then the task is halted with an error status (exception). The application in whose task the error occurred and its child applications are also halted. In this way, all tasks of the affected applications are also halted.
- Sensitivity: The sensitivity is used alongside the watchdog time to determine when to halt the task. The conditions that may trigger the watchdog are:
- Single timeout: The actual scan time is longer than watchdog [time] x [sensitivity].
Example: time = 100ms, sensitivity = 5 | A single scan cycle exceeding 500ms or watchdog [time] x [sensitivity] will cause an exception to occur. - Multiple timeouts: The actual scan time exceeds the watchdog [time] for n number of cycles, where n is equal to the [sensitivity].
Example: time = 100ms, sensitivity = 5 | A scan time exceeding the watchdog [time] for 5 consecutive scan cycles will cause an exception to occur.
- Single timeout: The actual scan time is longer than watchdog [time] x [sensitivity].
- Sensitivity: The sensitivity is used alongside the watchdog time to determine when to halt the task. The conditions that may trigger the watchdog are:
Execution:
As mentioned earlier, when multiple tasks are defined, their execution order is determined by their priority. In this example, the application will execute tasks in the following order:
In addition, the program calls within each task are executed in descending order:
That is to say that each program’s execution depends on the completion of the preceding ones. Consequently, a program call may be delayed if the scan time becomes excessively long, which can happen due to lengthy or deeply nested loops, or multiple nested program calls.
When executing a nested program call, as shown in the example below, the runtime will call each program sequentially and wait for its execution to complete before proceeding to the next program.
This means that each program is executed sequentially, and the runtime will not call the next program until the current one has finished, even if it requires a significantly longer scan time. For instance, in this example, the runtime will wait for PRG_1 to complete before calling PRG_2, regardless of how long PRG_1 takes to execute.
Shared priority:
When multiple tasks share the same priority, their execution order is determined by the task Type specified in the definitions section. For tasks defined as Cyclic, the CPU executes the task with the shortest interval time first. Additionally, at boot, the CPU may wait for the duration of the task interval before executing the first program call:
When multiple tasks share the same priority and interval time, or if the task Type is set to Freewheeling then CODESYS prioritizes the task that has been waiting in queue the longest. By observation, when all tasks are assigned equivalent, but ‘generous’ scan intervals, their IEC-Cycle Counts tend to be nearly identical, suggesting that the CPU effectively balances the load.
What is a ‘generous’ scan interval? This is an interval long enough to ensure the task can complete all scheduled programs with sufficient time remaining before the next execution cycle begins.
However, if the interval time is too short or a task is set to Freewheeling, tasks may execute at different rates, resulting in varying IEC-Cycle Counts:
The images above illustrate sequential execution in special cases. However, more complex behavior is achieved through nested program calls and Event type tasks. When a program within a task triggers an Event, the task associated with that event executes based on its priority.
It is unclear whether the CODESYS runtime explicitly supports Event type task interrupts. To explore this, we present the following observations.
By observing Trace logs we can tell that if the priority of the Event type task is the same or greater than the running task it will interrupt the task execution.
However, by observing the value returned by SysTimeGetUs, we can see that an Event type task with the same or higher priority as the current running task will execute only after the running task is completed, even if the Event condition is triggered at the start of the running task’s execution interval:
Varying priority:
The key takeaway from the section above is that setting proper priority values eliminates some of the ambiguity of scheduling order. The diagrams below depict the execution order of tasks with different priorities.
For tasks defined as Cyclic, the CPU usually executes the task with the shortest interval time first. However, the priority should determine which task to execute when both have valid execution conditions. The illustration below shows two cyclic tasks with intervals of 3000 ms and 5000 ms, and their execution at 15 seconds after boot. At this point, both tasks may meet the conditions for execution. However, due to task priority, the task with the highest priority should execute first:
Furthermore, when all tasks are assigned equivalent, but generous scan intervals, their IEC-Cycle Counts tend to be nearly identical and will likely execute in a manner determined by their priority:
However, it remains that if the interval time is too short or a task is set to Freewheeling, tasks may execute at different rates, resulting in varying IEC-Cycle Counts:
We might assume that the priority is particularly important for Event type tasks and expect a task called by an Event to interrupt a running task. However, as mentioned this capability is not explicitly stated within CODESYS documentation. And, attempts to observe the execution order have yielded varying results. Therefore, it is recommended to perform an explicit program call instead of attempting to predict the outcome of an Event interrupt.
Setting intervals:
To set a proper interval time, we should determine the length of time needed in the ‘worst-case-scenario’. The ‘worst-case-scenario’ will occur when the program variables cause the logic to execute in the most time inefficient manner, such as by entering each IF
statement in a series of cascading IFs
. To measure the time, we can call the function SysTimeGetUs()
at the beginning and end of the program and determine the delta between the start and end values. The result should closely match that of the ‘Last Cycle Time’ found within the Task Configuration.
The examples below shows the difference in time needed to execute the same program depending on the value of bit xLoop.
Note: In the example above xLoop is false and stDelta ranges from 0 - 1 μs.
Note: In the example above xLoop is true and stDelta ranges from 8000 - 9000 μs.
Per this example, we may consider the worst-case-scenario to be when xLoop is true as this yields a far greater execution time. It is evident that loops require sufficient scan time, but it is not always evident what functions invoke loops or will produce sufficiently long scan times due to their internal complexity. As an example, the Reorder()
function below calls a similar loop, but this is not clear to the user.
What happens if we reduce the cyclic interval to a value shorter than the time required for the application to complete execution? The application will likely crash due to the ProcessorLoadWatchdog.
Note: The interval time used above was 1ms, but according to stDelta the application required ~8267 μs to complete.
The ProcessorLoad maximum value serves as a safeguard for CPU usage, with a default limit set at 80%. Approaching this threshold, even gradually, will cause the application to crash once the CPU reaches this upper limit.
Note: The last recorded CPU load value within the example shown above. Type “plcload” into the “PLC Shell” to view the CPU load in real-time.
The time needed to execute a particular program depends not only on what statements and functions are called within the program, but is also influenced by system load. It is common that users set an arbitrary interval based on the desired time, but as shown this practice is actually problematic.
Freewheeling:
Freewheeling is the fastest way to ensure cyclic execution, but it may also put significant strain on CPU resources. Well-defined cyclic intervals are often more practical as they can be used for repetitive time-based procedures such as totalizers. As such, it is recommended to use Freewheeling with caution.
Best Practices:
The below list is summary of best practices based on the information above:
-
Set unique priority values for each task to ensure predictable execution order and remember that lower values equate to higher priority.
-
Use explicit program calls instead of event type tasks to ensure predictable execution order.
-
To ensure reliability, always check the time required to execute a given program / task. Set the cyclic interval to something fairly large while testing such as 20s, then refer to the “Last Cycle Time” within the Task Configuration to determine the execution time.
-
When validating the potential task execution time, be sure that the application will encounter the ‘worst-case-scenario’. This means that the program variables should cause the logic to execute in the most time inefficient manner while testing.
Note: In the example, the worst-case-scenario occurs when xLoop is true. -
To avoid excessive CPU load that could cause an application crash, provide generous processing time—typically 2 to 3 times the measured worst-case execution time of the task.
Note: The task interval above is greater than 2x the measured ‘worst-case-scenario’ execution time of the previous example, which was ~8ms. -
Use the PLC shell to verify the PLC load during execution and whenever possible, test the application 3 - 4 days prior to deployment to ensure steady CPU load even under ‘worst-case-scenario’ processing time.
-
Take note that Freewheeling will result in higher CPU load than cyclic tasks with generous scan intervals. As such, it is recommended to use Freewheeling type tasks with caution.
-
Enable and use the Watchdog described within the definitions to halt the application when it exceeds the desired scan time.