QP/C  5.9.7
ARM Cortex-M

This section describes the QP™ port 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 processors is explained as well. This document assumes QP version 5.9.x or higher.

To focus the discussion, this section references the GNU-ARM toolset, the EK-TM4C123GXL (ARM Cortex-M4F) and the Eclipse-based IDE (CCS from Texas Instruments). However, the general implementation strategy applies equally to all toolsets for ARM Cortex-M, such as ARM-KEIL, IAR EWARM, GNU-ARM and TI-ARM, which are all supported as well. The QP code downloads contain also examples for other boards, such as STM32 Nucleo, NXP mbed-1768, SilLabs Gecko and others.

Directories and Files

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:

  • /pors/arm-cm/
    • qv/ — QV ports
      • arm/ — ARM-KEIL toolset
      • gnu/ — GNU-ARM toolset
        • qep_port.h — QEP port header
        • qf_port.h — QF port header
        • qv_port.h — QV port header
        • qv_port.c — QV port implementation
      • iar/ — IAR-EWARM toolset
      • ti/ — TI-ARM toolset
    • qk/ — QK ports
      • arm/ — ARM-KEIL toolset
      • gnu/ — GNU-ARM toolset
      • iar/ — IAR-EWARM toolset
      • ti/ — TI-ARM toolset
    • qxk/ — QXK ports"
      • arm/ — ARM-KEIL toolset
      • gnu/ — GNU-ARM toolset
      • iar/ — IAR-EWARM toolset
      • ti/ — TI-ARM toolset
    • qutest/QUTest Unit Testing port to Cortex-M"

Interrupts in the QP Ports to ARM Cortex-M

The QP 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 that in the ARM Cortex-M ports of all built-in real time kernels in QP, such as QV, QK, and QXK.

"Kernel-Aware" and "Kernel-Unaware" Interrupts

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 architectures), 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.

The BASEPRI register is not implemented in the ARMv6-M architecture (Cortex-M0/M0+), so Cortex-M0/M0+ need to use the PRIMASK register to disable interrupts globally. In other words, in Cortex-M0/M0+ ports, all interrupts are "kernel-aware".
Only "kernel-aware" interrupts are allowed to call QP services. "Kernel-unaware" interrupts are not allowed to call any QP services and they can communicate with QP only by triggering a "kernel-aware" interrupt (which can post or publish events).

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 above).

Kernel-aware and Kernel-unaware interrupts with 3 priority bits

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.

Kernel-aware and Kernel-unaware interrupts with 4 priority bits

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 up to be 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).

Starting with QP 5.9.x, the QF_init() call sets interrupt priority of all IRQs to the "kernel aware" value QF_BASEPRI. Still, it is highly recommended to set the priority of all interrupts used by an application explicitly.

The CMSIS provides the function NVIC_SetPriority() which you should use to set priority of every interrupt.

The priority scheme passed to 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 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)

[1] enum KernelUnawareISRs { /* see NOTE0 */
/* ... */
[2] MAX_KERNEL_UNAWARE_CMSIS_PRI /* keep always last */
/* "kernel-unaware" interrupts can't overlap "kernel-aware" interrupts */
[4] enum KernelAwareISRs {
/* ... */
[6] MAX_KERNEL_AWARE_CMSIS_PRI /* keep always last */
/* "kernel-aware" interrupts should not overlap the PendSV priority */
~ ~ ~
[8] void QF_onStartup(void) {
/* set up the SysTick timer to fire at BSP_TICKS_PER_SEC rate */
SysTick_Config(ROM_SysCtlClockGet() / BSP_TICKS_PER_SEC);
/* assing all priority bits for preemption-prio. and none to sub-prio. */
[9] NVIC_SetPriorityGrouping(0U);
/* set priorities of ALL ISRs used in the system, see NOTE00
* !!!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* Assign a priority to EVERY ISR explicitly by calling NVIC_SetPriority().
[10] NVIC_SetPriority(SysTick_IRQn, SYSTICK_PRIO);
~ ~ ~
/* enable IRQs... */
[12] NVIC_EnableIRQ(GPIOPortA_IRQn);
  • 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.

    NOTE: The NVIC allows you to assign the same priority level to multiple interrupts, so you can have more ISRs than priority levels running as “kernel-unaware” or “kernel-aware” interrupts.

  • 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 qf_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-11 The interrupt priories fall all interrupts (“kernel-unaware” and “kernel-aware” alike) are set explicitly by calls to the CMSIS function NVIC_SetPriority().
  • 12 All used IRQ interrupts need to be explicitly enabled by calling the CMSIS function.

Interrupts and the FPU (Cortex-M4F/M7)

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 does not use the FPU, or
  • if the interrupted program does not use the FPU.

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.

All built-in kernels in QP are designed to take advantage of the lazy stacking feature [ARM-AN298].


Next: Cooperative QV Kernel