An overview of FreeRTOS Kernel Priorities on ARM Cortex-M Platform
When i first started playing with FreeRTOS, one of the main confusion I had was about FreeRTOS Tasks Priority i.e. FreeRTOS Kernel Priorities, and the underlying hardware/Processor Core Interrupt Priorities. In this tutorial we will go one step deeper into how FreeRTOS Kernel manager various Hardware/Software priorities.
The Core platform we used for this tutorial is ARM Cortex-M4 but the concepts can be easily extended to any platform.
Note: We will not go into the details of ARM Cortex-M4 interrupts and interrupts priorities as we already have a dedicated tutorial for that. It is therefore highly recommended to go through that tutorial before moving forward with this tutorial.
Note: To be clear, its worth mentioning that the tutorial doesn’t discuss freeRTOS Tasks Priorities. The purpose of this tutorial is to somehow clarify the blurry line between the Processor Hardware Interrupt Priorities and the freeRTOS Kernel used Hardware Interrupts Priorities and subsequently freeRTOS APIs that can be called from Interrupt ISRs. FreeRTOS Tasks runs at application Layer Figure-1. The freeRTOS Tasks Priorities are discussed in the following related tutorial (Optional Study).
If we go deep down into freeRTOS Kernel we will see that the main Kernel source code that performs various OS functions is almost to absolutely 100% platform independent i.e. the code is not written for a specific Hardware. The beauty of this approach is that if the Kernel needs to be ported to some new Hardware, Only the services that Kernel requires from hardware (port.c) need to be implemented. Thus a system that runs FreeRTOS basically follows two layer structure program flow as shown in Figure-1.
As shown in Figure-1, the freeRTOS tasks priorities are only used relative to other tasks i.e. The highest priority task runs first. There is absolutely no link between freeRTOS tasks priorities and the Hardware (Processor Core) interrupts priorities. The first ones are handled by freeRTOS Kernel and exist at application layer while the later is managed by SoC Hardware (fixed priorities) e.g. ARM NVIC and the low level firmware (that configures the interrupt priorities).
Note: The freeRTOS Kernel requires few services from underlaying Hardware like a timer and associated Interrupt Service Routine (ISR) to be used as a reference for Task Switching (Systick in case of ARM Cortex-M3/4). The associated interrupts services are kept at minimum possible Hardware interrupt priorities (in
port.c). This means that all other hardware interrupts will have higher priorities. Thus NO freeRTOS task can preempt a hardware interrupt i.e. No matter what ever priority is assigned to freeRTOS task, when ever a hardware interrupt occurs the freeRTOS code/tasks will be interrupted to serve the ISR. It is very common to confuse between freeRTOS Task priorities and Hardware Interrupt Priorities. The priority assigned to a task is in no way related to the priority assigned to an interrupt. Hardware decides when an ISR will execute. Software decides when a task will execute. An ISR executed in response to a hardware interrupt will interrupt a task, but a task cannot preempt an ISR. A Hardware event (e.g. interrupts and associated ISR) always takes precedence over software events (freeRTOS Scheduler running a higher priority Task).
On a very general note, it is worth mentioning that each Hardware Interrupt is associated with a Numeric number and a Logical number. The Numeric number assigned to a hardware interrupt provides unique identification to the interrupt in the Interrupts Vector Table. Its like house number that identify a house in the street.
On the other hand the Logical number is related to the precedence and relative importance (Actual Priority) an interrupt has over the other Hardware Interrupt.
The assignment and relationship between both Hardware Interrupts number and logical number depends on the underlaying processor architecture. For some processor both numbers are aligned and is indicated by one number i.e. higher the Numeric/Logical number (as aligned) assigned to an interrupt, the higher will be the interrupt’s priority and vice versa (example – TMS320C6713 Texas DSP Processor). On other architectures, the Numeric and Logical numbers are not aligned e.g. ARM Cortex-M family. In ARM Cortex-M Hardware Interrupts are indentified by Numeric number in the NVIC (0-255) but their priorities are set via Logical number in the associated Interrupt Priority Register. In this case the lower the Logical number in Interrupt Priority Register, the higher will be the priority and vice versa. e.g. An interrupt with Logical priority -0 in Interrupt Priority Register will have high precedence/importance (priority) over the interrupt with Logical priority -10 in Interrupt Priority Register.
Now from this point onward, just forget about the lower hardware layer of Figure-1 i.e. the hardware interrupts and their priorities. From this point forward we will focus on freeRTOS Kernel layer only.
Why we need to know about freeRTOS Kernel used priorities?
To cut the story short, the purpose to know about freeRTOS Kernel priorities is:
- We wanna know how to call freeRTOS APIs from Hardware Interrupts ISRs! i.e. Not all FreeRTOS APIs can be called directly from ISRs also even those that can be called from ISRs; can’t be called from all Hardware ISRs. 🙁
- Where to place system critical code to be never delayed by any freeRTOS functionalities!
Consider an Embedded System in an automobile running freeRTOS. The system has the following few tasks to perform continuously.
- Update Digital speed-o-meter
- Manage Headlights
- Display various Sensors data like Engine Temperature etc.
- Manage Air Bags
- Manage Brakes
In above Embedded system, the most important jobs are the last two i.e. Air bags and Brakes. So the functionalities of these two safety critical tasks shouldn’t depend on whatever the freeRTOS Kernel is doing. In such cases we will place Brakes and Air bag managing code in critical sections (Within ISRs) that will be never delayed.
Now coming back to freeRTOS Kernel priorities. The FreeRTOS Kernel priorities fall into two categories and is managed by the following three MACROS defined in
FreeRTOSConfig.h file – Figure-2.
/* Interrupt nesting behavior configuration. */ #define configKERNEL_INTERRUPT_PRIORITY /* dependent of processor */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY /* dependent on processor and application */ #define configMAX_API_CALL_INTERRUPT_PRIORITY /* dependent on processor and application */
configMAX_API_CALL_INTERRUPT_PRIORITY is a new name for
configMAX_SYSCALL_INTERRUPT_PRIORITY that is used by newer ports only. The two are equivalent.
configKERNEL_INTERRUPT_PRIORITY: Sets the interrupt priority used by the FreeRTOS Kernel itself.
configMAX_SYSCALL_INTERRUPT_PRIORITY: Sets the highest interrupt priority from which the freeRTOS APIs ending with
"FromISR" can be called.
Note: Some freeRTOS Kernel implements only
configKERNEL_INTERRUPT_PRIORITY while some implements both
For ports that only implement configKERNEL_INTERRUPT_PRIORITY:
configKERNEL_INTERRUPT_PRIORITY sets the interrupt priority used by the RTOS kernel itself. Interrupts that call interrupt Safe APIs (ending with “FromISR”) must also execute at this priority . This means any code written in Interrupt ISR with higher priority will never be delayed but can never make a call to freeRTOS APIs as the priority is higher than the
For ports that implement both configKERNEL_INTERRUPT_PRIORITY and configMAX_SYSCALL_INTERRUPT_PRIORITY:
configKERNEL_INTERRUPT_PRIORITY sets the interrupt priority used by the RTOS kernel itself.
configMAX_SYSCALL_INTERRUPT_PRIORITY sets the highest interrupt priority from which interrupt safe FreeRTOS API functions can be called. No Interrupt Safe APIs (ending with “FromISR”) can be called from ISRs whose Logical interrupt priorities are higher than
Let’s get back to the above Example. In the above Example as the two most important tasks are Air Bags and Brake functions so the Code handling these functionalities must be placed in ISRs whose LOGICAL priorities are higher than
configMAX_SYSCALL_INTERRUPT_PRIORITY so that they are never delayed.