This section describes the QP/C++ ports to the ARM Cortex-M processor family (Cortex M0/M0+/M3/M4/M7/M33). Three main implementation options are covered: the non-preemptive QV kernel, the preemptive, run-to-completion QK kernel, and the preemptive, dual-mode blocking QXK kernel. Additionally, the use of the VFP (floating point coprocessor) in the M4F/M7/M33 CPUs is explained as well. This document assumes QP/C++ version 7.x or higher.
The QP ports to ARM Cortex-M are available in the standard QP/C++ distribution. Specifically, the ARM Cortex-M ports are placed in the following directories:
The QP/C++ real-time framework, like any real-time kernel, needs to disable interrupts in order to access critical sections of code and re-enable interrupts when done. This section describes the general policy used in the ARM Cortex-M ports of all built-in real time kernels in QP/C++ , such as QV, QK, and QXK.
"Kernel-Aware" and "Kernel-Unaware" Interrupts
The QP/C++ ports to ARMv7M or higher architectures never completely disables interrupts, even inside the critical sections. On ARMv7M or higher architectures, the QP/C++ port disables interrupts selectively using the BASEPRI register. This policy divides interrupts into "kernel-unaware" interrupts, which are never disabled, and "kernel-aware" interrupts, which are disabled in the QP/C++ critical sections.
As illustrated in the figures below, the number of interrupt priority bits actually available is implementation dependent, meaning that the various ARM Cortex-M silicon vendors can provide different number of priority bits, varying from just 3 bits (which is the minimum for ARMv7-M architecture) up to 8 bits. For example, the TI Tiva-C microcontrollers implement only 3 priority bits (see figure below).
On the other hand, the STM32 MCUs implement 4 priority bits (see figure below). The CMSIS standard provides the macro NVIC_PRIO_BITS, which specifies the number of NVIC priority bits defined in a given ARM Cortex-M implementation.
Another important fact to note is that the ARM Cortex-M core stores the interrupt priority values in the most significant bits of its eight bit interrupt priority registers inside the NVIC (Nested Vectored Interrupt Controller). For example, if an implementation of a ARM Cortex-M microcontroller only implements three priority bits, then these three bits are shifted to occupy bits five, six and seven respectively. The unimplemented bits can be written as zero or one and always read as zero.
And finally, the NVIC uses an inverted priority numbering scheme for interrupts, in which priority zero (0) is the highest possible priority (highest urgency) and larger priority numbers denote actually lower-priority interrupts. So for example, interrupt of priority 2 can preempt an interrupt with priority 3, but interrupt of priority 3 cannot preempt interrupt of priority 3. The default value of priority of all interrupts out of reset is zero (0).
QF_onStartup()
.The CMSIS provides the function NVIC_SetPriority()
which you should use to set priority of every interrupt.
NVIC_SetPriority()
is different again than the values stored in the NVIC registers, as shown in the figures above as "CMSIS priorities"Assigning Interrupt Priorities
The example projects included in the QP/C++ distribution the recommended way of assigning interrupt priorities in your applications. The initialization consist of two steps: (1) you enumerate the "kernel-unaware" and "kernel-aware" interrupt priorities, and (2) you assign the priorities by calling the NVIC_SetPriority()
CMSIS function. The following snippet of code illustrates these steps with the explanation section following immediately after the code.
[1]
The enumeration KernelUnawareISRs
lists the priority numbers for the "kernel-unaware" interrupts. These priorities start with zero (highest possible). The priorities are suitable as the argument for the NVC_SetPriority()
CMSIS function.
[2]
The last value in the enumeration MAX_KERNEL_UNAWARE_CMSIS_PRI keeps track of the maximum priority used for a "kernel-unaware" interrupt.
[3]
The compile-time assertion ensures that the "kernel-unaware" interrupt priorities do not overlap the "kernel-aware" interrupts, which start at QF_AWARE_ISR_CMSIS_PRI.
[4]
The enumeration KernelAwareISRs lists the priority numbers for the "kernel-aware" interrupts.
[5]
The "kernel-aware" interrupt priorities start with the QF_AWARE_ISR_CMSIS_PRI offset, which is provided in the qp_port.h header file.
[6]
The last value in the enumeration MAX_KERNEL_AWARE_CMSIS_PRI keeps track of the maximum priority used for a "kernel-aware" interrupt.
[7]
The compile-time assertion ensures that the "kernel-aware" interrupt priorities do not overlap the lowest priority level reserved for the PendSV exception.
[8]
The QF_onStartup() callback function is where you set up the interrupts.
[9]
This call to the CMIS function NVIC_SetPriorityGrouping()
assigns all the priority bits to be preempt priority bits, leaving no priority bits as subpriority bits to preserve the direct relationship between the interrupt priorities and the ISR preemption rules. This is the default configuration out of reset for the ARM Cortex-M3/M4 cores, but it can be changed by some vendor-supplied startup code. To avoid any surprises, the call to NVIC_SetPriorityGrouping(0U)
is recommended.
[10]
The interrupt priories fall all interrupts ("kernel-unaware" and "kernel-aware" alike) are set explicitly by calls to the CMSIS function NVIC_SetPriority()
.
[11]
All used IRQ interrupts need to be explicitly enabled by calling the CMSIS function.
Interrupts and the FPU (ARMv7M or higher architectures)
The QP/C++ ports described in this section support also the ARMv7M or higher architectures. Compared to all other members of the Cortex-M family, these cores includes the single precision variant of the ARMv7-M Floating-Point Unit (Fpv4-SP). The hardware FPU implementation adds an extra floating-point register bank consisting of S0-S31 and some other FPU registers. This FPU register set represents additional context that need to be preserved across interrupts and thread switching (e.g., in the preemptive QK kernel).
The ARM VFP has a very interesting feature called lazy stacking [ARM-AN298]. This feature avoids an increase of interrupt latency by skipping the stacking of floating-point registers, if not required, that is:
If the interrupt handler has to use the FPU and the interrupted context has also previously used by the FPU, then the stacking of floating-point registers takes place at the point in the program where the interrupt handler first uses the FPU. The lazy stacking feature is programmable and by default it is turned ON.