Performance and Runtime Analysis With FreeRTOS
Love using FreeRTOS? Love performance monitoring? Then take a look at how to configure both for your projects as well as the ARM Cortex Cycle Counter.
Join the DZone community and get the full member experience.
Join For FreeOne of the great things about the FreeRTOS operating system is that it comes with free performance analysis: It shows me how much time is spent on each task. Best of all: It shows it in a graphical way inside Eclipse, too:
In the above output, I see that my application is now 97.5% idle, which is a good thing and matches my expectations — as this robot is just waiting to run on a track.
How to Get That Kind of Information
For the graphical view in Eclipse, I need an Eclipse plugin (see Better FreeRTOS Debugging in Eclipse). That plugin is already pre-installed in the NXP MCUXpresso IDE.
Another way to see that information is using the ‘tasklist’ command, which sends the output to a console (Segger RTT, UART, USB or similar):
This command is available on the McuOnEclipse FreeRTOS available on GitHub.
How Does it Work?
FreeRTOS records, at the time of every task switch, how much time has been passed (or consumed) by that task switched out. For this, it uses a 32-bit counter inside the task information structure. This is actually the counter shown by the ‘tasklist’ command under the ‘Runtime’ column. The percentage is then calculated based on the numbers, which sum up as the total runtime.
The counter value inside FreeRTOS is a 32-bit value, so it is not really well-suited for very long measurement periods.
In order to collect these numbers, two FreeRTOS configuration defines have to be set to 1 in FreeRTOSConfig.h:
#define configUSE_TRACE_FACILITY 1 /* 1: include additional structure members and functions to assist with execution visualization and tracing, 0: no runtime stats/trace */
#define configGENERATE_RUN_TIME_STATS 1 /* 1: generate runtime statistics; 0: no runtime statistics */
The two above configuration items have a graphical setting if using Processor Expert:
Counter
As mentioned above: FreeRTOS keeps track of the time spent for each task. But this is actually not the real time, it is just some kind of timer counter value if config GENERATE_RUN_TIME_STATS is turned on. In that case, the FreeRTOSConfig.h needs two configuration macros with the two application functions provided:
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() AppConfigureTimerForRuntimeStats()
#define portGET_RUN_TIME_COUNTER_VALUE() AppGetRuntimeCounterValueFromISR()
The first one is to configure the timer at RTOS startup, the second one is to return the actual timer counter value.
The general rule of thumb is that the timer used for measuring the task should be around 10x faster than the real tick counter for good results.
Users of Processor Expert have yet another advantage: They can easily configure such a timer in the Processor Expert setting, and everything is taken care of:
Below is the setting for a timer that runs 10 times faster than the 1 kHz RTOS tick timer:
Below is the resulting timer code:
/*
** ===================================================================
** Method : FRTOS1_OnCounterRestart (component FreeRTOS)
**
** Description :
** This method is internal. It is used by Processor Expert only.
** ===================================================================
*/
void RTOSCNTRLDD1_OnCounterRestart(LDD_TUserData *UserDataPtr __attribute__((unused)))
{
FRTOS1_RunTimeCounter++; /* increment runtime counter */
}
/*
** ===================================================================
** Method : FRTOS1_AppConfigureTimerForRuntimeStats (component FreeRTOS)
** Description :
** Configures the timer for generating runtime statistics
** Parameters : None
** Returns : Nothing
** ===================================================================
*/
#if configGENERATE_RUN_TIME_STATS
void FRTOS1_AppConfigureTimerForRuntimeStats(void)
{
#if configGENERATE_RUN_TIME_STATS_USE_TICKS
/* nothing needed, the RTOS will initialize the tick counter */
#else
FRTOS1_RunTimeCounter = 0;
FRTOS1_RunTimeCounterHandle = RTOSCNTRLDD1_Init(NULL);
(void)RTOSCNTRLDD1_Enable(FRTOS1_RunTimeCounterHandle);
#endif
}
#endif /* configGENERATE_RUN_TIME_STATS */
/*
** ===================================================================
** Method : FRTOS1_AppGetRuntimeCounterValueFromISR (component FreeRTOS)
** Description :
** returns the current runtime counter. Function can be called
** from an interrupt service routine.
** Parameters : None
** Returns :
** --- - runtime counter value
** ===================================================================
*/
uint32_t FRTOS1_AppGetRuntimeCounterValueFromISR(void)
{
#if configGENERATE_RUN_TIME_STATS
#if configGENERATE_RUN_TIME_STATS_USE_TICKS
return xTaskGetTickCountFromISR(); /* using RTOS tick counter */
#else /* using timer counter */
return FRTOS1_RunTimeCounter;
#endif
#else
return 0; /* dummy value */
#endif
}
The interrupt service routine counts up a timer counter, which then is used to measure the time spent inside a task.
If interrupts are a concern from a performance point of view and no high precision is needed, then the Processor Expert port again has a nice feature: instead of using a dedicated timer, it simply re-uses the tick timer of the RTOS. For this, there is an extra setting to configure it:
#define configGENERATE_RUN_TIME_STATS_USE_TICKS 0 /* 1: Use the RTOS tick counter as runtime counter. 0: use extra timer */
The corresponding setting is the following in the UI:
With this, some basic measuring can be done. But this is not suitable for measuring short task execution times. Say the RTOS tick timer is 1 ms — then tasks running for less than 1 ms will be rarely measured.
Using ARM Cortex Cycle Counter
Another way to measure task execution time on ARM Cortex M (e.g. ARM Cortex-M4 or M7) is to use the Cortex Cycle Counter.
#include "KIN1.h"
static uint32_t prevCycleCounter, cycleCntCounter = 0;
void AppConfigureTimerForRuntimeStats(void) {
cycleCntCounter = 0;
KIN1_InitCycleCounter();
prevCycleCounter = KIN1_GetCycleCounter();
}
uint32_t AppGetRuntimeCounterValueFromISR(void) {
uint32_t newCntr, diff;
newCntr = KIN1_GetCycleCounter();
diff = newCntr-prevCycleCounter;
prevCycleCounter = newCntr;
cycleCntCounter += diff>>12; /* scale down the counter */
return cycleCntCounter;
}
That approach measures the cycle counter difference between two calls to AppGetRuntimeCounterValueFromISR() and counts up a counter based on that value. In order not to count up too fast, the counter value is scaled down with a shift by 12 bits in above implementation (using a 120 MHz ARM Cortex-M4). For faster or slower running cores you might need to tweak that value.
Summary
FreeRTOS has built-in functions to track task execution time. It is implemented with a counter inside each task descriptor so does not need much RAM. The application has to provide a counter which is typically 10x faster than the tick time to get some reasonable measurements. But even using the tick counter itself gives some rough performance analysis data. Otherwise, the application can offer a periodic timer counter. If using an ARM Cortex-M3/M4/M7, using the ARM Cortex Cycle counter is an alternative, as it does not need any timer or interrupts.
Happy performing!
Links
- MCUXpresso IDE web page: http://www.nxp.com/mcuxpresso/ide
- MCUXpresso IDE community: http://www.nxp.com/mcuxpresso/ide/forum
- Better FreeRTOS Debugging in Eclipse
- McuOnEclipse Library project: https://github.com/ErichStyger/McuOnEclipseLibrary/tree/master/lib/FreeRTOS/Source
- ARM Cortex-M Cycle Counter: https://mcuoneclipse.com/2017/01/30/cycle-counting-on-arm-cortex-m-with-dwt/
Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments