FreeRTOS stack size on ESP32 - words or bytes?
Although FreeRTOS1 is an indispensible tool for working on anything more than the simplest application on ESP32, there are some difficulties to master, such as multitasking. Multitasking using FreeRTOS
is accomplished by creating tasks with xTaskCreate()
or xTaskCreatePinnedToCore()
. In both of these calls, one of the parameters is uxStackDepth
which is the allocated stack size for the task. The FreeRTOS documentation on the subject is clear about the units for uxStackDepth
:
The number of words (not bytes!) to allocate for use as the task’s stack. For example, if the stack is 16-bits wide and uxStackDepth is 100, then 200 bytes will be allocated for use as the task’s stack. As another example, if the stack is 32-bits wide and uxStackDepth is 400 then 1600 bytes will be allocated for use as the task’s stack.
The FreeRTOS declaration of xTaskCreate()
is:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
);
where the units of uxStackDepth
are words not bytes.
So that’s the story in vanilla FreeRTOS. Then we would expect on ESP32 with 4-byte wide words that if wanted to provide a 4096 byte deep stack, we would use a uxStackDepth
value of 1024. But, the plot thickens. In the ESP-IDF implementation of FreeRTOS, the function declaration of xTaskCreate()
(and friends) differs:
static inline BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode,
const char *const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void *const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *const pxCreatedTask
);
Notice that the the stack size parameter is now const configSTACK_DEPTH_TYPE usStackDepth
and the documentation makes it clear:
IDF FreeRTOS also changes the units of ulStackDepth in the task creation functions. Task stack sizes in Vanilla FreeRTOS are specified in a number of words, whereas in IDF FreeRTOS, the task stack sizes are specified in bytes.2
So on ESP-IDF (and the ESP Arduino core), task creation is in bytes not words.
However, we also sometimes want to see how much of the stack allocation we’re using in order to fine-tune the allocation. In FreeRTOS, we can use the uxTaskGetStackHighWaterMark
API to inspect the stack high water mark. For example:
void vTask1( void * pvParameters ) {
UBaseType_t uxHighWaterMark;
/* Inspect our own high water mark on entering the task. */
uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
for( ;; )
{
/* Call any function. */
vTaskDelay( 1000 );
/* Calling the function will have used some stack space, we would
therefore now expect uxTaskGetStackHighWaterMark() to return a
value lower than when it was called on entering the task. */
uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
}
}
So does uxTaskGetStackHighWaterMark()
returns words or bytes? In the official FreeRTOS documentation for uxTaskGetStackHighWaterMark()
we read:
The value returned is the high water mark in words (for example, on a 32 bit machine a return value of 1 would indicate that 4 bytes of stack were unused). If the return value is zero then the task has likely overflowed its stack. If the return value is close to zero then the task has come close to overflowing its stack.3
So, then the answer is words. But does the ESP-IDF implementation differ? Here’s what the documentation says about it:
So the type returned by uxTaskGetStackHighWaterMark()
is determined by configSTACK_DEPTH_TYPE
. Out-of-the box in the ESP-IDF flavour of FreeRTOS.h, this is defined in FreeRTOS.h as:
#ifndef configSTACK_DEPTH_TYPE
/* Defaults to uint16_t for backward compatibility, but can be overridden
* in FreeRTOSConfig.h if uint16_t is too restrictive. */
#define configSTACK_DEPTH_TYPE uint16_t
#endif
Further, several comments on the official Espressif ESP32 forums posted by Espressif developers point to this function returning bytes not words. For example,
Stack is in bytes…4
and
Vanilla freertos is using dword for creating task and stack watermark. esp-idf is using bytes as base size in both functions.5
Conclusion
In the end, the short version of this story is that on ESP32, working in ESP-IDF and friends, that stack size is assigned and reported in bytes.