前言:FreeRTOS 是一个 RTOS 类的嵌入式实时操作系统,作为实时操作系统其必定拥有任务调度的属性。通过系统自身的任务调度算法实现任务安全且高效的切换,这就不可避免的引出了各个任务对 CPU 的利用率问题(裸机情况下 CPU 利用率默认100%)。实际工程项目中,通过对 CPU 利用率的实时监控可以清楚地熟知系统目前的运行状态,清楚地分析出各任务是否存在需要优化以及当前选定的CPU型号是否满足项目芯片性能的客观要求。
本项目教程以 STM32F103ZET6 为核心 MCU ,通过 FreeRTOS 系统实现简单地工程项目。以该项目为例进行教学CPU利用率计算的方法和代码(代码开源!)。
实验硬件:STM32F103ZET6;7针1.3寸TFT-LCD(240×240);
硬件实物图:
效果图:
在操作系统中 CPU 利用率是在软件架构设计中必须要考虑的一个重要性能指标。它直接影响到程序的执行时间以及优先级更高的任务能否实时响应的问题。CPU 利用率其实就是系统运行的程序占用的 CPU 资源,表示机器在某段时间程序运行的情况,如果这段时间中,程序一直在占用 CPU 的使用权,那么可以人为 CPU 的利用率是 100%。CPU 的利用率越高,说明机器在这个时间上运行了很多程序,反之较少。
补充说明:CPU 利用率的高低与 CPU 性能强弱有直接关系,就像一段一模一样的程序,如果使用运算速度很慢的 CPU,它可能要运行 1000ms,而使用很运算速度很快的 CPU 可能只需要 100ms,那么在 1000ms 这段时间中,前者的 CPU 利用率就是 100%,而后者的 CPU 利用率只有 10%,因为 1000ms 内前者都在使用 CPU 做运算,而后者只使用 10ms 的时间做运算,剩下的时间 CPU 可以做其他事情。
FreeRTOS操作系统是使用任务的累计运行时间来统计每一个任务自系统开始运行到当前时刻的CPU占用时间,即该任务的CPU利用率。举个例子:系统上电到当前时刻一共运行了 100s,其中任务A运行了 10s,任务B运行了 20s,剩下的 70s 由空闲任务在运行,那么在 10s 的时间内,任务A的CPU使用率是 10%,任务B的CPU使用率是 20%,空闲任务(IDLE)的CPU使用率是 70%。如下图是FreeRTOS系统CPU使用率统计示意图:
CPU利用率计算原理:在FreeRTOS中使用一个外部的变量(CPU_RunTime)进行统计时间的,并且消耗一个高精度的定时器(项目中TIM3),其用于定时的精度是系统时钟节拍的 10-20 倍(比如当前系统时钟节拍是 1000HZ,那么定时器的计数节拍就要是 10000-20000HZ)。
方法的缺陷:没有对进行 CPU 利用率统计时间的变量做溢出保护,使用的是 32 位变量来系统运行的时间计数值,而按 20000HZ 的中断频率计算,每进入一中断就是 50us,变量加一,最大支持计数时间:2^32 * 50us / 3600s = 59.6 分钟,运行时间超过了 59.6 分钟后统计的结果将不准确,除此之外整个系统一直响应定时器 50us 一次的中断会比较影响系统的性能。
★项目工程默认已经成功移植FreeRTOS系统(CubeMX配置仅保留CPU利用率计算需求部分)
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire;Timebase Source配置:设置TIM2为FreeRTOS系统心跳;
3、TIM3配置:通过设置50us中断一次计数CPU运行时间(开启TIM3定时器中断);
4、UART1配置:MCU与电脑进行串口通讯,打印出CPU利用率信息;
5、时钟树配置
6、工程配置
FreeRTOS系统中CPU利用率统计功能是一个可裁剪的功能,可通过宏定义来进行裁剪。在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
实现一个中断频率周期为 50us 的定时器,在定时器的中断回调函数中只需将 CPU_RunTime 变量自加即可,这个变量是用于记录系统运行时间的,中断服务函数见下(在main.c):
HAL_TIM_Base_Start_IT(&htim3); //开启TIM3的定时器中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM2) { //心跳
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if(htim->Instance == TIM3)
{
// HAL_IncTick();
CPU_RunTime++;
}
/* USER CODE END Callback 1 */
}
设置完成后,可以在任务中调用 vTaskGetRunTimeStats()
和 vTaskList()
函数获得任务的相关信息与 CPU 利用率的相关信息,然后打印出来即可:
void CPU_TRACE(void *pvParameters)
{
uint8_t CPU_RunInfo[512]; //信息缓冲区清零
while(1)
{
memset(CPU_RunInfo,0,512); //信息缓冲区清零
vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息
printf("---------------------------------------------\r\n");
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n");
memset(CPU_RunInfo,0,512); //信息缓冲区清零
vTaskGetRunTimeStats((char *)&CPU_RunInfo);
printf("任务名 运行计数 使用率\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n\n");
}
}
volatile uint32_t CPU_RunTime = 0;
int cpu = 0;
#define TASK0_TASK_PRIO 1 //任务优先级
#define TASK0_STK_SIZE 256 //任务堆栈大小
#define TASK1_TASK_PRIO 2 //任务优先级
#define TASK1_STK_SIZE 256 //任务堆栈大小
#define TASK2_TASK_PRIO 1 //任务优先级
#define TASK2_STK_SIZE 256 //任务堆栈大小
#define TASK3_TASK_PRIO 4 //任务优先级
#define TASK3_STK_SIZE 256 //任务堆栈大小
TaskHandle_t Task0Task_Handler; //任务句柄
TaskHandle_t Task1Task_Handler; //任务句柄
TaskHandle_t Task2Task_Handler; //任务句柄
TaskHandle_t Task3Task_Handler; //任务句柄
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//task0任务函数
void task0_task(void *pvParameters)
{
while(1)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,0);
vTaskDelay(600);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,1);
vTaskDelay(600);
}
}
//task1任务函数
void task1_task(void *pvParameters)
{
while(1)
{
for(int a=0;a<8;a++)
{
showimage4(gImage_1[a]);
if(a == 7)a=0;
vTaskDelay(200);
}
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
while(1)
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,0);
vTaskDelay(1000);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,1);
vTaskDelay(1000);
}
}
//task3_task任务函数
void task3_task(void *pvParameters)
{
uint8_t CPU_RunInfo[512]; //信息缓冲区清零
while(1)
{
memset(CPU_RunInfo,0,512); //信息缓冲区清零
vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息
printf("---------------------------------------------\r\n");
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n");
memset(CPU_RunInfo,0,512); //信息缓冲区清零
vTaskGetRunTimeStats((char *)&CPU_RunInfo);
printf("任务名 运行计数 使用率\r\n");
printf("%s", CPU_RunInfo);
printf("---------------------------------------------\r\n\n");
LCD_ShowNew(65,170,'C',24,0);
LCD_ShowNew(80,170,'P',24,0);
LCD_ShowNew(95,170,'U',24,0);
LCD_ShowNew(110,170,':',24,0);
LCD_ShowxNum2(125,170,cpu,2,24,0);
LCD_ShowNew(155,170,'%',24,0);
vTaskDelay(2000); /* 2000tick */
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RTC_Init();
MX_USART1_UART_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
//硬件初始化
HAL_TIM_Base_Start_IT(&htim3);
Lcd_Init(); //初始化TFT
LCD_Clear(WHITE); //清屏
BACK_COLOR=WHITE;
POINT_COLOR=RED;
showhanzi(40,20,0);
showhanzi(75,20,1);
showhanzi(110,20,2);
showhanzi(145,20,3);
showhanzi(180,20,4);
BACK_COLOR=WHITE;
POINT_COLOR=BLACK;
//创建任务0
xTaskCreate((TaskFunction_t )task0_task, //任务函数
(const char* )"task0_task", //任务名称
(uint16_t )TASK0_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK0_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Task0Task_Handler); //任务句柄
//创建任务1
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建任务2
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
//创建任务3
xTaskCreate((TaskFunction_t )task3_task,
(const char* )"task3_task",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_TASK_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
//启动任务调度器
vTaskStartScheduler();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
上述代码共创建了4个系统任务:2个LED灯定时闪烁任务;1个TFTLCD的GIF动图显示任务;1个CPU利用率信息打印输出任务。读者朋友可以根据自己的实际情况去修改自己的任务函数中具体需要实现的内容。
通过上述 FreeRTOSConfig.h 文件的配置过程可以看出,FreeRTOS本身就是提供 CPU 的监管功能的,只不过需要我们对配置文件进行修改。
/* FreeRTOSConfig.h */
/* 使能CPU使用率统计功能 */
#define configGENERATE_RUN_TIME_STATS 1
而如果使能了 CPU 使用率统计功能,则在每一个任务的任务控制块中,定义了一个变量 ulRunTimeCounter 用于统计该任务的运行时间,代码如下
/*task.c*/
/* 任务控制块接口体定义 */
typedef struct tskTaskControlBlock{
volatile StackType_t *pxTopOfStack;
// ...
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
// ...
}
在进行任务上下文切换时,会将当前任务的本次运行时间(从上一次任务上下文切换开始至当前时刻)累计到当前任务的任务控制块中的 ulRunTimeCounter 上,任务上下文切换的代码如下:
//task.c
void vTaskSwitchContext( void ){
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
/* 条件编译选项:如果使能了CPU使用率统计功能,则下面的代码用于统计当前任务的CPU占用总时间 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
/* **(1)获取当前时间ulTotalRunTime(CPU运行的总时间)** */
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
/* **(2)将当前任务的运行时间(当前时间(ulTotalRunTime) - 上一次任务切换的时间(ulTaskSwitchedInTime))累加到当前任务控制块** */
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* **(3)更新上一次任务切换的时间ulTaskSwitchedInTime,作为下一次任务切换时计算任务运行时间的基准时刻** */
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif /* configGENERATE_RUN_TIME_STATS */
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
/* Select a new task to run using either the generic C or port optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent structure specific to this task. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
}
}
由上面的代码可知,要实现任务的CPU使用率统计功能,还需要实现宏函数portGET_RUN_TIME_COUNTER_VALUE() 用于获取当前时间。为了提高 CPU 利用率的计算精度,一般使用一个高精度的定时器去计数运行时间,FreeRTOSConfig.h代码如下:
/* Run time stats gathering definitions. */
#define configGENERATE_RUN_TIME_STATS 1
extern volatile uint32_t ulHighFrequencyTimerCounts; /* 在高精度定时器中断中累加 */
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (ulHighFrequencyTimerCounts = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerCounts
最后只需要在定时器中断回调函数中进行累加CPU运行时间即可:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM2) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if(htim->Instance == TIM3)
{
CPU_RunTime++;
}
/* USER CODE END Callback 1 */
}
接下来给大家分析一下,CPU 利用率计算的核心函数:vTaskList() 和 vTaskGetRunTimeStats()
vTaskList()函数: 获取任务的相关信息
vTaskGetRunTimeStats() 函数:获取PU使用率的相关信息
#if ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) )
void vTaskGetRunTimeStats( char *pcWriteBuffer )
{
TaskStatus_t *pxTaskStatusArray;
volatile UBaseType_t uxArraySize, x;
uint32_t ulTotalTime, ulStatsAsPercentage;
#if( configUSE_TRACE_FACILITY != 1 )
{
#error configUSE_TRACE_FACILITY must also be set to 1 in FreeRTOSConfig.h to use vTaskGetRunTimeStats().
}
#endif
/* 确保 write buffer中没有字符串(写入字符串结束符). */
*pcWriteBuffer = 0x00;
/* 获取当前系统任务个数 */
uxArraySize = uxCurrentNumberOfTasks;
/* 为每一个任务申请一块内存空间用于存储任务状态信息. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) );
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* ulTotalTime为系统运行总时间. */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
if( ulTotalTime > 0 )
{
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
/* 计算当前任务的CPU占用率. */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
/* 将任务名写入到输出字符串. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
/* 将当前任务的运行时间ulRunTimeCounter和CPU占用率写入到输出字符串 */
if( ulStatsAsPercentage > 0UL )
{
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage );
}
#endif
}
else
{
/* 如果计算的CPU占用率为0, 则用"<1%"表示 */
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t<1%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t<1%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#endif
}
pcWriteBuffer += strlen( pcWriteBuffer );
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 释放内存块pxTaskStatusArray . */
vPortFree( pxTaskStatusArray );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) ) */
简而言之,vTaskGetRunTimeStats() 函数的作用就是通过当前任务的运行时间ulRunTimeCounter 和系统运行总时间 ulTotalTime 来计算当前任务的CPU占用率,并格式化输出(计算方法和本文所述的原理一致)。
项目中的CPU利用率如何来得:
FreeRTOS在启动任务调度时候,会默认创建一个IDLE空闲任务。当CPU运行闲置的时候就会默认去运行空闲任务(IDLE),保证系统运行调度的正常运行。所以,当CPU运载的任务越重,IDEA空闲任务的CPU利用率就越低。综上所述,作者采用100-空闲任务的CPU利用率就能间接反映出目前CPU的负载情况。
项目中CPU计算公式:
CPU = (100 - IDEA(CPU))*100%
修改vTaskGetRunTimeStats代码:
#if ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) && ( configUSE_TRACE_FACILITY == 1 ) )
void vTaskGetRunTimeStats( char * pcWriteBuffer )
{
TaskStatus_t * pxTaskStatusArray;
UBaseType_t uxArraySize, x;
configRUN_TIME_COUNTER_TYPE ulTotalTime, ulStatsAsPercentage;
/* Make sure the write buffer does not contain a string. */
*pcWriteBuffer = ( char ) 0x00;
/* Take a snapshot of the number of tasks in case it changes while this
* function is executing. */
uxArraySize = uxCurrentNumberOfTasks;
/* Allocate an array index for each task. NOTE! If
* configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
* equate to NULL. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* For percentage calculations. */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
if( ulTotalTime > 0UL )
{
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
/* What percentage of the total run time has the task used?
* This will always be rounded down to the nearest integer.
* ulTotalRunTime has already been divided by 100. */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
cpu = 100 - pxTaskStatusArray[ uxArraySize-4 ].ulRunTimeCounter / ulTotalTime;
/* Write the task name to the string, padding with
* spaces so it can be printed in tabular form more
* easily. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
if( ulStatsAsPercentage > 0UL )
{
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller
* printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
利用全局变量将 CPU 的数值提取出来,通过TCTLCD将数值显示出来。
CPU利用率
代码地址:FreeRTOS的CPU利用率计算工程代码资源-CSDN文库
如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!