On this page
- What This Article Prepares
- Keep FreeRTOS Separate From Application Code
- A Practical Folder Layout
- Where the Kernel Source Should Live
- Where FreeRTOSConfig.h Belongs
- Where CMake Should Reference FreeRTOS
- Include Boundaries
- What Not To Add Yet
- Common Layout Mistakes
- Checklist Before Adding Kernel Sources
- Next Steps
The previous article explained why this series ports FreeRTOS into an STM32 project manually instead of starting from a generated RTOS project.
This article prepares the project for that work.
It does not add the FreeRTOS kernel files yet. That is deliberate. Before source files are copied into the tree or added to CMake, the project needs clear ownership boundaries. FreeRTOS should not be scattered through application code, generated vendor folders, board support files, and random include paths. If the project layout is messy before the kernel arrives, debugging the first scheduler failure becomes harder than it needs to be.
The starting point is the STM32 CMake workflow from the earlier series: a project with visible startup code, linker script, board support, application code, generated STM32 support where needed, and a CMake build that can run outside the editor.
What This Article Prepares
This article prepares the structure around FreeRTOS before compiling it.
The goal is to decide:
- Where the FreeRTOS kernel source will live.
- Where project-specific FreeRTOS configuration will live.
- Which CMake file owns the RTOS source list and include paths.
- Which folders application code should include from directly.
- Which files should remain independent of FreeRTOS for now.
That may sound like bookkeeping, but it prevents a common failure mode: treating RTOS integration as a pile of files to make the compiler happy.
FreeRTOS becomes part of the firmware architecture. It affects scheduling, timing, interrupt rules, and application structure. The folder layout and build layout should make that role obvious.
Keep FreeRTOS Separate From Application Code
The application should not own the kernel.
Application code will eventually create tasks, use delays, send to queues, and call other FreeRTOS APIs. That does not mean the kernel source belongs inside app/.
Keep these concerns separate:
app/owns what the firmware does.boards/owns physical board facts.generated/owns vendor-generated STM32 support.startup/owns the reset path and vector table.linker/owns the target memory layout.freertos/owns the kernel import and project RTOS configuration.cmake/owns reusable build helper files.
That separation makes review easier. A change under app/ should usually be an application behavior change. A change under freertos/ should usually be a kernel import, RTOS configuration change, or port integration change.
Those are different kinds of changes, and they should not be hidden from each other.
A Practical Folder Layout
A practical layout for this point in the project is:
firmware/
├── app/
│ ├── Inc/
│ └── Src/
├── boards/
│ └── nucleo-l433rcp/
│ ├── Inc/
│ └── Src/
├── cmake/
│ └── arm-none-eabi-gcc.cmake
├── drivers/
│ ├── Inc/
│ └── Src/
├── freertos/
│ ├── FreeRTOS-Kernel/
│ └── config/
├── generated/
│ ├── Core/
│ └── Drivers/
├── linker/
│ └── STM32L433RCTx_FLASH.ld
├── startup/
│ └── startup_stm32l433xx.c
├── .vscode/
│ └── launch.json
└── CMakeLists.txt
The exact names can change, but the ownership should not be vague.
The freertos/FreeRTOS-Kernel/ folder is where the upstream kernel source will be placed. The freertos/config/ folder is where this STM32 project will keep its FreeRTOSConfig.h.
Keeping those two folders separate matters. The kernel is third-party source. The configuration is project source. You should be able to update the kernel without accidentally treating project configuration as part of the imported upstream code.
Where the Kernel Source Should Live
There are several reasonable ways to bring FreeRTOS source into a project:
- Copy a known kernel release into the repository.
- Add the kernel as a Git submodule.
- Use a package manager or dependency fetch step.
- Vendor only the files needed by the target.
For a learning-focused STM32 project, copying or vendoring a known kernel release into freertos/FreeRTOS-Kernel/ is the easiest layout to explain. Every file is present in the tree, CMake can reference it directly, and the reader does not need to learn dependency-management tooling at the same time as RTOS porting.
That does not mean copying source is always the best production strategy. A team may prefer submodules, lock files, or internal dependency mirrors. The important rule is that the FreeRTOS version should be explicit and repeatable.
Do not rely on an untracked folder outside the project. If a build only works because one developer has FreeRTOS installed somewhere locally, the project is not reproducible.
Where FreeRTOSConfig.h Belongs
FreeRTOSConfig.h is not a generic FreeRTOS file.
It belongs to this firmware project.
That header selects kernel features, tick rate, interrupt priority assumptions, heap behavior, assertion behavior, hook functions, and port-specific options. Some values are tied to the CPU and STM32 interrupt model. Other values are tied to application needs.
Put it somewhere project-owned, such as:
freertos/
└── config/
└── FreeRTOSConfig.h
Avoid hiding it inside the imported kernel tree. If FreeRTOSConfig.h lives inside FreeRTOS-Kernel/, it becomes less obvious which files are upstream and which files are local decisions.
Also avoid putting it in a broad application include folder just to satisfy the compiler. Application code may include FreeRTOS API headers later, but that does not make RTOS configuration part of application behavior.
Where CMake Should Reference FreeRTOS
The top-level CMakeLists.txt can reference FreeRTOS directly, but the RTOS source list can become noisy. A cleaner shape is to give FreeRTOS a small CMake boundary of its own.
For example:
freertos/
├── CMakeLists.txt
├── FreeRTOS-Kernel/
└── config/
Then the top-level build can include that boundary:
add_subdirectory(freertos)
target_link_libraries(${PROJECT_NAME}
PRIVATE
freertos_kernel
)
The exact target name is a project choice. The useful part is that the RTOS integration has a named build target. Later, when the project adds kernel sources, portable sources, heap implementation, and include directories, those details can live behind freertos_kernel instead of being mixed into every application target rule.
This article does not need the complete CMake target yet. The next article will add the actual kernel files. For now, decide where that target will live and what it will own.
Include Boundaries
Include paths are easy to over-broaden.
The FreeRTOS kernel needs access to its own headers and the project FreeRTOSConfig.h. Application code will eventually need access to public FreeRTOS API headers such as FreeRTOS.h, task.h, and queue.h.
That does not mean every source file should receive every include directory by default.
Keep the include boundary intentional:
- The FreeRTOS build target should include kernel headers and
freertos/config/. - Application code should include FreeRTOS API headers only in files that use RTOS APIs.
- Board support and low-level drivers should not become FreeRTOS-dependent unless there is a specific reason.
- Startup files should not include FreeRTOS headers just because the project uses an RTOS.
This keeps the dependency direction clean. FreeRTOS is a scheduling and synchronization tool. It should not leak into every hardware abstraction by accident.
What Not To Add Yet
Do not add tasks yet.
Do not call vTaskStartScheduler() yet.
Do not wire SysTick, PendSV, or SVC yet.
Do not create queues, semaphores, mutexes, or timers yet.
Those pieces come later. If too much is added at once, the first failure is hard to isolate. A missing source file, wrong include path, bad interrupt handler name, incorrect priority setting, and stack-size issue can all produce confusing symptoms.
The next article should have one job: add the FreeRTOS source files to the build and get the project compiling far enough to expose the next integration boundary.
Small steps are not slower when you are bringing up low-level firmware. They are how you keep each failure understandable.
Common Layout Mistakes
One common mistake is mixing kernel files into generated/ because CubeMX can generate FreeRTOS-enabled projects. In this workflow, generated/ is for STM32 vendor output. The FreeRTOS kernel is a separate dependency, not generated board configuration.
Another mistake is putting FreeRTOSConfig.h inside the upstream kernel folder. That works mechanically, but it blurs ownership. Configuration is local project policy.
A third mistake is adding broad include paths globally because it is convenient. If every file can include every FreeRTOS header, it becomes harder to see which modules actually depend on the RTOS.
A fourth mistake is building the first RTOS project around a large application change. The first RTOS bring-up should be small. When the scheduler starts for the first time, the project should have as few moving pieces as possible.
Checklist Before Adding Kernel Sources
Before adding the FreeRTOS source files, the project should have a clear answer for each item:
- The FreeRTOS kernel source location is decided.
- The project-owned
FreeRTOSConfig.hlocation is decided. - The CMake boundary for the RTOS is decided.
- Kernel source and project configuration are separate.
- Application code is not mixed into the RTOS import folder.
- Generated STM32 files remain separate from FreeRTOS files.
- Include paths will be added only where they are needed.
- The initial RTOS bring-up will stay minimal.
If those decisions are clear, the next step is mechanical: add the kernel source files, select the Cortex-M portable layer, choose a heap implementation, and make CMake describe that explicitly.
Next Steps
The next article adds FreeRTOS source files to the build.
That is where the project will choose the kernel files, portable layer, heap implementation, and include directories needed for an STM32 Cortex-M target. The goal will still be controlled bring-up, not a complete application. First the project has to compile with the kernel in the tree. Then the scheduler wiring can begin.