FreeRTOS学习笔记(11)——CPU使用率统计

一、基本概念

CPU 使用率其实就是系统运行的程序占用的 CPU 资源,表示机器在某段时间程序运行的情况,如果这段时间中,程序一直在占用 CPU 的使用权,那么可以人为 CPU 的利用率是 100%。CPU 的利用率越高,说明机器在这个时间上运行了很多程序,反之较少。利用率的高低与 CPU 强弱有直接关系,就像一段一模一样的程序,如果使用运算速度很慢的 CPU,它可能要运行 1000ms,而使用很运算速度很快的 CPU 可能只需要 10ms,那么在 1000ms 这段时间中,前者的 CPU 利用率就是 100%,而后者的 CPU 利用率只有 1%,因为 1000ms 内前者都在使用 CPU 做运算,而后者只使用 10ms 的时间做运算,剩下的时间 CPU 可以做其他事情。

FreeRTOS 是多任务操作系统,对 CPU 都是分时使用的:比如 A 任务占用 10ms,然后 B 任务占用 30ms,然后空闲 60ms,再又是 A 任务占 10ms,B 任务占 30ms,空闲 60ms;

二、CPU利用率统计

在调试的时候很有必要得到当前系统的 CPU 利用率相关信息,但是在产品发布的时候,就可以把 CPU 利用率统计这个功能去掉,因为使用任何功能的时候,都是需要消耗系统资源的,FreeRTOS 是使用一个外部的变量进行统计时间的,并且消耗一个高精度的定时器,其用于定时的精度是系统时钟节拍的 10-20 倍,比如当前系统时钟节拍是 1000HZ,那么定时器的计数节拍就要是 10000-20000HZ。而且 FreeRTOS 进行 CPU 利用率统计的时候,也有一定缺陷,因为它没有对进行 CPU 利用率统计时间的变量做溢出保护,我们使用的是 32 位变量来系统运行的时间计数值,而按 20000HZ 的中断频率计算,每进入一中断就是 50us,变量加一,最大支持计数时间:2^32 * 50us / 3600s = 59.6 分钟,运行时间超过了 59.6 分钟后统计的结果将不准确,除此之外整个系统一直响应定时器 50us 一次的中断会比较影响系统的性能。

三、配置选项

FreeRTOSConfig.h 配置与系统运行时间和任务状态收集有关的配置选项,并且实现
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()portGET_RUN_TIME_COUNTER_VALUE() 这两个宏定义

/********************************************************************
FreeRTOS 与运行时间和任务状态收集有关的配置选项
**********************************************************************/
//启用运行时间统计功能 
#define configGENERATE_RUN_TIME_STATS 1 
//启用可视化跟踪调试 
#define configUSE_TRACE_FACILITY 1 
/* 与宏 configUSE_TRACE_FACILITY 同时为 1 时会编译下面 3 个函数
* prvWriteNameToBuffer()
* vTaskList(),
* vTaskGetRunTimeStats()
*/
#define configUSE_STATS_FORMATTING_FUNCTIONS 1

extern volatile uint32_t CPU_RunTime; 

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul) 
#define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime

然后需要实现一个中断频率为 20000HZ 定时器,用于系统运行时间统计,其实很简单,只需将 CPU_RunTime 变量自加即可,这个变量是用于记录系统运行时间的,中断服务函数见下

/* 用于统计运行时间 */
volatile uint32_t CPU_RunTime = 0UL;

void BASIC_TIM_IRQHandler (void)
{
    if(TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) 
    {
        CPU_RunTime++; 
        TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
    }
}

然后我们就可以在任务中调用 vTaskGetRunTimeStats()vTaskList() 函数获得任务的相关信息与 CPU 使用率的相关信息,然后打印出来即可

memset(CPU_RunInfo,0,400); //信息缓冲区清零

vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息 

printf("---------------------------------------------\r\n");
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n");
 
memset(CPU_RunInfo,0,400); //信息缓冲区清零

vTaskGetRunTimeStats((char *)&CPU_RunInfo); 
 
printf("任务名 运行计数 使用率\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n\n");

四、示例

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_TiMbase.h"
#include "string.h"

/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
 /* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED任务句柄 */
static TaskHandle_t LED1_Task_Handle = NULL;
static TaskHandle_t LED2_Task_Handle = NULL;
static TaskHandle_t CPU_Task_Handle = NULL;

/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void LED1_Task(void* pvParameters);/* LED1_Task任务实现 */
static void LED2_Task(void* pvParameters);/* LED2_Task任务实现 */
static void CPU_Task(void* pvParameters);/* CPU_Task任务实现 */
static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{   
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

    /* 开发板硬件初始化 */
    BSP_Init();
    /* 创建AppTaskCreate任务 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                          (const char*    )"AppTaskCreate",/* 任务名字 */
                          (uint16_t       )512,  /* 任务栈大小 */
                          (void*          )NULL,/* 任务入口函数参数 */
                          (UBaseType_t    )1, /* 任务的优先级 */
                          (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
    /* 启动任务调度 */           
    if(pdPASS == xReturn)
    {
        vTaskStartScheduler();   /* 启动任务,开启调度 */
    }
    else
    {
        return -1;  
    }
  
    while(1);   /* 正常不会执行到这里 */    
}

/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
    taskENTER_CRITICAL();           //进入临界区
  
    /* 创建LED_Task任务 */
    xReturn = xTaskCreate((TaskFunction_t )LED1_Task, /* 任务入口函数 */
                          (const char*    )"LED1_Task",/* 任务名字 */
                          (uint16_t       )512,   /* 任务栈大小 */
                          (void*          )NULL,    /* 任务入口函数参数 */
                          (UBaseType_t    )2,       /* 任务的优先级 */
                          (TaskHandle_t*  )&LED1_Task_Handle);/* 任务控制块指针 */
    if(pdPASS == xReturn)
    {
        printf("创建LED1_Task任务成功!\r\n");
    }
  
    /* 创建LED_Task任务 */
    xReturn = xTaskCreate((TaskFunction_t )LED2_Task, /* 任务入口函数 */
                          (const char*    )"LED2_Task",/* 任务名字 */
                          (uint16_t       )512,   /* 任务栈大小 */
                          (void*          )NULL,    /* 任务入口函数参数 */
                          (UBaseType_t    )3,       /* 任务的优先级 */
                          (TaskHandle_t*  )&LED2_Task_Handle);/* 任务控制块指针 */
    if(pdPASS == xReturn)
    {
        printf("创建LED2_Task任务成功!\r\n");
    }

    /* 创建LED_Task任务 */
    xReturn = xTaskCreate((TaskFunction_t )CPU_Task, /* 任务入口函数 */
                          (const char*    )"CPU_Task",/* 任务名字 */
                          (uint16_t       )512,   /* 任务栈大小 */
                          (void*          )NULL,    /* 任务入口函数参数 */
                          (UBaseType_t    )4,       /* 任务的优先级 */
                          (TaskHandle_t*  )&CPU_Task_Handle);/* 任务控制块指针 */
    if(pdPASS == xReturn)
    {
        printf("创建CPU_Task任务成功!\r\n");
    }
  
    vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
    taskEXIT_CRITICAL();            //退出临界区
}

/**********************************************************************
  * @ 函数名  : LED_Task
  * @ 功能说明: LED_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void LED1_Task(void* parameter)
{   
    while (1)
    {
        LED1_ON;
        vTaskDelay(500);   /* 延时500个tick */
        printf("LED1_Task Running,LED1_ON\r\n");
        LED1_OFF;     
        vTaskDelay(500);   /* 延时500个tick */             
        printf("LED1_Task Running,LED1_OFF\r\n");
    }
}

static void LED2_Task(void* parameter)
{   
    while (1)
    {
        LED2_ON;
        vTaskDelay(300);   /* 延时500个tick */
        printf("LED2_Task Running,LED2_ON\r\n");
    
        LED2_OFF;     
        vTaskDelay(300);   /* 延时500个tick */             
        printf("LED2_Task Running,LED2_OFF\r\n");
    }
}

static void CPU_Task(void* parameter)
{   
    uint8_t CPU_RunInfo[400];       //保存任务运行时间信息
  
    while (1)
    {
        memset(CPU_RunInfo,0,400);              //信息缓冲区清零
    
        vTaskList((char *)&CPU_RunInfo);  //获取任务运行时间信息
    
        printf("---------------------------------------------\r\n");
        printf("任务名      任务状态 优先级   剩余栈 任务序号\r\n");
        printf("%s", CPU_RunInfo);
        printf("---------------------------------------------\r\n");
    
        memset(CPU_RunInfo,0,400);              //信息缓冲区清零
    
        vTaskGetRunTimeStats((char *)&CPU_RunInfo);
    
        printf("任务名       运行计数         利用率\r\n");
        printf("%s", CPU_RunInfo);
        printf("---------------------------------------------\r\n\n");
        vTaskDelay(1000);   /* 延时500个tick */        
    }
}

/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
    /*
     * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
     * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
     * 都统一用这个优先级分组,千万不要再分组,切忌。
     */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
    
    /* LED 初始化 */
    LED_GPIO_Config();

    /* 串口初始化    */
    USART_Config();
  
    /* 基本定时器初始化 */
    BASIC_TIM_Init();
}

• 由 Leung 写于 2021 年 1 月 5 日

• 参考:野火FreeRTOS视频与PDF教程

你可能感兴趣的:(FreeRTOS学习笔记(11)——CPU使用率统计)