When working with preemptive multitasking on Cortex-M microcontrollers, it is often necessary to save and restore the context of a task when switching between tasks. The context includes the values of the CPU registers, stack pointer, and other key state information that must be preserved when transitioning between tasks.
What is Task Context?
The task context consists of all the CPU, stack, and other hardware resources that are unique to a particular task. This includes:
- General purpose CPU register values (R0-R12)
- Stack pointer (SP)
- Link register (LR)
- Program counter (PC)
- Priority mask registers
- Control and status registers (xPSR, EPSR)
- Stack memory for local variables
When a task is interrupted or paused to allow another task to run, the entire task context must be preserved. Then when the original task is resumed, the context is restored so it can pick up right where it left off.
Saving Task Context
On Cortex-M devices, the processor automatically handles most of the context saving when an interrupt or exception occurs. This includes pushing the general purpose registers, LR, PC, and xPSR onto the current stack.
To fully save a task’s context when manually switching tasks, the following steps should be taken in the scheduler or RTOS:
- Save remaining system registers not automatically saved by the processor (control, status, stack pointer, priority masks, etc.)
- Store stack pointer so the task’s stack can be restored later
- Update scheduler state to mark the task as inactive/waiting
The RTOS kernel will provide special functions or macros to make this easy. For example, saving the stack pointer can be done by reading the current value of the SP register and storing it in the task control block (TCB) structure for that task.
Restoring Task Context
Later, when the scheduler switches back to the original task, the context must be fully restored before the task resumes. This process happens in reverse:
- Mark the task as active/ready to run in scheduler state
- Load stack pointer from stored value in TCB
- Restore system registers not handled automatically by CPU
With the stack pointer restored, the general purpose registers, LR, PC, xPSR will automatically be popped off the stack by the processor when the first instruction of the task executes.
Context Switching Example
Here is some example pseudocode to demonstrate manually saving/restoring task context when switching between two hypothetical tasks Task1 and Task2: // Inside Scheduler // Save Context of Task1 Store SP of Task1 Mark Task1 as Inactive // Restore Context of Task2 Mark Task2 as Active Load SP of Task2 // Context restore triggers CPU to pop registers on first instruction // Task2 executes… // Eventually Task2 does something to make it wait // Scheduler executes again… // Save Context of Task2 Store SP of Task2 Mark Task2 as Inactive // Restore Context of Task1 Mark Task1 as Active Load SP of Task1 // Context restore triggers CPU to pop registers on first instruction // Task1 resumes…
Using PendSV for Context Switching
The Cortex-M processor includes the PendSV interrupt that can be used to trigger context switches in a RTOS. When PendSV executes, the system will automatically save the context of the current task.
The PendSV handler can then be used to store task states, select the next task to run, and restore its context by loading its stack pointer. Setting the PENDSVSET bit in the ICSR register pends the PendSV interrupt which starts the context switch process.
What Code Runs During Context Switch?
The context switch code depends on the specific RTOS being used. But in general, the following operations have to be performed:
- Save remaining context of current task (system regs, stack ptr)
- Update scheduler task states
- Select next task to run
- Restore context of next task (load stack ptr, system regs)
- Modify stack to unlink current task
- Modify stack to link next task
This often happens inside the PendSV interrupt handler when using that for context switching. The scheduler hooks into PendSV through a special registration function.
Minimizing Context Switch Time
To make a RTOS as efficient as possible, context switches must be kept as fast as possible. Here are some techniques used on Cortex-M devices:
- Use a fixed-size stack for each task to allow faster context saves
- Store stack pointers in CPU registers instead of memory
- Use a separate stack pointer for handling interrupts
- Nest interrupt priorities to avoid overhead
- Store task states compactly using bitmasks
- Optimize PendSV handler to minimize execution time
By carefully managing the context switching process, interrupt latency and the overall responsiveness of a RTOS system can be greatly improved on Cortex-M microcontrollers.
Context Switching in Popular RTOSes
Most popular RTOSes and real-time frameworks provide efficient context switching on Cortex-M devices. A few examples:
- FreeRTOS: Uses PendSV for context switch, fixed stack size, stores TCB in CPU registers.
- STM32Cube: Uses SVC handler with PendSV, maintains stack in MSP register.
- Mbed: Switches context in PendSV, keeps stack pointer in psp register.
- Zephyr: Each thread has separate kernel stack, uses fiber registers for context.
- ChibiOS: Uses VTOR to optimize PendSV handler, stores contexts in RAM.
So context switching should be well optimized by using any major RTOS. Focus on configuring task stack sizes, priorities, and writing efficient code.
Managing task contexts is a key responsibility of the scheduler and RTOS kernel. By properly saving and restoring CPU registers, stack pointers, and other hardware resources during a context switch, multiple tasks can share the MCU as if each had the entire system to itself. Using features like PendSV and keeping switch overhead to a minimum helps enable effective multitasking on Cortex-M platforms.