tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第5~6章 + 配套代码01/02/03/06" author: "尚硅谷研究院" date: 2026-07-12 url: ""
用生活理解:可以把 RTOS 想象成一个"公司",每个任务就是一个"员工"。创建任务就是招人,删除就是开除,挂起就是让员工休假(随时能叫回来),调度器就是 CEO —— 决定谁在什么时候干活。
| 函数 | 作用 | 通俗理解 |
|---|---|---|
xTaskCreate() |
动态创建任务 | 公司招人,HR 帮忙找工位(内存) |
xTaskCreateStatic() |
静态创建任务 | 公司招人,但工位你自己提前准备好 |
vTaskDelete() |
删除任务 | 开除员工,工位回收 |
vTaskSuspend() |
挂起任务 | 让员工休假,保留工位 |
vTaskResume() |
恢复任务 | 休假员工叫回来上班 |
vTaskSuspendAll() |
暂停调度器 | CEO 说"都别动,听我讲两句" |
xTaskResumeAll() |
恢复调度器 | CEO 讲完了,大家继续工作 |
有 4 个任务:
#include "FreeRTOS.h"
#include "task.h"
/* ========== 任务配置:相当于每个员工的工牌编号 ========== */
#define START_TASK_STACK 128 // 主管的办公桌大小(128个Word=512字节)
#define START_TASK_PRIORITY 1 // 优先级1(数字越小优先级越低)
TaskHandle_t start_task_handle; // 主管的工牌(句柄)
#define TASK1_STACK 128
#define TASK1_PRIORITY 2 // 比主管高一点
TaskHandle_t task1_handle;
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
#define TASK3_STACK 128
#define TASK3_PRIORITY 4 // 最高优先级
TaskHandle_t task3_handle;
/* ========== 启动 FreeRTOS ========== */
void freertos_start(void)
{
// 第一步:创建主管任务(start_task)
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); // 工牌(句柄)
// 第二步:启动调度器 = CEO 开始上班
vTaskStartScheduler();
// 注意:vTaskStartScheduler 不会返回!
// CEO 一旦开始工作,就永远不停止
}
/* ========== 主管任务 ========== */
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); // 进入临界区 = "关门开大会,谁都不许打扰"
// 主管负责创建三个员工
xTaskCreate((TaskFunction_t)task1, (char *)"task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK, NULL,
(UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle);
xTaskCreate((TaskFunction_t)task2, (char *)"task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK, NULL,
(UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handle);
xTaskCreate((TaskFunction_t)task3, (char *)"task3",
(configSTACK_DEPTH_TYPE)TASK3_STACK, NULL,
(UBaseType_t)TASK3_PRIORITY, (TaskHandle_t *)&task3_handle);
// 主管的使命完成,辞职(删除自己)
vTaskDelete(NULL); // NULL = 删除调用者自己
taskEXIT_CRITICAL(); // 退出临界区
}
/* ========== task1:每500ms闪一次LED1 ========== */
void task1(void *pvParameters)
{
while (1) // 所有任务基本都是死循环(不停干活)
{
LED_Toggle(LED1_Pin); // 翻转LED1
vTaskDelay(500); // 延时500ms = "睡500ms,让出CPU"
}
}
/* ========== task2:每500ms闪一次LED2 ========== */
void task2(void *pvParameters)
{
while (1)
{
LED_Toggle(LED2_Pin);
vTaskDelay(500);
}
}
/* ========== task3:按KEY1删除task1 ========== */
void task3(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = Key_Detect(); // 读按键
if (key == KEY1_PRESS)
{
if (task1_handle != NULL) // 防止重复删除
{
vTaskDelete(task1_handle); // 删除task1
task1_handle = NULL; // 标记已删除
}
}
vTaskDelay(500);
}
}
vTaskDelay vs HAL_Delay:
vTaskDelay(500):告诉调度器"我睡500ms,这期间让别人干活"——主动让出CPUHAL_Delay(500):死等500ms,期间CPU空转——浪费CPUvTaskDelay 而不是 HAL_Delay!vTaskDelete(NULL):传入 NULL 表示删除自己。任务执行完后如果不删除自己,调度器会一直给它分配时间片。
| 方式 | 通俗理解 | 内存谁管 |
|---|---|---|
| 动态创建 | 你只管招人,工位(内存)让操作系统去准备 | FreeRTOS 从堆里自动分配 |
| 静态创建 | 你招人,还得自己给他准备好工位 | 用户提前声明数组和结构体 |
// ======== 必须提前准备的"硬件资源" ========
StackType_t task1_stack[TASK1_STACK]; // 手动准备办公桌(栈空间)
StaticTask_t task1_tcb; // 手动准备工牌(TCB结构体)
// ======== 空闲任务也需要手动准备! ========
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
StaticTask_t idle_task_tcb;
// FreeRTOS 会通过这个接口找你要空闲任务的资源
void vApplicationGetIdleTaskMemory(
StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &idle_task_tcb; // "空闲任务的工牌在这儿"
*ppxIdleTaskStackBuffer = idle_task_stack; // "空闲任务的办公桌在这儿"
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
// ======== 静态创建任务 ========
void freertos_start(void)
{
start_task_handle = xTaskCreateStatic(
(TaskFunction_t)start_task,
(char *)"start_task",
(uint32_t)START_TASK_STACK,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(StackType_t *)start_task_stack, // ⚠️ 把准备好的栈传进去
(StaticTask_t *)&start_task_tcb); // ⚠️ 把准备好的TCB传进去
vTaskStartScheduler();
}
4 个按键分别对应不同操作:
void task3(void *pvParameters)
{
uint8_t key = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
vTaskSuspend(task1_handle); // 让 task1 休假
}
else if (key == KEY2_PRESS)
{
vTaskResume(task1_handle); // 叫 task1 回来
}
else if (key == KEY3_PRESS)
{
vTaskSuspendAll(); // CEO:全部暂停!
}
else if (key == KEY4_PRESS)
{
xTaskResumeAll(); // CEO:继续工作!
}
vTaskDelay(500);
}
}
| 状态 | 怎么进去 | 怎么出来 | 还占内存吗 |
|---|---|---|---|
| 挂起态 | vTaskSuspend() |
vTaskResume() |
占着,随时恢复 |
| 阻塞态 | vTaskDelay() 或等队列/信号量 |
时间到或资源可用 | 占着 |
| 删除态 | vTaskDelete() |
回不来了 | 回收了 |
打个比方:
- 挂起 = 员工长期休假(工位保留,被动——领导让休的)
- 阻塞 = 员工去茶水间接水(一会儿就回来,主动行为)
- 删除 = 员工被开除了(工位回收,回不来了)
vTaskSuspendAll() 只是暂停任务切换,但中断不受影响。这就像 CEO 说"都别动",但快递员(中断)来了照样可以送快递。而 taskENTER_CRITICAL() 是直接把公司大门锁了,连快递都进不来。
// 获取任务优先级 = 查这个员工的职级
UBaseType_t prio = uxTaskPriorityGet(task1_handle);
// 设置任务优先级 = 给员工升职
vTaskPrioritySet(task1_handle, 4);
// 获取系统中任务总数 = 统计公司有多少员工
UBaseType_t count = uxTaskGetNumberOfTasks();
// 获取所有任务状态 = 打印花名册
TaskStatus_t status[10];
uxTaskGetSystemState(status, count, NULL);
for (i = 0; i < count; i++) {
printf("任务名:%s 编号:%d 状态:%d 优先级:%d\r\n",
status[i].pcTaskName,
status[i].xTaskNumber,
status[i].eCurrentState, // 0=运行 1=就绪 2=阻塞 3=挂起
status[i].uxCurrentPriority);
}
// 获取栈历史剩余最小值 = 检查办公桌还剩多少空间
// 用来判断任务栈是否设得太大(浪费)或太小(会溢出)
UBaseType_t remain = uxTaskGetStackHighWaterMark(task2_handle);
// 如果 remain ≈ 0,说明栈快爆了,得加大 STACK 值
// 以表格形式打印所有任务状态
char info[500];
vTaskList(info);
printf("%s\r\n", info);
// 输出示例:
// Task Name State Priority Stack Num
// task1 X 2 45 1
// task2 B 3 120 2
// IDLE R 0 80 3
// 状态: X=运行 B=阻塞 R=就绪 S=挂起 D=删除
code/01_动态创建任务和删除/code/02_静态创建任务和删除/code/03_任务挂起与恢复/code/06_任务状态查询/code/07_任务时间统计/后续章节:[[06-中断管理与时间管理]]