The Cortex-M0 processor implements a basic exception model with 7 exception types. The PendSV (pendable service request) exception is one of these and allows low priority or background tasks to be run when no other exception is active. Handling the PendSV exception properly is key to implementing an RTOS or multitasking firmware on Cortex-M0 devices.
What is the PendSV Exception?
The PendSV exception is triggered by software to request a context switch to a low priority thread or task. This allows background processing or lower priority tasks to execute when the processor is idle or not handling other exceptions. The PendSV has the lowest priority of all exceptions, below even the SysTick timer interrupt. This ensures background tasks only run when all other system events are serviced.
PendSV Exception Priority Level
The Cortex-M0 processor implements a system of prioritized exceptions called the Nested Vectored Interrupt Controller (NVIC). There are 256 programmable priority levels, with 0 being the highest priority. Reset has fixed priority of -3, NMI has -2, HardFault has -1, and PendSV has priority 254 which is the lowest possible priority level.
Enabling the PendSV Exception
The PendSV exception must be enabled in order for it to trigger a context switch:
- Set the PENDSVSET bit in the Interrupt Control and State Register (ICSR) to pending.
- Configure the PendSV exception priority in the NVIC to level 254.
- Enable PendSV interrupts in the NVIC Interrupt Set Enable Register (ISER).
Once enabled, any write to the ICSR PENDSVSET bit will trigger a PendSV exception once all higher priority exceptions complete.
PendSV Exception Handler
The PendSV_Handler code is called when the processor vectors to the PendSV exception: void PendSV_Handler(void) { // Context save // Switch context // Context restore }
This handler must save the context of the current task, select the next task to run, restore its context, then return to Thread Mode to resume it. This completes the context switch to the new task.
Saving Context
The context of the current task must be saved before it can be switched out. This usually involves pushing CPU registers onto the current stack: // Save context __asm volatile( “MRS R0, PSP\n\t” “STMDB R0!, {R4-R11}\n\t” “PUSH {R0, LR}\n\t” );
The stack pointer, R0 through R11 registers, and the link register are saved. The PSP stack pointer should be used for context saving.
Switching Contexts
Once the current task context is saved, the next task can be selected and its context restored: // Switch context currentTask = nextTask; nextTask = pickNextTask();
This involves updating the current and next task pointers to the appropriate TCBs (Task Control Blocks). The pickNextTask() function selects the next highest priority task that is ready to run.
Restoring Context
The context of the next task must be restored before returning to Thread Mode: // Restore context __asm volatile( “LDR R0, =currentTask\n\t” “LDR R0, [R0]\n\t” “LDMIA R0!, {R4-R11}\n\t” “POP {R0, PC}\n\t” );
This loads the stack pointer and registers from the task’s TCB, before returning to the task using POP and PC. The PSP stack pointer should be restored before registers.
Returning to Thread Mode
Once the next task’s context is restored, the exception return takes place: __asm volatile( “MOV LR, #0xFFFFFFFD\n\t” “BX LR\n\t” );
The exception return value is set before performing a BX to LR. This restores the task’s EXC_RETURN value and switches back to Thread Mode.
Setting PendSV in Thread Mode
To request a context switch from Thread Mode, the PENDSVSET bit must be set in the ICSR register: void yield(void) { SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; }
This will trigger a PendSV exception once interrupts are enabled and all higher priority exceptions complete. The yield() function can be called by tasks to voluntarily give up the CPU.
Using SysTick and PendSV
The SysTick timer is commonly used with PendSV to create periodic context switches for an RTOS. When the SysTick expires, it sets the PENDSVSET bit to trigger a context switch: void SysTick_Handler(void) { SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; }
This allows the RTOS to periodically pause the current task and switch to the next highest priority task that is ready. The SysTick provides the heartbeat while PendSV performs the context switches.
Testing PendSV Switching
To test PendSV exception handling and context switching:
- Enable SysTick interrupts at a known frequency.
- Setup the PendSV handler and enable PendSV interrupts.
- Implement context saving/restoring in the PendSV handler.
- Toggle LEDs on each context switch to visually track switching.
- Call yield() to voluntarily invoke PendSV.
The LEDs blinking at the SysTick frequency confirm PendSV exception handling and context switches are working as expected.
Conclusion
The Cortex-M0 PendSV exception provides essential context switching capabilities for multitasking firmware. Combined with the SysTick timer, it enables simple cooperative RTOS implementations. Proper exception handling, context saving/restoring, and return procedures are key to leveraging PendSV in Cortex-M0 designs.