0
0
Cnc-programmingHow-ToIntermediate · 4 min read

How to Implement Context Switch on ARM Cortex-M Processors

To implement a context switch on ARM Cortex-M, save the current task's CPU registers (context) on its stack, update the stack pointer, then load the next task's context from its stack. Use the PendSV exception to trigger the switch safely in an interrupt context.
📐

Syntax

Context switching on ARM Cortex-M involves saving and restoring CPU registers and switching the stack pointer to the next task's stack. The key parts are:

  • Saving context: Push registers R4-R11 and the current stack pointer to the current task's stack.
  • Switching stack pointer: Update the Process Stack Pointer (PSP) to point to the next task's stack.
  • Restoring context: Pop registers R4-R11 and load the PSP for the next task.
  • Triggering switch: Use the PendSV exception to perform the switch safely.
armasm
/* PendSV Handler for context switch */
void PendSV_Handler(void) {
    __asm volatile (
        "MRS R0, PSP             \n" /* Get current process stack pointer */
        "STMDB R0!, {R4-R11}     \n" /* Save registers R4-R11 on current stack */
        "LDR R1, =current_tcb    \n" /* Load address of current TCB pointer */
        "LDR R2, [R1]            \n" /* Load current TCB */
        "STR R0, [R2]            \n" /* Save updated stack pointer to current TCB */
        "BL switch_task          \n" /* Call function to switch current_tcb to next task */
        "LDR R1, =current_tcb    \n" /* Reload current TCB pointer */
        "LDR R2, [R1]            \n" /* Load new current TCB */
        "LDR R0, [R2]            \n" /* Load new task's stack pointer */
        "LDMIA R0!, {R4-R11}     \n" /* Restore registers R4-R11 from new stack */
        "MSR PSP, R0             \n" /* Update PSP to new stack pointer */
        "BX LR                   \n" /* Return from exception */
    );
}
💻

Example

This example shows a simple PendSV handler that saves the current task's context, switches to the next task, and restores its context. It assumes a current_tcb pointer holding the current task's stack pointer and a switch_task() function that updates current_tcb to the next task.

c
#include <stdint.h>

// Simulated task control block holding stack pointer
uint32_t *current_tcb;

// Dummy function to switch current_tcb to next task
void switch_task(void) {
    // In real use, update current_tcb to next task's stack pointer
}

void PendSV_Handler(void) {
    __asm volatile (
        "MRS R0, PSP             \n" // Get current process stack pointer
        "STMDB R0!, {R4-R11}     \n" // Save registers R4-R11
        "LDR R1, =current_tcb    \n" // Load address of current_tcb
        "LDR R2, [R1]            \n" // Load current_tcb
        "STR R0, [R2]            \n" // Save updated stack pointer
        "BL switch_task          \n" // Switch to next task
        "LDR R1, =current_tcb    \n" // Reload current_tcb
        "LDR R2, [R1]            \n" // Load new current_tcb
        "LDR R0, [R2]            \n" // Load new stack pointer
        "LDMIA R0!, {R4-R11}     \n" // Restore registers
        "MSR PSP, R0             \n" // Update PSP
        "BX LR                   \n" // Return
    );
}
⚠️

Common Pitfalls

Common mistakes when implementing context switch on ARM Cortex-M include:

  • Not saving/restoring all necessary registers (R4-R11 must be saved manually).
  • Using the Main Stack Pointer (MSP) instead of Process Stack Pointer (PSP) for task stacks.
  • Triggering context switch outside PendSV, which can cause timing issues.
  • Not disabling interrupts properly during critical sections.

Always use PendSV for context switching and ensure stack pointers are correctly managed.

armasm
/* Wrong: Not saving R4-R11 registers */
void PendSV_Handler_Wrong(void) {
    __asm volatile (
        "MRS R0, PSP             \n"
        // Missing STMDB R0!, {R4-R11}
        "LDR R1, =current_tcb    \n"
        "LDR R2, [R1]            \n"
        "STR R0, [R2]            \n"
        "BL switch_task          \n"
        "LDR R1, =current_tcb    \n"
        "LDR R2, [R1]            \n"
        "LDR R0, [R2]            \n"
        // Missing LDMIA R0!, {R4-R11}
        "MSR PSP, R0             \n"
        "BX LR                   \n"
    );
}

/* Correct: Save and restore R4-R11 as shown in previous example */
📊

Quick Reference

Context Switch Steps on ARM Cortex-M:

  • Save R4-R11 registers on current task stack.
  • Store updated PSP in current task's control block.
  • Call scheduler to select next task.
  • Load PSP from next task's control block.
  • Restore R4-R11 registers from next task stack.
  • Return from PendSV handler to resume next task.

Use PendSV exception for context switching to avoid interrupt conflicts.

Key Takeaways

Use the PendSV exception to perform context switches safely on ARM Cortex-M.
Save and restore registers R4-R11 manually as they are not automatically saved.
Switch the Process Stack Pointer (PSP) to change task stacks during context switch.
Avoid using the Main Stack Pointer (MSP) for task stacks to prevent conflicts.
Ensure interrupts are managed properly to avoid corruption during switching.