在之前的博文中我们移植验证了STM32H750XBH6上运行FreeRTOS系统,在实际项目开发中我们经常会遇到获取系统实时运行负载的情况,进而对系统进行优化。
针对这类问题FreeRTOS源码中其实已经自带此类功能,可以分析每个任务线程的系统占用,对我们分析线程的执行效率、任务栈占用分析、优化内存占用等等有很大帮助。
本篇的主要目的是讲解如何通过一种简单方式获取系统整体负载情况。
文章末尾有工程文件
下图是开发板运行截图,从右下角的osCPU_Usage的值我们可以看到当前的CPU占用为40%

下面我们就来讲讲基于FreeRTOS我们如何实现此功能。
熟悉RTOS原理(很重要)的同学应该都知道FreeRTOS有个Idle任务,当没有任何业务线程要运行的时候,OS调度器会调度Idle线程执行,而在Idle线程中我们一般进行低功耗处理(休眠),如果在Idle任务中运行的时间越长就说明系统越空闲,系统负载越低;基于此我们就可以想到这样一种方法:
我们可以统计在一定周期时间内系统执行Idle线程的Tick数(时长),这样就可以获取到CPU空闲率(比如说1s内Idle任务运行了0.9s,那么CPU占用就是10%)。
基于此我们从代码层面来实现(此代码来自STM32Cube软件包)
相关代码如下:
- /**
- ******************************************************************************
- * @file cpu_utils.h
- * @author MCD Application Team
- * @version V1.1.0
- * @date 20-November-2014
- * @brief Header for cpu_utils module
- ******************************************************************************
- * @attention
- *
- *
© COPYRIGHT(c) 2014 STMicroelectronics
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * 3. Neither the name of STMicroelectronics nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- ******************************************************************************
- */
-
- /* Define to prevent recursive inclusion -------------------------------------*/
- #ifndef _CPU_UTILS_H__
- #define _CPU_UTILS_H__
-
- #ifdef __cplusplus
- extern "C" {
- #endif
-
- /* Includes ------------------------------------------------------------------*/
- #include "main.h"
-
- /* Exported types ------------------------------------------------------------*/
- /* Exported constants --------------------------------------------------------*/
- /* Exported variables --------------------------------------------------------*/
- /* Exported macro ------------------------------------------------------------*/
- #define CALCULATION_PERIOD 1000
-
- /* Exported functions ------------------------------------------------------- */
- uint16_t osGetCPUUsage (void);
-
- #ifdef __cplusplus
- }
- #endif
-
- #endif /* _CPU_UTILS_H__ */
-
- /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
- /**
- ******************************************************************************
- * @file cpu_utils.c
- * @author MCD Application Team
- * @version V1.1.0
- * @date 20-November-2014
- * @brief Utilities for CPU Load calculation
- ******************************************************************************
- * @attention
- *
- *
© COPYRIGHT(c) 2014 STMicroelectronics
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * 3. Neither the name of STMicroelectronics nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- ******************************************************************************
- */
-
- /********************** NOTES **********************************************
- To use this module, the following steps should be followed :
- 1- in the _OS_Config.h file (ex. FreeRTOSConfig.h) enable the following macros :
- - #define configUSE_IDLE_HOOK 1
- - #define configUSE_TICK_HOOK 1
- 2- in the _OS_Config.h define the following macros :
- - #define traceTASK_SWITCHED_IN() extern void StartIdleMonitor(void); \
- StartIdleMonitor()
- - #define traceTASK_SWITCHED_OUT() extern void EndIdleMonitor(void); \
- EndIdleMonitor()
- *******************************************************************************/
-
-
- /* Includes ------------------------------------------------------------------*/
- #include "cpu_utils.h"
-
- #include "freertos.h"
- #include "task.h"
-
- /* Private typedef -----------------------------------------------------------*/
- /* Private define ------------------------------------------------------------*/
- /* Private macro -------------------------------------------------------------*/
- /* Private function prototypes -----------------------------------------------*/
- /* Private variables ---------------------------------------------------------*/
-
- static xTaskHandle xIdleHandle;
- volatile uint32_t osCPU_Usage;
- static uint32_t osCPU_IdleStartTime;
- static uint32_t osCPU_IdleSpentTime;
- static uint32_t osCPU_TotalIdleTime;
-
- /* Private functions ---------------------------------------------------------*/
- /**
- * @brief Application Idle Hook
- * @param None
- * @retval None
- */
- void vApplicationIdleHook(void)
- {
- if( xIdleHandle == NULL )
- {
- /* Store the handle to the idle task. */
- xIdleHandle = xTaskGetCurrentTaskHandle();
- }
- }
-
- /**
- * @brief Application Idle Hook
- * @param None
- * @retval None
- */
- void vApplicationTickHook (void)
- {
- static int tick = 0;
-
- if(tick ++ > CALCULATION_PERIOD)
- {
- tick = 0;
-
- if(osCPU_TotalIdleTime > 1000)
- {
- osCPU_TotalIdleTime = 1000;
- }
- osCPU_Usage = (100 - (osCPU_TotalIdleTime * 100) / CALCULATION_PERIOD);
- osCPU_TotalIdleTime = 0;
- }
- }
-
- /**
- * @brief Start Idle monitor
- * @param None
- * @retval None
- */
- void StartIdleMonitor (void)
- {
- if( xTaskGetCurrentTaskHandle() == xIdleHandle )
- {
- osCPU_IdleStartTime = xTaskGetTickCountFromISR();
- }
- }
-
- /**
- * @brief Stop Idle monitor
- * @param None
- * @retval None
- */
- void EndIdleMonitor (void)
- {
- if( xTaskGetCurrentTaskHandle() == xIdleHandle )
- {
- /* Store the handle to the idle task. */
- osCPU_IdleSpentTime = xTaskGetTickCountFromISR() - osCPU_IdleStartTime;
- osCPU_TotalIdleTime += osCPU_IdleSpentTime;
- }
- }
-
- /**
- * @brief Stop Idle monitor
- * @param None
- * @retval None
- */
- uint16_t osGetCPUUsage (void)
- {
- return (uint16_t)osCPU_Usage;
- }
-
-
- /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
另外我们需要配置FreeRTOSConfig.h文件
- /*
- * FreeRTOS V202107.00
- * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
- * the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
- * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *
- * http://www.FreeRTOS.org
- * http://aws.amazon.com/freertos
- *
- * 1 tab == 4 spaces!
- */
-
-
- #ifndef FREERTOS_CONFIG_H
- #define FREERTOS_CONFIG_H
-
- /*-----------------------------------------------------------
- * Application specific definitions.
- *
- * These definitions should be adjusted for your particular hardware and
- * application requirements.
- *
- * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
- * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
- *
- * See http://www.freertos.org/a00110.html
- *----------------------------------------------------------*/
-
- #include "stm32h7xx_hal.h"
-
- #define configUSE_PREEMPTION 1
- #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
- #define configUSE_QUEUE_SETS 1
- #define configUSE_IDLE_HOOK 1
- #define configUSE_TICK_HOOK 1
- #define configCPU_CLOCK_HZ ( SystemCoreClock )
- #define configTICK_RATE_HZ ( 1000 )
- #define configMAX_PRIORITIES ( 5 )
- #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 )
- #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 46 * 1024 ) )
- #define configMAX_TASK_NAME_LEN ( 10 )
- #define configUSE_TRACE_FACILITY 1
- #define configUSE_16_BIT_TICKS 0
- #define configIDLE_SHOULD_YIELD 1
- #define configUSE_MUTEXES 1
- #define configQUEUE_REGISTRY_SIZE 8
- #define configCHECK_FOR_STACK_OVERFLOW 0
- #define configUSE_RECURSIVE_MUTEXES 1
- #define configUSE_MALLOC_FAILED_HOOK 0
- #define configUSE_APPLICATION_TASK_TAG 0
- #define configUSE_COUNTING_SEMAPHORES 1
-
- /* The full demo always has tasks to run so the tick will never be turned off.
- The blinky demo will use the default tickless idle implementation to turn the
- tick off. */
- #define configUSE_TICKLESS_IDLE 0
-
- /* Run time stats gathering definitions. */
- #define configGENERATE_RUN_TIME_STATS 0
-
- /* This demo makes use of one or more example stats formatting functions. These
- format the raw data provided by the uxTaskGetSystemState() function in to human
- readable ASCII form. See the notes in the implementation of vTaskList() within
- FreeRTOS/Source/tasks.c for limitations. */
- #define configUSE_STATS_FORMATTING_FUNCTIONS 1
-
- /* Co-routine definitions. */
- #define configUSE_CO_ROUTINES 0
- #define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
-
- /* Software timer definitions. */
- #define configUSE_TIMERS 1
- #define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
- #define configTIMER_QUEUE_LENGTH 5
- #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
-
- /* Set the following definitions to 1 to include the API function, or zero
- to exclude the API function. */
- #define INCLUDE_vTaskPrioritySet 1
- #define INCLUDE_uxTaskPriorityGet 1
- #define INCLUDE_vTaskDelete 1
- #define INCLUDE_vTaskCleanUpResources 1
- #define INCLUDE_vTaskSuspend 1
- #define INCLUDE_vTaskDelayUntil 1
- #define INCLUDE_vTaskDelay 1
- #define INCLUDE_eTaskGetState 1
- #define INCLUDE_xTimerPendFunctionCall 1
-
- /* Cortex-M specific definitions. */
- #ifdef __NVIC_PRIO_BITS
- /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
- #define configPRIO_BITS __NVIC_PRIO_BITS
- #else
- #define configPRIO_BITS 4 /* 15 priority levels */
- #endif
-
- /* The lowest interrupt priority that can be used in a call to a "set priority"
- function. */
- #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
-
- /* The highest interrupt priority that can be used by any interrupt service
- routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
- INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
- PRIORITY THAN THIS! (higher priorities are lower numeric values. */
- #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 4
-
- /* Interrupt priorities used by the kernel port layer itself. These are generic
- to all Cortex-M ports, and do not rely on any particular library functions. */
- #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
- /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
- See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
- #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
-
- /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
- standard names. */
- #define xPortPendSVHandler PendSV_Handler
- #define vPortSVCHandler SVC_Handler
- //#define xPortSysTickHandler SysTick_Handler
-
- #define traceTASK_SWITCHED_IN() extern void StartIdleMonitor(void); \
- StartIdleMonitor()
- #define traceTASK_SWITCHED_OUT() extern void EndIdleMonitor(void); \
- EndIdleMonitor()
-
- #endif /* FREERTOS_CONFIG_H */
我们需要设置
- #define configUSE_IDLE_HOOK 1
- #define configUSE_TICK_HOOK 1
configUSE_IDLE_HOOK用于打开Idle线程钩子函数,当OS调度到Idle任务去运行时,会执行vApplicationIdleHook这个函数

上图是task.c里面空闲任务线程调用vApplicationIdleHook的截图。
下面是我们自己实现的空闲任务钩子函数
- static xTaskHandle xIdleHandle;
- void vApplicationIdleHook(void)
- {
- if( xIdleHandle == NULL )
- {
- /* Store the handle to the idle task. */
- xIdleHandle = xTaskGetCurrentTaskHandle();
- }
- }
我们在第一次进入Idle任务钩子函数中获取一下Idle任务的任务句柄(指针),保存在静态全局变量xIdleHandle中。
configUSE_TICK_HOOK用于打开Tick中断的钩子函数,每次Tick中断中都会被调用
- void vApplicationTickHook (void)
- {
- static int tick = 0;
-
- if(tick ++ > CALCULATION_PERIOD)
- {
- tick = 0;
-
- if(osCPU_TotalIdleTime > 1000)
- {
- osCPU_TotalIdleTime = 1000;
- }
- osCPU_Usage = (100 - (osCPU_TotalIdleTime * 100) / CALCULATION_PERIOD);
- osCPU_TotalIdleTime = 0;
- }
- }
我们的代码中通过静态局部变量tick值判断(每进入一次tick钩子函数时就讲tick值累加一),当CALCULATION_PERIOD次数个tick发生时,通过计算空闲任务中执行的tick数来计算出CPU占用
那如何实现osCPU_TotalIdleTime变量的更新呢?
- #define traceTASK_SWITCHED_IN() extern void StartIdleMonitor(void); \
- StartIdleMonitor()
- #define traceTASK_SWITCHED_OUT() extern void EndIdleMonitor(void); \
- EndIdleMonitor()
traceTASK_SWITCHED_IN()当每次切入到一个任务中时执行,traceTASK_SWITCHED_OUT当每次从一个任务中切出的时候运行,所以我们代码中通过判断当前是不是进入和退出空闲任务,记录开始和结束时间
- /**
- * @brief Start Idle monitor
- * @param None
- * @retval None
- */
- void StartIdleMonitor (void)
- {
- if( xTaskGetCurrentTaskHandle() == xIdleHandle )
- {
- osCPU_IdleStartTime = xTaskGetTickCountFromISR();
- }
- }
-
- /**
- * @brief Stop Idle monitor
- * @param None
- * @retval None
- */
- void EndIdleMonitor (void)
- {
- if( xTaskGetCurrentTaskHandle() == xIdleHandle )
- {
- /* Store the handle to the idle task. */
- osCPU_IdleSpentTime = xTaskGetTickCountFromISR() - osCPU_IdleStartTime;
- osCPU_TotalIdleTime += osCPU_IdleSpentTime;
- }
- }
好,基本原理已经讲清楚了,我们来调试一下。
首先我们在原有工程中添加如下代码
- static void led2_task(void *args) {
- while (1)
- {
- HAL_GPIO_TogglePin(GPIOI, GPIO_PIN_8);
- //①
- for (int i = 0; i < 0xFFFFF; i++);;
- //②
- vTaskDelay(100);
- }
- //vTaskDelete(NULL);
- }
注意for循环那一行,如果我们调整循环次数,通过硬件调试我们就可以看到osCPU_Usage在动态变化,如果我们注释掉for循环,我们会发现osCPU_Usage基本为0(因为我们代码中没有耗时任务,故就基本不消耗CPU)
但是如果我们将标记为②的代码注释掉,此时你在看CPU占用,你就会发现CPU为100%,实际上这是一种错误的情况,即空闲任务被饿死的情况。
注意右下角osCPU_Usage的变化
FreeRTOS CPU占用
工程代码:
链接:https://pan.baidu.com/s/1u3MPAtHvUZB3Xmo7h1LSAA
提取码:9vxl