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();
}

运行与观察

  1. 将代码烧录至STM32开发板(LED1、LED2分别连接至两个GPIO引脚)。
  2. 打开串口调试助手(波特率115200),观察任务调度行为。
  3. 预期现象:
    - 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字节)足以容纳本示例的简单操作,实际工程中应根据任务复杂度调整。
0ms 1000ms 200ms 400ms 600ms 800ms 高优先级 运行中 延时200ms 运行中 延时200ms 低优先级 被抢占 运行中 被抢占 运行中 抢占点 抢占点

上图展示了1000ms时间窗口内两个任务的执行状态。高优先级任务每200ms唤醒并运行极短时间(图中以30ms宽度示意,实际远小于此),其余时间处于延时状态。低优先级任务仅在两个高优先级运行窗口之间获得CPU执行权,直观体现了抢占式调度的行为特征。