On this page
- Starting Point
- What FreeRTOS Adds to the Build
- Generic Kernel Source Files
- The Cortex-M Portable Layer
- Choosing a Heap Implementation
- A Minimal FreeRTOS CMake Target
- Linking FreeRTOS Into the Firmware
- Include Paths and FreeRTOSConfig.h
- What Should Compile Now
- Common Build Mistakes
- Why This Step Matters
- Next Steps
The previous article prepared a clean place for FreeRTOS in the STM32 CMake project. The project now has a boundary for imported kernel source, project-owned RTOS configuration, and CMake rules that belong to the RTOS integration.
This article adds the FreeRTOS source files to that build boundary.
The goal is not to start tasks yet. The goal is to make the build describe FreeRTOS explicitly: which generic kernel files are used, which Cortex-M portable layer is selected, which heap implementation is included, and which include paths are needed.
That may feel like a small step, but it is one of the most useful places to slow down. Many early FreeRTOS problems are not scheduler problems at all. They are build problems: the wrong port file, a missing heap file, an accidental include path, or a configuration header that is not the one the project thinks it is using.
Starting Point
This article assumes the project has a layout like this:
firmware/
├── app/
├── boards/
├── cmake/
├── drivers/
├── freertos/
│ ├── CMakeLists.txt
│ ├── FreeRTOS-Kernel/
│ └── config/
│ └── FreeRTOSConfig.h
├── generated/
├── linker/
├── startup/
└── CMakeLists.txt
The FreeRTOS-Kernel/ directory represents a known FreeRTOS kernel release copied or vendored into the project. The config/ directory belongs to this STM32 firmware project.
If your project uses a Git submodule or another dependency method instead of copying the kernel source, the same build concepts still apply. CMake still needs to point at the generic kernel files, the portable layer, the heap implementation, and the project configuration header.
What FreeRTOS Adds to the Build
FreeRTOS is not a single source file.
For a small Cortex-M project, the build normally needs four categories of input:
- Generic kernel source files.
- One CPU/compiler-specific portable layer.
- One heap implementation.
- Include paths for kernel headers and
FreeRTOSConfig.h.
Those categories should be visible in the build. Do not treat the kernel folder as an opaque blob and add every .c file recursively. That can pull in portable layers for architectures you are not using, heap implementations you did not choose, or files intended for other compilers.
A firmware build should say what it means.
Generic Kernel Source Files
The generic kernel files implement the scheduler and common RTOS features. In a typical FreeRTOS kernel tree, the core files live at the top level of FreeRTOS-Kernel/.
A small initial build may include files such as:
FreeRTOS-Kernel/
├── croutine.c
├── event_groups.c
├── list.c
├── queue.c
├── stream_buffer.c
├── tasks.c
└── timers.c
The exact set depends on the FreeRTOS version and which features your configuration enables.
Even if the first application only creates tasks, it is often practical to include the common kernel files from the start. Later articles will use delays, queues, and timer-related behavior. Keeping the source list explicit still matters more than minimizing it prematurely.
The file that must be present for almost every FreeRTOS project is tasks.c. The scheduler and task management live there. list.c is also fundamental because the kernel uses lists internally. Queue and timer-related files support common APIs that will matter once the project moves beyond first task bring-up.
The Cortex-M Portable Layer
The generic kernel does not know how to switch context on every CPU.
That is the portable layer's job.
For an STM32 project using GCC on an Arm Cortex-M target, the portable source is usually selected from a path similar to:
FreeRTOS-Kernel/
└── portable/
└── GCC/
└── ARM_CM4F/
└── port.c
The exact folder depends on the Cortex-M core and floating-point configuration. For an STM32L433-based board, the MCU uses a Cortex-M4F core, so the ARM_CM4F GCC port is the relevant starting point.
This choice matters. Selecting ARM_CM3, ARM_CM4F, ARM_CM7, or another port is not cosmetic. The port layer contains the architecture-specific code that starts the scheduler, prepares task stacks, manages critical sections, and performs context switching through Cortex-M exception behavior.
If the wrong port is selected, the project may fail to compile, fail to link, or behave incorrectly at runtime.
Choosing a Heap Implementation
FreeRTOS also needs a heap implementation if the project creates tasks, queues, timers, or other kernel objects dynamically.
The kernel includes several heap implementations under a path like:
FreeRTOS-Kernel/
└── portable/
└── MemMang/
├── heap_1.c
├── heap_2.c
├── heap_3.c
├── heap_4.c
└── heap_5.c
Do not add all of them. Choose one.
For a first STM32 bring-up, heap_4.c is a common practical choice. It supports allocation and freeing with coalescing of adjacent free blocks. It is more flexible than the simplest heap implementations while still being straightforward for learning.
The heap choice is a project decision. It should appear as a single selected source file in the FreeRTOS build target.
Later, if the project moves to static allocation only, or if memory regions need more explicit control, the heap strategy can change. For now, the important point is to make the choice deliberate and visible.
A Minimal FreeRTOS CMake Target
One clean way to represent FreeRTOS is to give it a CMake target under freertos/CMakeLists.txt.
For example:
set(FREERTOS_KERNEL_DIR ${CMAKE_CURRENT_LIST_DIR}/FreeRTOS-Kernel)
add_library(freertos_kernel STATIC
${FREERTOS_KERNEL_DIR}/croutine.c
${FREERTOS_KERNEL_DIR}/event_groups.c
${FREERTOS_KERNEL_DIR}/list.c
${FREERTOS_KERNEL_DIR}/queue.c
${FREERTOS_KERNEL_DIR}/stream_buffer.c
${FREERTOS_KERNEL_DIR}/tasks.c
${FREERTOS_KERNEL_DIR}/timers.c
${FREERTOS_KERNEL_DIR}/portable/GCC/ARM_CM4F/port.c
${FREERTOS_KERNEL_DIR}/portable/MemMang/heap_4.c
)
target_include_directories(freertos_kernel
PUBLIC
${FREERTOS_KERNEL_DIR}/include
${FREERTOS_KERNEL_DIR}/portable/GCC/ARM_CM4F
${CMAKE_CURRENT_LIST_DIR}/config
)
This target does a few useful things.
It names the dependency as freertos_kernel. It lists the selected source files explicitly. It exposes the public FreeRTOS include directories to anything that links against the target. It also exposes the local config/ directory so the kernel can find FreeRTOSConfig.h.
This is not the only possible CMake shape, but it keeps the RTOS boundary easy to inspect.
Linking FreeRTOS Into the Firmware
The top-level firmware build can then add the FreeRTOS directory and link the target into the firmware executable.
For example:
add_subdirectory(freertos)
target_link_libraries(${PROJECT_NAME}
PRIVATE
freertos_kernel
)
The exact firmware target name may be different in your project. Use the target that builds the STM32 firmware image.
Linking the RTOS as a named CMake target is better than dumping all kernel files into the main executable source list. The separate target keeps ownership clear. If the project later changes the heap implementation, updates the port layer, or adjusts FreeRTOS include paths, those changes belong in the FreeRTOS CMake boundary.
Include Paths and FreeRTOSConfig.h
FreeRTOS source files include FreeRTOSConfig.h by name.
That means the include path containing the project's configuration header must be visible when compiling the kernel. In the example above, that is this directory:
freertos/config/
The portable layer include directory is also important. Some FreeRTOS headers include port-specific definitions from files such as portmacro.h, which live alongside the selected port source.
For the Cortex-M4F GCC port, that means a path like:
FreeRTOS-Kernel/portable/GCC/ARM_CM4F
If this include path is missing, the compiler may report that portmacro.h cannot be found. That is a build boundary issue, not a scheduler issue.
What Should Compile Now
After this step, the project should be able to compile the FreeRTOS kernel source files as part of the firmware build, assuming FreeRTOSConfig.h already contains enough required definitions for the selected kernel version.
That does not mean the scheduler is ready to run.
At this point, the project has not yet wired the Cortex-M exception handlers that FreeRTOS needs. It has not configured the RTOS tick deliberately. It has not created tasks. It has not called vTaskStartScheduler().
That is fine.
The checkpoint for this article is narrower: CMake knows what FreeRTOS is, which source files belong to it, which port is selected, which heap implementation is selected, and where the configuration header lives.
Common Build Mistakes
The first common mistake is selecting the wrong portable layer. An STM32 Cortex-M4F target should not use a random Cortex-M port because the folder name looks close enough. Match the port to the CPU core, compiler, and floating-point expectations.
The second mistake is adding every heap implementation. FreeRTOS expects one heap implementation when dynamic allocation is used. Multiple heap files define overlapping allocation functions and will usually create duplicate-symbol errors.
The third mistake is using recursive source collection across the whole kernel tree. That can accidentally include sources for other architectures or tools. Explicit source lists are more boring, but they are safer and easier to review.
The fourth mistake is hiding FreeRTOSConfig.h in the wrong include path. If more than one configuration header exists, the build may compile against a different configuration than the one you are editing.
The fifth mistake is assuming a successful compile means the port is complete. Compiling the kernel is only one step. Runtime integration still depends on startup code, vector table entries, exception handlers, tick configuration, interrupt priorities, and task creation.
Why This Step Matters
This article is intentionally focused on build mechanics because the build is the first contract between your project and FreeRTOS.
If that contract is vague, every later failure becomes harder to diagnose. A scheduler bring-up bug may actually be a missing source file. A missing header may be caused by the wrong include boundary. A linker error may point to a heap implementation choice that was never made explicitly.
When the build is clear, the next questions become clearer too.
The generic kernel is now separate from the portable layer. The heap strategy is a visible decision. The project configuration header is project-owned. The firmware links against a named RTOS target instead of a pile of hidden files.
That gives the rest of the port a stable foundation.
Next Steps
The next article explains the FreeRTOS portable layer in more detail.
The build now points at a Cortex-M port file, but that file deserves attention before the scheduler is started. It is the bridge between the generic FreeRTOS scheduler and the STM32 CPU's exception, stack, and context-switching behavior. Understanding that boundary will make the later SysTick, PendSV, and SVC wiring much easier to reason about.