
一、任务创建创建任务可以分为动态创建、静态创建。核心区别就在于任务控制块TCB和堆栈的内存由谁分配。动态创建xTaskCreate()内存由 FreeRTOS 在运行时自动从堆heap里分配。静态创建xTaskCreateStatic()内存由你自己预先定义好数组FreeRTOS 直接用不碰堆。1、任务栈每个任务独立拥有的一块内存RAM专门用来保存该任务运行时的现场和临时数据。1、任务栈里有什么上下文Context发生任务切换时CPU 的寄存器R0–R12、LR、PC、xPSR 等会被自动压入该任务的栈——这就是现场保护。局部变量函数里int a;这种自动变量存在栈上。函数调用链调用函数时的返回地址、传参。中断嵌套时的现场若在该任务运行时进中断。2、任务栈大小的创建动态创建// 第3个参数 128 栈深度(字数)M3上 128 * 4 512 字节 xTaskCreate(vTask, Task1, 128, NULL, 1, NULL); vTaskStartScheduler();静态创建#define TASK1_STACK_SIZE 128 //定义栈的大小 static StackType_t Task1Stack[TASK1_STACK_SIZE]; // static StaticTask_t Task1TCB;2任务函数void vMyTask(void *pvParameters) { // 可选任务启动前的初始化 for (;;) { // 任务要循环做的事 vTaskDelay(pdMS_TO_TICKS(100)); // 延时100ms让出CPU } // 注意for(;;) 永不返回任务函数不能执行到这里 }1、TCBTask Control Block任务控制块是 FreeRTOS 用来管理每个任务的核心数据结构——相当于任务的身份证 档案袋。TCB 里主要记了什么FreeRTOS 源码tskTaskControlBlock结构体typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /* 栈顶 */ ListItem_t xStateListItem; /* 任务节点 */ StackType_t *pxStack; /* 任务栈起始地址 */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名称字符串形式 */ TickType_t xTicksToDelay; /* 用于延时 */ UBaseType_t uxPriority; }tskTCB; typedef tskTCB TCB_t;2、创建任务函数1、动态函数BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // ① 任务函数名(入口地址) const char *pcName, // ② 任务名(调试用字符串) uint16_t usStackDepth, // ③ 栈深度(单位:字, 128512字节M3) void *pvParameters, // ④ 传给任务的参数(不用就NULL) UBaseType_t uxPriority, // ⑤ 优先级(数值越大越高) TaskHandle_t *pxCreatedTask); // ⑥ 句柄输出(可NULL,要删任务时必用) xTaskCreate(led_task, led_task, 128, NULL, 1, led_task_handle); // ①函数 ②名字 ③栈 ④参数 ⑤优先级 ⑥句柄地址2、静态函数TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, // ① 任务函数名 const char *pcName, // ② 任务名 uint32_t ulStackDepth, // ③ 栈深度(字) void *pvParameters, // ④ 参数 UBaseType_t uxPriority, // ⑤ 优先级 StackType_t *puxStackBuffer, // ⑥ 你提供的栈数组 TCB_t *pxTaskBuffer); // ⑦ 你提供的TCB xTaskCreateStatic(vMyTask, T1, 128, NULL, 1, xStack, xTCB); // ①函数 ②名 ③栈 ④参数 ⑤优先级 ⑥栈数组 ⑦TCB地址二、任务调度FreeRTOS 决定这一刻该让哪个任务占用 CPU的机制。它是 RTOS 的大脑。1. 调度核心原则优先级抢占每个任务有优先级。高优先级任务一旦就绪立即抢走 CPU抢占低优先级任务马上暂停。同优先级之间用时间片轮转默认每个 tick 切换一次让大家轮流跑。2. 任务的 4 种状态就绪能跑等 CPU。运行正在跑。阻塞在等延时/事件vTaskDelay、ulTaskNotifyTake主动让出 CPU→ 别的任务趁机跑。挂起vTaskSuspend()强制停需显式恢复。任务调度 按优先级抢占 同优先级时间片规则在 SysTick/PendSV 驱动下不断保存旧任务现场、恢复新任务现场让多个任务看起来同时跑。三、任务切换把 CPU 从「任务 A」交给「任务 B」时保存 A 的现场、恢复 B 的现场的过程。调度决定切谁切换负责怎么切。1. 切换 换一套寄存器现场CPU 只有一套寄存器R0~R15、xPSR。任务 A 跑的时候这些寄存器里是 A 的数据。要让 B 跑必须保存把 A 的寄存器值存进 A 的任务栈之前pxPortInitialiseStack摆好的那片。恢复从 B 的任务栈把 B 的寄存器值弹回 CPU。切换瞬间完成B 就像从来没被打断过一样继续跑2、两个栈指针MSP vs PSPMSP主栈中断/启动代码用。PSP进程栈任务用。切换的本质就是把 PSP 指向新任务的栈顶TCB 里的pxTopOfStackCPU 之后就从新栈取数据。3、切换触发 执行位置关键切换不在 SysTick 里直接做而在PendSV 中断里做SysTick中断 → 判断需要切换? → 挂起PendSV(请求切换) ↓ (SysTick返回) PendSV中断 → 真正做上下文切换(保存A/恢复B) ← 切换在这里发生 ↓ 返回线程模式, 运行任务B为什么绕一层 PendSV因为 SysTick 可能被更高优先级中断打断把真正的切换放到最低优先级的 PendSV能保证切换完整、不被撕裂。任务切换 在 PendSV 里把旧任务寄存器现场压进旧栈、从新栈恢复新任务现场、并把 PSP 指向新栈从而无缝交接 CPU四、任务间通信FreeRTOS 任务之间传数据/通信主要有6 种机制① 全局变量最朴素但需注意int g_shared 0; // 任务A写、任务B读简单但不是线程安全的多任务同时读写会竞争。适合只读标志位、或配合临界区/关中断保护。② 队列 Queue最常用正经传数据QueueHandle_t xQ xQueueCreate(10, sizeof(int)); // 长度10,每项int // 任务A发 int val 123; xQueueSend(xQ, val, portMAX_DELAY); // 任务B收(阻塞等) int got; xQueueReceive(xQ, got, portMAX_DELAY);线程安全内部已加保护可传任意结构体。适合传传感器数据、命令包等。任务通知 Task Notification最轻量// 发送方(com_task) xTaskNotifyGive(power_task_handle); // 发通知 // 接收方(power_task) uint32_t res ulTaskNotifyTake(pdTRUE, 10000); // 阻塞等,超时10s if(res ! 0) { /* 收到通知,执行关机 */ }每个任务自带一个 32 位通知值比队列快、省内存。缺点只能一对一一个通知只能指定一个目标任务。④ 信号量 Semaphore用于同步/资源计数SemaphoreHandle_t xSem xSemaphoreCreateBinary(); xSemaphoreGive(xSem); // 释放 xSemaphoreTake(xSem, portMAX_DELAY); // 获取(阻塞)二值信号量事件通知类似单次通知。计数信号量资源池如缓冲区空位数量。只传有/无状态不传具体数据。⑤ 互斥量 Mutex保护共享资源SemaphoreHandle_t xMutex xSemaphoreCreateMutex(); xSemaphoreTake(xMutex, portMAX_DELAY); // 访问共享资源(如 LCD、全局结构体) xSemaphoreGive(xMutex);解决全局变量竞争问题带优先级继承防优先级反转。和信号量区别互斥量必须谁拿谁放用于保护资源而非传信号。⑥ 事件组 Event Group一对多/多事件组合EventGroupHandle_t xEv xEventGroupCreate(); xEventGroupSetBits(xEv, BIT_0); // 置位 xEventGroupWaitBits(xEv, BIT_0|BIT_1, pdTRUE, pdTRUE, ...); // 等多个事件一个变量里多个位可等任意一个/全部事件发生。适合多事件组合触发。五、任务同步1、为什么要同步多任务并行运行时任务间需要协调等某个事件发生、共享资源不被同时改写、多个条件都满足再执行。同步机制就是解决何时能继续和数据不被破坏的问题。2、主要同步机制信号量Semaphore二值信号量只有 0/1 两种状态常用于任务与中断/任务间的事件通知同步不保护资源。SemaphoreHandle_t xSem; // 任务A等事件 xSemaphoreTake(xSem, portMAX_DELAY); // 阻塞等待 // 事件到了往下执行 // 中断/任务B触发事件 xSemaphoreGiveFromISR(xSem, pxHigher); // 中断里用 FromISR 版本计数信号量计数值 0 表示还有资源/事件适合资源池如缓冲区数量互斥量Mutex专门用来保护共享资源临界区防止多个任务同时访问。SemaphoreHandle_t xMutex; xSemaphoreTake(xMutex, portMAX_DELAY); // 进入临界区 // 操作共享变量 xSemaphoreGive(xMutex); // 离开临界区关键点互斥量有优先级继承机制能缓解优先级反转问题。不能在中断里用互斥量中断不能阻塞。递归互斥量Recursive Mutex同一个任务可以多次获取必须释放相同次数适合函数递归调用共享资源。事件标志组Event Group一个变量里多个 bit支持等任意一个或等全部适合多事件组合触发。// 等 bit0 和 bit1 都置位才继续 xEventGroupWaitBits(eg, BIT0|BIT1, pdTRUE, pdTRUE, portMAX_DELAY);任务通知Task NotificationFreeRTOS 的轻量级机制取代信号量做同步更快、更省内存每个任务自带一个通知值。xTaskNotifyGive(taskHandle); // 发送通知 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待通知