The non-preemptive, cooperative 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])
The cooperative 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.
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).
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 QEP header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qep_port.h
. The following shows the qep_port.h
header file for ARM Cortex-M/GNU. The GNU-ARM compiler is a standard C99 compiler, so it simply includes the <stdint.h>
header file that defines the platform-specific exact-with integer types.
Listing: The qep_port.h header file for ARM Cortex-M
stdint.h
and stdbool.h
header files yourself, or you can define the standard integer types and Boolean types directly in the qep_port.h
header file.The QF header file for the ARM Cortex-M port is located in /ports/arm-cm/qv/gnu/qf_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]).
The following listing shows the qf_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 qf_port.h header file for ARM Cortex-M
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
qf_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.
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.
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_TYPE is NOT defined, meaning that the critical section uses the simple policy of "unconditional interrupt disablin".
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.
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.
QF_PRIMASK_DISABLE()
macro resolves to the inline assembly instruction CPSD i
, which sets the PRIMASK. 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 ARM Cortex-M7 core r0p1:
NOTE: The selective disabling of "QF-aware" interrupts with the BASEPRI register has a problem on ARM Cortex-M7 core r0p1 (see [ARM-AT610-611], Erratum 837070). The workaround recommended by ARM is to surround
MSR BASEPRI,...
with theCPSID 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_TYPE is NOT defined, 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.
QF_INT_DISABLE()
defined at label [12]. QF_INT_ENABLE()
defined at label [13]. QF_BASEPRI
value is defined such that it is the lowest priority for the minimum number of 3 priority-bits that the ARM7-M architecture must provide. This partitions the interrupts as "kernel-unaware" and "kernel-aware" interrupts, as shown in section Assigning Interrupt Priorities. 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.
QF_CRIT_EXIT_NOP()
provides the protection against merging two critical sections occurring back-to-back in the QP code. 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 cooperative QV kernel (see also Section 4.7) and [Samek 07]).
Listing: The qv_port.h header file for ARM Cortex-M
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. 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 ARM Cortex-M3/M4/M7 cores cannot be woken up by any interrupt blocked by the BASEPRI register. 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 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
SCnSCB_ICTR
register The ARM Cortex-M CPU is designed to use regular C functions as exception and interrupt service routines (ISRs).
Typically, ISRs are application-specific (with the main purpose to produce events for active objects). Therefore, ISRs are not part of the generic QP 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.
If you have the Cortex-M4/M7 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);
Depending on whether or not you use the FPU in your ISRs, the QV port allows you to configure the FPU in various ways, as described in the following sub-sections.
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 Cortex-M4F processor handles the ISRs in the exact-same way as Cortex-M0-M3, that is, only the standard interrupt frame with R0-R3,R12,LR,PC,xPSR is used. This scheme is the fastest and incurs no additional CPU cycles to save and restore the FPU registers.
If you use the FPU both at the thread-level (inside active objects) and in any of your ISRs as well, you should setup 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);
This will enable the lazy stacking feature of the Cortex-M4F/M7 processor [ARM-AN298]. The the "automatic state saving" and "lazy stacking" are enabled by default, so you typically don't need to change these settings.
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).
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