3. 任务优先级实验
任务目标
创建两个RTOS任务,分别赋予不同优先级。高优先级任务周期性唤醒并执行短暂工作,低优先级任务持续运行。通过观察串口日志,验证高优先级任务能够抢占低优先级任务,并在高优先级任务休眠期间低优先级任务恢复执行。
实现原理
FreeRTOS采用基于优先级的抢占式调度。当高优先级任务进入就绪态时,调度器立即暂停当前运行的低优先级任务,切换到高优先级任务。高优先级任务调用vTaskDelay主动让出CPU后,低优先级任务才继续运行。本实验使用两个LED翻转作为任务工作的可视化指示。
完整代码
/* FreeRTOS + STM32 HAL 环境 */
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
/* 任务句柄 */
TaskHandle_t xHighTaskHandle = NULL;
TaskHandle_t xLowTaskHandle = NULL;
/* 高优先级任务:每200ms翻转LED1 */
void vHighPriorityTask(void *pvParameters)
{
for (;;)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
vTaskDelay(pdMS_TO_TICKS(200));
}
}
/* 低优先级任务:持续翻转LED2,无阻塞 */
void vLowPriorityTask(void *pvParameters)
{
for (;;)
{
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
}
}
void vTaskPriorityDemo_Init(void)
{
/* 创建高优先级任务:优先级3 */
xTaskCreate(vHighPriorityTask, "High", 128, NULL, 3, &xHighTaskHandle);
/* 创建低优先级任务:优先级1 */
xTaskCreate(vLowPriorityTask, "Low", 128, NULL, 1, &xLowTaskHandle);
/* 启动调度器(若尚未启动) */
vTaskStartScheduler();
}
运行与观察
- 将代码烧录至STM32开发板(LED1、LED2分别连接至两个GPIO引脚)。
- 打开串口调试助手(波特率115200),观察任务调度行为。
- 预期现象:
- LED1(高优先级):以200ms为周期稳定翻转,不受低优先级任务影响。
- LED2(低优先级):仅在高优先级任务调用vTaskDelay期间翻转。由于高优先级任务每200ms唤醒一次且执行时间极短,LED2的翻转频率远低于LED1,且表现为“间歇性快速翻转”。
验证方法
在vHighPriorityTask和vLowPriorityTask中分别加入串口打印语句(如printf("H\r\n")和printf("L\r\n")),观察串口输出:
- 输出序列中
H严格按200ms间隔出现,且每次H出现时立即打断L的输出流。 - 在两次
H之间,L会连续输出多条,证明低优先级任务在高优先级任务休眠期间获得CPU。 - 若将两个任务优先级改为相同值(如均为2),则LED2翻转频率显著提升,且两个LED的翻转模式变为时间片轮转,验证优先级差异是抢占行为的原因。
关键说明
- 低优先级任务中不能调用任何阻塞API(如
vTaskDelay),否则高优先级任务休眠时低优先级任务也会休眠,无法观察到抢占恢复过程。 - 若使用printf重定向,需确保在FreeRTOS启动前完成串口初始化,并注意中断优先级设置(FreeRTOS管理的中断优先级范围需与HAL库配置一致)。
- 任务栈大小128字(512字节)足以容纳本示例的简单操作,实际工程中应根据任务复杂度调整。
上图展示了1000ms时间窗口内两个任务的执行状态。高优先级任务每200ms唤醒并运行极短时间(图中以30ms宽度示意,实际远小于此),其余时间处于延时状态。低优先级任务仅在两个高优先级运行窗口之间获得CPU执行权,直观体现了抢占式调度的行为特征。