QP/C  6.3.7
Preemptive "Dual-Mode" QXK Kernel

This section describes how to use QP on ARM Cortex-M with the preemptive, dual-mode QXK real-time kernel, which combines the lightweight non-blocking basic threads of QK with traditional blocking extended threads found in conventional RTOS kernels. QXK provides all typical services of a conventional blocking RTOS, such as blocking time-delays, semaphores, mutextes, and message queues.

QXK has been designed specifically for mixing event-driven active objects with traditional blocking code, such as commercial middleware (TCP/IP stacks, UDP stacks, embedded file systems, etc.) or legacy software.

If you are currently using QP on top of a conventional 3rd-party RTOS, consider moving your application to the QXK kernel. QXK is not only more efficient than running QP on top of a traditional 3rd-party RTOS (because non-blocking basic threads take far less stack space and CPU cycles for context switch than the much heavier extended threads). But the biggest advantage of QXK is that it protects the application-level code from inadvertent mixing of blocking calls inside the event-driven active objects. Specifically, QXK "knows" the type of the thread context (extended/basic) and asserts internally if a blocking call (e.g., semaphore-wait or a time-delay) is attempted in a basic thread (active object). This is something that a QP port to a conventional 3rd-party RTOS cannot do, because such an RTOS runs all code (including active objects) in the context of havyweight extended threads.

Synopsis of the QXK Port on ARM Cortex-M

The preemptive, blocking QXK kernel works on ARM Cortex-M as follows:

  1. The ARM Cortex-M processor executes application code in the Privileged Thread mode, which is exactly the mode entered out of reset. The exceptions (including all interrupts) are always processed in the Privileged Handler mode.
  2. QXK uses the Main Stack Pointer (MSP) for basic threads, interrupts and exceptions (such as the PendSV exception). The MSP is also used for the QXK idle thread (which is a non-blocking basic thread).
  3. QXK uses the Process Stack Pointer (PSP) for handling extended threads. Each extended thread must provide a private stack space to be associated with the PSP.
  4. The QXK port uses the PendSV (exception number 14) and the NMI exception (number 2) to perform context switch. The application code (your code) must initialize the Interrupt Vector Table with the addresses of the PendSV_Handler and NMI_Handler exception handlers.

    NOTE: QXK uses only the CMSIS-compliant exception and interrupt names, such as PendSV_Handler, NMI_Handler, etc.
    NOTE: The QXK port specifically does not use the SVC exception (Supervisor Call). This makes the QXK ports compatible with various "hypervisors" (such as mbed uVisor or Nordic SoftDevice), which use the SVC exception.

  5. You need to explicitly assign priorities of the all interrupts used in your application, as described in Interrupts in the QP Ports to ARM Cortex-M.

    NOTE: For Cortex-M3/M4/M7 (ARMv7M architecture), the QXK initialization code (executed from the QF initialization) initializes all interrupt priorities to the safe value maskable with the BASEPRI register. However, this is just a safety precaution not to leave the interrupts kernel-unaware, which they are out of reset. It is highly recommended to set the priorities of all interrupts explicitly in the application-level code.

  6. It is strongly recommended that you do not assign the lowest NVIC priority (0xFF) to any interrupt in your application, because it is used by the PendSV handler. For example, with 3 bits of priority implemented in the NVIC, this leaves the following 7 priority levels for you (listed from the lowest to the highest urgency): 0xC0, 0xA0, 0x80, 0x60, 0x40, 0x20, and 0x00 (the highest priority).

    NOTE: The prioritization of interrupts, including the PendSV exception, is performed entirely by the NVIC. Because the PendSV has the lowest priority in the system, the NVIC tail-chains to the PendSV exception only after exiting the last nested interrupt.

  7. ISRs are written as regular C functions, but they need to call QXK_ISR_ENTRY() before using any QF services, and they must call QXK_ISR_EXIT() after using any of the QF services.
  8. ARM Cortex-M enters interrupt context without disabling interrupts. Generally, you should not disable interrupts inside your ISRs. In particular, the QF services (such as 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 or equal level as other interrupts (use a lower numerical value of priority).

  9. In compliance with the ARM Application Procedure Call Standard (AAPCS), the QXK kernel always preserves the 8-byte alignment of the stack (both MSP and PSP).

Using the VFP

If you have the Cortex-M4F/M7 CPU and your application is compiled with the VFP present, the QXK kernel will enable the VFP along with the VFP automatic state preservation and lazy stacking features. This will cause the NVIC to automatically use the VFP-exception stack frame (with additional 18 VFP registers S0-S15 plus VFP status and stack "aligner"). The QXK context switch will add to this the rest of the VFP registers (S16-S31) on context switches to and from extended threads.

With VFP enabled, any QXK thread (both a basic and an extended thread) will use 136 more bytes of its stack space, regardless if VFP is actually used by this thread. However, due to the "lazy-stacking" hardware feature, only a thread that actually uses the VFP will save and restore the VFP registers on the stack (which will cost some additional CPU cycles to perform a context switch).