在 FreeRTOS 工程中,我们经常需要知道每个任务占用了多少 CPU 时间,以便找出性能瓶颈。FreeRTOS 自带运行时间统计功能,只要提供一个高精度计时器,就可以打印出各任务的运行时间和 CPU 占比。
本文以 STM32F103 + 标准外设库 为例,介绍配置方法和完整代码。
一、原理简介
FreeRTOS 的每个任务控制块(TCB)中都有一个 ulRunTimeCounter 成员,用来累计该任务的运行时间。
- 在任务切换时,FreeRTOS 会读取高精度计时器的值;
- 用本次读数和上次读数的差值,累加到当前任务的 ulRunTimeCounter 中;
- 空闲任务运行时间占总时间的比例就是 CPU 空闲率;
- CPU使用率 = 100% – 空闲任务占比。
因此我们需要一个比系统节拍 tick 快 10~100 倍的硬件计时器,通常用 STM32 的定时器实现。本文使用 TIM2 产生 100kHz 时基,统计精度为 10us,32位计数器溢出时间约 11.9 小时,比较适合长时间运行。
二、FreeRTOS 宏配置
在 FreeRTOSConfig.h 中开启统计功能,并指定计时器函数:
#define configUSE_TRACE_FACILITY 1
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define INCLUDE_xTaskGetIdleTaskHandle 1
extern void vConfigureTimerForRunTimeStats(void);
extern unsigned long ulGetRunTimeCounterValue(void);
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() vConfigureTimerForRunTimeStats()
#define portGET_RUN_TIME_COUNTER_VALUE() ulGetRunTimeCounterValue()
说明:
- configUSE_TRACE_FACILITY:必须为 1;
- configGENERATE_RUN_TIME_STATS:开启运行时间统计;
- configUSE_STATS_FORMATTING_FUNCTIONS:允许使用 vTaskGetRunTimeStats();
- portCONFIGURE_TIMER_FOR_RUN_TIME_STATS:配置统计计时器初始化函数;
- portGET_RUN_TIME_COUNTER_VALUE:获取计数器当前值函数。
三、STM32标准库计时器代码
使用 TIM2 产生 100kHz 计数时基,代码如下:
#include "stm32f10x.h"
void vConfigureTimerForRunTimeStats(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 打开 TIM2 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 0xFFFFFFFF;
// 系统时钟 / 100000 – 1,得到 100kHz 计数频率
TIM_TimeBaseStructure.TIM_Prescaler = (SystemCoreClock / 100000) – 1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_Cmd(TIM2, ENABLE);
}
unsigned long ulGetRunTimeCounterValue(void)
{
return (unsigned long)TIM2->CNT;
}
四、任务与CPU使用率计算完整代码
下面给出一个完整的示例工程关键代码,包含任务创建、运行时间统计打印和 CPU 使用率计算。
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
static void vTask1(void *pvParameters);
static void vTask2(void *pvParameters);
static void vStatsTask(void *pvParameters);
static float fGetCpuUsage(void);
int main(void)
{
// 初始化串口并重定向 printf,这里省略
// USART1_Config();
// 创建任务
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE * 2, NULL, 1, NULL);
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE * 2, NULL, 2, NULL);
xTaskCreate(vStatsTask, "Stats", configMINIMAL_STACK_SIZE * 4, NULL, 3, NULL);
// 启动调度器,会自动调用 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
vTaskStartScheduler();
while (1);
}
// 任务1:模拟持续工作
static void vTask1(void *pvParameters)
{
for (;;)
{
for (volatile int i = 0; i < 100000; i++);
vTaskDelay(pdMS_TO_TICKS(50));
}
}
// 任务2:模拟间歇工作
static void vTask2(void *pvParameters)
{
for (;;)
{
for (volatile int i = 0; i < 50000; i++);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 统计任务:周期性打印运行时间和CPU使用率
static void vStatsTask(void *pvParameters)
{
char pcBuffer[1024];
for (;;)
{
vTaskDelay(pdMS_TO_TICKS(2000));
printf("———- CPU RunTime Stats ———-\\r\\n");
vTaskGetRunTimeStats(pcBuffer);
printf("%s", pcBuffer);
printf("Total CPU Usage: %.2f%%\\r\\n", fGetCpuUsage());
printf("—————————————\\r\\n");
}
}
// 计算总CPU使用率:100% – 空闲任务占比
static float fGetCpuUsage(void)
{
TaskStatus_t *pxTaskStatusArray = NULL;
UBaseType_t uxArraySize, uxIndex;
uint32_t ulTotalRunTime = 0;
uint32_t ulIdleRunTime = 0;
float fCpuUsage = 0.0f;
TaskHandle_t xIdleTaskHandle = NULL;
uxArraySize = uxTaskGetNumberOfTasks();
pxTaskStatusArray = (TaskStatus_t *)pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
if (pxTaskStatusArray != NULL)
{
uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, &ulTotalRunTime);
xIdleTaskHandle = xTaskGetIdleTaskHandle();
for (uxIndex = 0; uxIndex < uxArraySize; uxIndex++)
{
if (pxTaskStatusArray[uxIndex].xHandle == xIdleTaskHandle)
{
ulIdleRunTime = pxTaskStatusArray[uxIndex].ulRunTimeCounter;
break;
}
}
vPortFree(pxTaskStatusArray);
}
if (ulTotalRunTime > 0)
{
fCpuUsage = 100.0f – ((float)ulIdleRunTime * 100.0f / (float)ulTotalRunTime);
}
return fCpuUsage;
}
五、运行结果示例
串口输出类似如下:
———- CPU RunTime Stats ———-
Task Abs Time % Time
IDLE 1234567 38.50
Task1 987654 30.81
Task2 654321 20.41
Stats 326789 10.19
Tmr Svc 10234 0.09
Total CPU Usage: 61.50%
—————————————
可以看到:
- IDLE 任务占比 38.50%,说明 CPU 空闲率为 38.50%;
- Total CPU Usage 为 61.50%,与空闲率对应;
- 每个任务都有对应的绝对运行时间和百分比。
六、注意事项
计时器频率不要太高
1MHz 的 32位计数器约 71 分钟溢出,100kHz 约 11.9 小时。长时间运行建议处理溢出,或使用 10kHz~100kHz 的时基。
不要在高频率任务或中断中调用统计函数
vTaskGetRunTimeStats() 内部会动态申请内存,建议在低优先级任务中周期性调用。
任务栈要足够大
尤其是统计任务中使用 printf 和 1024 字节缓冲区,需要适当增大任务栈。
开启统计会增加任务切换开销
对实时性要求极高的场景要谨慎使用。
确保 SystemCoreClock 正确
标准库工程中该变量通常在 system_stm32f10x.c 中定义,需与实际系统时钟一致。
总结
通过 FreeRTOS 自带的运行时间统计功能,配合 STM32 标准库定时器,可以方便地查看各任务的 CPU 占用率。核心就是:
- 配置一个高精度计时器;
- 开启 configGENERATE_RUN_TIME_STATS;
- 调用 vTaskGetRunTimeStats() 打印统计表;
- 通过空闲任务占比计算整体 CPU 使用率。
这套方法简单实用,适合大部分 FreeRTOS 嵌入式项目。





