On this page
- Starting Point
- Why SysTick Is Not Enough
- What PendSV Does
- What SVC Does
- Handler Names the Port Expects
- Wiring With Wrapper Handlers
- Wiring With Preprocessor Mapping
- Vector Table Ownership
- Exception Priorities
- What Happens When the Scheduler Starts
- Common Handler Wiring Mistakes
- Debugging Default Handler Traps
- Checklist Before Creating the First Tasks
- Next Steps
The previous article connected the FreeRTOS scheduler tick to the Cortex-M SysTick timer. That gives the kernel a periodic time base, but it is not the whole scheduler port.
FreeRTOS on Cortex-M also depends on exception handlers used to start the first task and switch between task contexts.
The key names are PendSV and SVC.
This article wires those exception paths conceptually and practically. The goal is to understand what each exception contributes, how the startup vector table reaches the FreeRTOS port handlers, and why a project can compile successfully but still fall into a default handler if these names are wrong.
This article still does not create the first task. The next article will do that. Here, the focus is the exception plumbing that must exist before vTaskStartScheduler() can hand control to tasks safely.
Starting Point
At this point, the project should have:
- FreeRTOS kernel source files in the CMake build.
- The correct Cortex-M portable layer selected.
- A project-owned
FreeRTOSConfig.h. - A tick configuration that uses
SysTickfor kernel time. - Startup code with a vector table for Cortex-M exceptions.
The missing piece is the context-switching exception path.
The Cortex-M FreeRTOS port usually needs the vector table to route these handlers correctly:
SVC_Handler -> vPortSVCHandler
PendSV_Handler -> xPortPendSVHandler
SysTick_Handler -> xPortSysTickHandler
The exact symbol names can vary by FreeRTOS version and selected port, so always check the port.c and portmacro.h files you are actually building. The pattern is stable: the startup exception names must reach the port's handler functions.
Why SysTick Is Not Enough
SysTick gives the kernel a regular interrupt.
On each tick, FreeRTOS can update its internal tick count, unblock tasks whose delays have expired, and decide whether a context switch should happen. But deciding that a different task should run is not the same as performing the switch.
A context switch has to preserve enough CPU state for one task to resume later, then restore the state for another task. On Cortex-M, FreeRTOS uses the CPU exception mechanism to make that switch controlled and predictable.
That is where PendSV matters.
The tick can request a switch. PendSV performs the deferred context-switch work.
What PendSV Does
PendSV means pending supervisor call.
It is a Cortex-M exception designed for deferred service work. FreeRTOS uses it for context switching because it can be pended by software and run at a low priority after higher-priority interrupt work has finished.
That is the right shape for a scheduler context switch.
An interrupt should do its urgent work and get out. If that interrupt unblocks a higher-priority task, the system can request a context switch. The actual switch can then happen through PendSV after the interrupt exits or at a controlled exception boundary.
The FreeRTOS PendSV handler is architecture-specific. It saves the context of the current task, updates the current task pointer, restores the context of the next task, and returns so the CPU resumes execution in the selected task.
That work belongs in the portable layer, not in application code.
What SVC Does
SVC means supervisor call.
Some Cortex-M FreeRTOS ports use SVC during scheduler startup. The handler is part of the path that starts the first task after vTaskStartScheduler() has prepared the kernel state.
Starting the first task is different from switching between two tasks that have already run. There is no previous task context to save. The port needs to restore the first task's prepared stack frame and enter task mode in the way the Cortex-M exception model expects.
That is why the SVC handler is wired to the port.
You do not need to write the SVC implementation yourself for a normal FreeRTOS Cortex-M port. You need to make sure the vector table reaches the handler that the port provides.
Handler Names the Port Expects
The startup file usually defines weak exception handlers with names such as:
void SVC_Handler(void);
void PendSV_Handler(void);
void SysTick_Handler(void);
The FreeRTOS port provides or expects functions with names such as:
void vPortSVCHandler(void);
void xPortPendSVHandler(void);
void xPortSysTickHandler(void);
Those two naming worlds must be connected.
The Cortex-M vector table uses the startup handler names. FreeRTOS implements the port handler names. If nothing connects them, the CPU may call the weak startup handler instead of the FreeRTOS implementation.
That is one of the most common early RTOS bring-up failures.
Wiring With Wrapper Handlers
One explicit style is to define wrapper handlers in project code:
void SVC_Handler(void)
{
vPortSVCHandler();
}
void PendSV_Handler(void)
{
xPortPendSVHandler();
}
void SysTick_Handler(void)
{
xPortSysTickHandler();
}
This style is easy to read. The startup vector table calls the standard Cortex-M handler names, and those handlers forward to the FreeRTOS port.
It also gives the project a clear place to handle integration decisions. For example, if the STM32 HAL tick must still be incremented from SysTick_Handler, the project can make that explicit in the wrapper.
Be careful with wrappers around low-level port handlers. Do not add logging, blocking calls, or application logic. These handlers run in exception context and are part of scheduler machinery.
Wiring With Preprocessor Mapping
Another common style is to map FreeRTOS port handler names directly to startup handler names with preprocessor definitions.
Some FreeRTOS examples use a shape like this in FreeRTOSConfig.h:
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
With that mapping, the port code emits or references the standard startup handler names directly.
This can be clean, but it is less obvious if you do not know where to look. A reader opening the startup file may not immediately see why PendSV_Handler resolves to the FreeRTOS port.
Either wrapper functions or macro mapping can work. Pick one style and make it consistent. Do not partially mix both unless you understand exactly which symbols are being defined.
Vector Table Ownership
The vector table is the final authority on which handler runs for an exception.
In a C startup file, the relevant entries may look conceptually like:
extern void SVC_Handler(void);
extern void PendSV_Handler(void);
extern void SysTick_Handler(void);
void (* const vector_table[])(void) =
{
/* ... */
SVC_Handler,
/* ... */
PendSV_Handler,
SysTick_Handler,
};
If those names still resolve to weak default handlers, FreeRTOS is not wired correctly.
When debugging a bring-up failure, inspect the symbols in the final firmware image or set breakpoints on the handlers. Confirm that SVC_Handler, PendSV_Handler, and SysTick_Handler reach the intended code.
Do not assume the build is correct just because it links.
Exception Priorities
PendSV and SysTick normally run at low priority in a FreeRTOS Cortex-M port.
The reason is practical. Context switching should not preempt urgent hardware interrupt work. It should happen after higher-priority interrupt handling is complete.
The FreeRTOS port often configures PendSV and SysTick priorities during scheduler startup or tick setup. The project configuration also defines values such as:
#define configKERNEL_INTERRUPT_PRIORITY
#define configMAX_SYSCALL_INTERRUPT_PRIORITY
Those values must agree with the Cortex-M priority model and the CMSIS priority configuration used by the project.
Do not casually raise the priority of PendSV. A high-priority context-switch exception can break assumptions that the port relies on.
SVC is part of the scheduler startup path for some ports. Treat its priority as part of the port integration unless the FreeRTOS port documentation says otherwise.
What Happens When the Scheduler Starts
When application code eventually calls vTaskStartScheduler(), the flow is roughly:
- The generic kernel checks that tasks and scheduler state are ready.
- The portable layer configures CPU-specific scheduler machinery.
- The first task context is restored through the port startup path.
SVCmay be used to enter the first task.- Later context switches are requested and handled through
PendSV. SysTickadvances kernel time and may request a switch.
The exact sequence depends on the selected port implementation, but the relationship between startup code, vector table handlers, and port handlers is the important part.
If any handler is missing or misnamed, the scheduler may never reach the first task.
Common Handler Wiring Mistakes
One common mistake is leaving the startup file's weak handlers in place. The code builds, but the first SVC, PendSV, or SysTick event lands in a default infinite loop.
Another mistake is using handler names from a different FreeRTOS port or example. Check the actual port.c included in your build.
A third mistake is defining both wrappers and macro mappings in a way that creates duplicate symbols or routes handlers unexpectedly.
A fourth mistake is adding application behavior inside scheduler exception handlers. Keep these handlers focused on forwarding to the port or performing the minimal integration work required by the port.
A fifth mistake is changing exception priorities without understanding the FreeRTOS interrupt priority rules.
Debugging Default Handler Traps
If the firmware lands in a default handler after starting the scheduler, treat it as useful information.
Set breakpoints on:
SVC_Handler
PendSV_Handler
SysTick_Handler
Default_Handler
vPortSVCHandler
xPortPendSVHandler
xPortSysTickHandler
Then start the scheduler under the debugger and observe which breakpoint is hit.
If Default_Handler is hit, inspect the active exception number if your debugger exposes it. On Cortex-M, the interrupt control state registers can also help identify the active exception.
The goal is to answer one concrete question: which exception fired, and which symbol did the vector table call?
That is much faster than guessing at task code that has not even started yet.
Checklist Before Creating the First Tasks
Before moving on, confirm:
SysTick_Handlerreaches the FreeRTOS tick path.PendSV_Handlerreaches the FreeRTOS PendSV port handler.SVC_Handlerreaches the FreeRTOS SVC port handler when required by the selected port.- The project uses one consistent handler-wiring style.
- Weak default handlers are not accidentally swallowing scheduler exceptions.
PendSVandSysTickpriorities match the FreeRTOS port expectations.- No application logic has been added to scheduler exception handlers.
With those pieces wired, the project is ready to create a minimal task and try to start the scheduler.
Next Steps
The next article creates the first FreeRTOS tasks on STM32.
That is where this setup becomes visible. A small task will run, delay, and run again. If the scheduler starts cleanly, the earlier build, configuration, tick, and exception-wiring work has come together. If it fails, the debugging surface is now much smaller and easier to reason about.