前言
本文主要介绍FreeRTOS创建任务函数 xTaskCreate() 的函数原型及如何使用 xTaskCreate() 创建任务。
目录
一、xTaskCreate() API 函数
这是我们接触到的第一个FreeRTOS函数,可能也是所有API函数中最复杂的一个,但必须了解并掌握这个最基本的组件,才能打开学习FreeRTOS的大门。
下面是 xTaskCreate() 函数原型,通过注释就能大概了解 xTaskCreate() 各个参数的意义,本文到此结束,建议详细阅读注释,对xTaskCreate() 函数有个大体印象后,再阅读后面的内容。
代码1.1 xTaskCreate() 原型
/**
* task. h
*
* 创建新任务并将其添加到准备运行的任务列表中。
*
* 在FreeRTOS内部,任务使用两块内存,第一个块用于保存任务的数据结构, 第二个块作为任务的堆栈。
* 如果任务是使用xTaskCreate()创建的,那么这两个内存块将在xTaskCreate()函数中自动进行动态分配。
* (参见 https://www.FreeRTOS.org/a00111.html)
* 如果任务是使用xTaskCreateStatic()创建的,则应用程序必须提供任务所需的内存。
*
* 不使用任何动态内存的版本,请参见xTaskCreateStatic()。
*
* xTaskCreate()只能用于创建对整个微控制器内存映射无限制访问的任务。
* 包含MPU的系统可以使用xTaskCreateRestricted()创建受MPU约束的任务。
*
* @param pvTaskCode 指向任务函数的指针。注意,任务函数不能返回(即死循环)。
*
* @param pcName 任务函数的描述性名称。主要是为了方便调试。configMAX_TASK_NAME_LEN定义最大长度-默认值为16。
*
* @param usStackDepth 任务栈的大小,是字数而不是字节数。例如,16位的栈宽,深度定义为100,则将分配200字节的栈空间。
*
* @param pvParameters 指向任务函数参数的指针。
*
* @param uxPriority 设置任务运行的优先级。包含MPU的系统可以选择通过设置priority参数的portPRIVILEGE_BIT位,
* 以特权(系统)模式创建任务。例如,要创建优先级为2的特权任务,uxPriority参数应设置为( 2 | portPRIVILEGE_BIT )。
*
* @param pvCreatedTask 传出创建成功的任务的句柄。
*
* @return 如果任务已成功创建并添加到就绪列表则返回pdPASS,否则返回projdefs.h文件中定义的错误代码。
*/
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /* 仅能使用字符或字符串 */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask );
#endif
二、xTaskCreate() 参数和返回值
2.1 pvTaskCode
pvTaskCode是指向具体任务函数的函数指针,其类型如下。
代码2.1 pvTaskCode类型
typedef void (* TaskFunction_t)( void * );
由此可以看出FreeRTOS任务函数的形式:
- 返回类型为void
- 形参类型为void *
需要进一步指出的是:
- 任务函数的函数名是任意的
- 任务函数的形参名必须是pvParameters
- 任务函数不允许以任何方式返回,即在函数中不能存在returm,也不能执行到函数末尾
- 允许在任务函数中创建其他任务
- 每个任务函数都有属于自己的栈空间(其大小由usStackDepth参数指定)
下面的例程给出了一个典型的任务函数。同一个任务函数可以被多次创建,它们都有各自的栈空间。栈空间变量不会相互影响,但用static修饰的变量,将被各任务所共享。
小知识:for(;;) 和 while(1)相比,在有些编译器下会省略进入判断条件的步骤,因而更加高效一点。
代码2.2 任务函数的例子
/* 任务函数例程 */
void TaskFunction( void *pvParameters )
{
/* 栈空间变量,如果多次实现该函数,则每个任务都各自有自己的value */
int value = 0;
/* 静态变量,如果多次实现该函数,则各任务将共享parameter */
static int parameter = 0;
/* 实际功能通常都实现在一个死循环中 */
for( ;; )
{
/* 实际功能代码 */
if (value == 0)
{
value++;
parameter++;
printf("TaskFunction value=%d, parameter=%d.\r\n", value, parameter);
}
}
/* 安全起见,如果程序由于异常执行到这,则删除当前任务 */
vTaskDelete( NULL );
}
2.2 pcName
pcName并不会被FreeRTOS使用,它单纯的用于辅助调试的描述任务函数的字符串。
可以通过修改 configMAX_TASK_NAME_LEN 的值来定义pcName的最大长度,其默认定义长度为16,其定义如下:
代码2.3 configMAX_TASK_NAME_LEN 定义
#define configMAX_TASK_NAME_LEN ( 16 )
需要注意的是,这个长度已经包含了结束符'\0'。如果传入的pcName的实际长度超过了configMAX_TASK_NAME_LEN 的大小,字符串将会被自动截断。
2.3 usStackDepth
创建任务时,FreeRTOS内核会为每个任务分配固定的栈空间,usStackDepth的值就是用来告诉内核,要为这个任务分配多大的栈空间。usStackDepth指的是栈空间的字数(word),而不是字节数(byte),因此,栈大小=字宽*栈深度,在使用时,栈大小不能超过 configSTACK_DEPTH_TYPE 定义类型变量的最大值(因为会溢出)。
没有什么方法可以直接计算出一个合适的栈大小。一般情况下,都会根据经验,先简单的设置一个值,然后利用FreeRTOS提供的方法来验证分配的栈大小是否满足要求,并根据结果来调整栈空间的大小,具体的方法将在栈侦测部分进行详细说明。
2.4 pvParameters
pvParameters的值即在创建任务时传给任务函数的入参,因为是一个void*类型,故可以传入任意类型的数据。如果不需要传入参数,可以将pvParameters置为NULL。
2.5 uxPriority
uxPriority用来指定任务初始的执行优先级,优先级的取值范围为[0, configMAX_PRIORITIES - 1],其值越小,任务的执行优先级就越低。在设计上,FreeRTOS的优先级并没有规定上限,configMAX_PRIORITIES 的值由用户设置,但最好使用实际需要的最小数值。当uxPriority大于configMAX_PRIORITIES - 1时,FreeRTOS会自动将其限制为configMAX_PRIORITIES - 1。
FreeRTOS允许创建相同优先级的任务,同时也允许在程序执行过程中修改任务的优先级,具体的详情将在任务优先级部分进行说明。
代码2.4 uxPriority类型
#define portBASE_TYPE int
typedef unsigned portBASE_TYPE UBaseType_t;
2.6 pvCreatedTask
pvCreatedTask用于传出任务的句柄。这个句柄可以用来操作已经创建的任务,如改变任务优先级、删除任务等。如果不需要任务句柄,可以将pvCreatedTask置为NULL。
代码2.5 pvCreatedTask类型
/**
* task. h
*
* 引用任务的类型。例如,调用xTaskCreate通过参数指针返回一个TaskHandle_t变量,然后可以将该变量用作vTaskDelete删除任务的参数
*/
struct tskTaskControlBlock; /* 旧的命名用于兼容内核调试器 */
typedef struct tskTaskControlBlock * TaskHandle_t;
2.7 返回值
如果任务创建成功,则返回pdPASS;如果任务创建失败,则返回相应的错误码。大部分创建失败的原因,都是因为FreeRTOS无法为任务分配足够的空间导致的,在实际程序中,应该判断该返回值,失败时记录错误码,便于查找问题原因。
代码2.6 返回值类型
typedef long BaseType_t;
三、xTaskCreate() 使用例程
这个例程演示了创建任务的必要步骤以及各个参数如何使用,读者也可以自行测试。
代码3.1 创建任务例程
#define TASK_STACK_SIZE 100
#define TASK_PRIORITY 1
/* 任务函数例程 */
void TaskFunction( void *pvParameters )
{
/* 栈空间变量,如果多次实现该函数,则每个任务都各自有自己的value */
int value = 0;
/* 静态变量,如果多次实现该函数,则各任务将共享parameter */
static int parameter = 0;
/* 实际功能通常都实现在一个死循环中 */
for( ;; )
{
/* 实际功能代码 */
if (value == 0)
{
value++;
parameter++;
printf("TaskFunction value=%d, parameter=%d.\r\n", value, parameter);
}
}
/* 安全起见,如果程序由于异常执行到这,则删除当前任务 */
vTaskDelete( NULL );
}
/* 创建两个相同优先级的任务 */
void main( void )
{
BaseType_t result = xTaskCreate( TaskFunction, "Task1", TASK_STACK_SIZE, NULL, TASK_PRIORITY, NULL );
if (result != pdPASS)
{
printf("ERROR: task1 create fail!, return %d\r\n", result);
}
result = xTaskCreate( TaskFunction, "Task2", TASK_STACK_SIZE, NULL, TASK_PRIORITY, NULL );
if (result != pdPASS)
{
printf("ERROR: task2 create fail!, return %d\r\n", result);
}
return;
}
参考资料
- 《FreeRTOS实时内核使用指南》
- FreeRTOSv202012.00版源码