FreeRTOS 任务相关 API 函数

一、任务创建与删除(核心生命周期管理)

这是使用 FreeRTOS 的起点,文档介绍了两种创建方式和一种删除方式。

1. 动态创建:xTaskCreate() ------ 最常用
  • 内存来源 :任务控制块(TCB)和任务堆栈所需内存自动 从 FreeRTOS 堆(ucHeap)中分配。
  • 前提条件 :
    • 必须提供内存管理文件(如 heap_4.c)。
    • 宏 configSUPPORT_DYNAMIC_ALLOCATION 必须为 1(默认就是 1)。
  • 关键参数注意 :
    • usStackDepth:堆栈大小,单位是字(word),不是字节。在 32 位 MCU(如 STM32)上,实际字节数要乘以 4。
    • pxCreatedTask:用于返回任务句柄,后续挂起、删除、改变优先级等操作都需要它。
  • 返回值 :成功返回 pdPASS,失败返回 errCOULD_NOT_ALLOCATE_REQUESTED_MEMORY(通常是堆空间不足)。
2. 静态创建:xTaskCreateStatic() ------ 更可控,但稍复杂
  • 内存来源 :任务堆栈和控制块的内存完全由用户提供(通常定义为全局数组)。
  • 使用前提 :宏 configSUPPORT_STATIC_ALLOCATION 必须设为 1。
  • 额外要求 :当启用静态分配后,用户必须实现两个钩子函数 ,为空闲任务和定时器服务任务提供静态内存:
    • vApplicationGetIdleTaskMemory()
    • vApplicationGetTimerTaskMemory()
  • 适用场景:需要将任务内存放到特定区域(如内部 SRAM、外部 SDRAM),或者对动态分配的不确定性(碎片、失败)有顾虑时使用。在资源极其紧张或安全关键的系统中,静态方法更受青睐。
3. 受限任务创建:xTaskCreateRestricted() ------ 用于带 MPU 的芯片
  • 仅在 MCU 带有**内存保护单元(MPU)**时使用,可以为任务设置内存访问权限,提高系统安全性(如隔离用户任务和系统任务)。
4. 删除任务:vTaskDelete()
  • 参数为待删除任务的句柄。传入 NULL 表示删除任务自身。
  • 关键注意事项(高频踩坑点) :
    • 若任务是动态创建的,删除后其占用的 TCB 和堆栈内存会在空闲任务中释放 。因此,删除任务后必须给空闲任务运行机会 (比如调用 vTaskDelay(1) 或 taskYIELD()),否则内存无法被回收,造成泄漏。
    • 用户自行分配的内存,必须自行释放 。如果任务内部用 pvPortMalloc() 申请了内存,删除前必须调用 vPortFree(),否则会导致内存泄漏。

二、任务挂起与恢复(暂停/继续,不丢数据)

当你需要暂停某个任务,并在一段时间后从暂停点继续执行时,使用挂起和恢复机制,而非删除+重建(后者会丢失任务内部的变量状态)。

1. 挂起:vTaskSuspend()
  • 将任务置于挂起态(Suspended),该任务将永远不会被调度,直到被恢复。
  • 参数为任务句柄,传入 NULL 表示挂起自身。
2. 恢复(任务级):vTaskResume()
  • 在任务上下文中恢复一个被挂起的任务。
  • 只能恢复通过 vTaskSuspend() 挂起的任务。
3. 恢复(中断级):xTaskResumeFromISR()
  • 在**中断服务函数(ISR)**中恢复任务。
  • 特殊返回值 :
    • 返回 pdTRUE:表示被恢复的任务优先级等于或高于 当前被打断的任务,这意味着退出 ISR 后必须进行上下文切换 (通常调用 portYIELD_FROM_ISR())。
    • 返回 pdFALSE:不需要立即切换。
  • 与 vTaskResume() 的区别:此函数专用于中断环境,且会返回是否需要切换的指示,这是中断安全的必要设计。

三、实验例程深度解析

通过两个完整实验,清晰地展示了上述 API 的标准用法模式。

1. 标准工程结构模式(推荐学习)
  • 步骤 1 :在 main() 中完成硬件初始化。
  • 步骤 2 :在调度器启动前 ,使用 xTaskCreate() 创建一个"起始任务"(start_task)。
  • 步骤 3 :调用 vTaskStartScheduler() 启动调度器。
  • 步骤 4 :在 start_task 的任务函数中,创建所有其他应用任务、队列、信号量等内核对象。
  • 步骤 5 :创建完成后,调用 vTaskDelete(NULL) 删除 start_task 自身,释放其占用的内存。

这种模式的好处是:将系统初始化与业务任务分离,结构清晰,且所有资源创建都在一个任务上下文中完成,便于管理。

2. 动态创建实验(4.2 节)
  • 核心逻辑 :
    • 任务 2 的优先级(3)高于任务 1(2),因此一开始任务 2 会优先执行。
    • 任务 1 每运行一次,计数加 1。当运行满 5 次后,调用 vTaskDelete(Task2Task_Handler) 删除任务 2。
    • 删除后,任务 2 不再运行,LED1 停止闪烁,串口也不再打印任务 2 的信息。
  • 关键观察点 :删除任务后,空闲任务会回收任务 2 的内存(前提是给了空闲任务运行时间,例程中通过 vTaskDelay 让出了 CPU)。
3. 静态创建实验(4.3 节)
  • 与动态实验功能完全相同,但所有任务的堆栈(StackType_t Task1TaskStack[128])和控制块(StaticTask_t Task1TaskTCB)都由用户显式定义。
  • 需注意 :必须实现 vApplicationGetIdleTaskMemory 和 vApplicationGetTimerTaskMemory,否则编译会报链接错误(文档图 4.3.1 所示)。
4. 挂起/恢复实验(4.5 节)
  • 任务设计:

    • key_task:轮询按键状态。
    • task1_task 和 task2_task:执行周期任务并显示运行次数。
  • 按键功能:

    • KEY2:挂起任务 2(vTaskSuspend(Task2Task_Handler))。
    • KEY1:恢复任务 1(vTaskResume(Task1Task_Handler))。
    • WKUP:挂起任务 1。
    • KEY0(中断方式):在外部中断 ISR 中调用 xTaskResumeFromISR() 恢复任务 2。
  • 中断中的关键代码模式:

    c 复制代码
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xHigherPriorityTaskWoken = xTaskResumeFromISR(Task2Task_Handler);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 根据返回值决定是否切换

    这是所有在 ISR 中使用 FreeRTOS API 的标准范式,务必牢记。

  • 实验结论 :挂起任务后,其运行计数值保持不变;恢复后继续累加,证明了挂起不会丢失任务内部状态。


四、综合对比与选择建议

功能 API 函数 适用场景 注意事项
动态创建 xTaskCreate() 绝大多数常规应用,简单快捷 需确保堆空间足够;内存释放依赖空闲任务
静态创建 xTaskCreateStatic() 内存敏感、安全关键、需固定内存地址的场景 需用户提供堆栈和TCB;需实现两个系统钩子函数
删除任务 vTaskDelete() 任务不再需要时,回收资源 动态任务的内存由空闲任务回收;用户内存需手动释放
挂起任务 vTaskSuspend() 临时停止任务,保留上下文和数据 挂起后任务不再被调度,直到被恢复
恢复(任务级) vTaskResume() 在任务代码中恢复另一个任务 仅用于任务上下文
恢复(中断级) xTaskResumeFromISR() 在ISR中恢复任务,实现中断触发任务运行 必须检查返回值并决定是否切换上下文

五、总结与最佳实践

  1. 任务创建模式:强烈推荐"起始任务 + 动态创建"的模式,兼顾了代码清晰度和灵活性。除非有特殊内存要求,否则无需使用静态创建。
  2. 内存管理 :务必理解动态创建的任务删除后,内存不会立即释放,而是交给空闲任务。因此,频繁创建删除任务的系统,要保证空闲任务有足够机会运行,并注意堆碎片问题(使用 heap_4.c 可缓解)。
  3. 中断安全 :任何时候在 ISR 中调用 FreeRTOS API(如 xTaskResumeFromISR、xQueueSendFromISR),都要遵循"检查返回值 → 调用 portYIELD_FROM_ISR"的模式,确保调度器能及时响应高优先级任务。
  4. 调试手段 :实验代码中大量使用 printf 和 LCD 显示,这是学习 RTOS 行为的有力工具。在实际产品中,可用更轻量的日志方式代替。

掌握这些任务 API,就掌握了 FreeRTOS 任务管理的核心。后续的信号量、队列、事件组等,都是在此基础上构建的任务间通信与同步机制。

如果你对某个特定点(比如中断中的上下文切换细节,或 heap_4.c 的内存管理算法)还想深入了解,可以随时提出。

相关推荐
EatFans39 分钟前
全栈自造 Status Deck(四):硬件终于到了,从点亮屏幕到跑通第一个 Demo
单片机·物联网
qq_4017004140 分钟前
STM32 GPIO 精讲|推挽 / 开漏 / 上拉浮空,工程选型避坑
stm32·单片机·嵌入式硬件
创界工坊工作室1 小时前
19-基于单片机的宠物喂养控制系统设计
stm32·单片机·嵌入式硬件·proteus·宠物
神仙别闹2 小时前
基于C语言开发的捕捉流星小游戏
c语言·开发语言
兴通物联科技4 小时前
自助终端涉外证件核验选型:XT2010MRZ 嵌入式护照识别模块技术解析
单片机·嵌入式硬件·计算机视觉·硬件架构
水饺编程4 小时前
第1章,[Win32 章节]:API 及 内存管理模式
c语言·c++·windows·visual studio
Logic1014 小时前
C语言/数据结构位运算题解:异或XOR找出优秀项目组中的“独特编号“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
彦4 小时前
C和C++笔记
c语言·c++·笔记
myy-learn5 小时前
ARM-I2C 驱动优化与 LM75 温度传感器读取实践
arm开发·单片机·嵌入式硬件
元器件BOM超人5 小时前
R7FA6M3AH3CFC#AA0 瑞萨RA6M3系列MCU解析
单片机