Series / Porting FreeRTOS to STM32 From Scratch / Configuring FreeRTOSConfig.h for STM32

Configuring FreeRTOSConfig.h for STM32

Configure the FreeRTOSConfig.h settings needed for first STM32 scheduler bring-up, including tick rate, CPU clock assumptions, priorities, assertions, allocation, and minimal kernel features.

On this page

The previous articles put FreeRTOS source files in the STM32 CMake build and explained why the Cortex-M portable layer matters.

The next step is project configuration.

FreeRTOS looks for a file named FreeRTOSConfig.h. That file is not part of the generic kernel. It is the project's RTOS policy: clock assumptions, tick rate, interrupt priority rules, allocation choices, assertion behavior, and which kernel APIs are enabled.

This article builds a starter configuration for STM32 bring-up. It does not wire SysTick, PendSV, or SVC yet. It also does not create the first task yet. The goal is to make the kernel configuration explicit before runtime wiring begins.

Starting Point

The project should already have a FreeRTOS folder shaped roughly like this:

freertos/
├── CMakeLists.txt
├── FreeRTOS-Kernel/
└── config/
    └── FreeRTOSConfig.h

The FreeRTOS CMake target should include the generic kernel files, the selected Cortex-M portable layer, one heap implementation, and the freertos/config/ include directory.

For this series, the target board is the STM32 Nucleo L433RC-P. The MCU is based on a Cortex-M4F core, and the earlier build examples use GCC through the STM32 CMake workflow.

What FreeRTOSConfig.h Controls

FreeRTOSConfig.h controls how the kernel is built for this firmware project.

It answers questions such as:

  • Is preemptive scheduling enabled?
  • What is the CPU clock frequency used by the RTOS tick setup?
  • How often should the scheduler tick run?
  • How many task priorities are available?
  • How large is the FreeRTOS heap?
  • Which optional APIs are compiled in?
  • Are runtime assertions enabled?
  • Which interrupt priorities are safe for RTOS-aware interrupts?

Those choices are not generic FreeRTOS facts. They belong to this STM32 project.

That is why FreeRTOSConfig.h should live outside the imported kernel source tree. It is project-owned configuration, not upstream code.

Keep the First Configuration Small

FreeRTOS exposes many configuration options.

Do not try to configure every feature at once. A first bring-up should enable only the pieces needed to compile the kernel, start the scheduler, create simple tasks, and debug obvious mistakes.

A good first configuration should be:

  • Explicit enough to explain.
  • Small enough to debug.
  • Strict enough to catch invalid interrupt usage early.
  • Flexible enough to create tasks dynamically for early examples.

More features can be enabled after the scheduler is known to work.

CPU Clock and Tick Rate

Two early settings define the time base FreeRTOS expects:

#define configCPU_CLOCK_HZ    (80000000UL)
#define configTICK_RATE_HZ    ((TickType_t)1000)

configCPU_CLOCK_HZ should match the clock used when configuring the RTOS tick source. If the project later sets up SysTick from the core clock, this value must agree with the actual core clock.

The number above is an example. Use the frequency your STM32 clock configuration actually produces.

configTICK_RATE_HZ defines how many RTOS ticks occur per second. A value of 1000 gives a 1 ms tick period. That is easy to reason about during bring-up, but it creates more tick interrupts than a lower rate such as 100 Hz.

For early learning, 1000 Hz is acceptable because it makes delays like pdMS_TO_TICKS(10) straightforward. For a production project, choose the tick rate based on timing needs, power use, and interrupt overhead.

Preemption and Time Slicing

For a normal first FreeRTOS bring-up, enable preemption:

#define configUSE_PREEMPTION        1
#define configUSE_TIME_SLICING      1
#define configUSE_IDLE_HOOK         0
#define configUSE_TICK_HOOK         0

With preemption enabled, a higher-priority ready task can run without waiting for the current task to yield manually. That is the common FreeRTOS behavior most examples assume.

Time slicing lets tasks at the same priority share CPU time across ticks. It is useful for simple examples, but do not use it as a substitute for clear task priorities and blocking behavior.

Leave idle and tick hooks disabled at first. Hooks are useful later, but they add extra code paths during scheduler bring-up. The first goal is to get a minimal scheduler running and observable.

Task Priorities and Stack Size

Keep the priority range small at first:

#define configMAX_PRIORITIES        5
#define configMINIMAL_STACK_SIZE    ((uint16_t)128)

configMAX_PRIORITIES defines how many priority levels FreeRTOS supports. More priorities are not automatically better. A small number forces the project to keep scheduling decisions simple.

configMINIMAL_STACK_SIZE is expressed in stack words, not bytes. On a 32-bit Cortex-M target, 128 words means 512 bytes.

Do not treat this as a universal safe task stack size. It is a starting point for tiny tasks. Tasks that call formatting functions, use larger local variables, perform logging, or call deeper driver stacks may need more.

The first tasks in this series should stay deliberately small so stack issues do not hide scheduler bring-up issues.

Dynamic Allocation and Heap Size

The previous article selected heap_4.c as a practical first heap implementation.

Match that choice in the configuration:

#define configSUPPORT_DYNAMIC_ALLOCATION    1
#define configSUPPORT_STATIC_ALLOCATION     0
#define configTOTAL_HEAP_SIZE               ((size_t)(16 * 1024))

This enables dynamic allocation for early task and queue examples. configTOTAL_HEAP_SIZE defines the heap FreeRTOS manages internally. It is not the same thing as the linker script's total RAM size.

The heap size must fit in RAM alongside global data, stacks, and any other buffers. Start with a value large enough for simple examples, then inspect memory usage as the project grows.

Static allocation is useful in many production firmware projects, but it adds more setup decisions. For this learning path, dynamic allocation keeps first bring-up focused.

Interrupt Priority Settings

Cortex-M interrupt priorities are one of the easiest places to make a FreeRTOS project fail subtly.

FreeRTOS needs to know how many priority bits the MCU implements and which interrupt priorities are allowed to call FreeRTOS APIs.

A typical STM32 Cortex-M4 configuration shape is:

#define configPRIO_BITS                         4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

#define configKERNEL_INTERRUPT_PRIORITY \
    (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
    (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

The exact values must match the target MCU and project interrupt strategy.

The confusing part is that Cortex-M priority numbers are inverted: numerically lower values represent higher urgency. Priority 0 is more urgent than priority 5.

configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY defines the highest-urgency interrupt priority that may call FreeRTOS FromISR APIs. Interrupts with numerically lower priority values are too urgent to be masked by the kernel and must not call those APIs.

This is why assertions matter. A misconfigured interrupt priority can compile cleanly and fail only at runtime.

Enable Assertions Early

Enable configASSERT during bring-up.

A minimal assert shape can be:

#define configASSERT(x) \
    if ((x) == 0) { taskDISABLE_INTERRUPTS(); for (;;) { } }

This stops the firmware at the failure point instead of letting it continue with invalid assumptions.

In a debug session, an assert loop is useful. You can pause the CPU, inspect the call stack, and find the invalid configuration or API call. Later, the project can replace this with a board-specific error handler that logs or toggles a diagnostic pin.

Do not disable assertions just because they stop the program. During RTOS bring-up, stopping early is better than corrupting scheduler state and debugging a symptom far away from the real cause.

Minimal API Feature Flags

FreeRTOS lets you include or exclude many APIs.

For the early articles in this series, keep the common task and delay APIs available:

#define INCLUDE_vTaskDelay              1
#define INCLUDE_vTaskDelayUntil         1
#define INCLUDE_vTaskDelete             1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_xTaskGetSchedulerState  1

These settings do not create tasks by themselves. They only control whether specific API functions are compiled in.

Avoid enabling every optional API just in case. A smaller configuration makes examples easier to reason about and keeps the build intentional.

A Starter FreeRTOSConfig.h

A first configuration can look like this:

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#include <stdint.h>

extern uint32_t SystemCoreClock;

#define configUSE_PREEMPTION                    1
#define configUSE_TIME_SLICING                  1
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0

#define configCPU_CLOCK_HZ                      (SystemCoreClock)
#define configTICK_RATE_HZ                      ((TickType_t)1000)
#define configMAX_PRIORITIES                    5
#define configMINIMAL_STACK_SIZE                ((uint16_t)128)
#define configTOTAL_HEAP_SIZE                   ((size_t)(16 * 1024))
#define configMAX_TASK_NAME_LEN                 16

#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configSUPPORT_STATIC_ALLOCATION         0

#define configUSE_MUTEXES                       1
#define configUSE_COUNTING_SEMAPHORES           1
#define configUSE_RECURSIVE_MUTEXES             0
#define configUSE_TIMERS                        0

#define configPRIO_BITS                         4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

#define configKERNEL_INTERRUPT_PRIORITY \
    (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
    (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

#define configASSERT(x) \
    if ((x) == 0) { taskDISABLE_INTERRUPTS(); for (;;) { } }

#define INCLUDE_vTaskDelay                      1
#define INCLUDE_vTaskDelayUntil                 1
#define INCLUDE_vTaskDelete                     1
#define INCLUDE_vTaskSuspend                    1
#define INCLUDE_xTaskGetSchedulerState          1

#endif

This is a starter, not a universal configuration.

Check the FreeRTOS version you are using. Newer versions may require or recommend additional options, especially around memory allocation, stack overflow checking, run-time stats, or SMP-related defaults. The right configuration is the one that matches your kernel version, CPU, compiler, and project needs.

Common Configuration Mistakes

One common mistake is hard-coding configCPU_CLOCK_HZ to a value that no longer matches the actual STM32 clock tree. If the clock configuration changes, the RTOS tick assumptions must be reviewed.

Another mistake is misunderstanding Cortex-M priority numbering. A numerically lower interrupt priority is more urgent. That means priority 3 is more urgent than priority 5, not less.

A third mistake is disabling configASSERT during bring-up. Assertions catch exactly the kind of mistakes that are common when wiring the first RTOS project.

A fourth mistake is choosing a heap implementation in CMake but forgetting that the configuration must support the allocation model. If the project uses dynamic task creation, dynamic allocation support must be enabled and one heap implementation must be linked.

A fifth mistake is enabling hooks before the scheduler baseline works. Idle and tick hooks are useful, but they add behavior to code paths that should stay simple during first bring-up.

Checklist Before Wiring the Tick

Before moving to the scheduler tick, confirm:

  • FreeRTOSConfig.h lives in a project-owned config folder.
  • The FreeRTOS CMake target includes that config folder.
  • configCPU_CLOCK_HZ matches the clock source you will use for the tick.
  • configTICK_RATE_HZ is chosen deliberately.
  • Exactly one heap implementation is linked.
  • Dynamic allocation settings match the heap choice.
  • Cortex-M interrupt priority macros are present.
  • configASSERT is enabled for bring-up.
  • Optional hooks are disabled unless there is a specific reason to use them.

With those pieces in place, the project has enough configuration to move toward runtime wiring.

Next Steps

The next article sets up the RTOS tick using SysTick.

The tick is the periodic heartbeat that lets FreeRTOS track delays and decide when time-based scheduling decisions are needed. It connects the configuration in FreeRTOSConfig.h to real Cortex-M timer behavior, so the clock and priority assumptions made here will matter immediately.