tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第7章 时间片调度 + 第9章 时间管理/延时函数" author: "尚硅谷研究院" date: 2026-07-17
用生活理解:时间片调度就像自助餐厅的取餐规则——同等级的人轮流取菜,每人吃一口就轮到下一位(Round-Robin)。vTaskDelay 相当于"我过一会儿再来排队",vTaskDelayUntil 则是"以后每周二下午三点我来,雷打不动"。
时间片调度指多个相同优先级的任务轮流获得 CPU 使用权,每个任务执行一个时间片(一个 Tick)后被切换到下一个同优先级任务。
关键配置:
configUSE_TIME_SLICING:启用/禁用时间片调度(默认开启)configUSE_PREEMPTION:启用抢占式调度(必须开启,否则时间片不生效)configTICK_RATE_HZ:Tick 频率,决定时间片长度时间片 = 1 个 Tick = 1 / configTICK_RATE_HZ 秒
抢占式调度 + 时间片调度:
高优先级任务 → 总是先运行(抢占)
同优先级任务 → 轮流运行(时间片轮转)
低优先级任务 → 高/同优先级任务阻塞时才运行
configTICK_RATE_HZ = 20 → 每个 Tick 50ms → 时间片 50msconfigTICK_RATE_HZ = 1000 → 每个 Tick 1ms → 时间片 1ms| 函数 | 类型 | 描述 | 漂移 |
|---|---|---|---|
vTaskDelay(Tick) |
相对延时 | 从调用开始等待指定 Tick 数 | ❌ 会漂移 |
vTaskDelayUntil(pxPrevWakeTime, Tick) |
绝对延时 | 以固定周期执行,不受执行时间影响 | ✅ 固定周期 |
HAL_Delay(ms) |
忙等待 | 占用 CPU 循环等待 | 阻塞式查询 |
vTaskDelay 的漂移问题:
第1周期:任务执行 20ms + 延时 500ms = 间隔 520ms
第2周期:任务执行 35ms + 延时 500ms = 间隔 535ms
→ 实际周期越跑越慢(漂移累积)
vTaskDelayUntil 的固定周期:
第1周期:记录唤醒时间 t1,延时到 t1+500
第2周期:记录唤醒时间 t2,延时到 t2+500
→ 无论任务执行多久,周期始终为 500ms
P05(时间片调度):
HAL_Delay(10) 模拟工作量P08(延时对比):
vTaskDelay(500) + HAL_Delay(20) 模拟工作 → 演示漂移vTaskDelayUntil(&pxPreviousWakeTime, 500) + HAL_Delay(20) → 演示固定周期File: 05_时间片调度/Core/Src/freertos_demo.c
#include "freertos_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);
/* 两个任务使用相同优先级(均为2)→ 时间片轮转 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);
#define TASK2_STACK 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;
void task2(void *pvParameters);
void freertos_start(void)
{
xTaskCreate((TaskFunction_t)start_task,
(char *)"start_task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handle);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t)task1, (char *)"task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK, (void *)NULL,
(UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle);
xTaskCreate((TaskFunction_t)task2, (char *)"task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK, (void *)NULL,
(UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handle);
vTaskDelete(NULL);
taskEXIT_CRITICAL();
}
/**
* @description: task1——进入临界区打印计数,模拟工作
* 注意:临界区内不触发任务切换,所以每次打印会连续输出多次后切换到task2
*/
void task1(void *pvParameters)
{
uint16_t task1_count = 0;
while (1)
{
taskENTER_CRITICAL(); // 进入临界区:防止切换
printf("task1正在运行[%d]次...\r\n", ++task1_count);
// vTaskDelay(500); // 使用vTaskDelay会触发调度,观察不到时间片
HAL_Delay(10); // 忙等待模拟工作负载,不切换任务
taskEXIT_CRITICAL(); // 退出临界区:允许切换
}
}
/**
* @description: task2——与task1同优先级,时间片轮转
*/
void task2(void *pvParameters)
{
uint16_t task2_count = 0;
while (1)
{
taskENTER_CRITICAL();
printf("task2正在运行[%d]次...\r\n", ++task2_count);
HAL_Delay(10);
taskEXIT_CRITICAL();
}
}
/* 观察现象:
当configTICK_RATE_HZ=20时,时间片=50ms
HAL_Delay(10)执行时间<50ms,
所以每次进入临界区可以在一个时间片内打印多次
退出临界区后下次调度切换到另一个任务 */
File: 08_延时函数/Core/Src/freertos_demo.c
#include "freertos_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);
void freertos_start(void)
{
xTaskCreate((TaskFunction_t)start_task,
(char *)"start_task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handle);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t)task1, (char *)"task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK, (void *)NULL,
(UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle);
xTaskCreate((TaskFunction_t)task2, (char *)"task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK, (void *)NULL,
(UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handle);
vTaskDelete(NULL);
taskEXIT_CRITICAL();
}
/**
* @description: task1——使用vTaskDelay相对延时 + HAL_Delay模拟工作
* 现象:LED翻转间隔会逐渐漂移(越来越慢)
*/
void task1(void *pvParameters)
{
while (1)
{
LED_Toggle(LED1_Pin); // 翻转LED1
HAL_Delay(20); // 模拟工作任务耗时20ms
vTaskDelay(500); // 相对延时500ms(不包括HAL_Delay耗时)
} // 实际周期 = 20ms + 500ms = 520ms
}
/**
* @description: task2——使用vTaskDelayUntil绝对延时 + HAL_Delay模拟工作
* 现象:LED翻转周期严格固定为500ms
*/
void task2(void *pvParameters)
{
TickType_t pxPreviousWakeTime = xTaskGetTickCount(); // 获取当前Tick计数值作为基准
while (1)
{
LED_Toggle(LED2_Pin); // 翻转LED2
HAL_Delay(20); // 模拟工作任务耗时20ms
vTaskDelayUntil(&pxPreviousWakeTime, 500); // 绝对延时:等待到 pxPrevWakeTime + 500
} // 实际周期始终为500ms,不受HAL_Delay影响
}
P05 FreeRTOSConfig.h(降低 Tick 频率便于观察时间片):
#define configTICK_RATE_HZ 20 // Tick频率20Hz,时间片=50ms
#define configUSE_TIME_SLICING 1 // 启用时间片调度(默认即开启)
#define configUSE_PREEMPTION 1 // 启用抢占式调度(必须)
| Function | Parameters | Return | Description |
|---|---|---|---|
vTaskDelay |
xTicksToDelay | void | 相对延时,从调用时开始计时 |
vTaskDelayUntil |
pxPreviousWakeTime, xTimeIncrement | BaseType_t | 绝对延时,固定周期 |
xTaskGetTickCount |
无 | TickType_t | 获取当前 Tick 计数值 |
configTICK_RATE_HZ |
宏定义 | — | 配置每秒 Tick 数 |
configUSE_TIME_SLICING |
宏定义 | — | 启用/禁用的时间片调度 |
HAL_Delay |
Delay | void | HAL 库忙等待延时(阻塞 CPU) |
vTaskDelay 会导致周期逐渐漂移。对周期有严格要求的场景必须使用 vTaskDelayUntil。vTaskDelayUntil 要求 pxPreviousWakeTime 初始化为 xTaskGetTickCount(),且在每次调用后会被 API 自动更新。HAL_Delay 是忙等待不释放 CPU,在 RTOS 中应优先使用 vTaskDelay。如果定时器中断优先级配置不当,HAL_Delay 可能无法正常工作。