.
9.3.1 任务优先级
FreeRTOS 的任务优先级(Task Priority)是决定“谁先运行”的核心机制。
在 FreeRTOS 中,调度器(Scheduler)遵循一条铁律:
“永远让处于就绪态(Ready)且优先级最高的任务运行。”
1. 优先级的数值定义
数字越大 = 优先级越高
-
这是 FreeRTOS 的设定(注意:这与 ARM Cortex-M 硬件中断优先级的逻辑相反,硬件中断通常是数字越小优先级越高,不要混淆)。
2. 调度机制:抢占与时间片
FreeRTOS 的调度主要处理两种情况:
情况 A:优先级不同 —— 抢占 (Preemption)
如果系统中有一个优先级为 3 的任务和一个优先级为 1 的任务,且它们都处于“就绪态”:
-
结果:优先级 3 的任务会独占 CPU。
-
抢占:假设优先级 1 的任务正在运行,此时优先级 3 的任务(比如等待的定时器到了)突然变成了就绪态。调度器会立即暂停优先级 1 的任务,切换到优先级 3 的任务。
-
注意:除非优先级 3 的任务自己主动放弃 CPU(进入阻塞态,如调用
vTaskDelay),否则优先级 1 的任务永远得不到运行机会。
情况 B:优先级相同 —— 时间片轮转 (Time Slicing)
如果系统中有两个优先级都为 2 的任务(Task A 和 Task B),且都就绪:
-
结果:它俩会“轮流坐庄”。
-
机制:FreeRTOS 利用系统滴答定时器(Systick),将时间切成一个个“时间片”(Tick,通常是 1ms)。
-
第 1ms:运行 Task A
-
第 2ms:运行 Task B
-
第 3ms:运行 Task A
-
...
-
-
宏观上看,它们好像在同时运行。
3. 两个特殊的优先级任务

在学习调度方法之前,你只要初略地知道:
- FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
- 对于相同优先级的、可运行的任务,轮流执行
这无需记忆,就像我们举的例子:
- 厨房着火了,当然优先灭火
- 喂饭、回复信息同样重要,轮流做
----------------------------------------------分界线-----------------------------------------------------------
9.3.2 Tick

1. 什么是 Tick?
-
物理本质:它是一个周期性的硬件定时器中断。
-
来源:在 STM32(ARM Cortex-M)上,通常由内核自带的 SysTick 定时器 产生。
-
形象比喻:Tick 就像是一个节拍器或者心跳。
-
“滴...滴...滴...”,每响一下,操作系统就会醒过来处理一次事务。
-
在 FreeRTOS(以及大多数实时操作系统)中,Tick(系统节拍) 是整个系统运转的“心跳”。
如果没有 Tick,操作系统就无法感知时间的流逝,也就无法实现“延时”、“超时等待”或者“时间片轮转调度”。
以下是关于 Tick 的核心知识点讲解:
1. 什么是 Tick?
-
物理本质:它是一个周期性的硬件定时器中断。
-
来源:在 STM32(ARM Cortex-M)上,通常由内核自带的 SysTick 定时器 产生。
-
形象比喻:Tick 就像是一个节拍器或者心跳。
-
“滴...滴...滴...”,每响一下,操作系统就会醒过来处理一次事务。
-
2. Tick 的频率
Tick 的频率(configTICK_RATE_HZ)
Tick 发生的快慢由 FreeRTOSConfig.h 中的宏 configTICK_RATE_HZ 决定。
-
含义:每秒钟产生多少次 Tick 中断。
-
常见设置:1000 (即 1000Hz)。
-
如果设为 1000,意味着每 1ms 发生一次中断。
-
这是 STM32 开发中最常用的配置,因为它让 1 个 Tick 正好等于 1 毫秒,便于计算。(通常1ms)
-
3. 每当 Tick 发生时,OS 做了什么?
每当硬件产生一次 Tick 中断,FreeRTOS 的内核代码(xPortSysTickHandler)就会执行。它主要做两件事:
-
时间的维护 (Timekeeping):
-
系统内部有一个全局变量
xTickCount(Tick 计数器),它会 +1。 -
就像墙上的时钟走了一秒。系统通过由于这个变量来计算“现在运行了多久”。
-
检查延时列表:OS 会检查所有正在“睡觉”(Blocked)的任务:“喂,你设定的唤醒时间到了没?”。如果到了,就把任务叫醒(改为 Ready 态)。
-
-
调度决策 (Scheduling):
-
如果配置了时间片轮转,OS 会在 Tick 中断里判断:“当前任务的时间片用完了吗?是不是该换同优先级的另一个任务上场了?”
-
如果需要切换,就会触发上下文切换(Context Switch)。
-
4. 这里的坑:Tick 与 毫秒 (ms) 的换算

5. 深入理解:Tick 对精度的影响

该处结论,在第七点说明了.vTaskDelay误差并不大
6/总结

7/为什么用vTaskDelay来延迟

我们用 vTaskDelay 不是因为它准,而是因为它懂事(懂得让出 CPU)。 



9.3.4 修改优先级
1. 获取任务优先级 (uxTaskPriorityGet)

2. 设置任务优先级 (vTaskPrioritySet)

3. 核心机制:发生什么了?(重要!)
4. 代码示例
场景一:任务“自省” (操作自己)
场景描述:任务想知道自己当前的优先级是多少,并在串口打印出来。 用到函数:uxTaskPriorityGet(NULL)
void vTaskMyInfo( void * pvParameters )
{
UBaseType_t uxMyPriority;
for( ;; )
{
// 1. 获取自己的优先级
// 参数传 NULL,表示“获取我当前正在运行的这个任务的优先级”
uxMyPriority = uxTaskPriorityGet( NULL );
// 2. 打印出来
printf("My current priority is: %ld\n", uxMyPriority);
vTaskDelay( pdMS_TO_TICKS( 1000 ) );
}
}
场景二
void vTaskDataProcess( void * pvParameters )
{
UBaseType_t uxNormalPriority;
// 保存一下原来的优先级 (假设原来是 1)
uxNormalPriority = uxTaskPriorityGet( NULL );
for( ;; )
{
// --- 阶段1:普通工作 ---
printf("Working lazily...\n");
// --- 阶段2:进入紧急状态 ---
// 假设检测到大量数据涌入,需要全力处理
// 将自己提升到最高优先级 (configMAX_PRIORITIES - 1)
printf("Emergency! Boosting priority!\n");
vTaskPrioritySet( NULL, configMAX_PRIORITIES - 1 );
// 此时,我变成了全场最高,几乎没人能打断我
Heavy_Calculation_Function();
// --- 阶段3:恢复正常 ---
// 事情干完了,把优先级降回原来的值 (1)
// 否则其他任务就饿死了
printf("Done. Going back to normal.\n");
vTaskPrioritySet( NULL, uxNormalPriority );
vTaskDelay( pdMS_TO_TICKS( 500 ) );
}
}
场景三:老板指挥员工 (操作别的任务)
场景描述:
-
Task A (老板/按键检测):负责监控。
-
Task B (员工/LED):平时慢闪(优先级低)。
-
当 Task A 检测到按键按下时,把 Task B 的优先级调高,让它立刻响应。
用到函数:需要用到任务句柄 TaskHandle_t。
/* FreeRTOS 优先级动态修改示例 - "老板指挥员工"
场景:Task A (Boss) 检测按键,动态修改 Task B (Employee) 的优先级
*/
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
/* ============================================================ */
/* 1. 全局变量:用于存放"员工"的任务句柄 (相当于工号) */
/* ============================================================ */
TaskHandle_t xLedTaskHandle = NULL;
/* 模拟按键读取函数 (返回 1 表示按下,0 表示松开) */
int Key_Pressed(void) {
// 实际项目中这里读取 GPIO
// 这里为了演示,假设一直返回 1 (模拟按下状态) 或者你可以自己修改逻辑
return 1;
}
/* ============================================================ */
/* 2. Task B (员工/LED):被控制的对象 */
/* 平时优先级低(1),可能会被提升到(4) */
/* ============================================================ */
void vTaskLed(void *pvParameters)
{
UBaseType_t uxCurrentPriority;
for( ;; )
{
/* 获取自己当前的优先级 (NULL 代表自己) */
uxCurrentPriority = uxTaskPriorityGet(NULL);
/* 打印当前状态 */
printf("[Task B - LED] Running... My Current Priority is: %ld\r\n", uxCurrentPriority);
/* 模拟干活 */
/* 如果优先级变为 4,这里会抢占 Task A 的运行时间 */
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/* ============================================================ */
/* 3. Task A (老板/按键):控制者 */
/* 优先级固定为 3 */
/* ============================================================ */
void vTaskKey(void *pvParameters)
{
/* 定义两个常量表示优先级 */
const UBaseType_t uxLowPriority = 1;
const UBaseType_t uxHighPriority = 4;
for( ;; )
{
/* 模拟检测按键 */
if( Key_Pressed() == 1 )
{
/* 重点:使用全局句柄 xLedTaskHandle 来指定要修改谁 */
/* 将 Task B 提升到 4 (比老板自己 3 还高) */
/* 现象:这行代码执行完瞬间,如果 Task B 处于就绪态,CPU 会立即跳转去运行 Task B */
vTaskPrioritySet(xLedTaskHandle, uxHighPriority);
printf("[Task A - Boss] Key Pressed! Promoted Task B to High Priority!\r\n");
}
else
{
/* 按键松开,把 Task B 打回原形 */
vTaskPrioritySet(xLedTaskHandle, uxLowPriority);
printf("[Task A - Boss] Key Released. Reset Task B to Low Priority.\r\n");
}
/* 延时 100ms,避免按键检测太快占用 CPU */
vTaskDelay(pdMS_TO_TICKS(100));
}
}
/* ============================================================ */
/* 4. 主函数 */
/* ============================================================ */
int main(void)
{
/* 硬件初始化 (时钟、串口、GPIO等) ... */
/* 创建 Task B (员工) */
/* 关键点:最后一个参数 &xLedTaskHandle */
/* 只有传入这个地址,创建成功后 xLedTaskHandle 里才会有值,Task A 才能用 */
xTaskCreate(vTaskLed,
"LED_Task",
128,
NULL,
1, /* 初始优先级:1 */
&xLedTaskHandle /* 【关键】将句柄保存到全局变量 */
);
/* 创建 Task A (老板) */
/* 不需要被别人控制,句柄参数填 NULL */
xTaskCreate(vTaskKey,
"Key_Task",
128,
NULL,
3, /* 初始优先级:3 */
NULL
);
/* 启动调度器 */
vTaskStartScheduler();
/* 正常情况永远不会执行到这里 */
while(1);
}
句柄设置为null
写成 TaskHandle_t xLedTaskHandle = NULL; 的作用就像是: 给员工发一张空白的工牌,而不是随便捡一张废纸写个假号码。
-
安全:防止野指针乱指导致死机。
-
严谨:方便后续代码判断任务是否已经合法存在。
TCB
TCB是什么



TCB和句柄关系











被折叠的 条评论
为什么被折叠?



