一. 前言
FreeRTOS中,任务是程序执行的最小单位,也是调度器处理的基本单位。本文主要介绍两种任务创建的方式,一种是在CubeMX中创建任务;另一种是在工程中调用FreeRTOS源码来创建任务,两种方式在本质上是一样的。
二. 任务的创建
开发环境:CubeMX Vesion 5.4.0
Keil Vesion 5.28
2.1 CubeMX中创建任务
在Tasks and Queues中可以点击Add按钮添加新的任务:
名称 | 功能 |
---|---|
Task Name | 任务名称 |
Priority | 任务创建时的优先级 |
Stack Size | 任务栈的大小,默认单位为字节 |
Entry Function | 任务函数的入口(有别于Task Name,第一条为字符串格式) |
Code Generation Option | 任务函数代码生成方式: As weak: 产生一个用__weak 修饰符修饰的任务函数; As external: 产生一个外部引用的任务函数,用户需要自己实现该函数; Default: 产生一个默认格式的任务函数,用户需要在该函数实现功能 |
Parameter: | 传入的参数/指针,一般为NULL |
Allocation: | 创建方式(动态或静态,一般使用动态,很少使用静态方式) |
这里主讲参数Code Generation Option,其他参数根据自己需要设定即可。
设置完毕之后点击 OK,就可以看到列表中多出了自己创建的任务。
创建三个任务Code Generation Option分别选择Default、As external、As weak 。
1. 使用Default 。可以在main.c下找到其函数的定义,函数内容必须写在main.c的函数定义中。
2. 使用As weak。在main.c中能找到其函数定义。由于选择了通过__weak 修饰符创建一个弱函数,可以再在别处实现该任务函数。程序执行时会自动寻找到这个另外实现的任务函数。
比如:需要实现LED闪烁,则在其他文件中写上:
//程序执行时会自动寻找到这个任务函数。
void red_led_task(void const * argument)
{
while(1)
{
HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_SET);
osDelay(500);
HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_RESET);
osDelay(500);
HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_RESET);
osDelay(500);
}
}
3.使用As external。由于设置为 As external,故而需要再在别处实现任务函数。程序执行时会自动寻找到实现的任务函数。
比如:需要实现LED闪烁
void green_led_task(void const * argument)
{
while(1)
{
HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_RESET);
osDelay(500);
HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_SET);
osDelay(500);
HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_RESET);
osDelay(500);
}
}
和As weak不同,使用As external用户必须写上函数定义,否则会报错。而As weak由于使用了关键字__weak,即使用户不写函数定义,程序也会执行main.c中的弱函数。
2.2 使用FreeRTOS源码创建任务
BaseType_t xTaskCreate(
TaskFunction_tp vTaskCode, //函数指针
const char * constpcName, //任务描述
unsigned short usStackDepth, //堆栈大小
void *pvParameters, //参数指针
UBaseType_t uxPriority, //任务优先级
TaskHandle_t *pvCreatedTask //回传句柄
);
- pvTaskCode: 函数指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型
TaskFunction_t
定义在文件projdefs.h
中,定义为:typedef void(*TaskFunction_t)( void * )
,即参数为空指针类型并返回空类型。 - pcName: 任务描述。主要用于调试。字符串的最大长度(包括字符串结束字符)由宏
configMAX_TASK_NAME_LEN
指定,该宏位于FreeRTOSConfig.h
文件中。 - usStackDepth: 指定任务堆栈大小,能够支持的堆栈变量数量(堆栈深度),而不是字节数。比如,在 16 位宽度的堆栈下,
usStackDepth
定义为 100,则实际使用 200 字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t
类型所能表示的最大值。比如,size_t
为16位,则可以表示堆栈的最大值是 65535 字节。这是因为堆栈在申请时是以字节为单位的,申请的字节数就是堆栈宽度乘以深度,如果这个乘积超出size_t
所表示的范围,就会溢出。 - pvParameters: 指针,当任务创建时,作为一个参数传递给任务。
- uxPriority: 任务的优先级。具有 MPU 支持的系统,可以通过置位优先级参数的
portPRIVILEGE_BIT
位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为 2 的特权任务,参数uxPriority
可以设置为2
或者portPRIVILEGE_BIT
。 - pvCreatedTask: 用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。
举个例子,需要创建一个任务
TaskHandle_t Task1_Handle;//1.定义一个句柄
void Task_1(void *arg);//2.对任务函数进行声明
xTaskCreate(Task_1, "Task1", 256, NULL, 6, &Task1_Handle);//3.创建任务
void Task_1(void *arg)
{
for (;;)
{
//3.函数内容
}
}
三. 任务的删除
void vTaskDelete( TaskHandle_t xTask );
如何任务是用xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块和控制内存会在空闲任务中被释放掉,因此当调用函数vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
四. FreeRTOS文档资料
下面列出的是FreeRTOS比较好的学习资料:
1. FreeRTOS官网(可以下到源码与官方配套文档)
2. FreeRTOS相关API函数手册
3. 正点原子 《STM32F407+FreeRTOS开发手册》
4. 野火《FreeRTOS 内核实现与应用开发实战指南——基于Stm32》
4. FreeRTOS任务案例教学