A Guide to ARM Cortex-M, Interrupts, and FreeRTOS
Learn the ins and outs of ARM Cortex-M's interrupt and priority system, which is a good first step to mastering the device.
Join the DZone community and get the full member experience.
Join For FreeThe ARM Cortex-M microcontroller is insanely popular, and it features a flexible and powerful nested vectored interrupt controller (NVIC). But for many, including myself, the Cortex-M interrupt system can be counter-intuitive, complex, inconsistent, and confusing, leading to many bugs and lots of frustration.
Understanding the NVIC (Nested Vectored Interrupt Controller) and the ARM Cortex-M interrupt system is essential for every embedded application, but even for using a real-time operating system: if you mess up with interrupts, very bad things will happen.
FreeRTOS is probably the most popular and most used operating system for microcontrollers. It supports many different architectures, including the ARM Cortex-M architectures.
I’m covering the topic of FreeRTOS and interrupts in my university lecture material. But I have seen so many wrong uses of interrupts with the RTOS that I think it deserves a dedicated article. The amazing thing I see many times is that even if the interrupts are configured in a clearly wrong way, surprisingly, the application ‘seems’ to work, at least most of the time. Well, I think everyone agrees that "most of the time" is not good enough. Because problems with interrupts are typically hard to track down, they are not easy to fix.
Interrupt Vectors
The ARM Cortex-M is using an NVIC (Nested Vectored Interrupt Controller). The "vectored" means that it uses a vector table, shown for M0/M0+ and M4/M4 below:
The table is "vectored" because the 32bit entries in it (e.g. the Hard fault vector at address 0x000C’0000) points to the corresponding interrupt service routine: For example, the entry at address 0x08 ‘vectors’ to the NMI interrupt handler or function.
The exception numbers 1-15 are defined by ARM, that is, they are part of the core. The exceptions above 15 are ‘vendor specific,’ which means that they are implemented by vendors like NXP, TI, STM, and many others.
Or in other words: The negative IRQ numbers (from -1 (SysTick) to -14 (NMI) plus reset) are defined by the ARM core, everything else ‘above’ with IRQ number >=0 are vendor-specific and typically for devices like UART/I²C/USB/etc..
Note: In the above image, the numbering or Exception Numbers and IRQ are easily mixed up and cause confusion.
Interrupts Priorities
The ARM Cortex-M core is using a rather confusing interrupt priority numbering: Numerically low values are used to specify logically high interrupt priorities. Or in other words: The lower the number, the higher the urgency.
I try to use the word ‘urgency’ to indicate how ‘important’ an interrupt is, not to confuse with the (ARM hardware) interrupt priority (level). In a nested interrupt system such as on ARM Cortex-M, a ‘more urgent’ interrupt can interrupt a ‘less urgent’ interrupt.
This means that interrupt priority 0 is the most urgent one.
At reset, all interrupt priorities have a priority of zero assigned. I can assign a priority to each of them. Except that Reset, NMI, and HardFault have a fixed (negative) priority and cannot be disabled. The following table shows all the exceptions/interrupts, and for which ARM Cortex-M core they exist:
Priority Bits
The priority of the exception/interrupt is assigned with an 8-bit priority register, and the number of bits implemented is up to the vendor implementation. ARM specifies a minimum of 2 bits for the M0/M0+ and 3 bits for M3/M4/M7.
If using CMSIS-compliant libraries, the number of implemented bits can be checked with:
__NVIC_PRIO_BITS
The example below is for an NXP Cortex-M7, which has 4 bits implemented
#define __NVIC_PRIO_BITS 4 /**< Number of priority bits implemented in the NVIC */
Shifted Priority Bits
The implemented priority bits are left-aligned: This keeps priority values compatible between different implementations:
For three implemented bits, it means I can have 2^3 (8) priority levels, with the following (shifted) values: 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0.
There is much confusion about ‘shifted’ and ‘not shifted’ priority values. Carefully check the API of the CMSIS API function if they expect shifted or not shifted values! Using the CMSIS API is the preferred way to deal with the ARM core and its core registers.
NVIC Interrupt Configuration
The NVIC offers several registers to configure the interrupts. On the M0/M0+ there are the following:
- NVIC_ISER (Interrupt Set Enable Register): enable interrupt bit, one bit for each interrupt.
- NVIC_ICER (Interrupt Clear Enable Register): disable interrupt bit, one bit for each interrupt.
- NVIC_ISPR (Interrupt Set Pending Register): mark interrupt as pending bit, one bit for each interrupt.
- NVIC_ICPR (Interrupt Clear Pending Register): clear pending flag bit, one bit for each interrupt.
- NVIC_IPRx (Interrupt Priority Register): interrupt priority (8-bit for each interrupt, 4 interrupts in a 32bit register).
The above registers are 32-bit registers, with one bit for each interrupt. For example, the NXP KL25Z has 32 vendor-specific interrupts (exceptions 0x10-0x47), so 32bits for each bit of the above registers are enough. For the 32 interrupt,s priorities 32*8bit == 8 32bit priority registers (NVIC_PRI0…PRI7) are used.
The following screenshot shows the individual bits in NVIC_ISER:
The Cortex-M3/4/7 has one register more in addition to the one above:
- NVIC_IABR (Interrupt Active Bit Register): set if an interrupt is running, one bit for each interrupt
Here again, there is a single bit for each interrupt. For example, the NXP K22FX512 (ARM Cortex-M4F) has 82 vendor-specific interrupts (exceptions 0x10-0x61), so it needs four 32-bit registers to hold all the bits and 106 32bit registers for the 8bit priorities.
Assigning Interrupt Priority
To set the interrupt priority, the following CMSIS function is used:
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);
IRQn is the exception number (0 for the first vendor-specific exception, -1 for SysTick, -2 for PendSV, etc.). ‘priority’ is the not-shifted (!!!!) interrupt priority, e.g. 0-7 for a system with 3 bits.
This mix-up with shifted and non-shifted values is very confusing and a common source of incorrect code — even books have it wrong
For example ...
NVIC_SetPriority(SysTick_IRQn, (1UL<<__NVIC_PRIO_BITS)-1UL); /* set Priority for Systick Interrupt to lowest interrupt */
... sets the SysTick to the lowest interrupt level given the available levels (SysTick_IRQn is a macro having a value of -1).
Sub-Priorities
On the M3/M4/M7, it is possible to have sub-priorities for the interrupts, and the number of subpriority bits is configured by the PRIGROUP register. The PRIGROUP can be changed at runtime:
In the above example, the PRIGROUP is set to 0 (no sub-priorities). The value of PRIGROUP sets the bit position of the sub-priority bits: For example, if PRIGROUP has a value of 5 (bit position 5) and the number of priority bits are three, then there are two main/preemption priorities and one sub-priority:
With sub-priority, there are now two different priorities for an interrupt: <Preempt Prio>.<Sub Prio> to have a notation for it.:
- The Preempt Priority defines if an interrupt can nest/interrupt an already running interrupt. Remember that a lower preemption priority number means higher urgency. For example, an interrupt with 2.1 can nest/interrupt a running interrupt 3.0.
- The Subpriority is used when multiple interrupts with the same Preemption Priority are pending, then the one with the lower sub-priority (higher urgency) will be executed first. For example, if 3.1 and 3.0 are pending, then 3.0 will be executed first. It means as well that the interrupt 3.0 will not be able to interrupt/nest another 3.1 interrupt.
To change the number of sub-priority bits ...
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
... is used. While sub-priorities provide great flexibility, many systems do not use them because they add more complexity. Even worse, some libraries are setting non-standard sub-priority levels. In that case ...
</pre>
<pre>NVIC_SetPriorityGrouping(0);
... disables sub-priorities.
Interrupts and Devices
Multiple interrupt-enabled devices (e.g. UART, USB, etc) can have the same priority assigned, so they don’t need to be uniquely assigned on Cortex-M.
Out of reset, interrupts are disabled and the interrupt priorities for all are set to 0.
To enable interrupts for a given device (e.g. I²C interrupt), the following steps are needed:
- Optional: Set the priority level of the required interrupt in the NVIC.
- Enable the interrupt inside the device (usually a device-specific bit, e.g. bit in the I²C peripheral register).
- Enable the interrupt in the NVIC.
For an example of the I²C interrupt:
#define I2C1_IRQn 24 /* device specific interrupt for I2C */
#define I2C1_BASE (0x40067000u) /* address of peripheral */
#define I2C1 ((I2C_Type *)I2C1_BASE) /** Peripheral I2C0 base pointer */
NVIC_SetPriority(I2C1_IRQn, 1); /* set I2C interrupt level (note: 1 is *not* shifted! */
I2C1->C1 |= I2C_C1_IICIE_MASK; /* enable device specific interrupt flag */
NVIC_EnableIRQ(I2C1_IRQn); /* Enable NVIC interrupt */
To turn off the device interrupts, simply disable it with an NVIC call:
NVIC_DisableIRQ(I2C1_IRQn); /* Enable NVIC interrupt */
The fact that there are both the NVIC- and device-specific interrupt enable/disable bits might be confusing. My recommendation is to first disable the device's internal interrupt bit before doing any device register configuration. This ensures that the device is not creating any interrupts to the NVIC. To turn on/off the interrupts, use the NVIC interrupt enable/disable bits (NVIC_ISER and NVIC_ICER).
Masking Interrupts
The NVIC not only allows you to set interrupt priorities, but it also allows you to enable/disable each interrupt one by one. But for critical sections or atomic accesses, it is necessary to turn off all interrupts.
PRIMASK
All the cores discussed here have the ‘I’ (interrupt) bit in the PRIMASK (Primary Mask) register:
Setting that bit masks (disables) the interrupts. There is a simple assembly instruction to do this:
__asm volatile("cpsid i"); /* disable interrupts */
The other way around, clearing the bit enables (globally) all interrupts:
__asm volatile("cpsie i"); /* enable interrupts */
If using CMSIS libraries, then the following functions are available:
void __enable_irq(void);
void __disable_irq(void);
Disabling all interrupts in a system increases interrupt latency time, so this should be as short as possible.
BASEPRI
The M3/M4/M7 cores (not the M0/M0+!) have another great feature: the BASEPRI (Base Priority Mask) register.
The BASEPRI register is a mask register and masks all interrupt priorities that are ‘numerically equal or lower than the BASEPRI value.’
Examples:
- BASEPRI set to 3: disables interrupts with priority 3, 2 and 1.
- BASEPRI set to 5: disables interrupts with priority 5, 4, 3, 2 and 1.
Because BASEPRI is a mask register, setting it to 0 means interrupts are not masked and therefore enabled. It means that it cannot mask/disable interrupts with priority 0! Use the PRIMASK to disable interrupts with priority zero.
CMSIS offers the following function to set the BASEPRI register:
__set_BASEPRI(priority);
Using the BASEPRI it is possible to mask the interrupts up to a certain level. This is critical for a good interrupt partitioning of the system, and FreeRTOS takes advantage of the BASEPRI setting. There will be more about this in Part 2 of this article.
The ARM NVIC and interrupt system are complex, and in many areas with the CMSIS API, they are not logical or counter-intuitive, and they use an inverted priority vs. urgency scheme. The shifted vs. non-shifted priority values can cause many subtle bugs and issues that I have faced in many projects. Between the ARMv6-M, ARMv7-M, and ARMv7E-M, the interrupt system is similar and somewhat compatible, but still different. Vendor libraries might not be consistent in dealing with the interrupts or setting some grouping, so carefully check how they are implemented.
Understanding the interrupt system and how to use it is an essential part of embedded systems. The interrupt system and controller have an impact on how drivers and middleware are using the interrupts.
The RTOS is a case where understanding and mastering the interrupts is critical to ensure proper operation and minimize interrupt latency.
ARM Cortex-M
I’m using the NXP FRDM-K64F board, which uses an ARM Cortex-M4F (the F stands for Floating Point Unit). It implements four interrupt priority bits with optional sub-priority bits.
Note that the ARM Cortex-M0/M0+ does not implement the sub-priority bits.
To illustrate the interrupts, I’m using the Segger SystemView with Segger RTT: It can record the interrupts without any external hardware or trace pins.
Nested Interrupts in Segger SystemView
Another way to monitor interrupts is to toggle a GPIO pin at interrupt entry/exit and to record it with a logic analyzer. This requires free pins and an external logic analyzer. If you have SWO or other trace pins, these can be used too.
Interrupt Levels on K64F
The K64F on the FRDM-K64F board implements four priority bits:
Four NVIC Interrupt Bits Implemented
So, we have 2^4 (16) priority levels with the following shifted values:
Hexadecimal: 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90 0xA0, 0xB0 0xC0, 0xD0, 0xE0, 0xF0.
Decimal: 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240.
Remember: the lower the numerical value, the higher the urgency. Zero is the highest urgency level.
Be aware that some CMSIS APIs are using the non-shifted (0-16) priority values, not the shifted ones (0x00-0xF0).
Nested Interrupts
The ARM NVIC is a Nested Vectored Interrupt Controller: IRQs with a higher urgency can nest (interrupt) IRQs with lower urgency.
To illustrate this, I have two timer interrupts configured on the board:
ISR #58 (exception #58, IRQ #42) is for a timer interrupt with a frequency of 100 Hz (every 10 ms).
ISR #59 (exception #59, IRQ #43) is for a timer interrupt with a frequency of 33.33 Hz (every 3 ms).
Remember the relationship between exception number and IRQ number (see image below). This adds yet another confusing part in CMSIS: Carefully check if exception numbers or IRQ numbers are expected in the API.
Cortex-M Vector Table (Source of images: ARM)
I have configured ISR #58 and ISR #59 with the following priorities:
ISR #58: interrupt priority value of 160 (0xA0).
ISR #59: interrupt priority value of 112 (0x70).
NVIC_SetPriority(58-16, 0xA); /* IRQ Number 42, Interrupt Priority 0xA0 */
NVIC_SetPriority(59-16, 0x7); /* IRQ Number 43, Interrupt Priority 0x70 */
To visualize the interrupts better, I have added some delays in each of them:
void ISR59_OnInterrupt(void)
{
/* ISR #59, every 3 ms */
SYS1_RecordEnterISR(); /* record interrupt entry in Segger SystemViewer */
WAIT1_Waitms(1); /* burn time for 1 ms */
SYS1_RecordExitISR(); /* record interrupt exit in Segger SystemViewer */
}
void ISR58_OnInterrupt(void)
{
/* ISR #58, every 10 ms */
SYS1_RecordEnterISR(); /* record interrupt entry in Segger SystemViewer */
WAIT1_Waitms(2); /* burn time for 2 ms */
SYS1_RecordExitISR(); /* record interrupt exit in Segger SystemViewer */
}
ISR #59 (orange) happens every 3 ms and takes 1 ms.
ISR #58 (red) happens every 10 ms and takes 2 ms.
Nested Interrupts (click to enlarge)
As shown above, because ISR #59 has higher urgency (interrupt level value 0x70), it interrupts ISR #58 which has a lower urgency (interrupt level value 0xA0):
A higher urgency IRQ is interrupting a lower urgency one (nesting).
If two or more IRQs with the same urgency are occurring at the same time or are pending, then the one with the lower IRQ number will be executed first.
The image below shows the nesting in the Events view:
Interrupt Nesting
For the example below, I have swapped the priorities: ISR #58 has a more urgent priority (0x70) than ISR #59 (0xA0). As ISR #58 needs more time (2 ms in ISR #58 vs. 1 ms in ISR #59), it can extend the time needed for ISR #59:
Missed Deadlines (click to enlarge)
Interrupt Latency
In the earlier example, because ISR #59 has a higher urgency, it can interrupt #SR #57. Additionally, if ISR #59 is active, ISR #58 cannot start and therefore might be delayed until the higher urgency interrupt has been completed. This is causing interrupt latency: The time from where the interrupt is triggered until when it is serviced. The interrupt will be delayed for some time and won’t be able to start at the trigger time.
In the example below, ISR #58 should happen at +10 ms, but because ISR #59 is running at that time, it gets blocked and delayed by the higher urgency ISR #59.
Delayed Interrupt
The total execution time of ISR #58 is extended extra because ISR #59 is adding time because of nesting.
Sub-Priorities
On the ARM Cortex-M4F of the NXP FRDM-K64F, there are four priority bits implemented. I’m now configuring it so it has one sub-priority:
Note that the ARM Cortex-M0/M0+ does *not* have sub-priorities
Three preemption bits and one sub-priority bit
CMSIS offers the following two functions for the grouping:
/**
\brief Set Priority Grouping
\details Sets the priority grouping field using the required unlock sequence.
The parameter PriorityGroup is assigned to the field SCB->AIRCR [10:8] PRIGROUP field.
Only values from 0..7 are used.
In case of a conflict between priority grouping and available
priority bits (__NVIC_PRIO_BITS), the smallest possible priority group is set.
\param [in] PriorityGroup Priority grouping field.
*/
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
/**
\brief Get Priority Grouping
\details Reads the priority grouping field from the NVIC Interrupt Controller.
\return Priority grouping field (SCB->AIRCR [10:8] PRIGROUP field).
*/
uint32_t NVIC_GetPriorityGrouping(void);
I’m configuring it below for using one sub-priority bit, which remains 3 bits for preemption level. ISR #59 is configured with priority 4.0 (preemption priority 4, sub-priority 1) and ISR #58 is configured with priority 4.1.
For the "preemption priority," usually, the term "main priority" is used too.
NVIC_SetPriorityGrouping(4); /* Having 4 interrupt priority bits: sub-priority at bit 4 ==> 3 preemption priority, 1 sub-priority bit */
NVIC_SetPriority(59-16, 0x8); /* IRQ Number 43, 0b100.0 (preempt prio 4, subprio 0), Interrupt Priority value 0x80 */
NVIC_SetPriority(58-16, 0x9); /* IRQ Number 42, 0b100.1 (preempt prio 4, subprio 1), Interrupt Priority value 0x90 */
The sub-priorities use the following rules:
A higher preemption urgency IRQ (lower numerical preemption value) will interrupt any lower urgency ISR (normal nesting: 2.1 interrupts 3.0).
A pending IRQ with the same preemption urgency as the running ISR does *not* interrupt it (higher sub-urgency does not interrupt an already running main-urgency service routine): A pending 2.0 does not interrupt a running 2.1.
If two or more IRQ with the same preemption urgency are pending, then the one with the highest sub-urgency (lowest sub-priority value) is executed first: If 2.1 and 2.0 are pending, then 2.0 is executed first.
If multiple IRQ with the same preemption and sub-priority value are pending, then the one with the lowest IRQ number is executed first: If IRQ #59 and IRQ #58 is pending, then IRQ #58 is executed first.
Rule two is important, as this rule really makes the difference between a system with sub-priorities and one without sub-priorities. This rule says that an already running ISR cannot be interrupted by an ISR of the same main priority or grouping. This is useful for example having two I²C bus interrupts, and in my driver, I don’t want to have both busses active with interrupts the same time: Then I could put them into one group and that way they will not nest. Or in other words, the grouping disables interrupt nesting inside the group, with the sub-priority giving a way to assign an urgency level in case multiple interrupts of the group are pending.
The Segger SystemView shows below ISR #59 with priority 4.0 and ISR #58 with priority 4.1: They do not nest anymore, and 4.0 has precedence if both 4.0 and 4.1 are pending:
System with Sub-Priorities
FreeRTOS
FreeRTOS — and any other RTOS I’m aware of — uses the microcontroller interrupt system. It is critical to understand the interrupt system of the microcontroller for the application. Using an RTOS like FreeRTOS means that I have to have an understanding of its usage of the interrupt system because otherwise, it can cause conflicts and wrong behavior of the application.
In this part, I describe how FreeRTOS uses the ARM Cortex-M (0/0+/3/4/7) interrupts:
Interrupts used by the RTOS.
Priorities of the RTOS interrupts.
Critical Section handling.
Partitioning of interrupt priorities/urgencies between the application and the RTOS.
Application startup and interrupts.
From the FreeRTOS perspective, Cortex-M0 and M0+ are the same, so I’m using M0 both for the M0+ and M0. The Cortex-M3/M4/M7 including their floating point variants are pretty much treated the same by FreeRTOS.
I’m using GNU assembly syntax to keep it simple. The McuOnEclipse FreeRTOS port covers GNU, IAR, and Keil too.
Interrupts Used by FreeRTOS
FreeRTOS on ARM Cortex-M uses the two or three interrupts, depending on the architecture and port used:
In FreeRTOS, a "port" is the part of the Kernel which is microcontroller specific. This part deals with the low-level hardware. Everything else in FreeRTOS is generic and written in C. The port part is written in a mix of C and assembly.
Interrupts Used by FreeRTOS (Source of tables: ARM Info Center)
Earlier ports for ARM Cortex-M0 did use SVCall too. Current port files provide that interrupt service routine for backward compatibility only.
SysTick: This one is used as the time base (timer interrupt) for the RTOS. Typical frequencies are 1 kHz or 100 Hz. In preemptive RTOS mode, that interrupt provides a way for the RTOS to preempt a running task and to pass control to another task.
PendSV (Pendable SerVice) is an interrupt request is used by the OS to force a context switch if no other interrupt is active.
SVCall (SuperVisor Call) is triggered by the SVC instruction and is used by the FreeRTOS to start the scheduler. This one is not used for M0.
SysTick is the default time base. Of course, any other timer interrupt can be used instead. For example, for low-power applications, I’m using a special low-power timer instead. And if enabling the FreeRTOS trace facility to measure task execution time, an extra timer might be needed.
When adding FreeRTOS to a "bare-metal" (without RTOS) application, these three interrupts need to be routed to the FreeRTOS port.
Below is an example interrupt vector table for the NXP K20 (ARM Cortex-M4) with these three FreeRTOS interrupts highlighted (vPortSVCHandler, vPortPendSVHandler and vPortTickHandler):
If using CMSIS compliant interrupt names, then it would be SVC_Handler, PendSV_Handler and SysTick_Handler.
__attribute__ ((section (".vectortable"))) const tVectorTable __vect_table = { /* Interrupt vector table */
/* ISR name No. Address Pri Name Description */
&__SP_INIT, /* 0x00 0x00000000 - ivINT_Initial_Stack_Pointer */
{
(tIsrFunc)&__thumb_startup, /* 0x01 0x00000004 - ivINT_Initial_Program_Counter */
(tIsrFunc)&Cpu_INT_NMIInterrupt, /* 0x02 0x00000008 -2 ivINT_NMI */
(tIsrFunc)&HF1_HardFaultHandler, /* 0x03 0x0000000C -1 ivINT_Hard_Fault */
(tIsrFunc)&Cpu_ivINT_Mem_Manage_Fault, /* 0x04 0x00000010 - ivINT_Mem_Manage_Fault */
(tIsrFunc)&Cpu_ivINT_Bus_Fault, /* 0x05 0x00000014 - ivINT_Bus_Fault */
(tIsrFunc)&Cpu_ivINT_Usage_Fault, /* 0x06 0x00000018 - ivINT_Usage_Fault */
(tIsrFunc)&Cpu_ivINT_Reserved7, /* 0x07 0x0000001C - ivINT_Reserved7 */
If using the NXP Processor Expert system, then the above interrupts get automatically added to the vector table by the FreeRTOS component. If using an example from FreeRTOS.org, then the vector table already has these interrupts added.
SVCall
The M4/M7 ports of FreeRTOS are using a SuperVisor to start the first task. The SVC (SuperVisor Call) instruction is designed by ARM to access OS Kernel functions and device drivers. The SVCall exception is raised by the SVC assembly instruction which takes an additional argument/number, e.g.
SVC <number>
For M4/M7, FreeRTOS uses the SVC in a single place: when it starts the scheduler to run the first task. The function vPortStartFirstTask() gets called at the end when you do a call to the FreeRTOS vTaskStartScheduler():
void vPortStartFirstTask(void) {
#if configCPU_FAMILY_IS_ARM_M4_M7(configCPU_FAMILY) /* Cortex M4/M7 */
__asm volatile (
" ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */
" ldr r0, [r0] \n" /* load address of vector table */
" ldr r0, [r0] \n" /* load first entry of vector table which is the reset stack pointer */
" msr msp, r0 \n" /* Set the msp back to the start of the stack. */
" cpsie i \n" /* Globally enable interrupts. */
" svc 0 \n" /* System call to start first task. */
" nop \n"
);
SVC uses an argument that could be checked in the SVCall interrupt handler. Because FreeRTOS only uses the zero, no handling is needed. Below is an implementation of the SVCHandler:
Unlike the ‘normal’ FreeRTOS ports, the McuOnEcliopse FreeRTOS port covers multiple ARM architectures in a single port. For this, the configCPU_FAMILY configuration macro is used. This makes sense as most of the port is the same for M0/M4/M7.
__attribute__ ((naked)) void vPortSVCHandler(void) {
#if configCPU_FAMILY_IS_ARM_M4_M7(configCPU_FAMILY) /* Cortex M4 or M7 */
__asm volatile (
" ldr r3, pxCurrentTCBConst2 \n" /* Restore the context. */
" ldr r1, [r3] \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
/* pop the core registers */
#if configCPU_FAMILY_IS_ARM_FPU(configCPU_FAMILY)
" ldmia r0!, {r4-r11, r14} \n"
#else
" ldmia r0!, {r4-r11} \n"
Don’t be worried about the (GNU) ARM assembly code above: Basically what it does is perform a context switch — jumping from the vPortSVCHandler() interrupt handler/context to the task described by the pxCurrentTCB.
pxCurrentTCB points to an RTOS structure that contains a descriptor of the task (TCB stands for Task Control Block).
MSP and PSP
You might notice that the above handler sets the PSP (Process Stack Pointer):
"msr psp, r0 \n" /* Remember the new top of stack for the task. */
The Cortex-M has two different stack pointers:
MSP (Main Stack Pointer) is used during startup and during main(). Interrupts are using this stack.
PSP (Process Stack Pointer) is used by the process/tasks.
ARM Cortex MSP and PSP Registers
The good thing with this is that the task stacks only need to count for the task stack/variables, and not include the stack needed for the interrupts. On the other hand, it means that the MSP stack needs to hold all the (nested) interrupt stacks.
In the above port for Cortex-M4/M7, the MSP stack pointer is reset to the reset vector stack pointer: This gives some extra stack bytes for the interrupt service routines. Because not all Cortex-M0 have the vector table at address zero and there is no dedicated register to locate the initial stack pointer, this is not used for the Cortex-M0 port.
To my knowledge, all NXP Cortex-M0+ parts do have the default vector table at address 0x0000’0000. It is true that the NVIC for Cortex-M0 does not implement a VTOR (Vector Table Offset Register) at 0xE000ED08. However, all the NXP Cortex-M0+ parts I’m aware of have it implemented. Not as part of NVIC, but as part of the SystemControl. So for the NXP Cortex-M0+, the same approach as for M4/M7 with VTOR and SVC could be used.
VTOR on NXP Kinetis L Family (ARM Cortex-M0+)
PendSV
The Pendable Service interrupt is used by the RTOS to perform a context switch. Such a context switch can be originated by the RTOS or by the application with a "yield."
The vPortPendSVHandler() It is similar to the vPortSVCHandler(). But it does not switch from the MSP to the PSP. It performs a task context switch between different PSP values. Additionally, it calls the FreeRTOS vTaskSwitchContext(), which selects the highest (RTOS!) priority ready task:
Don’t get confused by the RTOS priorities. FreeRTOS uses a system where numerically lower values mean lower task priorities. Zero is the lowest task priority in FreeRTOS. Don’t get this confused with the ARM Cortex-M interrupt priority levels where zero is the highest urgency!
__attribute__ ((naked)) void vPortPendSVHandler(void) {
#if configCPU_FAMILY_IS_ARM_M4_M7(configCPU_FAMILY) /* Cortex M4 or M7*/
__asm volatile (
" mrs r0, psp \n"
" ldr r3, pxCurrentTCBConst \n" /* Get the location of the current TCB. */
" ldr r2, [r3] \n"
#if configCPU_FAMILY_IS_ARM_FPU(configCPU_FAMILY)
" tst r14, #0x10 \n" /* Is the task using the FPU context? If so, push high vfp registers. */
" it eq \n"
" vstmdbeq r0!, {s16-s31} \n"
Task Yielding
As mentioned above, a task context switch can be requested by a task. For this taskYIELD() is used. This is actually a macro:
/**
* task. h
*
* Macro for forcing a context switch.
*
* \defgroup taskYIELD taskYIELD
* \ingroup SchedulerControl
*/
#define taskYIELD() portYIELD()
Which is yet another macro:
#define portYIELD() vPortYieldFromISR()
Which is implemented as:
void vPortYieldFromISR(void) {
/* Set a PendSV to request a context switch. */
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET_BIT;
/* Barriers are normally not required but do ensure the code is completely
within the specified behavior for the architecture. */
__asm volatile("dsb");
__asm volatile("isb");
}
It sets the portNVIC_PENDSVSET_BIT
to trigger the PendSV interrupt in the first statement of the function.
The above implementation uses data and instruction barrier instructions. The Cortex-M has several special instructions to flush the internal pipelines/caches/bus registers. Check the ARM Infocenter for more details about the need for barrier instructions.
SysTick
The SysTick interrupt is a 24-bit timer designed to be used by the RTOS as a time base. Typical time bases are 10ms or 1ms. Basically, the tick interrupt preempts any running task and passes control to the scheduler so it can increment its internal tick counter and perform a context switch as needed. Below is a (simplified) version of the tick handler interrupt routine:
void vPortTickHandler(void) {
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
executes all interrupts must be unmasked. There is therefore no need to
save and then restore the interrupt mask value as its value is already
known. */
portDISABLE_INTERRUPTS(); /* disable interrupts */
if (xTaskIncrementTick()!=pdFALSE) { /* increment tick count */
traceISR_EXIT_TO_SCHEDULER();
taskYIELD();
}
portENABLE_INTERRUPTS(); /* re-enable interrupts */
As a side note: In FreeRTOS non-preemptive (cooperative, configUSE_PREEMPTION set to zero) mode the call to xTaskIncrementTick() will always return pdTRUE, so the tick interrupt will not cause a context switch.
Notice the pair of portDISABLE_INTERRUPTS() and portENABLE_INTERRUPTS(): some ports will not completely turn off interrupts, more about later.
configKERNEL_INTERRUPT_PRIORITY
You might have noticed that comment inside vPortTickHandler() from above:
/* The SysTick runs at the lowest interrupt priority, ... */
In FreeRTOS, the kernel and its interrupts are running with the lowest interrupt priority/urgency.
FreeRTOS uses the macro configKERNEL_INTERRUPT_PRIORITY
in FreeRTOSConfig.h for the interrupts used by the kernel port layer. Below is an implementation for a system with 4 interrupt priority bits.
#define configPRIO_BITS 4 /* 4 bits/16 priority levels on ARM Cortex M4 (Kinetis K Family) */
/* The lowest interrupt priority that can be used in a call to a "set priority" function. */
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
Note that configKERNEL_INTERRUPT_PRIORITY
is in the form the NVIC expects it: already *shifted* to the left.
As noted above, the important point to note is that configKERNEL_INTERRUPT_PRIORITY
is set to the lowest urgency interrupt level in the system. So this means the interrupts of the kernel (SysTick and context switches) run with the lowest possible urgency. This makes sense as the kernel should not block or interrupt the other interrupts in the system. The other reason is that this approach keeps the kernel simple, as it does not have to deal with nested interrupts ‘below’ the urgency level of the kernel.
PendSV and SysTick interrupt priorities are configured by the FreeRTOS at scheduler start:
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
#define portNVIC_SYSPRI3 ((volatile unsigned long*)0xe000ed20) /* system handler priority register 3 (SHPR3), used for SysTick and PendSV priority, http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0662b/CIAGECDD.html */
#define portNVIC_SYSTICK_PRI (((unsigned long)configKERNEL_INTERRUPT_PRIORITY)<<24) /* priority of SysTick interrupt (in portNVIC_SYSPRI3) */
#define portNVIC_PENDSV_PRI (((unsigned long)configKERNEL_INTERRUPT_PRIORITY)<<16) /* priority of PendableService interrupt (in portNVIC_SYSPRI3) */
*(portNVIC_SYSPRI3) |= portNVIC_PENDSV_PRI; /* set priority of PendSV interrupt */
*(portNVIC_SYSPRI3) |= portNVIC_SYSTICK_PRI; /* set priority of SysTick interrupt */
The SVCall priority (only used on M4/M7) is not touched. Out of reset, this priority is zero (highest urgency for the ARM core). The SVCall interrupt is only used once at the startup of the RTOS (see above).
The reason why the SVCall priority is still at the highest urgency (0 value) is when executing the SVC instruction, the SVCall interrupt shall not be masked (disabled). If SVCall would be at the same priority as the SysTick, then a SysTick might happen earlier, and then using SVC would cause a hard fault. Leaving it at 0 (highest interrupt urgency) ensures that it is not masked by a running PendSV or SysTick interrupt, and that it is not masked by the BASEPRI register.
Calling RTOS API Functions from ISR
It is not uncommon to call RTOS functions (e.g. to set/clear a semaphore) from an interrupt service routine (ISR). There is one very important rule with FreeRTOS:
Only RTOS API functions ending with “FromISR” are allowed to be called from an interrupt service routine.
The “FromISR” functions are ‘interrupt safe, for example xSemaphoreGiveFromISR().
xSemaphoreGiveFromISR
(
SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken
)
If pxHigherPriorityTaskWoken returns pdTRUE, then the API call caused a higher priority task to be unlocked. I that case the interrupt should call a yield at the end of the service routine to perform a context switch.
The following example implements a timer interrupt which gives a semaphore:
/* Timer ISR */
void vTimerISR(void) {
signed BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
/* If xHigherPriorityTaskWoken was set to true you we should yield. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
Kernel Critical Sections
FreeRTOS stores internal information, like the list of tasks and other data used by the kernel. Because this data is accessed both by tasks through FreeRTOS API calls (e.g. accessing a semaphore) and by the kernel itself (trigged through a SysTick timer interrupt), access to this data needs to be reentrant and protected with a critical section.
Remember the simple critical section we saw earlier for the tick handler:
void vPortTickHandler(void) {
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
executes all interrupts must be unmasked. There is therefore no need to
save and then restore the interrupt mask value as its value is already
known. */
portDISABLE_INTERRUPTS(); /* disable interrupts */
if (xTaskIncrementTick()!=pdFALSE) { /* increment tick count */
traceISR_EXIT_TO_SCHEDULER();
taskYIELD();
}
portENABLE_INTERRUPTS(); /* re-enable interrupts */
Inside xTaskIncrementTick()
the kernel is running: checking if there is another task to schedule in preemptive mode. In cooperative mode, it will return pdFALSE
.
So now you could think that the RTOS is disabling all interrupts during its critical sections. This is actually true for some FreeRTOS ports (e.g. for ARM Cortex-M0/M0+). Disabling interrupts for critical sections increases interrupt latency and should be avoided as much as possible, and such a critical section should be as small as possible. While FreeRTOS makes every effort to keep such critical sections as small and fast as possible, they are certainly longer than a few CPU instructions. The good news is that for the Cortex-M3/M4/M7 ports, not all interrupts are disabled: FreeRTOS is taking advantage of the BASEPRI register.
They are behind yet another macro as below:
#define portDISABLE_INTERRUPTS() portSET_INTERRUPT_MASK()
#define portENABLE_INTERRUPTS() portCLEAR_INTERRUPT_MASK()
The implementation depends on the Cortex-M core used:
#if configCPU_FAMILY_IS_ARM_M4_M7(configCPU_FAMILY) /* Cortex M4/M7 */
/*
* Set basepri to portMAX_SYSCALL_INTERRUPT_PRIORITY without effecting other
* registers. r0 is clobbered.
*/
#define portSET_INTERRUPT_MASK() \
__asm volatile \
( \
" mov r0, %0 \n" \
" msr basepri, r0 \n" \
: /* no output operands */ \
You might wonder why in above vPortTickHandler()
it enables all interrupts with portENABLE_INTERRUPTS()
, and why it does not need to store the current BASEPRI level? Because the tick interrupt has the lowest interrupt value, it only can run if all interrupts are enabled. So BASEPRI must be zero, so it does not make sense to save and restore a known value.
So on Cortex-M0, it globally disables all interrupts, while on M4/M7 FreeRTOS only disables the interrupts up to the configMAX_SYSCALL_INTERRUPT_PRIORITY
evel (more about this FreeRTOS configuration setting later). So using an ARM Cortex-M3/M4/M7 not only makes sense from the performance point of view: with the BASEPRI NVIC hardware it is possible that some interrupts are not blocked by the Kernel and their interrupt latency is not impacted by the RTOS. However, the range of interrupt levels needs to be carefully selected, more about this later as well.
Task Critical Sections
So far, we had a look a the port critical section handling for the tick interrupt. Another aspect is how the Kernel implements critical sections inside the RTOS API.
For example, below is an example of how FreeRTOS implements a critical section inside:
void* xQueueGetMutexHolder( QueueHandle_t xSemaphore )
{
void *pxReturn;
/* This function is called by xSemaphoreGetMutexHolder(), and should not
be called directly. Note: This is a good way of determining if the
calling task is the mutex holder, but not a good way of determining the
identity of the mutex holder, as the holder may change between the
following critical section exiting and the function returning. */
taskENTER_CRITICAL();
{
As shown above, FreeRTOS uses the following:
taskENTER_CRITICAL();
/* critical section here */
taskEXIT_CRITICAL();
They are macros, and the implementation is part of the port for a microcontroller:
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
Below is the implementation for ARM Cortex-M (simplified):
void vPortEnterCritical(void) {
/*
* Disable interrupts before incrementing the count of critical section nesting.
* The nesting count is maintained so we know when interrupts should be
* re-enabled. Once interrupts are disabled the nesting count can be accessed
* directly. Each task maintains its own nesting count.
*/
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
}
/*-----------------------------------------------------------*/
The two functions are using a nesting counter to allow the nesting of critical sections. They are using:
portDISABLE_INTERRUPTS();
and
portENABLE_INTERRUPTS();
to disable/enable the interrupts we have seen above. So here again: FreeRTOS is masking all interrupts on Cortex-M0, but leaves some interrupt levels enabled based on the configMAX_SYSCALL_INTERRUPT_PRIORITY
.
configMAX_SYSCALL_INTERRUPT_PRIORITY
We already saw earlier the configMAX_SYSCALL_INTERRUPT_PRIORITY setting of FreeRTOS in FreeRTOSConfig.h. On Cortex-M3/M4/M7 it is used to mask interrupts using the BASEPRI register.
In FreeRTOSConfig.h it is present as a macro:
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
Because the MAX_SYSCALL
can be confusing, there is an alias configMAX_API_CALL_INTERRUPT_PRIORITY
, which much better reflects up to which interrupt level the FreeRTOS API can be called from an interrupt. 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.
The macro configMAX_SYSCALL_INTERRUPT_PRIORITY
uses configPRIO_BITS
(the number of priority bits available), and configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
:
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
It is used in the port to mask interrupts as below:
#define portSET_INTERRUPT_MASK() \
__asm volatile \
( \
" mov r0, %0 \n" \
" msr basepri, r0 \n" \
/* no output operands */ \
:"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY) /* input */\
:"r0" /* clobber */ \
)
/*
* Set basepri back to 0 without effective other registers.
Because the kernel only masks out interrupts up and equal to the configMAX_SYSCALL_INTERRUPT_PRIORITY
numerical value, FreeRTOS API calls from interrupts can only be called from interrupts *numerically equal or lower* than configMAX_SYSCALL_INTERRUPT_PRIORITY
(remember: ARM NVIC uses zero as the highest urgency level).
Let’s assume a system with 2 priority bits on a Cortex-M3/M4/M7:
#define configPRIO_BITS 2 /* prios: 0, 1, 2, and 3 */
The lowest priority is 3
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 3
So the FreeRTOS Kernel (PendSV and Systick) will run on that level which is 0xC0:
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
Let’s set the ‘Max API Call Interrupt level’ to 2:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2
That means the kernel critical section (BASEPRI) is set to 0x80:
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))
I configure the rest of my interrupts and get the following NVIC priorities:
USB: 0 (0x00).
I2C: 1 (0x40).
SCI: 2 (0x80).
BASEPRI: 0x80.
SysTick: 3 (0xC0).
PendSV: 3 (0xC0).
FreeRTOS and Interrupts
The FreeRTOS kernel through SysTick and PendSV runs on the lowest interrupt urgency. Whenever the FreeRTOS is creating a critical section, it writes 0x80 to the BASEPRI mask register, effectively blocking any interrupts with NVIC value 0x80 or greater.
Kernel and ISRs calling RTOS API
Any interrupt (SCI in the above case) with a priority value equal to or higher than BASEPRI can call FromISR() FreeRTOS API functions. The ability of an interrupt service routine to use FreeRTOS API functions comes with the price that the RTOS is blocking that interrupt in the kernel critical sections. All the interrupts with NVIC priority values lower (higher urgency) than the BASEPRI value are not blocked by the RTOS.
Here’s an extract that applies to the Cortex-M3/M4/M7:
“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.
A full interrupt nesting model is achieved by setting configMAX_SYSCALL_INTERRUPT_PRIORITY
above (that is, at a higher priority level) than configKERNEL_INTERRUPT_PRIORITY
. This means the FreeRTOS kernel does not completely disable interrupts, even inside critical sections. Further, this is achieved without the disadvantages of a segmented kernel architecture. Note, however, that certain microcontroller architectures will (in hardware) disable interrupts when a new interrupt is accepted – meaning interrupts are unavoidably disabled for the short period between the hardware accepting the interrupt, and the FreeRTOS code re-enabling interrupts.
Interrupts that do not call API functions can execute at priorities above configMAX_SYSCALL_INTERRUPT_PRIORITY
and therefore never be delayed by the RTOS kernel execution.” - FreeRTOS.org
Of course, not every interrupt service routine needs FreeRTOS API functions: They can use an NVIC priority numerically lower than the BASEPRI value and are not affected by the RTOS critical section. However, if an interrupt service routine *is* using RTOS API calls (e.g. dealing with a semaphore or using FreeRTOS queues or events), then the NVIC priority of it has to be numerically equal or higher (lower urgency) than the BASEPRI value.
It is highly recommended to keep the configASSERT()
in FreeRTOS enabled, as these checks are catching many wrong settings. Failure to configure the Cortex-M priorities in alignment with how the RTOS is using it can cause a strange and sporadic failure of the system!
What about systems without BASEPRI NVIC hardware support as the Cortex-M0+?
“For ports that only implement configKERNEL_INTERRUPT_PRIORITY
, configKERNEL_INTERRUPT_PRIORITY
sets the interrupt priority used by the RTOS kernel itself. Interrupts that call API functions must also execute at this priority. Interrupts that do not call API functions can execute at higher priorities and therefore never have their execution delayed by the RTOS kernel activity (within the limits of the hardware itself).” - FreeRTOS.org
Here I think this is not correct for the Cortex-M0+ or may be misleading: As the port for Cortex-M0 blocks all interrupts in the RTOS critical section, they are indeed delayed by the RTOS kernel activity. On the other side, as they are all blocked and affected, they can use any priority. But any ISR using FreeRTOS API calls has to use FromISR() API variants.
System Startup and Interrupts
In FreeRTOS, the scheduler gets started with a call to vTaskStartScheduler:
void vTaskStartScheduler(void);
By default on ARM Cortex-M, interrupts are disabled out of reset. Typically they get enabled by the startup code on some systems. In most systems, they get enabled in or after main().
With using FreeRTOS, I recommend that interrupts are disabled during system initialization. FreeRTOS will enable interrupts inside vTaskStartScheduler()
.
It is safe to call FreeRTOS API functions before the scheduler is started:
“If a FreeRTOS API function is called before the scheduler has been started then interrupts will deliberately be left disabled and not re-enable again until the first task starts to execute. This is done to protect the system from crashes caused by interrupts attempting to use FreeRTOS API functions during system initialization, before the scheduler has been started, and while the scheduler may be in an inconsistent state.
Do not alter the microcontroller interrupt enable bits or priority flags using any method other than calls to taskENTER_CRITICAL() and taskEXIT_CRITICAL(). These macros keep a count of their call nesting depth to ensure interrupts become enabled again only when the call nesting has unwound completely to zero. Be aware that some library functions may themselves enable and disable interrupts.” - FreeRTOS.org
FreeRTOS and Sub-priorities?
I have made some experiments, and with careful settings of priority values and macros with a few tweaks, it seems to be possible to run FreeRTOS in a system with sub-priorities. I have not made extensive tests, and currently, I don’t have the need to run it with sub-priorities. For sure it is (not yet?) supported out of the box, but certainly doable. And I’m sure if there is demand for it, it can get added to FreeRTOS.
Summary
The key takeaways are:
The FreeRTOS kernel uses two-three interrupts, depending on the core: SysTick is used as the time base, PendSV for context switches, and SVCall on Cortex-M3/4/7 to start the scheduler.
SysTick and PendSV are configured for the lowest urgency: The RTOS runs with the lowest urgency level.
FreeRTOS task priorities start with 0 as the lowest urgent RTOS task priority, while the ARM NVIC is using zero as the highest urgent interrupt priority.
The tasks are using the PSP (Process Stack Pointer), while the interrupts are using the MSP (Main Stack Pointer). On the Cortex-M3/4/7 port, the MSP is set back to the reset stack pointer at scheduler start.
The FreeRTOS kernel needs to use a critical section to protect its internal data structures.
On Cortex-M0, the critical section of the Kernel masks all interrupts.
On Cortex-M3/4/7, the critical section of the kernel does not mask all interrupts: only masks interrupts up and equal to
configMAX_SYSCALL_INTERRUPT_PRIORITY
using the NVIC BASEPRI. Interrupts with NVIC values lower than BASEPRI (higher urgency) are not affected by the Kernel.Only interrupts with NVIC numerical values greater-equal than
configMAX_SYSCALL_INTERRUPT_PRIORITY/BASEPRI
are allowed to use the RTOS API.Interrupt service routines calling the RTOS API have to use the interrupt safe
FromISR()
variants.Sub-priorities would be possible, but not supported out-of-the-box in FreeRTOS V9.0.0.
Happy FreeRTOSing!
Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments