Series / Porting FreeRTOS to STM32 From Scratch / Why Port FreeRTOS Yourself?

Why Port FreeRTOS Yourself?

Understand why manually porting FreeRTOS to STM32 is useful when you want to learn the scheduler, interrupt, stack, and build-system boundaries instead of treating the RTOS as generated project code.

On this page

FreeRTOS can be added to an STM32 project in several ways. You can enable it in STM32CubeMX, generate a project, write a task, and let the generated files handle most of the integration. That is often the fastest way to get an RTOS running.

This series takes a slower route on purpose.

It ports FreeRTOS into an STM32 project step by step, using the same CMake and VSCode workflow from the earlier STM32 articles. The goal is not to prove that generated projects are bad. The goal is to make the RTOS boundary visible: which files belong to the kernel, which files belong to the port layer, which exception handlers must be connected, which timer produces the scheduler tick, and what has to happen before the first task can run.

If you have only used FreeRTOS through generated project code, it can feel like a black box. You create tasks, call vTaskStartScheduler(), and hope the scheduler starts. When it does not, the failure often lands somewhere unfamiliar: a weak interrupt handler, a missing FreeRTOSConfig.h option, a wrong priority setting, a stack problem, or a build that accidentally left out a port file.

Porting FreeRTOS yourself gives those pieces names.

What Porting FreeRTOS Means

In this series, "porting FreeRTOS" does not mean rewriting the FreeRTOS kernel.

FreeRTOS already provides a portable kernel and architecture-specific port layers. For an STM32 based on an Arm Cortex-M core, the kernel already has support for the CPU architecture. The work is to integrate that kernel into a specific firmware project deliberately.

That includes:

  • Adding the FreeRTOS kernel source files to the build.
  • Selecting the correct portable layer for the target core and compiler.
  • Creating a project-specific FreeRTOSConfig.h.
  • Connecting the exception handlers used by the scheduler.
  • Choosing and configuring the RTOS tick source.
  • Making sure interrupt priorities follow the rules FreeRTOS expects.
  • Creating the first tasks and proving that context switching works.

Those steps sit between the generic FreeRTOS source tree and your application code. That middle layer is where many RTOS bring-up problems happen.

Why Do It Manually?

Manual porting is useful because it forces every integration decision into the open.

An STM32 project with FreeRTOS is not just "an STM32 project plus tasks." It changes how the firmware is structured. The main loop is no longer the only owner of time. Interrupt handlers need clearer rules. Delays may block a task instead of spinning the CPU. Shared state needs stronger boundaries. Stack sizing becomes visible for every task, not only for the process stack defined in the linker script.

Those are engineering decisions, not just library setup details.

Doing the port by hand helps answer questions such as:

  • Which source files are actually required to build the kernel?
  • What does the portable layer provide that the generic kernel does not?
  • Why do SysTick, PendSV, and SVC matter on Cortex-M?
  • What happens when vTaskStartScheduler() is called?
  • How does a task get its initial stack frame?
  • Why can an interrupt priority break FreeRTOS API calls?
  • Where should application code stop and RTOS-specific code begin?

Those questions are hard to answer if the first RTOS project appears fully formed from a generator.

How This Builds on the Previous STM32 Courses

This series assumes the earlier STM32 material is already familiar.

The CMake and VSCode workflow matters because the build needs to stay explicit. When FreeRTOS source files, include directories, and compile definitions are added, they should appear in project files that can be reviewed and changed. The editor should not be the only place where the project knows about the RTOS.

The startup and linker script material matters because FreeRTOS depends on the same early runtime setup as the rest of the firmware. The vector table, stack pointers, reset handler, and memory layout still exist. FreeRTOS does not replace startup code. It runs after startup has prepared enough of the C environment for normal code to execute.

The peripheral and interrupt material matters because FreeRTOS changes how time and events flow through the program. A timer interrupt that used to set a flag for the main loop may now unblock a task. A UART receive interrupt may send data into a queue. A display refresh loop may become a periodic task. Those changes are easier to understand if GPIO, timers, UART logging, and interrupt callbacks are already familiar.

The bare-metal material matters most of all. FreeRTOS uses low-level CPU mechanisms that are easy to ignore until something breaks. On Cortex-M, context switching is tied to exceptions. The scheduler tick is tied to a timer source. Critical sections interact with interrupt masking. If those words are still vague, the RTOS will feel more magical than it needs to.

What This Is Not

This is not a claim that every production project should manually assemble its FreeRTOS port from scratch.

Generated projects can be the right tool. STM32CubeMX can quickly produce a project with FreeRTOS enabled, vendor drivers configured, and middleware wired together. For teams that already understand the generated structure and have a repeatable process around it, that can be practical.

This series is also not a full FreeRTOS application course. It will not try to cover every kernel feature, every synchronization primitive, every memory allocation strategy, or every production safety concern. FreeRTOS is large enough that a complete application guide would be its own path.

The focus here is narrower: bring FreeRTOS into an STM32 project without hiding the porting mechanics.

That means the early articles care more about the boundary between STM32 startup code, the Cortex-M port layer, the scheduler tick, and task startup than about building a feature-rich application.

The STM32 Pieces FreeRTOS Needs

Before any application task can run, several STM32 and Cortex-M pieces must line up.

The firmware still starts at the reset vector. Startup code still sets the initial stack pointer, copies initialized data to RAM, clears .bss, and calls into application startup. C runtime setup still happens before normal C code can rely on global variables.

After that, FreeRTOS needs a configured kernel and a working port layer. On a Cortex-M target, the port layer depends on exception behavior provided by the CPU. The exact details vary by port, but the important concepts are stable:

  • SysTick is commonly used to generate the RTOS tick.
  • PendSV is commonly used to perform context switching.
  • SVC is used by some Cortex-M FreeRTOS ports during scheduler startup.
  • Interrupt priorities must be configured so RTOS-aware interrupts follow FreeRTOS rules.

Those names should look familiar if you have worked through vector tables and interrupts. FreeRTOS is not bypassing the microcontroller architecture. It is using it.

That is why porting is a good learning exercise. The RTOS becomes a concrete user of mechanisms that might otherwise feel theoretical.

The Build-System Boundary

The first practical boundary is the build.

FreeRTOS is delivered as source code. Your firmware build must include the kernel files, the selected portable layer, and the correct include directories. It must also provide a FreeRTOSConfig.h that matches the project.

In a CMake-based STM32 project, that should be explicit. There should be a visible place where the FreeRTOS source files are listed or collected. There should be a visible include path for the kernel headers. There should be a clear location for project configuration.

This matters because build mistakes can look like RTOS problems.

If the wrong port file is included, the project may compile incorrectly or fail at link time. If the heap implementation is missing, memory allocation symbols may be unresolved. If the include paths point to the wrong configuration header, the kernel may build with options you did not intend.

A generated project may make those choices for you. A manual CMake integration makes them part of the project architecture.

The Interrupt Boundary

The second practical boundary is interrupts.

Before an RTOS, an interrupt might set a flag that the main loop checks later. That pattern still exists in RTOS projects, but now there are more options and more rules. An interrupt might unblock a task, send to a queue, give a semaphore, or request a context switch after it exits.

FreeRTOS also has rules about which interrupts may call FreeRTOS APIs. On Cortex-M, those rules are tied to interrupt priority. Misconfigured priorities are one of the easiest ways to create an RTOS project that builds but behaves unpredictably.

That is why this series does not start with a large application. It starts with the scheduler plumbing. The project needs a known-good baseline before adding queues, delays, and peripheral work.

The Stack and Context Boundary

The third practical boundary is task context.

In a simple bare-metal program, there is one main path of execution. The linker script defines memory regions. Startup code establishes an initial stack. main() runs. Interrupts temporarily interrupt that path and then return.

In an RTOS program, each task needs its own stack. The scheduler switches between task contexts. The CPU registers for one task must be saved so another task can run. Later, the first task must resume as if nothing happened except that time passed.

That is the heart of context switching.

You do not need to memorize every assembly instruction in the port layer before using FreeRTOS, but you should understand what problem the port layer solves. It connects the generic scheduler to the CPU-specific rules for saving registers, restoring registers, entering exceptions, and returning from exceptions.

That is the difference between calling FreeRTOS functions and understanding why a FreeRTOS port works.

Why Not Start from CubeMX?

CubeMX is useful, but it optimizes for getting a configured project quickly.

This series optimizes for learning the integration points.

Starting from CubeMX can hide important questions behind generated files and configuration panels. Which FreeRTOS files were added? Which interrupt handlers were renamed or wrapped? Which tick source is used? Which priority values were chosen? Where is FreeRTOSConfig.h, and which options came from the generator?

Those questions can still be answered in a CubeMX project, but the answers are mixed with generator conventions. For this series, it is cleaner to assemble the pieces directly in the existing STM32 CMake workflow.

After you understand the manual port, generated projects become easier to inspect. You can open a CubeMX FreeRTOS project and recognize the same boundaries instead of treating the output as a special case.

What This Series Will Build Toward

The next articles will bring FreeRTOS into the STM32 project in layers.

First, the project will be prepared so FreeRTOS has a clear location in the source tree and CMake build. Then the required kernel and portable source files will be added. After that, the series will create a minimal FreeRTOSConfig.h, connect the scheduler tick, and wire the exception handlers needed for context switching.

Only after the scheduler can start cleanly will the series create the first tasks.

From there, the articles will cover debugging scheduler bring-up, using queues and delays carefully, and moving selected peripheral work into task boundaries without making every driver depend directly on the RTOS.

The end goal is not just "FreeRTOS runs." The goal is to know why it runs, where it is connected to the STM32 platform, and how to debug it when those connections are wrong.

Next Steps

The next article prepares the STM32 CMake project for FreeRTOS.

That means deciding where the kernel source should live, where FreeRTOSConfig.h belongs, how the build should reference the RTOS files, and how to keep the RTOS boundary visible as the firmware grows.