问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

FreeRTOS任务管理与相关API函数详解

创作时间:
作者:
@小白创作中心

FreeRTOS任务管理与相关API函数详解

引用
CSDN
1.
https://blog.csdn.net/weixin_74473465/article/details/145597319

FreeRTOS是广泛应用于嵌入式系统的实时操作系统内核,其任务管理机制是系统的核心组成部分。本文将详细介绍FreeRTOS中任务管理相关的API函数,包括任务创建、删除、优先级管理、暂停与恢复、延时函数以及空闲任务等关键概念。通过本文的学习,读者将能够掌握FreeRTOS任务管理的基本原理和具体实现方法。

5.1 创建任务

创建任务时使用的函数如下:

BaseType_t xTaskCreate(
    TaskFunction_t pxTaskCode, // 函数指针, 任务函数
    const char * const pcName, // 任务的名字
    const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
    void * const pvParameters, // 调用任务函数时传入的参数
    UBaseType_t uxPriority,    // 优先级
    TaskHandle_t * const pxCreatedTask // 任务句柄, 以后使用它来操作这个任务
);

参数说明:

  • pvTaskCode:函数指针,任务对应的C函数。任务应该永远不退出,或者在退出时调用"vTaskDelete(NULL)"。
  • pcName:任务的名称,仅用于调试目的,FreeRTOS内部不使用。pcName的长度为configMAX_TASK_NAME_LEN
  • usStackDepth:每个任务都有自己的栈,usStackDepth指定了栈的大小,单位为word。例如,如果传入100,表示栈的大小为100 word,即400字节。最大值为uint16_t的最大值。确定栈的大小并不容易,通常是根据估计来设定。精确的办法是查看反汇编代码。
  • pvParameters:调用pvTaskCode函数指针时使用的参数:pvTaskCode(pvParameters)
  • uxPriority:任务的优先级范围为0~`(configMAX_PRIORITIES – 1)。数值越小,优先级越低。如果传入的值过大,xTaskCreate会将其调整为(configMAX_PRIORITIES – 1)`。
  • pxCreatedTask:用于保存xTaskCreate的输出结果,即任务的句柄(task handle)。如果以后需要对该任务进行操作,如修改优先级,则需要使用此句柄。如果不需要使用该句柄,可以传入NULL

返回值:成功时返回pdPASS,失败时返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因是内存不足)。请注意,文档中提到的失败返回值是pdFAIL是不正确的。pdFAIL的值为0,而errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY的值为-1。

使用静态分配内存的函数如下:

TaskHandle_t xTaskCreateStatic(
    TaskFunction_t pxTaskCode,   // 函数指针, 任务函数
    const char * const pcName,   // 任务的名字
    const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节
    void * const pvParameters,   // 调用任务函数时传入的参数
    UBaseType_t uxPriority,      // 优先级
    StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer
    StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);

相比于使用动态分配内存创建任务的函数,最后2个参数不一样:

  • puxStackBuffer:静态分配的栈内存,比如可以传入一个数组,它的大小是usStackDepth*4
  • pxTaskBuffer:静态分配的StaticTask_t结构体的指针

返回值:成功:返回任务句柄;失败:NULL

5.2 删除任务

删除任务时使用的函数如下:

void vTaskDelete(TaskHandle_t xTaskToDelete);

参数说明:

  • pvTaskCode:任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。也可传入NULL,这表示删除自己。

5.3 任务优先级

5.3.1 介绍

优先级的取值范围是:0~`(configMAX_PRIORITIES – 1)`,数值越大优先级越高。

FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES的取值有所不同。

  • 通用方法:使用C函数实现,对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时,使用此方法。
  • 架构相关的优化的方法:架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。使用这种方法时,configMAX_PRIORITIES的取值不能超过32。configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用此方法

FreeRTOS的任务优先级高低与其对应的优先级数值,是成正比的,也就是说任务优先级数值为0的任务优先级是最低的任务优先级,任务优先级数值为(configMAX_PRIORITIES-1)的任务优先级是最高的任务优先级。FreeRTOS的任务优先级高低与其对应数值的逻辑关系正好与STM32的中断优先级高低与其对应数值的逻辑关系相反,如下图所示。

5.3.2 任务优先级的相关API函数:

使用uxTaskPriorityGet来获得任务的优先级:

UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);

使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。

使用vTaskPrioritySet来设置任务的优先级:

void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);

使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;
参数uxNewPriority表示新的优先级,取值范围是0~`(configMAX_PRIORITIES – 1)`。

5.4 暂停任务与恢复任务

FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend(TaskHandle_t xTaskToSuspend);

参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。

要退出暂停状态,只能由别人来操作:

  • 别的任务调用:vTaskResume
  • 中断程序调用:xTaskResumeFromISR

实际开发中,暂停状态用得不多。

5.5 两个Delay函数

这2个函数原型如下:

  1. void vTaskDelay(const TickType_t xTicksToDelay);:按给定的tick数延迟任务,任务进入阻塞状态,即在延迟时间内不参与任务的调度。任务保持阻塞的实际时间取决于tick频率。
    参数:xTicksToDelay调用任务应阻塞的tick周期数。

  2. void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement);
    参数:

  • pxPreviousWakeTime:指向一个变量的指针,该变量用于保存任务最后一次解除阻塞的时间。该变量在第一次使用前必须用当前时间进行初始化。在这之后,该变量会在vTaskDelayUntil()中自动更新。
  • xTimeIncrement:周期时间段。

两者的区别:后者比前者更精确,使用的绝对时间片段,不非前者相对时间片段。

5.6 空闲任务及其钩子函数

5.6.1 介绍

空闲任务(Idle任务)的作用之一:释放被删除的任务的内存。

除了上述目的之外,为什么必须要有空闲任务?一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用vTaskStartScheduler()函数来创建、启动调度器时,这个函数内部会创建空闲任务:

  • 空闲任务优先级为0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

空闲任务的优先级为0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。

要注意的是:如果使用vTaskDelete()来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
  • 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。

空闲任务的钩子函数的限制:

  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。

5.6.2 使用钩子函数的前提

在FreeRTOS\Source\tasks.c中,可以看到如下代码,所以前提就是:

  • 把这个宏定义为1:configUSE_IDLE_HOOK
  • 实现vApplicationIdleHook函数

5.6.3 CubeMX使用钩子函数

我们也可以在CubeMX中设置

生成代码之后我们可以在freertos.c中找到自动生成的钩子函数,在其中添加我们自己的功能就行了。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号