FreeRTOS菜鸟入门(十九)·CPU 使用率统计

目录

1.  CPU利用率的基本概念

2.  CPU 利用率的作用

3.  CPU 利用率统计

4.  实例演示


1.  CPU利用率的基本概念

        CPU 使用率其实就是系统运行的程序占用的 CPU 资源,表示机器在某段时间程序运行的情况,如果这段时间中,程序一直在占用 CPU 的使用权,那么可以人为 CPU 的利用率是 100%。

        FreeRTOS 是多任务操作系统,对 CPU 都是分时使用的:比如 A 任务占用 10ms,然后 B 任务占用 30ms,然后空闲 60ms,再又是 A任务占 10ms,B 任务占 30ms,空闲 60ms;如果在一段时间内都是如此,那么这段时间内的利用率为 40%,因为整个系统中只有 40%的时间是 CPU 处理数据的时间。

2.  CPU 利用率的作用

        一个系统设计的好坏,可以使用 CPU 使用率来衡量,一个好的系统必然是能完美响应急需的处理,并且系统的资源不会过于浪费(性价比高)。举个例子,假设一个系统的 CPU 利用率经常在 90%~100%徘徊,那么系统就很少有空闲的时候,这时候突然有一些事情急需 CPU 的处理,但是此时 CPU 都很可能被其他任务在占用了,那么这个紧急事件就有可能无法被相应,即使能被相应,那么占用 CPU 的任务又处于等待状态,这种系统就是不够完美的,因为资源处理得太过于紧迫;反过来,假如 CPU 的利用率在 1%以下,那么我们就可以认为这种产品的资源过于浪费,搞一个那么好的 CPU 去干着没啥意义的活(大部分时间处于空闲状态),使用,作为产品的设计,既不能让资源过于浪费,也不能让资源过于紧迫,这种设计才是完美的,在需要的时候能及时处理完突发事件,而且资源也不会过剩,性价比更高。

3.  CPU 利用率统计

        FreeRTOS 是一个很完善很稳定的操作系统,当然也给我们提供测量各个任务占用CPU 时间的函数接口,我们可以知道系统中的每个任务占用 CPU 的时间,从而得知系统设计的是否合理,出于性能方面的考虑,有的时候,我们希望知道 CPU 的使用率为多少,进而判断此 CPU 的负载情况和对于当前运行环境是否能够“胜任工作”。所以,在调试的时候很有必要得到当前系统的 CPU 利用率相关信息,但是在产品发布的时候,就可以把CPU 利用率统计这个功能去掉,因为使用任何功能的时候,都是需要消耗系统资源的。

        用户想要使用使用 CPU 利用率统计的话,需要自定义配置一下,首先在 FreeRTOSConfig.h 配置与系统运行时间和任务状态收集有关的配置选项,并且实现 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 与portGET_RUN_TIME_COUNTER_VALUE()这两个宏定义:

//启用运行时间统计功能
#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 使用率的相关信息,然后打印出来即可。

4.  实例演示

        CPU 利用率实验是是在 FreeRTOS 中创建了三个任务,其中两个任务是普通任务,另一个任务用于获取 CPU 利用率与任务相关信息并通过串口打印出来。

这里继续使用空白工程:

基于STM32F1系列移植FreeRTOS模版资源-CSDN文库

        首先我们配备一个中断,通过中断计数,创建一个TIM.c文件,中断优先级配置:

static void BASIC_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);		
		// 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;	
		// 设置主优先级为 0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;	 
	  // 设置抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

        中断配置:

static void BASIC_TIM_Mode_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
		
		// 开启定时器时钟,即内部时钟CK_INT=72M
    BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
	
		// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;	

	  // 时钟预分频数为
    TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;
	
		// 时钟分频因子 ,基本定时器没有,不用管
    //TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
		
		// 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
    //TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 
		
		// 重复计数器的值,基本定时器没有,不用管
		//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
	
	  // 初始化定时器
    TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
		
		// 清除计数器中断标志位
    TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
	  
		// 开启计数器中断
    TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
		
		// 使能计数器
    TIM_Cmd(BASIC_TIM, ENABLE);	
}

        初始化:

void BASIC_TIM_Init(void)
{
	BASIC_TIM_NVIC_Config();
	BASIC_TIM_Mode_Config();
}

        相关宏定义:

#ifndef __BSP_TIMEBASE_H
#define __BSP_TIMEBASE_H


#include "stm32f10x.h"


/********************基本定时器TIM参数定义,只限TIM6、7************/
#define BASIC_TIM6 // 如果使用TIM7,注释掉这个宏即可

#ifdef  BASIC_TIM6 // 使用基本定时器TIM6
#define            BASIC_TIM                   TIM6
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM6
#define            BASIC_TIM_Period            (50-1)
#define            BASIC_TIM_Prescaler         71
#define            BASIC_TIM_IRQ               TIM6_IRQn
#define            BASIC_TIM_IRQHandler        TIM6_IRQHandler

#else  // 使用基本定时器TIM7
#define            BASIC_TIM                   TIM7
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM7
#define            BASIC_TIM_Period            1000-1
#define            BASIC_TIM_Prescaler         71
#define            BASIC_TIM_IRQ               TIM7_IRQn
#define            BASIC_TIM_IRQHandler        TIM7_IRQHandler

#endif
/**************************函数声明********************************/

void BASIC_TIM_Init(void);


#endif	/* __BSP_TIMEBASE_H */


        然后stm32f10x.c中配置中断服务函数:

/* 用于统计运行时间 */
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);  		 
	}		 	
}

        然后找到FreeRTOSConfig.h文件对一下两处进行更改:

/********************************************************************
          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     


/****************************************************************
            FreeRTOS与中断服务函数有关的配置选项                         
****************************************************************/
#define xPortPendSVHandler 	PendSV_Handler
#define vPortSVCHandler 	SVC_Handler


/* 以下为使用Percepio Tracealyzer需要的东西,不需要时将 configUSE_TRACE_FACILITY 定义为 0 */
#if ( configUSE_TRACE_FACILITY == 1 )
//#include "trcRecorder.h"
//#define INCLUDE_xTaskGetCurrentTaskHandle               1   // 启用一个可选函数(该函数被 Trace源码使用,默认该值为0 表示不用)
#endif

        回到主函数,我们想要创建三个任务,那就是三个任务句柄:

static TaskHandle_t LED1_Task_Handle = NULL;
static TaskHandle_t LED2_Task_Handle = NULL;
static TaskHandle_t CPU_Task_Handle = NULL;

        对于LED1和LED2两个任务,就是纯点灯程序:

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,LED1_ON\r\n");
    
    LED2_OFF;     
    vTaskDelay(300);   /* 延时500个tick */		 		
    printf("LED2_Task Running,LED1_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 */		
  }
}

        main函数完整代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

#include "LED.h"
#include "Usart.h"
#include "Key.h"  
#include "TIM.h" 

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"

#include "string.h"

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

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 All_Function_Init(void);/* 用于初始化板载相关资源 */

int main(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

	All_Function_Init();//硬件初始化
	
	while (1)
	{
		 /* 创建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;  
	}
}

//任务创建函数
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();            //退出临界区
}

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,LED1_ON\r\n");
    
    LED2_OFF;     
    vTaskDelay(300);   /* 延时500个tick */		 		
    printf("LED2_Task Running,LED1_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 */		
  }
}

//初始化声明
static void All_Function_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* 串口初始化	*/
	USART_Config();

//	//按键初始化
//	Key_GPIO_Config();

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



        可以看到第一次有些出入,后面就能正常统计:

完整工程:

FreeRTOS-CPU利用率.zip资源-CSDN文库

FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值