HomeUncategorizedStack Management & Overflow Detection

How to Manage FreeRTOS Task Stack Memory and Prevent Stack Overflow in STM32

Stack memory management is one of the most critical aspects of developing embedded systems with FreeRTOS. Every task you create in your FreeRTOS system gets its own dedicated stack. When a task uses more stack memory than allocated, the excess data overflows into adjacent memory areas. This causes data corruption and unpredictable system crashes.

In this tutorial, we will see how to monitor stack usage effectively. We will use practical examples with STM32 microcontrollers and FreeRTOS. You will learn how to create a monitor task that tracks stack usage of all your other tasks. We will also enable stack overflow detection and implement a hook function that catches overflow events automatically.

This is the 8th tutorial in the STM32 CMSIS-OS series. If you have not gone through the previous parts of this series, here are the links:

How to Manage FreeRTOS Task Stack Memory and Prevent Stack Overflow in STM32

Why Stack Management Matters in FreeRTOS Projects

Stack memory is critical in FreeRTOS systems. Every task you create gets its own dedicated stack space. When a task uses more memory than allocated, it creates serious problems. Understanding stack management helps you build reliable embedded systems that do not crash unexpectedly.

Understanding Stack Overflow Problems

A stack is a memory region where a task stores temporary data. This includes local variables, function parameters, and return addresses. The stack grows downward as a task executes more functions. When you allocate stack size during task creation, you set a maximum limit for that task’s stack memory.

Stack overflow happens when a task uses more memory than its allocated stack size. The excess data spills into adjacent memory areas. This corrupts data that belongs to other tasks or system components. The system then behaves unpredictably or crashes completely.

Here is what makes stack overflow particularly dangerous:

  • Stack overflows do not always cause immediate crashes. Instead, they silently corrupt data in nearby memory. The system continues running until the corrupted data is actually accessed by the processor. This unpredictable behavior makes stack overflows extremely difficult to find and fix during development and testing.

Common Issues with Stack Overflow Detection

Several common issues make stack overflow detection challenging.

  • Unexpected memory consumption: Functions like printf() consume far more stack space than developers expect. A single printf() call can use around 150 bytes of stack for internal buffers. This happens regardless of how short your text string is. The function reserves buffers in advance, not based on the actual string length.
  • Silent failures: Unlike other programming errors, stack overflow does not trigger immediate exceptions. The overflow might happen, but your code keeps running. It only crashes when the corrupted memory gets accessed. By then, you have lost the connection between the overflow event and the crash.
  • Compiler optimizations: Different compiler optimizations affect stack usage differently. Code compiled with optimization level O2 uses different amounts of stack than the same code compiled with O0. This makes it difficult to estimate stack sizes during initial development.
  • Function call chains: When function A calls function B, which calls function C, the stack grows with each call. All local variables and parameters from all three functions occupy stack space simultaneously. Long function call chains can consume unexpected amounts of stack memory.
  • Multiple tasks with limited memory: Embedded systems often have limited RAM. When you create multiple tasks, you must allocate stack space to each one. The total allocated stack across all tasks must fit within available RAM. Balancing individual task stack sizes while managing total system memory becomes complex.
image shows the common problems that make stack overflow detection difficult

FreeRTOS Tools for Stack Monitoring

The FreeRTOS provides two essential functions to help you monitor stack usage. These functions give you visibility into how much memory your tasks are using. Understanding these tools is the first step toward preventing stack overflows.

Using osThreadGetStackSpace() Function

The osThreadGetStackSpace() function is your primary tool for monitoring stack usage during development and testing. This function retrieves information about how much free stack memory remains available in a task.

The function requires one parameter: the task handle. This is simply the identifier of the task you want to monitor. The function returns a 32-bit unsigned integer representing the number of free bytes currently available in the task’s stack.

Here is a simple example of calling this function:

uint32_t free_stack = osThreadGetStackSpace(task_handle);
printf("Free stack: %u bytes\n", free_stack);

This small code tells you how much room is left in the task’s stack. If the value is very small, your task is using a lot of its allocated memory. If the value is large, your task has plenty of room to spare.


Measuring High Water Mark Values

The value that osThreadGetStackSpace() returns is called the high water mark. This term comes from observing water levels in a tank. The high water mark is the highest level the water ever reached. Similarly, the stack high water mark is the minimum amount of free stack space that has ever been recorded for that task.

Understanding high water mark values is crucial. It represents the worst-case scenario for stack usage. If a task’s high water mark is 100 bytes, it means the task has used at least 28 bytes of its allocated 128-byte stack at some point during execution.

Here is an example that demonstrates this concept:

Imagine a task with 256 bytes of allocated stack. During the first execution, the task uses 120 bytes, leaving 136 bytes free. This becomes the high water mark. Later, the task uses 150 bytes, leaving only 106 bytes free. The high water mark remains 106 bytes. The high water mark never increases. It only stays the same or decreases as the task executes more code paths and uses more memory.

This behavior is useful because it tells you the absolute minimum stack space that is safe for that task. Even if the task appears to be running fine right now, you know it once needed at least that much stack space. Therefore, you should allocate at least that amount to the task permanently.

The image below shows how high water mark tracking works:

image shows how high water mark tracking works in STM32 FreeRTOS.

Setting Up Project in STM32 CubeMX

Let us set up the project in CubeMX. We will configure the Tasks, UART, and GPIO for this project.

Creating the Tasks in CubeMX

Go to the Tasks and Queues tab and add 3 regular tasks and one monitor task. The monitor task will monitor the stack usage of the regular tasks.

The images below shows the task configurations.

Tasks and Queues tab showing the task1 configuration
Tasks and Queues tab showing the task12configuration
Tasks and Queues tab showing the task3 configuration
Tasks and Queues tab showing the monitor task configuration

Here Task1, Task2 and Task3 have the Normal Priorities while their stack sizes differs. This is intentional, as we will see how much free stack is available in each case.

monitorTask has the lowest priority. It is kept to low so it does not preempt other tasks while they are running.


The parameter uxTaskGetStackHighWaterMark is already enabled by default. This parameter is needed for the stack monitoring to work, so confirm if this parameter is enabled.

Image shows the uxTaskGetStackHighWaterMark is enabled by default in STM32CubeMX.

Configuring LPUART1 for printf Output

We will use printf to print the received data to a serial console. On the STM32L496 Nucleo board, the Virtual COM port routes UART data through the ST-LINK USB connection. Looking at the board schematic, ST-LINK RX is connected to LPUART1 TX and ST-LINK TX is connected to LPUART1 RX. These map to pins PG7 and PG8.

Image shows the virtual com port connection in STM32L496 Nucleo.

Go to Connectivity and enable LPUART1 in Asynchronous mode. By default, CubeMX assigns pins PC0 and PC1, so change these to PG7 (TX) and PG8 (RX).

Image shows the LPUART configuration in Nucleo L496 to print the data on the serial console.

Use the following settings:

ParameterValue
Baud Rate115200
Word Length8 bits
ParityNone
Stop Bits1

This is the standard UART configuration and matches what we will set in the serial console later.


Changing HAL Time Base from SysTick to TIM6

This step is very important in STM32 FreeRTOS projects. By default, STM32 uses SysTick as the HAL time base. But FreeRTOS also uses SysTick for its scheduler.
If both HAL and FreeRTOS share SysTick, it may create timing conflicts.

So we should dedicate:

  • SysTick → FreeRTOS Scheduler
  • TIM6 (or TIM7) → HAL Time Base
Image shows how to change the timebase when using FreeRTOS in STM32.
  • Go to System Core → SYS
  • Change Timebase Source from SysTick to TIM6 (or TIM7)

TIM6 and TIM7 are basic timers. They do not support PWM or advanced features. That is why they are ideal for this purpose.


Enabling Newlib Reentrant Support

Go to Middleware → FreeRTOS → Advanced Settings and Enable Use newlib reentrant.

Image shows how to enable NewLib Reentrant for FreeRTOS in STM32.

In an RTOS environment, multiple tasks may call functions like printf(); sprintf(); malloc(); etc. These standard C library functions are not thread-safe by default.

If two tasks call printf() at the same time, the output may get corrupted. Enabling newlib reentrant makes these functions safe in multitasking systems. It uses slightly more memory, but it prevents difficult debugging issues later.

Creating a Monitor Task to Track Stack Usage

A monitor task is a dedicated FreeRTOS task that continuously observes the stack usage of other tasks without interfering with them.

Retrieving Stack Information from Tasks

We will Use osThreadGetStackSpace() to read the free stack space from each task. The function requires a task handle as parameter:

uint32_t task1Stack = osThreadGetStackSpace(Task1Handle);
uint32_t task2Stack = osThreadGetStackSpace(Task2Handle);
uint32_t task3Stack = osThreadGetStackSpace(Task3Handle);

Printing Stack Data to Serial Console

After measuring the task space, we will print it to the serial console using printf():

printf("Stack Free - Task1: %lu Bytes\t Task2: %lu Bytes\t Task3: %lu Bytes\n", 
       task1Stack, task2Stack, task3Stack);

Complete Monitor Task Implementation

Here is the complete monitor task code:

void StartmonitorTask(void *argument)
{
  uint32_t task1Stack, task2Stack, task3Stack;
  
  for(;;)
  {
    osDelay(5000);  // Wait 5 seconds between measurements
    
    task1Stack = osThreadGetStackSpace(Task1Handle);
    task2Stack = osThreadGetStackSpace(Task2Handle);
    task3Stack = osThreadGetStackSpace(Task3Handle);
    
    printf("Stack Free - Task1: %lu Bytes\t Task2: %lu Bytes\t Task3: %lu Bytes\n", 
           task1Stack, task2Stack, task3Stack);
  }
}

The monitor task runs continuously. Every 5 seconds it reads the free stack from all three tasks and prints the values. The application tasks (Task1, Task2, Task3) remain empty at this stage. This shows the baseline stack usage when tasks contain no code.

When you run this code with empty tasks, the serial console displays the following:

Image shows the serial console displaying the free stack available in all 3 tasks.

These values are the highest possible stack available because the tasks contain no code. As we add code to the tasks, these numbers will decrease, showing exactly how much stack the new code consumes.

Testing Stack Usage with Printf and Arrays

Measuring Printf Stack Consumption

Now add a printf() call to Task1 to see how much stack it consumes:

void StartTask1(void *argument)
{
  for(;;)
  {
	  printf ("Hello from Task 1\n");
    osDelay(2000);
  }
}

Now run the monitor task and observe the output:

Image shows the task1 free stack dropped after using the printf statement.

Task1’s free stack dropped from 384 bytes to 196 bytes. This means printf() consumed approximately 188 bytes in Task1’s context. Task2 and Task3 remain unchanged because they contain no code.

The string length does not matter. A single character print uses the same stack space as a long string because printf() reserves internal buffers in advance.


Testing with Local Arrays

Now add a local array of 100 bytes to Task1:

void StartTask1(void *argument)
{
  uint8_t foo[100];
  
  for(;;)
  {
	  printf ("Hello from Task 1\n");
    osDelay(2000);
  }
}

The image below shows the output on the serial console:

Image shows the task1 free stack dropped after creating a local array inside the task function.

Free stack dropped from 196 bytes to 92 bytes. The array consumed approximately 104 bytes (100 bytes for the array plus 4 bytes of compiler overhead for alignment and function frame setup).


Understanding Stack Overflow Silent Failures

Now increase the array size to 200 bytes:

void StartTask1(void *argument)
{
  uint8_t foo[200];
  
  for(;;)
  {
	  printf ("Hello from Task 1\n");
    osDelay(2000);
  }
}

The image below shows the output on the serial console:

Image shows the task1 free stack dropped to 0 bytes after creating a local array inside the task function.

The monitor shows 0 bytes free. This indicates a serious stack overflow. However, Task1 continues running. The task does not crash immediately.

This is the dangerous nature of stack overflow. The overflow happens, but the corrupted memory area is not accessed yet. The task keeps executing. The system crashes later when the corrupted data is actually used. This unpredictable behavior makes stack overflow extremely difficult to find.

The solution is early detection. Monitor your stack usage during development. When you see free stack approaching zero, increase the task’s allocated stack size immediately. Do not wait for crashes to happen.

Implementing Stack Overflow Detection

The stack overflow detection is not enabled by default. We need to first enable it ourselves in the STM32CubeMX.

Enabling Stack Overflow Hook in CubeMX

Go to Middleware > FreeRTOS > Config Parameters and find the section Hook Function Related Definitions. Look for the parameter Check for Stack Overflow Hook. By default, it is disabled.

Enable it and select Option 2 from the dropdown. This activates automatic stack overflow detection.

Image shows how to enable CHECK FOR STACK OVERFLOW in STM32CubeMX.

When enabled, a function called vApplicationStackOverflowHook is automatically generated in the FreeRTOS.c file. We need to write the implementation code inside this function to handle overflow events.


Option 1 vs Option 2 Detection Methods

FreeRTOS provides two detection methods:

Option 1: Checks if the stack pointer has gone out of bounds at every task context switch. This method is faster but may miss overflows that happen between context switches.

Option 2: Uses a pattern or marker value placed at the end of the stack to detect overflows more reliably. This method catches more overflow events because it checks continuously, not just during context switches. Option 2 uses slightly more processing power but provides better detection accuracy.

For most projects, Option 2 is recommended because it catches stack overflows more reliably.


Creating vApplicationStackOverflowHook Function

After enabling the hook in CubeMX and regenerating the code, open the FreeRTOS.c file. Find the vApplicationStackOverflowHook function:

void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{
    // Add your overflow handling code here
}

This function receives two parameters:

  • xTask: The handle of the task that overflowed
  • pcTaskName: The name of the task as a string

Add an infinite loop to stop execution when overflow is detected:

void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{
    while(1)
    {
        
    }
}

When a stack overflow occurs, this infinite loop prevents the system from continuing. This stops further corruption and prevents unpredictable behavior.

To log which task caused the overflow, use direct UART transmission (not printf(), as it may fail in this critical context):

extern UART_HandleTypeDef hlpuart1;

void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{
    // Re-initialize UART to ensure it works
    HAL_UART_DeInit(&hlpuart1);
    HAL_UART_Init(&hlpuart1);
    
	  char msg[100];
	  int len = sprintf (msg, "Stack Overflow Detected in: %s\n", pcTaskName);
	  HAL_UART_Transmit(&hlpuart1, (uint8_t *)msg, len, 1000);
    
    // Stop execution
    while(1);
}

When a stack overflow occurs, the hook function prints the task name to the serial console, then enters an infinite loop. The system halts, preventing further corruption. You see the error message on your serial terminal and know exactly which task caused the problem.


Output

When a stack overflow is detected, the serial console displays the error message:

Image shows the log printed by stack overflow hook function, after the stack overflow is triggered on task 1.

At this point, the system stops executing all tasks. The infinite loop in the hook function prevents any further code execution.

How to fix it:

Reduce the stack overflow-causing code or increase Task1’s allocated stack size in CubeMX. Regenerate the code, rebuild the project, and flash it again. The system should now run without triggering the overflow hook.

Video Tutorial

STM32 FreeRTOS Stack Overflow Detection — Monitor and Handle Stack Overflow

In this video, you will see how to enable the stack overflow hook in CubeMX, create a monitor task that tracks stack usage, add code that causes overflow in Task1, observe the overflow detection on the serial console, increase the task stack size, and verify the system runs without errors.

Watch the FreeRTOS Stack Overflow Detection Tutorial

Conclusion

In this article, we explored how to manage FreeRTOS task stack memory and prevent stack overflow problems in STM32 microcontroller projects. We learned why stack management matters, discovered the two essential FreeRTOS monitoring tools—osThreadGetStackSpace() and osThreadGetStackSize()—and created a dedicated monitor task to continuously track stack usage across all your tasks. We demonstrated how to enable stack overflow detection in CubeMX, implemented the vApplicationStackOverflowHook() function to catch overflow events, and tested stack consumption with practical examples using printf() and local arrays.

These tools and techniques transform stack management from guesswork into data-driven engineering. By monitoring stack usage during development, detecting overflows early, and adjusting task sizes based on actual measurements, you prevent stack corruption and system crashes before they reach production. The monitor task provides continuous visibility into how your tasks use memory, while the overflow hook acts as a safety net that catches critical problems immediately. This comprehensive approach ensures your FreeRTOS systems remain reliable, predictable, and production-ready.

Browse More CMSIS RTOS Tutorials

STM32 RTOS Project Download

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

STM32 FreeRTOS Stack Management FAQs

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.