一、基本概念
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教程