RTOS-F429-HAL-(动/静态)任务的创建(2026/7/27) 目录一动态任务创建与删除 API1三个 API2xTaskCreate 参数3动态创建的前提4函数内部干了什么5vTaskDelete(handle)6:临界区7:中断屏蔽8PendSV中断与SVC中断二动态创建工程代码逻辑1工程概览2任务定义3后台硬件中断独立于任务运行4时间轴假设 KEY1 未按下5如果 KEY1 被按下6如果写为退出缓存区前删除任务关键时序总结三空闲任务介绍1空闲任务的本质2你这个工程的情况3所以当前的内存分配格局是4如果要全部静态创建5任务内存的处理四静态创建任务1区别2静态创建五步3两个接口函数的关系4删除函数句柄区分5静态任务我们创建的TCB是什么6动态为什么没有 TCB 和栈五vTaskDelete删除任务1函数原型2怎么用3内部干了什么4关键细节5正点 start_task 的自杀六Task Control Block任务控制块1任务函数名和任务名2参数NULL3句柄4静态任务TCB5静态任务和动态任务创建的区别一动态任务创建与删除 API1三个 APIAPI方式栈内存TCB 内存xTaskCreate()动态FreeRTOS 堆里分配FreeRTOS 堆里分配xTaskCreateStatic()静态你自己定义数组你自己定义结构体vTaskDelete()—释放回堆释放回堆2xTaskCreate 参数BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // ① 任务函数名 const char * const pcName, // ② 任务名调试用 const uint16_t usStackDepth, // ③ 栈大小单位 word void * const pvParameters, // ④ 传给任务的参数 UBaseType_t uxPriority, // ⑤ 优先级0 ~ configMAX_PRIORITIES-1 TaskHandle_t * const pxCreatedTask // ⑥ 任务句柄输出 );参数我们的写法含义pxTaskCodetask1函数名就是指针不用pcNametask1最长configMAX_TASK_NAME_LEN-1字符usStackDepth128128 words 512 字节局部变量多就加大pvParametersNULL没参数传 NULL有参数传结构体指针uxPriority2数字越大越优先pxCreatedTaskTask1_Handler存起来以后删任务/查状态用这个句柄返回值pdPASS 成功errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 堆不够。3动态创建的前提// FreeRTOSConfig.h #define configSUPPORT_DYNAMIC_ALLOCATION 1 // 必须开xTaskCreate 依赖它 #define configTOTAL_HEAP_SIZE 36*1024 // 堆总大小所有任务栈TCB 都从这扣4函数内部干了什么xTaskCreate(task1, task1, 128, NULL, 2, handle) │ ├─ ① 从 FreeRTOS 堆里申请 TCB 栈空间 │ TCB: 任务控制块存状态、优先级、栈指针 │ 栈: 128 × 4 512 字节 │ ├─ ② 填 TCB 字段函数入口、优先级、栈顶指针... │ 栈顶部预填充寄存器初值xPSR、PC、LR... │ └─ ③ 把 TCB 挂到就绪列表 pxReadyTasksLists[2] 任务立刻处于就绪态调度器随时可以选它跑创建完 立刻可跑。不是先创建后面再手动启动——调度器一跑优先级最高的就绪任务直接上场。5vTaskDelete(handle)vTaskDelete(Task1_Handler); // 删别人释放栈和 TCB vTaskDelete(NULL); // 删自己删完句柄立马无效——正点原子的做法是同时把句柄置 NULL 防止误用vTaskDelete(Task1_Handler); Task1_Handler NULL;6:临界区在实时操作系统如 FreeRTOS、RT-Thread 等中临界区Critical Section是一段必须“一气呵成”执行的代码区域。通过进入临界区taskENTER_CRITICAL() 等接口和退出临界区taskEXIT_CRITICAL()内核会暂时屏蔽任务调度乃至可屏蔽硬件中断保证当前代码在执行期间不会被其他任务或调度器打断。这里乃至可屏蔽硬件优先级5~15的中断。7:中断屏蔽Thread 模式 vs Handler 模式Cortex-M 有两个运行模式模式运行什么进入方式Thread 模式普通任务代码FreeRTOS 的 Task 函数体上电 / 从异常返回EXC_RETURNHandler 模式异常/中断服务函数ISR、PendSV、SysTick硬件自动响应异常你的任务代码比如vTaskA、vTaskB始终在 Thread 模式下运行。优先级谁高谁低ARM 的优先级逻辑是数值越小、优先级越高硬件优先级数值: 0 ─── 16 ─── 255 ─── 256 ▲ ▲ ▲ 最高Reset/NMI PendSV Thread 模式 (0xFF) (无异常激活)执行体优先级数值实际含义SysTick / IRQ 0~4较小 (如 16、32)高优先级可打断 PendSVPendSV0xFF (255)最低异常优先级但仍高于 Thread 模式Thread 模式256等效比任何异常都低任何时候都能被异常打断结论PendSV (255) 的硬件优先级 Thread 模式 (256)。只要有 PendSV 挂起CPU 在不被屏蔽、且没有更高优先级异常活动的情况下必然会响应它。3.我们定义的宏ARM 优先级寄存器是 8 位但 F429 只实现了高 4 位——所以有效值只有 16 个0~15。存到寄存器时要左移 4 位#define configMAX_SYSCALL_INTERRUPT_PRIORITY 0x50 设 BASEPRI 0x50优先级 5~15 的 ISR 全被走廊门挡住不采用0~255的中断优先级//taskEXIT_CRITICAL() 把 BASEPRI 归零走廊门打开憋着的 PendSV 立刻触发切任务。8PendSV中断与SVC中断PendSV 是整个系统里最低优先级的可屏蔽异常。任何硬件中断包括 SysTick优先级都比它高。所以 PendSV 不会打断任何 ISR——等所有硬件中断都处理完了CPU 空闲下来才切任务。这就是 FreeRTOS 任务切换不破坏 ISR 上下文的关键。不需要设优先级为多少——FreeRTOS 启动时自己设的。vTaskStartScheduler()→vPortSetupTimerInterrupt()→ 把 PendSV 和 SysTick 设到最低优先级15#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY (8 - configPRIO_BITS))PendSV 优先级 15 最低。能打断它的中断只有硬件故障NMI、HardFault所有外设中断、SysTick 都在它上面。所以无论如何切任务一定得等所有中断跑完才动手。SVC 从门外裸机进 RTOS 的门一生只推一次。SVC 的优先级 FreeRTOS 没动——用芯片上电的默认值。它跟 PendSV 不一样SVC 一生只触发一次启动第一个任务不需要显式设优先级能跑完就行。二动态创建工程代码逻辑1工程概览硬件平台STM32F429, 180MHz 主频RTOSFreeRTOS抢占式调度 同优先级时间片轮转Tick 频率1000Hz1 tick 1ms2任务定义任务优先级栈大小功能start_task1128 word一次性启动任务创建 task1/2/3 后自删除task12128 word蓝灯 500ms 翻转 printftask23128 word每 1000ms printf 一次task34 (最高)128 word每 5ms 扫描按键按下 KEY1 则删除 task1idle0 (最低)128 wordFreeRTOS 自带空闲任务3后台硬件中断独立于任务运行中断源周期NVIC 优先级作用SysTick1ms最低FreeRTOS 调度心跳 TimingDelay 延时计数器TIM71ms15 (最低)HAL_IncTick()— HAL 库时基TIM6500ms1回调函数体目前为空占位EXTI0(KEY1)异步—软消抖后翻转红灯EXTI15_10(KEY2)异步—软消抖后翻转绿灯USART1 DMA异步—DMA IDLE 中断接收变长帧4时间轴假设 KEY1 未按下T0 调度器启动 ├─ start_task (prio1) 运行 │ ├─ 进入临界区 │ ├─ xTaskCreate(task1, prio2) ← 就绪 │ ├─ xTaskCreate(task2, prio3) ← 就绪 │ ├─ xTaskCreate(task3, prio4) ← 就绪 │ ├─ 退出临界区 │ └─ vTaskDelete(NULL) ← 自杀 │ ├─ task3 (prio4, 最高) 立即抢占运行 │ └─ Key_Scan → 未按下 → vTaskDelay(5) 阻塞 │ ├─ task2 (prio3) 运行 │ └─ printf(task2 正在运行!) → vTaskDelay(1000) 阻塞 │ └─ task1 (prio2) 运行 └─ printf(task1 正在运行!) → LED_B ON → vTaskDelay(500) 阻塞 ​ T0~5ms 全部用户任务阻塞 → 空闲任务运行 ​ T5ms task3 到期唤醒 ├─ Key_Scan → 未按下 └─ vTaskDelay(5) 阻塞 ← 此时 task1/2 仍在阻塞空闲任务运行 ​ T10ms task3 唤醒 → 同上 → vTaskDelay(5) T15ms task3 唤醒 → 同上 → vTaskDelay(5) ... 每 5ms 重复一次 ... ​ T500ms task1 到期但 task3 也恰好在 T500ms 到期500 是 5 的倍数 ├─ task3 (prio4) 先运行 → Key_Scan → vTaskDelay(5) └─ task1 (prio2) 后运行 └─ printf(task1 正在运行!) → LED_B OFF → vTaskDelay(500) ​ T1000ms task2 task1 同时到期 ├─ task2 (prio3) 先运行 │ └─ printf(task2 正在运行!) → vTaskDelay(1000) ← 下次 T2000ms └─ task1 (prio2) 后运行 └─ printf(task1 正在运行!) → LED_B ON → vTaskDelay(500) ← 下次 T1500ms ​ T1500ms task1 到期 └─ printf(task1 正在运行!) → LED_B OFF → vTaskDelay(500) ​ T2000ms task2 task1 同时到期 → 同上模式循环...5如果 KEY1 被按下TX task3 在某次 5ms 扫描中检测到 KEY1按下 ├─ printf(删除 task1!) ├─ vTaskDelete(Task1_Handler) ← task1 被杀死栈和TCB释放 └─ vTaskDelay(5) 此后 只剩 task2 (prio3) 和 task3 (prio4) ├─ task3: 仍然每 5ms 扫键 └─ task2: 仍然每 1000ms 打印 task1 不复存在蓝灯不再闪烁6如果写为退出缓存区前删除任务补充一下自己删除自己的函数流程 vTaskDelete(NULL);如果这句代码在taskEXIT_CRITICAL();之前 那么正确流程 start_task 优先级是 START_TASK_PRIO 1你代码里写的。 taskEXIT_CRITICAL() ← BASEPRI 归零PendSV 立刻触发 → 调度器找最高优先级就绪任务 task3优先级 4 → 切 task3 → task3 干完 Delay(10) → task2 → task1 → 都睡 → 空闲任务跑 → 扫到待删除列表里的 start_task → 释放 start_task 的 TCB 栈 → 彻底消失 vTaskDelete(NULL) 不是当场执行完的——分两步设 PendSV 挂起当场→ →真正回收内存空闲任务异步完成。 start_task 在 taskEXIT_CRITICAL() 那一刻已经被摘出就绪列表了只是尸体还在等空闲任务来收。关键时序总结task3 是老板——优先级最高 (4)每 5ms 必定准时醒来扫键。它占用 CPU 极短几 μs几乎不影响其他任务task2 居中——优先级 3每秒打印一次阻塞 1000ms 期间不占 CPUtask1 最低——优先级 2500ms 翻转一次蓝灯总是在 task3 和 task2 都没事干时才运行利用阻塞实现协作三个任务全部通过vTaskDelay()把自己挂起谁都不占着 CPU 空转空闲时间全部给 idle 任务可以进低功耗TIM6 中断500ms目前回调是空的虽然每 500ms 产生一次中断但不做任何事——是个预留的钩子位三空闲任务介绍1空闲任务的本质空闲任务是 FreeRTOS 内核的一部分由vTaskStartScheduler()自动创建。它不是可选的——调度器必须保证任何时候至少有一个任务能跑。当所有用户任务都阻塞vTaskDelay、等信号量等时空闲任务就顶上。2你这个工程的情况你的FreeRTOSConfig.h同时开了两个宏#define configSUPPORT_DYNAMIC_ALLOCATION 1 // xTaskCreate 可用动态创建任务 #define configSUPPORT_STATIC_ALLOCATION 1 // xTaskCreateStatic 可用静态创建任务当configSUPPORT_STATIC_ALLOCATION 1时内核会优先调用你提供的回调来获取空闲任务的内存。你的 freertos_demo.c:28-48 正好提供了这个回调static StaticTask_t xIdleTaskTCB; static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE]; // 128 × 4 512 字节 void vApplicationGetIdleTaskMemory(...) { *ppxIdleTaskTCBBuffer xIdleTaskTCB; *ppxIdleTaskStackBuffer uxIdleTaskStack; *pulIdleTaskStackSize configMINIMAL_STACK_SIZE; }3所以当前的内存分配格局是任务创建方式内存来源start_task / task1 / task2 / task3xTaskCreate()FreeRTOS 堆36KB空闲任务内核自动创建静态vApplicationGetIdleTaskMemory提供的全局数组目前我们的工程任务走动态空闲任务走静态——混着用完全没问题。4如果要全部静态创建你把xTaskCreate换成xTaskCreateStatic自己传 TCB 和栈的地址那些用户任务就也走静态了。但空闲任务依然是内核自动创建的你还得保留vApplicationGetIdleTaskMemory——这是configSUPPORT_STATIC_ALLOCATION1的强制要求否则编译报错。顺带一提如果你以后开了configUSE_TIMERS1软件定时器还得多提供一对vApplicationGetTimerTaskMemory()套路跟空闲任务一模一样。5任务内存的处理1、当传入参数为NULL则代表删除任务自身当前正在运行的任务2、空闲任务会负责释放被删除的动态任务中由系统分配的内存但是由用户在静态任务删除前申请的内存则需要由用户在静态任务被删除前提前释放否则将导致内存泄露动态任务xTaskCreate栈和 TCB 是 FreeRTOS 堆里分配的。删自己 → 挂到待删除列表 → 空闲任务来收尸释放 TCB 栈回堆。你什么都不用管。静态任务xTaskCreateStatic栈和 TCB 是你自己定义的全局数组和结构体。内存从来不在 FreeRTOS 堆里空闲任务不会、也没权限释放你定义的全局变量。// 你定义的 static StaticTask_t xIdleTaskTCB; // ← 全局变量永远在那 static StackType_t uxIdleTaskStack[128]; // ← 全局变量永远在那删了静态任务后这块内存还是你的——可以拿来创新任务也可以不管它全局变量反正不占堆。不存在释放只存在标记为可用。官方说法就是你理解的那样两种情况之间毫无关系。空闲任务只管FreeRTOS 自己分配的任务栈、TCB全是pvPortMalloc挖的动态任务创建时自动分配删除后空闲任务自动回收。而静态任务的内存你自己定义的全局数组本来就从未进过 FreeRTOS 堆、空闲任务无从管起也不该管——这块内存根本不需要释放。官方那段话后一句是提醒如果你在任务函数里malloc了一段缓存、又把任务删了没人替你free缓存就会变成孤儿。跟栈和 TCB 无关。官网说的是任务函数内部malloc的// task 函数里申请了一块临时内存 static void task1(void *pvParameters) { uint8_t *buf (uint8_t *)malloc(1024); // ← 这是从系统堆里挖的 // ...用 buf... vTaskDelete(NULL); // ← 自杀 // buf 泄漏FreeRTOS 只回收 TCB栈不会替你 free(buf) }跟前面静态任务时解释的一样vTaskDelete只回收 FreeRTOS 自己分配的东西——TCB 和任务栈。你在任务内部自己malloc的是从 C 库的堆里拿的FreeRTOS 根本不认识它更不可能替你free。正确做法是任务自杀前先free(buf)或者干脆用全局变量和动态分配共存的方式——不过这种情况极少大部分直接建个数组就完了。四静态创建任务1区别动态创建 (xTaskCreate)静态创建 (xTaskCreateStatic)栈内存FreeRTOS 堆里自动分配你手动定义数组TCB 内存FreeRTOS 堆里自动分配你手动定义结构体用完释放vTaskDelete自动归还堆你自己管或不管反正全局变量永不释放前提宏configSUPPORT_DYNAMIC_ALLOCATION 1configSUPPORT_STATIC_ALLOCATION 1额外要求无必须实现两个接口函数2静态创建五步① 开宏configSUPPORT_STATIC_ALLOCATION 1 ② 定义内存 static StaticTask_t xIdleTaskTCB; // 空闲任务 TCB static StackType_t uxIdleTaskStack[128]; // 空闲任务栈 ③ 实现接口函数FreeRTOS 内部会调 vApplicationGetIdleTaskMemory() → 返回空闲任务的栈和 TCB vApplicationGetTimerTaskMemory() → 返回定时器任务的栈和 TCB开 configUSE_TIMERS 才需要我们是关了软件定时器的宏 ④ 定义任务函数 static void task1(void *pvParameters) { for(;;) { ... } } ⑤ 创建任务 xTaskCreateStatic(task1, task1, 128, NULL, 1, task1_stack, task1_TCB); 函数原型 TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, // ① 任务入口函数必须符合 void func(void*) 原型 const char * const pcName, // ② 任务名纯调试用长度不超configMAX_TASK_NAME_LEN你设的16 const uint32_t usStackDepth, // ③ 栈大小 void * const pvParameters, // ④ 传给任务的参数 UBaseType_t uxPriority, // ⑤ 任务优先级数值越大优先级越高范围0~31 StackType_t * const puxStackBuffer, // ⑥ 【静态专属】栈起始地址 StaticTask_t * const pxTaskBuffer // ⑦ 【静态专属】TCB任务控制块 ); 返回值TaskHandle_t任务句柄3两个接口函数的关系FreeRTOS 启动时内部流程 │ ├─ 创建空闲任务 │ └─ 调 vApplicationGetIdleTaskMemory(tcb, stack, size) │ 拿你提供的 TCB 和栈 → 创建空闲任务 │ └─ 创建定时器任务如果 configUSE_TIMERS1 └─ 调 vApplicationGetTimerTaskMemory(tcb, stack, size) 拿你提供的 TCB 和栈 → 创建定时器任务我们configUSE_TIMERS 0所以只写空闲任务那一个就够了。4删除函数句柄区分vTaskDelete(StartTask_Handler)改vTaskDelete(NULL)——没区别做的事完全一样——把任务从所有列表里摘掉设 PendSV 切走。区别只是谁被删vTaskDelete(StartTask_Handler); // 删别人通过句柄找到 TCB vTaskDelete(NULL); // 删自己拿当前任务的 TCB底层都是找到同一个 TCBstart_task 的结果一模一样。正点动态版用NULL、静态版用句柄——就是因为静态版句柄在xTaskCreateStatic返回时已经拿到了直接用名字删更可读用vTaskDelete(NULL)代码逻辑也是一样的。5静态任务我们创建的TCB是什么TCB 任务控制块 任务的档案。FreeRTOS 调度器不看人、不看函数名——它只看 TCB。StaticTask_tTCB核心字段 ├─ pxTopOfStack → 栈顶指针——切任务时从这弹寄存器 ├─ uxPriority → 任务优先级2/3/4 ├─ xStateListItem → 挂在哪个状态链表里就绪/阻塞/挂起 ├─ xEventListItem → 挂在哪个事件等待链表里等队列/等信号量 ├─ pcTaskName → 任务名task1调试用 ├─ uxTCBNumber → 任务编号比名字好查 └─ ulRunTimeCounter → 跑了多久统计用你创建任务时传的栈大小、优先级、任务名全部写进这个结构体。调度器切换时读这个 TCB → 知道它优先级是 4 → 知道它的栈在哪 → 弹栈 → 跳转运行。切走时保存寄存器到它的栈 → 更新 TCB → 选下一个 TCB。6动态为什么没有 TCB 和栈有只是你看不见。xTaskCreate内部第一件事就是pvPortMalloc// xTaskCreate 内部简化 BaseType_t xTaskCreate(...) { // ① 从 FreeRTOS 堆里挖一块内存当 TCB TCB_t *pxNewTCB (TCB_t *)pvPortMalloc(sizeof(TCB_t)); // ② 从 FreeRTOS 堆里挖一块内存当任务栈 StackType_t *pxStack (StackType_t *)pvPortMalloc(usStackDepth * 4); // ③ 填 TCB 字段、把栈和 TCB 挂起来 prvInitialiseNewTask(pxTaskCode, pcName, pxStack, pxNewTCB, ...); }动态堆里挖 → 用完空闲任务free回收。静态你自己定义全局数组 → 永远在那不挖也不收。五vTaskDelete删除任务1函数原型void vTaskDelete(TaskHandle_t xTaskToDelete);参数含义xTaskToDelete要删的任务句柄。NULL 删自己2怎么用两步开宏 调函数。// FreeRTOSConfig.h #define INCLUDE_vTaskDelete 1 // 代码里 vTaskDelete(Task1_Handler); // 删别人 vTaskDelete(NULL); // 删自己3内部干了什么vTaskDelete(handle) │ ├─ ① 通过句柄找到对应的 TCB任务控制块 │ ├─ ② 把任务从所有列表中踢出去 │ 就绪列表、阻塞列表、挂起列表、事件等待列表 │ → 这个任务从此消失调度器再也不会选中它 │ ├─ ③ 删的是自己handle NULL │ ✅ 自己 → 把自己丢进待删除列表等空闲任务来收尸 │ 空闲任务会释放 TCB 栈内存 │ ❌ 别人 → 立刻释放别人的 TCB 栈内存任务总数-1 │ └─ ④ 更新阻塞超时时间 防止刚才被删的任务恰好是下一个要超时唤醒的4关键细节内存谁管内存类型谁释放TCB 任务栈FreeRTOS 分配的空闲任务自动释放你不用管任务里自己malloc的你自己在删之前free不然内存泄漏// 正确做法删别人前先让人家把资源清了 // 错误做法任务里 malloc 了一坨外面直接 vTaskDelete → 泄漏删完句柄置 NULLvTaskDelete(Task1_Handler); Task1_Handler NULL; // 防止后面误调 vTaskSuspend(NULL_Task)5正点 start_task 的自杀static void start_task(void *pvParameters) { xTaskCreate(task1, ...); xTaskCreate(task2, ...); xTaskCreate(task3, ...); vTaskDelete(NULL); // 三个工作任务已就位自己没用了自杀 }三个工作任务的句柄是全局变量 → TCB 不会被释放。自杀的 start_task 自己的 TCB 栈被空闲任务回收。六Task Control Block任务控制块TCB Task Control Block任务控制块就是一个 C 结构体里面存了调度器需要的所有信息。调度器不看函数名、不看代码——它只看 TCBtypedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /* 栈顶指针——切换时弹/压寄存器 */ StackType_t *pxStack; /* 栈起始地址——删除时释放用 */ UBaseType_t uxPriority; /* 优先级 ——谁先跑靠这个 */ ListItem_t xStateListItem; /* 链表节点 ——挂在哪个状态链表 */ ListItem_t xEventListItem; /* 事件节点 ——等队列/信号量用 */ uint32_t ulRunTimeCounter;/* 运行时间——统计用 */ char pcTaskName[16]; /* 任务名 ——调试时一眼看出 */ // ... 还有任务通知值、TCB编号、临界嵌套计数等 } TCB_t; xTaskCreate 参数 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // ① 任务函数名 const char * const pcName, // ② 任务名调试用 const uint16_t usStackDepth, // ③ 栈大小单位 word void * const pvParameters, // ④ 传给任务的参数 UBaseType_t uxPriority, // ⑤ 优先级0 ~ configMAX_PRIORITIES-1 TaskHandle_t * const pxCreatedTask // ⑥ 任务句柄输出 );两者的联系TCB是控制任务的一个大结构体我们创建任务xTaskCreate 是一个函数把要初始化的内容填进去后续控制任务就不直接操控TCB怕乱改东西就控制句柄来控制任务我们填入6个参数任务函数名 任务名栈大小参数这个一会有例子解释优先级句柄1任务函数名和任务名函数名 CPU 跳去哪。任务名 调试时怎么称呼它。xTaskCreate(task1, // ① 函数指针 CPU来跑这个函数 task1, // ② 字符串 调试器这人叫 task1 128, NULL, 2, Task1_Handler);第一个是函数入口地址——PendSV 弹栈时 PC 寄存器 这个地址CPU 就跳过去跑了。不能乱写。第二个是调试用的标签——存在TCB.pcTaskName[16]里。你挂调试器、调vTaskList()、IDE 的 FreeRTOS 任务视图里看到的都是这个字符串。可以不跟函数同名但同名方便定位。2参数NULL你写了NULL是因为 task1、task2、task3 各写了一个函数。如果三个灯逻辑一模一样// 不用写三个函数一个函数用参数区分 static void Led_Task(void *pvParameters) { uint32_t pin (uint32_t)pvParameters; // 把参数转回来 for (;;) { HAL_GPIO_TogglePin(GPIOH, pin); // 传哪个引脚就翻哪个 vTaskDelay(500); } } // 创建三个任务同一份代码 xTaskCreate(Led_Task, LedR, 128, (void *)GPIO_PIN_10, 1, h1); // 传红灯 xTaskCreate(Led_Task, LedG, 128, (void *)GPIO_PIN_11, 1, h2); // 传绿灯 xTaskCreate(Led_Task, LedB, 128, (void *)GPIO_PIN_12, 1, h3); // 传蓝灯一个函数三个任务参数决定谁亮谁灭。我们目前每个任务逻辑不一样、分三个函数写没共同参数就全填 NULL。3句柄// ① 你定义空指针变量 TaskHandle_t Task1_Handler; // 现在值是垃圾不知道指向哪 // ② 传句柄变量的地址进去 xTaskCreate(task1, ..., Task1_Handler); // 告诉函数往这个地址写东西 // ↑ // Task1_Handler 变量的地址 // ③ FreeRTOS 内部这一行帮你完成 *pxCreatedTask pxNewTCB; // ↑ ↑ // 解引用写到 新分配的 TCB 地址 // Task1_Handler 里 // 调用完回到你手里 // Task1_Handler TCB 的地址 ✅ 有值了你提供空信封句柄变量传真地址给 FreeRTOS句柄FreeRTOS 往信封里塞 TCB 的地址——全程不需要你碰 TCB4静态任务TCBStaticTask_t 就是 TCB_t 换了个马甲方便你在外面声明静态 TCB // FreeRTOS 内部 task.h typedef struct tskTaskControlBlock StaticTask_t; // ↑ 同一个结构体 // 所以这两行完全等价 StaticTask_t Task3_TCB; // 用这个FreeRTOS 给你的名字 struct tskTaskControlBlock Task3_TCB; // 等于这个原名TCB 的真实结构体定义在tasks.c里不对外暴露。FreeRTOS 可以改 TCB 内部字段而不影响你的代码。StaticTask_t这个空壳只有一个用途——让你声明变量FreeRTOS 知道它有多大就行。你永远不需要访问StaticTask_t的字段操作任务照旧通过句柄。5静态任务和动态任务创建的区别TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, // ① 任务入口函数必须符合 void func(void*) 原型 const char * const pcName, // ② 任务名纯调试用长度不超configMAX_TASK_NAME_LEN你设的16 const uint32_t usStackDepth, // ③ 栈大小 void * const pvParameters, // ④ 传给任务的参数 UBaseType_t uxPriority, // ⑤ 任务优先级数值越大优先级越高范围0~31 StackType_t * const puxStackBuffer, // ⑥ 【静态专属】栈起始地址 StaticTask_t * const pxTaskBuffer // ⑦ 【静态专属】TCB任务控制块 ); 返回值TaskHandle_t任务句柄 动态任务创建函数 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // ① 任务入口函数必须符合 void func(void*) 原型 const char * const pcName, // ② 任务名纯调试用长度不超configMAX_TASK_NAME_LEN你设的16 const uint16_t usStackDepth, // ③ 栈大小单位「字(word)4字节」比如128就是512字节 void * const pvParameters, // ④ 传给任务的参数 UBaseType_t uxPriority, // ⑤ 任务优先级数值越大优先级越高范围0~31你设的configMAX_PRIORITIES32 TaskHandle_t * const pxCreatedTask // ⑥ 任务句柄输出参数二级指针内核把TCB地址就是句柄存到你传的指针里不用就传NULL ); 返回值pdPASS 就是创建成功失败只会返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY说明堆不够调大你之前设的configTOTAL_HEAP_SIZE36 * 1024就行。Task3_Handler xTaskCreateStatic((TaskFunction_t) start_task, (char *) start_task, (uint32_t) START_TASK_STACK_SIZE, (void *) NULL, (UBaseType_t) START_TASK_PRIO, (StackType_t *) StartTask_Stack, /* 传栈地址 */ (StaticTask_t *) StartTask_TCB); /* 传 TCB 地址 */ TaskHandle_t Task3_Handler; /* 任务3句柄按键 */ StackType_t Task3_Stack[TASK3_STACK_SIZE]; /* 任务3栈512B */ StaticTask_t Task3_TCB; /* 任务3控制块 */ StartTask_Stack 栈 是我们自己创建的全局数组Task3_TCB 是我们直接创建的结构体两个传进去 然后 Task3_Handler 用做返回值接收 TCB 地址的地址

本月热点