QP/C++  8.0.0
Real-Time Embedded Framework
Loading...
Searching...
No Matches
Non-preemptive QV Kernel

The non-preemptive QV kernel executes active objects one at a time, with priority-based scheduling performed after run-to-completion (RTC) processing of each event. Due to naturally short duration of event processing in state machines, the simple QV kernel is often adequate for many real-time systems. (NOTE: Long RTC steps can be often broken into shorter pieces by means of the "Reminder" state pattern [Reminder])

Remarks
In the QV port, the only components requiring platform-specific porting are QF and QV itself. The other two components: QEP and QS require merely recompilation and will not be discussed here. With the QV port you're not using the QK or QXK kernels. The QV port to ARM Cortex-M is located in the folder /ports/arm-cm/qv/.

Synopsis of the QV Port on ARM Cortex-M
The non-preemptive QV kernel works essentially as the traditional foreground-background system (a.k.a. "superloop") in that all active objects are executed in the main loop and interrupts always return back to the point of preemption. To avoid race conditions between the main loop and the interrupts, QV briefly disables interrupts.

  1. The ARM Cortex-M processor executes application code (the main loop) in the Privileged Thread mode, which is exactly the mode entered out of reset.
  2. The exceptions (including all interrupts) are always processed in the Privileged Handler mode.
  3. QV uses only the Main Stack Pointer. The Process Stack Pointer is not used and is not initialized.
  4. ARM Cortex-M enters interrupt context without disabling interrupts (without setting the PRIMASK bit or the BASEPRI register). Generally, you should not disable interrupts inside your ISRs. In particular, the QP services QF_PUBLISH(), QF_TICK_X(), and QACTIVE_POST() should be called with interrupts enabled, to avoid nesting of critical sections.
Note
If you don't wish an interrupt to be preempted by another interrupt, you can always prioritize that interrupt in the NVIC to a higher level (use a lower numerical value of priority).
  1. The QF_init() function calls the function QV_init() to set the interrupt priority of all IRQs available in the MCU to the safe value of QF_BASEPRI (for ARM-v7 architecture).

The qp_port.h Header File
The QF header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qp_port.h. This file specifies the interrupt disabling policy (QF critical section) as well as the configuration constants for QF (see Chapter 8 in [PSiCC2]).

Note
The ARM Cortex-M allows you to use the simplest "unconditional interrupt disabling" policy (see Section 7.3.2 in [PSiCC2]), because ARM Cortex-M is equipped with the standard nested vectored interrupt controller (NVIC) and generally runs ISRs with interrupts enabled (so the body of an ISR is not a critical section).

The following listing shows the qp_port.h header file for ARM Cortex-M with the GNU-ARM toolchain. Other toolchains use slightly different conditional compilation macros to select the Cortex-M variants, but implement the same policies.

Listing: The qp_port.h header file for ARM Cortex-M
#include <stdint.h> /* Exact-width types. WG14/N843 C99 Standard */
#include <stdbool.h> /* Boolean type. WG14/N843 C99 Standard */
/* The maximum number of active objects in the application, see NOTE1 */
[1] #define QF_MAX_ACTIVE 32
/* The maximum number of system clock tick rates */
[2] #define QF_MAX_TICK_RATE 2
/* QF interrupt disable/enable and log2()... */
[3] #if (__ARM_ARCH == 6) /* Cortex-M0/M0+/M1(v6-M, v6S-M)? */
/* Cortex-M0/M0+/M1(v6-M, v6S-M) interrupt disabling policy, see NOTE2 */
[4] #define QF_INT_DISABLE() __asm volatile ("cpsid i")
[5] #define QF_INT_ENABLE() __asm volatile ("cpsie i")
/* QF critical section (unconditional interrupt disabling) */
[6] #define QF_CRIT_STAT
[7] #define QF_CRIT_ENTRY() QF_INT_DISABLE()
[8] #define QF_CRIT_EXIT() QF_INT_ENABLE()
/* CMSIS threshold for "QF-aware" interrupts, see NOTE2 and NOTE5 */
[9] #define QF_AWARE_ISR_CMSIS_PRI 0
/* hand-optimized LOG2 in assembly for Cortex-M0/M0+/M1(v6-M, v6S-M) */
[10] #define QF_LOG2(n_) QF_qlog2((n_))
[11] #else /* Cortex-M3/M4/M7 */
/* Cortex-M3/M4/M7 alternative interrupt disabling with PRIMASK */
[12] #define QF_PRIMASK_DISABLE() __asm volatile ("cpsid i")
[13] #define QF_PRIMASK_ENABLE() __asm volatile ("cpsie i")
/* Cortex-M3/M4/M7 interrupt disabling policy, see NOTE3 and NOTE4 */
[14] #define QF_INT_DISABLE() __asm volatile (\
"cpsid i\n" "msr BASEPRI,%0\n" "cpsie i" :: "r" (QF_BASEPRI) : )
[15] #define QF_INT_ENABLE() __asm volatile (\
"msr BASEPRI,%0" :: "r" (0) : )
/* QF critical section (unconditional interrupt disabling) */
[16] #define QF_CRIT_STAT
[17] #define QF_CRIT_ENTRY() QF_INT_DISABLE()
[18] #define QF_CRIT_EXIT() QF_INT_ENABLE()
/* BASEPRI threshold for "QF-aware" interrupts, see NOTE3 */
[19] #define QF_BASEPRI 0x3F
/* CMSIS threshold for "QF-aware" interrupts, see NOTE5 */
[20] #define QF_AWARE_ISR_CMSIS_PRI (QF_BASEPRI >> (8 - __NVIC_PRIO_BITS))
/* Cortex-M3/M4/M7 provide the CLZ instruction for fast LOG2 */
[21] #define QF_LOG2(n_) ((uint_fast8_t)(32U - __builtin_clz(n_)))
#endif
[22] #define QF_CRIT_EXIT_NOP() __asm volatile ("isb")
#if (__ARM_ARCH == 6) /* Cortex-M0/M0+/M1(v6-M, v6S-M)? */
/* hand-optimized quick LOG2 in assembly */
[23] uint_fast8_t QF_qlog2(uint32_t x);
#endif /* Cortex-M0/M0+/M1(v6-M, v6S-M) */
// include files -------------------------------------------------------------
#include "qequeue.h" // QV kernel uses the native QP event queue
#include "qmpool.h" // QV kernel uses the native QP memory pool
#include "qp.h" // %QP Framework
#include "qv.h" // QV kernel
#define QF_CRIT_EXIT_NOP()
Definition qp.hpp:1312
#define QF_MAX_TICK_RATE
Maximum # clock tick rates in the system (0..15)
#define QF_MAX_ACTIVE
Maximum # Active Objects in the system (1..64)
#define QF_INT_DISABLE()
Disable interrupts.
Definition qp_port.hpp:36
#define QF_CRIT_ENTRY()
Definition qsafe.h:58
#define QF_CRIT_STAT
Definition qsafe.h:54

[1] The QF_MAX_ACTIVE specifies the maximum number of active object priorities in the application. You always need to provide this constant. Here, QF_MAX_ACTIVE is set to 32, but it can be increased up to the maximum limit of 63 active object priorities in the system.

Note
The qp_port.h header file does not change the default settings for all the rest of various object sizes inside QF. Please refer to Chapter 8 of [PSiCC2] for discussion of all configurable QF parameters.

[2] The macro QF_MAX_TICK_RATE specifies the maximum number of clock tick rates for QP/C++ time events. If you do not need to specify this limit, in which case the default of a single clock rate will be chosen.

[3] As described in the previous Section, the interrupt disabling policy for the ARMv6-M architecture (Cortex-M0/M0+) is different than the policy for the ARMv7-M. In GNU-ARM, the macro __ARM_ARCH is defined as 6 for the ARMv6-M architecture (Cortex-M0/M0+), and 7 for ARMv7-M (Cortex-M3/M4/M4F).

Note
The __ARM_ARCH macro is specific to the GNU-ARM compiler. Other compilers for ARM Cortex-M provide different macros to detect the CPU type.

[4-5] For the ARMv6-M architecture, the interrupt disabling policy uses the PRIMASK register to disable interrupts globally. The QF_INT_DISABLE() macro resolves in this case to the inline assembly instruction "CPSD i", which sets the PRIMASK. The QF_INT_ENABLE() macro resolves to the inline assembly instruction "CPSE i", which clears the PRIMASK.

[6] The QF_CRIT_STAT is empty, meaning that the critical section uses the simple policy of "unconditional interrupt disabling".

Note
The "unconditional interrupt disabling" policy precludes nesting of critical sections, but this is not needed for ARM Cortex-M, because this CPU never disables interrupts, even when handling exceptions/interrupts.

[7] The QF_CRIT_ENRY() enters a critical section. Interrupts are disabled by setting the PRIMASK register.

[8] The QF_CRIT_EXIT() macro leaves the critical section. Interrupts are unconditionally re-enabled by clearing the PRIMASK register.

[9] For the ARMv6-M architecture, the QF_AWARE_ISR_CMSIS_PRI priority level is defined as zero, meaning that all interrupts are "kernel-aware", because all interrupt priorities are disabled by the kernel.

[10] The QF_LOG2() macro is defined as a call to the function QF_qlog2() ("quick log-base-2 logarithm"). This function is coded in hand-optimized assembly, which always takes only 14 CPU cycles to execute (see also label [23]).

Note
ARM Cortex-M0/M0+ does NOT implement the CLZ instruction. Therefore the log-base-2 calculation cannot be accelerated in hardware, as it is for ARM Cortex-M3 and higher.

[11] For the ARMv7-M (Cortex-M3/M4/M4F) architecture...

[12] The QF_PRIMASK_DISABLE() macro resolves to the inline assembly instruction CPSD i, which sets the PRIMASK.

[13] The QF_PRIMASK_ENABLE() macro resolves to the inline assembly instruction CPSE i, which clears the PRIMASK.

[14] Interrupts are disabled by setting the BASEPRI register to the value defined in the QF_BASEPRI macro (see label [19]). This setting of the BASEPRI instruction msr BASEPRI,... is surrounded by setting and clearing the PRIMASK register, as a workaround a hardware problem in ARMv7M or higher architectures core r0p1:

Note
The selective disabling of "QF-aware" interrupts with the BASEPRI register has a problem on ARMv7M or higher architectures core r0p1 (see [ARM-EPM-064408], Erratum 837070). The workaround recommended by ARM is to surround MSR BASEPRI,... with the CPSID i/CPSIE i pair, which is implemented in the QF_INT_DISABLE() macro. This workaround works also for Cortex-M3/M4 cores.

[15] The QF_INT_ENABLE() macro sets the BASEPRI register to zero, which disables BASEPRI interrupt masking.

Note
This method can never disable interrupt of priority 0 (highest).

[16] The QF_CRIT_STAT is empty, meaning that the critical section uses the simple policy of "unconditional interrupt disabling".

Note
The "unconditional interrupt disabling" policy precludes nesting of critical sections, but this is not needed for ARM Cortex-M, because this CPU never disables interrupts, even when handling exceptions/interrupts.

[17] The QF_CRIT_ENTRY() enters a critical section. Interrupts are disabled with the macro QF_INT_DISABLE() defined at label [12].

[18] The QF_CRIT_EXIT() macro leaves the critical section. Interrupts are unconditionally re-enabled with the macro QF_INT_ENABLE() defined at label [13].

[19] The QF_BASEPRI value is defined such that it is the lowest priority for the minimum number of 3 priority-bits that the ARMv7M or higher architectures architecture must provide. This partitions the interrupts as "kernel-unaware" and "kernel-aware" interrupts, as shown in section arm-cm_int-assign.

[20] For the ARMv7-M architecture, the QF_AWARE_ISR_CMSIS_PRI priority level suitable for the CMSIS function NVIC_SetPriority() is determined by the QF_BASEPRI value.

[21] The macro QF_LOG2() is defined to take advantage of the CLZ instruction (Count Leading Zeroes), which is available in the ARMv7-M architecture.

Note
The __builtin_cls() intrinsic function is specific to the GNU-ARM compiler. Other compilers for ARM Cortex-M use different function names for this intrinsic function.

[22] The macro QF_CRIT_EXIT_NOP() provides the protection against merging two critical sections occurring back-to-back in the QP/C++ code.

[23] For ARMv6 architecture, the prototype of the quick, hand-optimized log-base-2 function is provided (see also label [10]).

The qv_port.h Header File
The QV header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qv_port.h. This file provides the macro QV_CPU_SLEEP(), which specifies how to enter the CPU sleep mode safely in the non-preemptive QV kernel (see also Section 4.7) and [Samek 07]).

Note
To avoid race conditions between interrupts waking up active objects and going to sleep, the non-preemptive QV kernel calls the QV_CPU_SLEEP() callback with interrupts disabled.

[1] For the ARMv6-M architecture, the macro QV_CPU_SLEEP() stops the CPU with the WFI instruction (Wait For Interrupt). After the CPU is woken up by an interrupt, interrupts are re-enabled with the PRIMASK.

[2] For the ARMv7-M architecture, the macro QV_CPU_SLEEP() first disables interrupts by setting the PRIMASK, then clears the BASEPRI to enable all "kernel-aware" interrupts and only then stops the CPU with the WFI instruction (Wait For Interrupt). After the CPU is woken up by an interrupt, interrupts are re-enabled with the PRIMASK. This sequence is necessary, because the ARMv7M or higher architectures cannot be woken up by any interrupt blocked by the BASEPRI register.

[3] The macro QV_INIT() is defined as a call to the QV_init() function, which means that this function will be called from QF_init(). The QV_init() function initializes all available IRQ priorities in the MCU to the safe value of QF_BASEPRI.

The qv_port.c Implementation File
The QV implementation file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qf_port.c. This file defines the function QV_init(), which for the ARMv7-M architecture sets the interrupt priorities of all IRQs to the safe value QF_BASEPRI.

Listing: The qv_port.c header file for ARM Cortex-M
#include "qp_port.h"
[1] #if (__ARM_ARCH != 6) /* NOT Cortex-M0/M0+/M1 ? */
#define SCnSCB_ICTR ((uint32_t volatile *)0xE000E004)
#define SCB_SYSPRI ((uint32_t volatile *)0xE000ED14)
#define NVIC_IP ((uint32_t volatile *)0xE000E400)
void QV_init(void) {
uint32_t n;
/* set exception priorities to QF_BASEPRI...
* SCB_SYSPRI1: Usage-fault, Bus-fault, Memory-fault
*/
[2] SCB_SYSPRI[1] |= (QF_BASEPRI << 16) | (QF_BASEPRI << 8) | QF_BASEPRI;
/* SCB_SYSPRI2: SVCall */
[3] SCB_SYSPRI[2] |= (QF_BASEPRI << 24);
/* SCB_SYSPRI3: SysTick, PendSV, Debug */
[4] SCB_SYSPRI[3] |= (QF_BASEPRI << 24) | (QF_BASEPRI << 16) | QF_BASEPRI;
/* set all implemented IRQ priories to QF_BASEPRI... */
[5] n = 8 + (*SCnSCB_ICTR << 3); /* # interrupt priority registers */
do {
--n;
[6] NVIC_IP[n] = (QF_BASEPRI << 24) | (QF_BASEPRI << 16)
| (QF_BASEPRI << 8) | QF_BASEPRI;
} while (n != 0);
}
#endif /* NOT Cortex-M0/M0+/M1 */

[1] For the ARMv7-M architecture (Cortex-M3/M4/M7)...

[2] The exception priorities for User-Fault, Bus-Fault, and Mem-Fault are set to the value QF_BASEPRI.

[3] The exception priority for SVCCall is set to the value QF_BASEPRI.

[4] The exception priority for SysTick, PendSV, and Debug is set to the value QF_BASEPRI.

[5] The number of implemented IRQs is read from the SCnSCB_ICTR register

[6] The interrupt priority of all implemented IRQs is set to the safe value QF_BASEPRI in a loop.

Writing ISRs for QV
The ARM Cortex-M CPU is designed to use regular C functions as exception and interrupt service routines (ISRs).

Note
The ARM EABI (Embedded Application Binary Interface) requires the stack be 8-byte aligned, whereas some compilers guarantee only 4-byte alignment. For that reason, some compilers (e.g., GNU-ARM) provide a way to designate ISR functions as interrupts. For example, the GNU-ARM compiler provides the attribute((interrupt)) designation that will guarantee the 8-byte stack alignment.

Typically, ISRs are application-specific (with the main purpose to produce events for active objects). Therefore, ISRs are not part of the generic QP/C++ port, but rather part of the BSP (Board Support Package).

The following listing shows an example of the SysTick_Handler() ISR (from the DPP example application). This ISR calls the QF_TICK_X() macro to perform QF time-event management.

Listing: An ISR header for QV
void SysTick_Handler(void) __attribute__((__interrupt__));
void SysTick_Handler(void) {
~ ~ ~
// process time events at rate 0
QTIMEEVT_TICK_X(0U, &l_SysTick_Handler);
}
Note
The QP/C++ port to ARM Cortex-M complies with the CMSIS standard, which dictates the names of all exception handlers and IRQ handlers.

Using the FPU in the QV Port
If you use ARMv7M or higher CPU and your application uses the hardware FPU, it should be enabled because it is turned off out of reset. The CMSIS-compliant way of turning the FPU on looks as follows:

    SCB->CPACR |= (0xFU << 20);
Note
The FPU must be enabled before executing any floating point instruction. An attempt to execute a floating point instruction will fault if the FPU is not enabled.
If the FPU is configured in the project, the QV kernel initializes the FPU to use the automatic state preservation and the lazy stacking feature as follows:
    FPU->FPCCR |= (1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos);

FPU NOT used in the ISRs
If you use the FPU only at the thread-level (inside active objects) and none of your ISRs use the FPU, you can setup the FPU not to use the automatic state preservation and not to use the lazy stacking feature as follows:

    FPU->FPCCR &= ~((1U << FPU_FPCCR_ASPEN_Pos) | (1U << FPU_FPCCR_LSPEN_Pos));

With this setting, the processor uses only the standard 8-register interrupt stack frame with R0-R3,R12,LR,PC,xPSR. This scheme is the fastest and incurs no additional CPU cycles to save and restore the FPU registers.

Attention
This FPU setting will lead to FPU errors, if any of the ISRs indeed starts to use the FPU

QV Idle Processing Customization in QV_onIdle()
When no events are available, the non-preemptive QV kernel invokes the platform-specific callback function QV_onIdle(), which you can use to save CPU power, or perform any other "idle" processing (such as Quantum Spy software trace output).

Note
The idle callback QV_onIdle() must be invoked with interrupts disabled, because the idle condition can be changed by any interrupt that posts events to event queues. QV_onIdle() must internally enable interrupts, ideally atomically with putting the CPU to the power-saving mode (see also [Samek 07] and Chapter 7 in [PSiCC2]).

Because QV_onIdle() must enable interrupts internally, the signature of the function depends on the interrupt locking policy. In case of the simple "unconditional interrupt locking and unlocking" policy, which is used in this ARM Cortex-M port, the QV_onIdle() takes no parameters. Listing 6 shows an example implementation of QV_onIdle() for the TM4C MCU. Other ARM Cortex-M embedded microcontrollers (e.g., NXP's LPC1114/1343) handle the power-saving mode very similarly.

Listing: QV_onIdle() for ARM Cortex-M
[1] void QV_onIdle(void) { /* entered with interrupts DISABLED, see NOTE01 */
~ ~ ~
[2] #if defined NDEBUG
/* Put the CPU and peripherals to the low-power mode */
[3] QV_CPU_SLEEP(); /* atomically go to sleep and enable interrupts */
#else
[4] QF_INT_ENABLE(); /* just enable interrupts */
#endif
}
#define QV_CPU_SLEEP()
! def QF_MEM_ISOLATE
Definition qp_port.hpp:100
#define QF_INT_ENABLE()
Enable interrupts.
Definition qp_port.hpp:39

[1] The non-preemptive QV kernel calls the QV_onIdle() callback with interrupts disabled, to avoid race condition with interrupts that can post events to active objects and thus invalidate the idle condition.

[2] The sleep mode is used only in the non-debug configuration, because sleep mode stops CPU clock, which can interfere with debugging.

[3] The macro QV_CPU_SLEEP() is used to put the CPU to the low-power sleep mode safely. The macro QV_CPU_SLEEP() is defined in the qv_port.h header file for the QV kernel and depends on the interrupt disabling policy used.

[4] When a sleep mode is not used, the QV_onIdle() callback simply re-enables interrupts.

Preemptive Non-Blocking QK Kernel