This section describes the QP™ ports to the ARM Cortex-M processor family (Cortex M0/M0+/M3/M4/M7). Three main implementation options are covered: the cooperative, priority-based 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 CPUs is explained as well. This document assumes QP version 5.9.x or higher.
The QP ports to ARM Cortex-M are available in the standard QP distribution. Specifically, the ARM Cortex-M ports are placed in the following directories:
The QP 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, such as QV, QK, and QXK.
The QP ports to ARM Cortex-M3/M4/M7 never completely disables interrupts, even inside the critical sections. On Cortex-M3/M4/M7 (ARMv7-M architecture), the QP 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 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"The example projects included in the QP 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.
Listing: Assigning the interrupt priorities (see file bsp.c in the example projects)
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. NVIC_SetPriority()
to explicitly set the priority of a kernel unaware interrupt with interrupt priority number lower than QF_AWARE_ISR_CMSIS_PRI (defined in qf_port.h / qf_port.hpp). NOTE: Such "kernel unaware" interrupts are never disabled by the QV/QK/QXK kernels, but they are NOT allowed to make calls to the QP framework.
NVIC_SetPriority()
to explicitly set the priority of kernel aware interrupts with interrupt priority number equal or higher than QF_AWARE_ISR_CMSIS_PRI (defined in qf_port.h / qf_port.hpp). NOTE: Such "kernel aware" interrupts are disabled by the QV/QK/QXK kernels, and they are allowed to make calls to the QP framework.
NVIC_EnableIRQ()
. The QP ports described in this section support also the ARM Cortex-M4F/M7. 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.
Next: Cooperative QV Kernel