2 useful tricks for debugging freeRTOS
For less complicated projects, the foreground-background architecture (ie. main loop supported by interrupts) is perfectly fine. But at some level of complexity it is much better to switch to RTOS for many reasons, from maintainability to concurrect execution. However, what’s not so great on RTOS is debugging – that extra level of complexity and unpredictability makes it a nighmare. Thankfully, there’s a couple of functions you can use to get more useful info from your system.
List all running tasks & their info in a human readable format
This is simply done using the function vTaskList(char* buffer)
, which will print some useful, human-readable information into the passed buffer (note that you need about 30 – 40 bytes per task). You can then, for example, send this buffer via UART:
Note that the first row is not part of the buffer – neither is the heap info, I got that using xPortGetFreeHeapSize()
and xPortGetMinimumEverFreeHeapSize()
.
The first column states the name of the task (which you pass during the task creation), the second is state (X = current task, R = running, B = blocked, etc.). Next column is priority (self-explanatory), followed by the lowest amount of free bytes in the stack of that particular task. In other words, it’s a watermark of how close you came to overflowing the stack of that individual task. And the last column is a simple number, which freeRTOS assigns to every task (it follows the order in which the tasks were created AFAIK).
Also note: For this to work configUSE_TRACE_FACILITY
and configUSE_STATS_FORMATTING_FUNCTIONS
must be set to 1 in your config file.
Trace hook macros
This cryptic name means a set a macros, which are included at certain points of the core RTOS functionality (ie. mostly the context switching part). Normally, these macros are defined empy, but you can substitue them with your own code.
For example, the macro traceTASK_SWITCHED_IN()
gets called every time there is a switch into a task. So what you can do is place some debugging code here – you could write a number to a parallel GPIO, write a voltage to DAC or put a message into log. However, since these macros will be called relatively frequently and they are inside an interrupt, you should try to keep them as short as possible.
This is the output from one of my testing projects. Binary 0 is idle task, the upper line is a low priority display task and the lower is a high priority radio task, programmed for periodic execution by using vTaskDelayUntil
. Since they are both using the same SPI bus, there is a mutex – you can actually spot the place where “fight” for the control of the SPI bus.
Implementing such a functionality requires the following:
- each task must call
vTaskSetApplicationTaskTag(NULL, (long int(*)(void *))3);
before entering it’s loop. You can pass any argument you want, but numbers are probably the best choice. - for this function to be available, you must define
configUSE_APPLICATION_TASK_TAGĀ
as 1 (I had to add it to the RTOS config file, as it is not there by default). - in the same RTOS config header I had to include the definition for the macro itself, but also the
stm32fxxx.h
header (since we are using the register definitions from this file. The macro looks as follows:#define traceTASK_SWITCHED_IN() {uint16_t val = GPIOA->ODR; val &=~(0b11 << 8); GPIOA->ODR = (val | (((int)pxCurrentTCB->pxTaskTag) << 8);}
. This clears bit PA8 and PA9 and writes the value into them in one R-M-W (read-modify-write).
Not only does this allow you to visualize what task is running when, but if you assing one bit to each task, you could measure duty cycle and thus how much CPU time each task takes (there is also a dedicated functionality for that in freeRTOS – but this is much more universal and also it does not bloat the code that much). Also it is extremely helpful for debugging timing issues.
There’s a ton of these macros defined all across freeRTOS – have a look here.