1. Introduction
For performance considerations, sometimes we want to know the CPU‘s usage rate in order to assess the load of this CPU and whether it can adequately handle the current operating environment. This article will introduce a method for calculating the CPU occupancy rate and its implementation principles.
2. Porting Algorithm
2.1 Algorithm Overview
This algorithm is based on the operating system and is theoretically not limited to any operating system as long as there is task scheduling. This article will take FreeRTOS as an example to introduce the usage of this algorithm.
The algorithm introduced in this article comes from the Cube library, as shown in the following figure:
This article will use STM32F4 as an example, with the testing environment being the STM3240G-EVAL evaluation board.
2.2 Start Porting
This article takes the example code project STM32Cube_FW_F4_V1.10.0\Projects\STM324xG_EVAL\Applications\FreeRTOS\FreeRTOS_ThreadCreation as an example, using the IDE IAR.
Step 1:
Open the IAR and load the FreeRTOS_ThreadCreation project, add the cpu_utils.c file to the project, and add the corresponding header file directory in the project:
Step 2: Open the FreeRTOS configuration header file FreeRTOSConfig.h and modify the macro values of configUSE_IDLE_HOOK and configUSE_TICK_HOOK to 1:
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1 // Set this macro to 1
#define configUSE_TICK_HOOK 1 // Set this macro to 1
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ ((TickType_t) 1000)
#define configMAX_PRIORITIES (8)
#define configMINIMAL_STACK_SIZE ((uint16_t) 128)
Step 3: Continue to add traceTASK_SWITCHED_IN and traceTASK_SWITCHED_OUT definitions at the end of the FreeRTOSConfig.h header file:
#define traceTASK_SWITCHED_IN() extern void StartIdleMonitor(void); \
StartIdleMonitor()
#define traceTASK_SWITCHED_OUT() extern void EndIdleMonitor(void); \
EndIdleMonitor()
Step 4: In the main.h header file, include “cmsis_os.h”
Main.h :
#include “stm32f4xx_hal.h”
#include “stm324xg_eval.h”
#include “cmsis_os.h” // Include this header file
//…
Step 5: Modify the project properties to avoid requiring function prototypes during compilation:
Step 6: You can call the osGetCPUUsage() function anywhere in the user code to get the current CPU usage rate:
static void LED_Thread2(void const *argument)
{
uint32_t count;
uint16_t usage = 0;
(void) argument;
for(;;)
{
count = osKernelSysTick() + 10000;
/* Toggle LED2 every 500 ms for 10 s */
while (count >= osKernelSysTick())
{
BSP_LED_Toggle(LED2);
osDelay(500);
}
usage = osGetCPUUsage(); // Get the current CPU usage rate
/* Turn off LED2 */
BSP_LED_Off(LED2);
/* Resume Thread 1 */
osThreadResume(LEDThread1Handle);
/* Suspend Thread 2 */
osThreadSuspend(NULL);
}
}
Step 7: Compile and run the test
Monitor the value of the osCPU_Usage variable in the Live Watch window while in debug mode:
osCPU_Usage is a global variable defined in the cpu_utils.c file, representing the current CPU usage rate, a dynamic value. As shown in the figure above, the dynamic value of CPU usage rate is 20%. In the actual code, the current CPU usage rate is obtained by calling the osGetCPUUsage() function.
Thus, the introduction to the algorithm usage method is complete.
3. Algorithm Implementation Principle Analysis
During the operation of the operating system, it constantly switches between different tasks, and the scheduling process is driven by the system tick. Each time a system tick occurs, it checks whether the current running task’s environment requires a task switch, i.e., scheduling. If needed, it triggers PendSV and implements task scheduling by calling the vTaskSwitchContext() function in the PendSV interrupt. The CPU usage rate algorithm discussed in this article calculates the total time slices occupied by the idle task within a certain time period (1000 time slices), and subtracts the percentage of the idle task from 100 to obtain the percentage of working tasks, i.e., the CPU usage rate.
void vApplicationIdleHook(void)
{
if( xIdleHandle == NULL )
{
/* Store the handle to the idle task. */
xIdleHandle = xTaskGetCurrentTaskHandle(); // Record the idle task handle
}
}
This function is the idle task hook function, which runs every time the system switches to the idle task. Its purpose is to record the current idle task’s handle and save it to the global variable xIdleHandle.
void vApplicationTickHook (void)
{
static int tick = 0;
if(tick ++ > CALCULATION_PERIOD) // Every 1000 ticks, refresh the CPU usage rate
{
tick = 0;
if(osCPU_TotalIdleTime > 1000)
{
osCPU_TotalIdleTime = 1000;
}
osCPU_Usage = (100 – (osCPU_TotalIdleTime * 100) / CALCULATION_PERIOD); // This line of code is the specific calculation method for the CPU usage rate
osCPU_TotalIdleTime = 0;
}
}
This function is the tick hook function of the operating system. Every time a system tick interrupt occurs, it enters this hook function. This hook function is actually the specific algorithm for calculating CPU usage rate. The osCPU_TotalIdleTime is a global variable representing the total time slices occupied by the idle task within 1000 ticks, and the CALCULATION_PERIOD macro is set to 1000, meaning the CPU usage rate is recalculated every 1000 ticks.
The following two functions illustrate how to calculate osCPU_TotalIdleTime:
void StartIdleMonitor(void)
{
if( xTaskGetCurrentTaskHandle() == xIdleHandle ) // If it switches to the idle task
{
osCPU_IdleStartTime = xTaskGetTickCountFromISR();// Record the time point when switching to the idle task
}
}
void EndIdleMonitor(void)
{
if( xTaskGetCurrentTaskHandle() == xIdleHandle ) // If it switches out of the idle task
{
/* Store the handle to the idle task. */
osCPU_IdleSpentTime = xTaskGetTickCountFromISR() – osCPU_IdleStartTime; // Calculate how long the idle task has run
osCPU_TotalIdleTime += osCPU_IdleSpentTime;// Accumulate the time occupied by the idle task
}
}
These two functions are scheduler hook functions, called when tasks are switched in and out. The StartIdleMonitor() function records the time point when switching to the idle task, while the EndIdleMonitor() calculates how long the idle task has run and accumulates it to osCPU_TotalIdleTime, representing the total time slices occupied by the idle task.
uint16_t osGetCPUUsage(void)
{
return (uint16_t) osCPU_Usage; // Directly return the global variable osCPU_Usage, which is the CPU usage rate
}
The global variable osCPU_Usage stores the CPU usage rate, which is recalculated every 1000 ticks in the operating system’s tick hook function.
4. Conclusion
This method can be used to evaluate the running performance of the STM32 MCU.