Embedded HandbookApps
All sections › IV. FreeRTOS
STM32F103, STM32F407 · level intermediate · 14 min read
Open in the interactive handbook

33. Configuration and power saving

FreeRTOSConfig.h has been mentioned in every section of part IV, and it is time to go through it in full. The kernel knows about seventy macros with the config prefix and about fifteen with INCLUDE_; the build is stopped by the absence of only six of them, the rest have defaults — and not always the ones you need. The second half of the section is about the mode in which the device does not wake up a thousand times a second for the tick, but sleeps until the next job.

Terms used in the documentation

Term Meaning
configuration file the file with the kernel settings
tick rate how often the tick interrupt occurs
tickless idle stopping the tick while the system is idle
suppress ticks and sleep skip ticks and go to sleep
expected idle time how long the system is expected to be idle
pre / post sleep processing actions before / after sleep
wait for interrupt (WFI) the instruction that sleeps until an interrupt
idle hook / tick hook a callback from the idle task / from the tick
run time stats CPU usage statistics per task
trace facility kernel support for tracing tools

33.1 Basic parameters

The first group is what must match the hardware and the intent of the device.

#define configCPU_CLOCK_HZ      72000000u
#define configTICK_RATE_HZ      1000u
#define configMAX_PRIORITIES    5
#define configMINIMAL_STACK_SIZE 128

⚠️ configCPU_CLOCK_HZ is the true core frequency, as set in RCC. The port divides it by the tick rate and gets the SysTick reload: 72000 cycles at 72 MHz and 1 kHz. Specify 36 MHz with a real 72 — the tick becomes half a millisecond, and vTaskDelay(1000) half a second. No check will catch this: the kernel trusts the configuration. The symptom is recognizable — "all the delays in the device are wrong, but proportionally".

The true core frequency in the configuration: 72 MHz and a 1 kHz tick give a SysTick reload of 72000 cycles and vTaskDelay(1000) of exactly one second; 36 MHz in the configuration with a real 72 — a reload of 36000, a half-millisecond tick and the same delay of half a second; the lower limit of the tick rate from the 24-bit SysTick is 4.29 Hz at 72 MHz

Tick rate. 1000 Hz is a sensible default: a delay resolution of one millisecond, an overhead of about 0.2 % of the processor at 72 MHz (23.4). It is lowered to 100 Hz to save power in devices without precise delays; raising it above 1000 makes almost no sense — the overhead grows, and resolution better than a millisecond is done with hardware timers. The lower limit is set by the width of SysTick: at 72 MHz the tick cannot be slower than four hertz.

Number of priorities. Each level is a separate list of ready tasks in kernel memory. Five are enough for most devices; more — only when you have something to fill them with.

Minimal stack — in words, and it is not a "recommended" value but the lower limit for the idle task. Your own tasks get as much as has been measured (section 26).

Interrupt priorities and checking. Without this group the Cortex‑M port does not work:

#define configPRIO_BITS                               4      /* у F1 и F4 */
#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 (;;); }

The kernel reads only the two shifted lines: 0xF0 is the priority of its own PendSV and SysTick, 0x50 is the BASEPRI threshold (section 28). The "library" values are for NVIC_SetPriority, which does the shift itself. The threshold cannot be zero: starting the scheduler will stop on configASSERT. A forgotten configKERNEL_INTERRUPT_PRIORITY is replaced by the port with 255 — on STM32 this is the same lowest level 0xF0. configASSERT is the most useful line of the configuration: without it the kernel checks neither priorities nor calls from handlers (section 24.4).


33.2 What is enabled

The second group is the kernel's features. Each enabled one costs code, and some also cost memory.

Macro What it enables Default
configUSE_PREEMPTION preemption mandatory
configUSE_TIME_SLICING time slicing between equal priorities on each tick 1
configUSE_MUTEXES mutexes 0
configUSE_RECURSIVE_MUTEXES recursive mutexes 0
configUSE_COUNTING_SEMAPHORES counting semaphores 0
configUSE_TIMERS software timers and the daemon 0
configUSE_TASK_NOTIFICATIONS task notifications 1
configUSE_QUEUE_SETS queue sets 0
configUSE_TICKLESS_IDLE tickless mode 0
configSUPPORT_STATIC_ALLOCATION static creation 0
configSUPPORT_DYNAMIC_ALLOCATION dynamic creation 1

"Mandatory" means: if it is not defined, the build stops with a clear error from FreeRTOS.h ("Missing definition"). There are six such lines: besides configUSE_PREEMPTION, these are the two hooks below (configUSE_IDLE_HOOK and configUSE_TICK_HOOK), configUSE_16_BIT_TICKS (33.4) and two parameters from 33.1 — configMAX_PRIORITIES and configMINIMAL_STACK_SIZE.

The most dangerous is the default of 0 for mutexes and timers: forget the line and the functions are silently thrown out. A call to xSemaphoreCreateMutex will not compile ("undeclared function"), while xTimerCreate will compile and fail only at link time.

Hooks

#define configUSE_IDLE_HOOK   0     /* vApplicationIdleHook на каждом проходе простоя */
#define configUSE_TICK_HOOK   0     /* vApplicationTickHook на каждом тике */
#define configUSE_MALLOC_FAILED_HOOK    1
#define configCHECK_FOR_STACK_OVERFLOW  2

The last two are always enabled — they catch silent errors (sections 25, 26). The first two — as needed. The idle hook is called from the idle task and must not block; the tick hook is called from the SysTick handler and allows only FromISR calls.

Functions one by one

#define INCLUDE_vTaskDelay                   1
#define INCLUDE_uxTaskGetStackHighWaterMark  1
#define INCLUDE_xTaskGetSchedulerState       1

The INCLUDE_ macros do not configure behavior — they add or remove code. A forgotten one gives a link error on a function that exists in the sources. Some pull each other in: setting event group flags from an interrupt needs INCLUDE_xTimerPendFunctionCall — xEventGroupSetBitsFromISR works through the timer daemon — and without it the link error will be on a function you never called.


33.3 Memory and statistics

#define configTOTAL_HEAP_SIZE           (10 * 1024)
#define configUSE_TRACE_FACILITY        1
#define configUSE_STATS_FORMATTING_FUNCTIONS  0
#define configGENERATE_RUN_TIME_STATS   0

The heap is the whole of section 25; here we only recall that the 10 KB from this configuration is half the memory of the F103C8, and the kernel has no default of its own.

Tracing adds fields for the debugger to the kernel structures: task numbers, queue state. It costs a few bytes per object and gives visibility in the debugger (section 35) — enable it during development.

Statistics formatting is the functions that print the task list as text. They pull in printf-like code and the stack for it. Enable them for debugging, disable them in the product.

Run time statistics require a time source more precise than the tick — usually a hardware timer at 10–100 kHz — and two macros to configure it and read it. It shows what percentage of the processor each task has consumed; the only honest answer to the question "why is the device slow".


33.4 Advanced

#define configUSE_CO_ROUTINES               0
#define configUSE_APPLICATION_TASK_TAG      0
#define configUSE_16_BIT_TICKS              0
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1

Co-routines are an outdated mechanism for very small processors; they are not needed on Cortex‑M, and croutine.c need not be compiled.

Task tags are an arbitrary value attached to a task; rarely needed.

16-bit ticks — no, never on Cortex‑M (section 24).

The name from kernel 11. There this macro is replaced by configTICK_TYPE_WIDTH_IN_BITS: in a new configuration you write #define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS, and the old name remains only in configurations inherited from kernel 10 (section 24).

Several notifications per task — when one slot is not enough and different sources must wake one task independently.


33.5 Tickless mode

The normal mode: SysTick wakes the processor a thousand times a second, even when all tasks are asleep and there is nothing to do. Each wakeup is an exit from sleep, a handler, a list check, a return to sleep. For a battery-powered device this is the main consumer.

Tickless mode works differently. When the idle task sees that the next job is N ticks away, it:

  1. stops SysTick;
  2. reprograms it for the whole expected idle time;
  3. executes WFI — the processor goes to sleep;
  4. on wakeup counts how much time has passed and corrects the tick count as if the ticks had been running.

The device sleeps until the nearest deadline or until an external interrupt — whichever comes first. Tasks do not notice the difference: their delays expire on time.

#define configUSE_TICKLESS_IDLE                 1
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP   2

The second macro is a threshold: sleeping makes sense only if the idle time is at least two ticks. Reprogramming the timer and correcting the count cost more than a sleep of one tick saves.

The limit: 233 milliseconds

SysTick is 24-bit, it holds 16777215 cycles. At 72 MHz and a 1 kHz tick one tick is 72000 cycles; so in one sleep no more than 233 ticks can be suppressed.

Core One tick, cycles Sleep limit
72 MHz 72000 233 ms
8 MHz 8000 2.1 s

A task that sleeps for a second will wake the processor five times — because of the SysTick limit, not because of its own deadline. Each wakeup has the same overhead, only less often.

A second of idle time: a thousand wakeups with the tick, five without the tick at 72 MHz and one at 8 MHz in sleep

Two ways forward:

Keep the core at a lower frequency all the time. The 2.1 s limit from the table belongs to a device whose core actually runs at 8 MHz (configCPU_CLOCK_HZ equals 8000000). You cannot lower the frequency only for the duration of sleep: the sleep function computes the SysTick reload in 72 MHz cycles and starts it before calling the macros, and SysTick is clocked from the core. If you switch to 8 MHz in the macro, each suppressed tick lasts not 1 ms but 9: a sleep of 233 ticks takes 2.1 s, and after wakeup the tick count will add the same 233. Kernel time runs 9 times slower during sleep, and vTaskDelay(1000) in an idle device stretches to almost 9 s. If you really need to change the frequency for the duration of sleep — do it only in your own sleep function, which recalculates the reload for the new frequency.

The sleep function calls the macros themselves before and after WFI:

#define configPRE_SLEEP_PROCESSING(x)   periph_off()   /* выключить лишнее */
#define configPOST_SLEEP_PROCESSING(x)  periph_on()    /* вернуть как было */

The argument x is the expected idle time; by writing zero into it you can cancel the kernel's sleep and do your own.

How the kernel handles this argument. The sleep function in the Cortex‑M port copies the expected idle time, hands the copy to the macro and executes WFI only if the copy is still greater than zero. Here is the same logic on its own, with two variants of the macro:

/* передать время простоя как есть — ядро заснёт само */
#define PRE_SLEEP_PASS(x)  PreSleepProcessing(x)

/* обнулить аргумент до вызова — ядро не выполнит WFI, функция получит ноль */
#define PRE_SLEEP_ZERO(x)  do { (x) = 0; PreSleepProcessing(x); } while (0)

/* логика port.c: копия, которую макрос вправе обнулить */
#define PORT_SLEEP(PRE, expected)                       \
    do {                                                \
        idle_t xModifiableIdleTime = (expected);        \
        PRE(xModifiableIdleTime);                       \
        if (xModifiableIdleTime > 0)                    \
            WFI();                                      \
    } while (0)

The expected idle time is 100 ticks:

Macro PreSleepProcessing receives Kernel WFI
PRE_SLEEP_PASS 100 ticks executed
PRE_SLEEP_ZERO 0 not executed — the compiler removed it from the code

⚠️ An argument zeroed in advance turns sleep off. The PRE_SLEEP_ZERO variant is found in ready-made configurations. With it tickless mode is enabled, but the current stays the same: the kernel does not sleep, and your function gets zero instead of the idle time and does not know how long it may sleep. Pass the time as it is — or go to sleep in PreSleepProcessing yourself and then write zero deliberately.

Replace the timer. The sleep function in the port is declared weak — it can be overridden entirely and use a timer that keeps running in deep sleep modes: the RTC or a low-frequency timer (section 10). Then the sleep limit is hours, not milliseconds. This is the only way to get into Stop mode: the port itself only does WFI, that is, Sleep mode — the core is stopped, the clocks are alive. In Stop SysTick is stopped, and without your own sleep function the tick count would be left uncorrected (vTaskStepTick). Standby cannot be combined with tasks at all: memory is lost, and waking from it is a fresh start of the firmware (section 10.4).

⚠️ Peripherals work as usual in tickless mode. A sleeping processor does not get in the way of the ADC, DMA and UART; an interrupt from any of them will wake it. But they also draw current as usual — tickless mode saves only on the core. Real savings start with switching off peripherals in PRE_SLEEP_PROCESSING.

What to check after enabling the mode

Software timers — the kernel takes their deadlines into account when computing idle time, but the timer daemon's tick is suppressed along with the rest.

Tasks that poll frequently break the mode: if even one does not sleep longer than two ticks, there will be no sleep. Processor load without the tick should be close to zero when idle — otherwise there is no point.

The debugger — many stop tracing in WFI; for the duration of debugging the mode is often turned off.


33.6 Pitfalls

All delays are wrong, but proportionally. configCPU_CLOCK_HZ does not match the real frequency.

An #error from FreeRTOS.h. A mandatory macro is not defined; the message names it.

A link error on a function that exists. Its INCLUDE_ is not enabled.

xSemaphoreCreateMutex is "undeclared", xTimerCreate is not found at link time. configUSE_MUTEXES or configUSE_TIMERS is not defined: their default is 0.

Software timers do not build. One of the three configTIMER_* is missing.

xEventGroupSetBitsFromISR does not link. INCLUDE_xTimerPendFunctionCall is missing: flags from an interrupt are set by the timer daemon.

The device does not sleep in tickless mode. Some task wakes up more often than every two ticks; or the expected idle time is below the threshold.

Tickless mode is enabled, but the current has not dropped. The configPRE_SLEEP_PROCESSING macro zeroes the argument before calling your function: the kernel does not execute WFI, and your function has no sleep of its own (33.5).

It sleeps, but not for long. The SysTick limit is 233 ms at 72 MHz. You need another timer or the core at a lower frequency all the time.

Sleep is 9 times longer than requested, the kernel clock lags. The core frequency was lowered in configPRE_SLEEP_PROCESSING, while the SysTick reload had already been computed for 72 MHz by that moment (33.5).

Woke up — and the time drifted. Your own sleep function did not correct the tick count with vTaskStepTick.

printf does not work after enabling statistics. The formatting functions need stack that the calling task does not have.


33.7 Summary

Next: section 33а "A watchdog for several tasks: who has hung and how to notice it" — a supervisor task with a deadline for each task, IWDG and WWDG, a snapshot before the reset.

Open in the interactive handbook