3. 任务优先级实验
任务目标
创建两个不同优先级的RTOS任务,通过串口输出观察高优先级任务抢占低优先级任务的行为,理解基于优先级的抢占式调度机制。
环境依赖
本实验基于FreeRTOS(版本≥10.0),硬件平台为STM32F407开发板(或任意支持FreeRTOS的Cortex-M内核MCU)。需要准备:
- STM32CubeIDE或Keil MDK开发环境
- FreeRTOS内核源码(已移植到目标平台)
- 串口调试助手(115200-8-N-1)
核心实现
创建两个任务:高优先级任务(优先级2)和低优先级任务(优先级1)。低优先级任务持续打印字符L,高优先级任务周期性打印字符H并短暂延时。由于高优先级任务就绪时会立即抢占低优先级任务,串口输出中H会周期性打断L的连续输出。
#include "FreeRTOS.h"
#include "task.h"
#include "uart.h" // 平台串口驱动,提供 UART_SendString()
// 任务函数声明
void vLowPriorityTask(void *pvParameters);
void vHighPriorityTask(void *pvParameters);
// 任务句柄
TaskHandle_t xLowTaskHandle = NULL;
TaskHandle_t xHighTaskHandle = NULL;
// 低优先级任务:持续打印 'L'
void vLowPriorityTask(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
UART_SendString("L");
}
}
// 高优先级任务:周期性打印 'H' 并延时
void vHighPriorityTask(void *pvParameters)
{
(void)pvParameters;
const TickType_t xDelay = pdMS_TO_TICKS(200);
for (;;)
{
UART_SendString("H");
vTaskDelay(xDelay); // 阻塞自身,让低优先级任务运行
}
}
// 主函数(在 main.c 中调用)
int main(void)
{
// 硬件初始化(时钟、GPIO、UART等)——由平台BSP提供
BSP_Init();
// 创建低优先级任务(优先级1)
xTaskCreate(vLowPriorityTask, "Low", 128, NULL, 1, &xLowTaskHandle);
// 创建高优先级任务(优先级2)
xTaskCreate(vHighPriorityTask, "High", 128, NULL, 2, &xHighTaskHandle);
// 启动调度器
vTaskStartScheduler();
// 正常情况下不会执行到这里
for (;;);
}
运行与验证
- 将上述代码中的
UART_SendString()和BSP_Init()替换为你的平台实际驱动函数。 - 编译并烧录到开发板,打开串口调试助手。
- 预期输出模式:串口持续输出大量
L,但每隔约200ms会插入一个H,形成类似LLLLHLLLLLHLLLLL...的序列。 - 判定通过标准:
- 输出中
H的出现间隔约为200ms(与vTaskDelay设定一致); - 在
H出现之前,L连续输出无中断——证明低优先级任务在无高优先级任务就绪时独占CPU; - 每次
H输出后,L立即恢复输出——证明高优先级任务延时阻塞后,低优先级任务自动恢复运行。
调度机制说明
FreeRTOS采用优先级抢占式调度:
- 当高优先级任务进入就绪态(如延时结束),内核立即暂停当前运行的低优先级任务,切换到高优先级任务;
- 高优先级任务调用
vTaskDelay()后进入阻塞态,此时低优先级任务成为最高优先级就绪任务,恢复运行; - 两个任务共享CPU时间,但高优先级任务总能“插队”——这正是抢占式调度的核心特征。
常见问题
- 串口只输出L或只输出H:检查任务优先级是否设置正确(高优先级数值必须大于低优先级);确认
vTaskDelay延时是否生效。 - 输出乱码:检查串口波特率与
UART_SendString实现是否匹配。 - 系统卡死无输出:确认堆栈大小(128字)是否足够,可增大至256;检查
vTaskStartScheduler前是否已完成必要外设初始化。