目录一vTaskGetRunTimeStats() — 任务运行时间统计1函数原型2需要的三个宏缺一不可3输出格式4跟 uxTaskGetSystemState() 的关系5时基定时器的要求6跟昨天学的 API 对比二最终改动清单 — rtos\8\ 任务运行时间统计实验TIM6.hTIM6.cstm32f4xx_it.cFreeRTOSConfig.hmain.cfreertos_demo.c — 整个文件重写Key.c完整中断链路跟正点代码的对比三rtos\8 任务运行时间轴启动阶段start_task优先级 1活不过 1ms稳态运行无限循环CPU 占用占比为什么 IDLE 是 99%按下 KEY1 那一刻一vTaskGetRunTimeStats()— 任务运行时间统计1函数原型void vTaskGetRunTimeStats( char *pcWriteBuffer );形参含义pcWriteBuffer输出缓冲区指针写入格式化后的表格字符串。每个任务约需 40 字节返值无。数据写入pcWriteBuffer。2需要的三个宏缺一不可宏在哪设干什么configGENERATE_RUN_TIME_STATS 1FreeRTOSConfig.h开运行时间统计功能configUSE_STATS_FORMATTING_FUNCTIONS 1FreeRTOSConfig.h开格式化输出昨天已开 ✅portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()我们自己在 FreeRTOSConfig.h 实现初始化一个高精度时基定时器portGET_RUN_TIME_COUNTER_VALUE()我们自己在 FreeRTOSConfig.h 实现读该定时器的当前计数值3输出格式Task Abs Time % Time task2 12345 48% task1 10000 39% IDLE 3333 13% start_task 0 1%列含义Task任务名Abs Time该任务自启动以来累计跑了多久计时器 tick 数% Time占总 CPU 时间的百分比4跟uxTaskGetSystemState()的关系vTaskGetRunTimeStats内部就是调了uxTaskGetSystemState()然后把它返回的ulRunTimeCounter字段格式化成表格。核心逻辑源码 tasks.c// 简化版内部实现 void vTaskGetRunTimeStats(char *pcWriteBuffer) { TaskStatus_t *pxTaskStatusArray; UBaseType_t uxArraySize; uint32_t ulTotalRunTime; ​ uxArraySize uxTaskGetNumberOfTasks(); pxTaskStatusArray pvPortMalloc(uxArraySize * sizeof(TaskStatus_t)); ​ if (pxTaskStatusArray ! NULL) { // ★ uxTaskGetSystemState 第三个参数传 ulTotalRunTime不是 NULL 了 uxArraySize uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, ulTotalRunTime); ​ // 用 sprintf 格式化每个任务的百分比 for (x 0; x uxArraySize; x) { ulStatsAsPercentage pxTaskStatusArray[x].ulRunTimeCounter / ulTotalRunTime; sprintf(pcWriteBuffer, %s\t\t%lu\t\t%lu%%\r\n, ...); } ​ vPortFree(pxTaskStatusArray); } }5时基定时器的要求计时精度需高于系统时钟节拍精度的10 至 100 倍我们 SysTick 是 1ms1KHz所以时基定时器至少10KHz ~ 100KHz。比如用 TIM6 配 10KHz 0.1ms 精度。6跟昨天学的 API 对比vTaskListvTaskGetRunTimeStats输出内容状态、优先级、栈剩余运行时间、CPU 占比内部调用uxTaskGetSystemStateuxTaskGetSystemState额外宏configUSE_STATS_FORMATTING_FUNCTIONS还要configGENERATE_RUN_TIME_STATS 两个 port 宏需要外设无需要一个硬件定时器不能用 SysTick它已经是心跳了二最终改动清单 — rtos\8\ 任务运行时间统计实验TIM6.h行号内容12-13新增RT_STATS_PSC(89)/RT_STATS_ARR(9)— 100KHz 时基宏17新增void ConfigureTimeForRunTimeStats(void)声明18新增extern uint32_t FreeRTOSRunTimeTicks全局计数器声明TIM6.c行号内容7新增uint32_t FreeRTOSRunTimeTicks全局计数器定义29-49新增ConfigureTimeForRunTimeStats()— 配 TIM6 为 100KHzPSC89, ARR956-60HAL_TIM_PeriodElapsedCallback里 TIM6 分支FreeRTOSRunTimeTicksstm32f4xx_it.c行号内容172-178新增TIM6_DAC_IRQHandler()— ISR 入口调HAL_TIM_IRQHandler(htim6)FreeRTOSConfig.h行号内容172configGENERATE_RUN_TIME_STATS0→1173新增#include ../BSP/TIM6/TIM6.h— 引入 ConfigureTimeForRunTimeStats 声明174新增portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()→ConfigureTimeForRunTimeStats()175新增portGET_RUN_TIME_COUNTER_VALUE()→FreeRTOSRunTimeTicksportCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 这是一个 初始化函数宏。 当 FreeRTOS 启动调度器vTaskStartScheduler()时会自动调用这个宏。 它的职责是 初始化一个高精度定时器这里是 TIM6为任务时间统计做准备。 你需要在 ConfigureTimeForRunTimeStats() 里做什么 通常需要完成以下几件事 开启 TIM6 时钟 配置 TIM6 的预分频PSC让计数频率 远高于系统节拍 配置自动重装值ARR决定溢出周期 开启 TIM6 更新中断可选但推荐 启动 TIM6 ​ void ConfigureTimeForRunTimeStats(void) { FreeRTOSRunTimeTicks 0; /* 计数器归零 */ ​ TIM6_CLK_ENABLE(); /* 使能 TIM6 时钟 */ htim6.Instance TIM6; htim6.Init.Prescaler RT_STATS_PSC; /* 89 */ htim6.Init.CounterMode TIM_COUNTERMODE_UP; htim6.Init.Period RT_STATS_ARR; /* 9 */ HAL_TIM_Base_Init(htim6); ​ HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 6, 0); /* 抢占 6低于关键中断 */ HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn); ​ HAL_TIM_Base_Start_IT(htim6); }#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks ​ 这是 获取当前“系统运行时间基准值”的宏。 FreeRTOS 在以下时刻会调用它 任务切换时 系统节拍中断中 调用 vTaskGetRunTimeStats() 时 它的返回值代表“从系统启动到现在一共经过了多少个高精度时间单位”。main.c行号内容5新增#include ./Key/Key.h— 按键模块7新增#include ./TIM6/TIM6.h— TIM6 运行时间统计18新增ConfigureTimeForRunTimeStats()— 调度器启动前配好 TIM620新增Key_Init()— 初始化按键 GPIO28printf 标题 →FreeRTOS Run Time Stats Test!freertos_demo.c — 整个文件重写行号内容37task2 优先级3比 task1 高方便中断响应46static char task_buff[512]—vTaskGetRunTimeStats输出缓冲区88-97task1LED 红 500ms 交替闪烁109-129task2扫描 KEY1(PA0) → 按键触发vTaskGetRunTimeStats(task_buff)→ printf132vTaskDelay(10)— 10ms 扫一次按键Key.c行号内容2-3新增#include FreeRTOS.h/#include task.h— RTOS API30HAL_Delay(10)→vTaskDelay(10 / portTICK_PERIOD_MS)— 消抖不卡 CPU33-34while(...);死循环 →while(...) vTaskDelay(...)— 等松开时让出 CPU完整中断链路TIM6 硬件 (100KHz10μs) → 向量表 TIM6_DAC_IRQHandler [stm32f4xx_it.c:175] → HAL_TIM_IRQHandler(htim6) → HAL_TIM_PeriodElapsedCallback [TIM6.c:54] → FreeRTOSRunTimeTicks [TIM6.c:58] ↓ FreeRTOS 内核 portGET_RUN_TIME_COUNTER_VALUE() 读数 ↓ vTaskGetRunTimeStats() → sprintf 格式化 → printf 输出跟正点代码的对比正点我们时基TIM6btim.c/btim.h专用模块TIM6复用BSP/TIM6/加功能时基初始化port宏在调度器里调main 里主动调 port 宏双重保险ISR 位置btim.c里BTIM_TIMX_INT_IRQHandlerstm32f4xx_it.c里TIM6_DAC_IRQHandler按键触发key_scan(0) KEY0Key_Scan(PA0) KEY1RTOS 版消抖LEDLED0_TOGGLE()LED_R(0/1)红灯交替临界区taskENTER/EXIT_CRITICAL包创建无临界区之前验证过不需要三rtos\8 任务运行时间轴启动阶段上电 → HAL_Init → SysTick_Init(180MHz) → TIM7_Init(HAL时基) → ConfigureTimeForRunTimeStats(TIM6 100KHz 开始计数) → LED_Init / Key_Init / USART1_Init → printf(FreeRTOS Run Time Stats Test!) → freertos_demo() ├── xTaskCreate(start_task, prio1) └── vTaskStartScheduler() └── xPortStartScheduler → prvStartFirstTask → svc 0 → SVC → 第一个任务 start_task 开始跑start_task优先级 1活不过 1msstart_task 跑 ├── xTaskCreate(task1, prio2) → task1 就绪 ├── xTaskCreate(task2, prio3) → task2 就绪比 task1 高 └── vTaskDelete(NULL) → 自杀触发 PendSV → 找到最高优先级 task2(prio3) → task2 开始跑稳态运行无限循环时间轴 → task2 (prio3, 最高): Key_Scan(PA0) → 没按下 → vTaskDelay(10) → 睡 10ms ├─ 10ms 后醒来 ──→ Key_Scan → vTaskDelay(10) → 睡 ├─ 10ms 后醒来 ──→ ...循环... └─ 某次醒来 → KEY1 按下→ vTaskGetRunTimeStats → printf(表格) → vTaskDelay(10) → 睡 task1 (prio2): LED_R(0) 亮 → vTaskDelay(500) → 睡 500ms ├─ 500ms 后醒来 → LED_R(1) 灭 → vTaskDelay(500) → 睡 ├─ 500ms 后醒来 → LED_R(0) 亮 → vTaskDelay(500) → 睡 └─ ...永远红灯亮 500ms / 灭 500ms... IDLE (prio0): 当 task1 和 task2 都在睡的时候跑 → 回收被删任务的尸体 → 空转省电CPU 占用占比为什么 IDLE 是 99%每 1 秒内 ──────────────────────────────────────────────── task2每 10ms 醒来一次读 GPIO vTaskDelay醒着时间 ≈ 几十 μs → 每 1 秒内实际干活 ≈ 0.01 秒 → 1% task1每 500ms 醒来一次LED 翻转 vTaskDelay → 每 1 秒实际干活 ≈ 0.001 秒 → 1% IDLE以上两个任务都睡了的 99% 时间 → IDLE 在空转按下 KEY1 那一刻task2 扫到按键按下 → delay_ms(10) 消抖CPU 轮询不切换任务 → 确认按下 → while(按下) 等松开CPU 轮询 → 松开 → 返回 KEY_ON → vTaskGetRunTimeStats(task_buff) ├── pvPortMalloc → uxTaskGetSystemState(ulTotalTime) │ └── ulTotalTime FreeRTOSRunTimeTicksTIM6 100KHz 累计值 ├── 算出每个任务占比 → sprintf 格式化 └── 写入 task_buff → printf(%s, task_buff) → 串口输出表格 → vTaskDelay(10) → 继续扫按键核心就是两个任务都在睡 → CPU 交给 IDLE → 99% 空闲率。这也证明了 FreeRTOS 的调度没有额外开销——不干活的时候 CPU 就真闲着。