2. 任务优先级与抢占式调度
任务说明
本案例在RTOS中创建三个不同优先级的任务,通过LED翻转与串口打印的组合,直观展示高优先级任务对低优先级任务的抢占过程。任务A(最高优先级)每500ms翻转LED1并打印一次;任务B(中等优先级)每1000ms翻转LED2并打印一次;任务C(最低优先级)每2000ms翻转LED3并打印一次。由于任务A周期最短且优先级最高,当任务A就绪时会立即抢占正在运行的低优先级任务,从而在串口输出中观察到任务A的打印总是准时出现,而任务B和任务C的打印可能被延迟。
硬件连接
- LED1 → GPIO2(开发板载LED)
- LED2 → GPIO4(外接LED,串联330Ω电阻到GND)
- LED3 → GPIO5(外接LED,串联330Ω电阻到GND)
- 串口默认配置:115200-8-N-1
接线示意:ESP32开发板GPIO4/GPIO5分别通过限流电阻连接LED阳极,LED阴极接GND。GPIO2为板载LED,无需外接。
代码实现
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define LED1_GPIO 2
#define LED2_GPIO 4
#define LED3_GPIO 5
// 任务函数声明
void taskA(void *arg);
void taskB(void *arg);
void taskC(void *arg);
void app_main(void)
{
// 初始化GPIO
gpio_set_direction(LED1_GPIO, GPIO_MODE_OUTPUT);
gpio_set_direction(LED2_GPIO, GPIO_MODE_OUTPUT);
gpio_set_direction(LED3_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(LED1_GPIO, 0);
gpio_set_level(LED2_GPIO, 0);
gpio_set_level(LED3_GPIO, 0);
// 创建三个不同优先级的任务
// 参数:任务函数、任务名、栈大小、参数、优先级、任务句柄
xTaskCreate(taskA, "taskA", 2048, NULL, 3, NULL); // 最高优先级
xTaskCreate(taskB, "taskB", 2048, NULL, 2, NULL); // 中等优先级
xTaskCreate(taskC, "taskC", 2048, NULL, 1, NULL); // 最低优先级
}
// 任务A:最高优先级,每500ms翻转LED1
void taskA(void *arg)
{
while (1) {
gpio_set_level(LED1_GPIO, 1);
printf("[TaskA] LED1 ON, tick=%lu\n", (unsigned long)xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_level(LED1_GPIO, 0);
printf("[TaskA] LED1 OFF, tick=%lu\n", (unsigned long)xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 任务B:中等优先级,每1000ms翻转LED2
void taskB(void *arg)
{
while (1) {
gpio_set_level(LED2_GPIO, 1);
printf("[TaskB] LED2 ON, tick=%lu\n", (unsigned long)xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(1000));
gpio_set_level(LED2_GPIO, 0);
printf("[TaskB] LED2 OFF, tick=%lu\n", (unsigned long)xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 任务C:最低优先级,每2000ms翻转LED3
void taskC(void *arg)
{
while (1) {
gpio_set_level(LED3_GPIO, 1);
printf("[TaskC] LED3 ON, tick=%lu\n", (unsigned long)xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(2000));
gpio_set_level(LED3_GPIO, 0);
printf("[TaskC] LED3 OFF, tick=%lu\n", (unsigned long)xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
抢占调度时序示意
编译与运行
# 在ESP-IDF环境中编译
idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
验证方法与判定标准
- 观察LED闪烁节奏:LED1以约500ms周期快速闪烁,LED2约1000ms,LED3约2000ms。若三个LED均按各自周期闪烁,说明三个任务均被正常调度执行。
- 串口输出分析:在串口监视器中观察打印信息。TaskA的打印间隔严格保持500ms(tick差值约500),不受其他任务影响;TaskB和TaskC的打印间隔可能因被TaskA抢占而出现微小抖动,但平均周期仍接近其设定值。
- 抢占行为确认:当TaskA每次就绪时(tick为500的整数倍),若此时TaskB或TaskC正在运行,TaskA会立即获得CPU控制权。可通过串口时间戳验证:TaskA的打印tick值总是精确出现在500ms的整数倍附近,而TaskB/TaskC的打印tick值可能被推迟到TaskA休眠后才出现。
- 判定通过标准:串口输出中TaskA的打印间隔误差小于10ms(即tick差值在490~510之间),且TaskB/TaskC的打印间隔平均值接近其设定周期,即可判定抢占式调度机制工作正常。
预期输出示例(tick值基于1000Hz系统节拍):
[TaskA] LED1 ON, tick=0
[TaskB] LED2 ON, tick=0
[TaskC] LED3 ON, tick=0
[TaskA] LED1 OFF, tick=500
[TaskA] LED1 ON, tick=1000
[TaskB] LED2 OFF, tick=1000
[TaskA] LED1 OFF, tick=1500
[TaskA] LED1 ON, tick=2000
[TaskC] LED3 OFF, tick=2000