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_HZis the true core frequency, as set in RCC. The port divides it by the tick rate and gets theSysTickreload: 72000 cycles at 72 MHz and 1 kHz. Specify 36 MHz with a real 72 — the tick becomes half a millisecond, andvTaskDelay(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".
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:
- stops
SysTick; - reprograms it for the whole expected idle time;
- executes
WFI— the processor goes to sleep; - 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.
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_ZEROvariant 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 inPreSleepProcessingyourself 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
- The kernel knows about seventy
configmacros and about fifteenINCLUDE_; the mandatory ones (there are six) stop the build with a clear error, while mutexes and timers default to 0 — a forgotten line silently disables them. configCPU_CLOCK_HZmust match the real frequency: otherwise all delays are scaled silently.- Priorities for the kernel are two shifted lines:
0xF0forPendSVandSysTick, theBASEPRIthreshold0x50; a zero threshold stops the start onconfigASSERT, andconfigASSERTitself is always enabled. - A 1 kHz tick is a sensible default; the lower limit at 72 MHz is four hertz.
INCLUDE_macros add and remove code; a forgotten one gives a link error.- The idle hook does not block, the tick hook — only
FromISR. - Tickless mode stops
SysTick, reprograms it for the whole idle time and sleeps withWFI; tasks see no difference. - The limit of one sleep on
SysTickat 72 MHz is 233 ms; for longer — either keep the core at a lower frequency all the time, or override the sleep function to use another timer. Changing the frequency inPRE_SLEEP_PROCESSINGis not allowed: theSysTickreload has already been computed for 72 MHz by that moment. - The port does only
WFI— Sleep mode. Stop — only in your own sleep function on a timer that runs in Stop (LPTIM or RTC), with the tick count corrected throughvTaskStepTick. Standby is a shutdown with loss of memory: waking from it is a fresh start of the firmware, not a continuation of the tasks. - Peripherals in tickless mode stay alive and draw current as usual.
configPRE_SLEEP_PROCESSINGreceives a copy of the idle time, and zero in it cancels the kernel'sWFI. A macro that writes zero before calling your function turns sleep off unless that function sleeps by itself.
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.