任务创建是我们第一个要学习的 API 函数,同时它也是 FreeRTOS 众多 API 函数中最复杂的一个,但是没办法,这个函数是我们第一个要学习的,也是非常重要的。
那么来看一下咱们本节的主要内容有哪些:
首先我们来介绍一下任务创建和删除的 API 函数,讲解一下这些 API 函数我们要怎么去控制它,它入口参数有哪些,代表什么意思,那这些都会在这里进行讲解。
然后就是实战环节。首先是动态方法来实现任务创建和删除;接着是静态方法来实现任务创建以及删除。
最后就是我们的总结。
那么我们先来看一下第一部分内容。
1. 任务创建和删除的 API 函数
任务的创建和删除本质就是调用 FreeRTOS 的 API 函数。那我们来看一下这些 API 函数有哪些:
API函数 | 描述 |
---|---|
xTaskCreate() | 动态方式创建任务 |
xTaskCreateStatic() | 静态方式创建任务 |
vTaskDelete() | 删除任务 |
vTaskDelete 就是删除我们创建好的任务,没有创建好的是删除不了的,肯定要是创建成功了,然后才能进行删除。
- 动态创建任务。任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配
就是由 FreeRTOS 自己自动去实现这个分配,不需要人为的去操作,这个是由 FreeRTOS 它自己去实现的。
- 静态创建任务。任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供
那这个跟动态就有区别了。动态创建的话,它所需要的这些堆栈空间以及控制块的空间 都是由 FreeRTOS 自动的从它所管理的堆中去分配了;而静态是人为的去定义一段内存给它,比如说定义一个数组来当它的一个任务栈空间。那这个就是它们最大的一个区别了。
当然还有一个区别。动态的话它所申请的这些空间是属于 FreeRTOS 所管理的,而静态它就不属于 FreeRTOS 管理,是独立的。比如说我申请一个全局数组,那这个全局数组它不属于 FreeRTOS 所管理的一个堆空间。那这个也是要注意的一个点。
那么我们就来看一下第一个函数:动态创建任务的函数。
1.1 动态创建任务
函数原型:
大家看到它这个函数有 6 个的入口参数,是比较多的。那么我们来看一下这些入口参数有哪些含义。
BaseType_t xTaskCreate
( TaskFunction_t pxTaskCode, /* 指向任务函数的指针(就比如说我们创建了一个任务,那这个任务它所需要实现的一些功能就是放在它这个任务函数里面,我们通过这个参数来指向它的任务函数)*/ const char * const pcName, /* 任务名字,最大长度configMAX_TASK_NAME_LEN(config.h 中定义默认为 16,也就是说任务名字最大可以 16 个字符)*/const configSTACK_DEPTH_TYPE usStackDepth, /* 任务堆栈大小,注意字为单位(就比如说这里定义的是 100,那实际是 100*4 = 400 个字节,这里要注意了)*/void * const pvParameters, /* 传递给任务函数的参数(我们一般没用到,所以我们传入的是空 NULL)*/UBaseType_t uxPriority, /* 任务优先级,范围:0 ~ configMAX_PRIORITIES - 1(每个任务它都有自己的一个优先级,宏也在 config.h 中,我们配置 32,所以它的取值范围是 0~31,总共 32 个)*/TaskHandle_t * const pxCreatedTask /* 任务句柄,就是任务的任务控制块(那我们要删除任务或怎么都是通过它句柄来进行操作的,当然这个任务句柄其实就是它的任务控制块,那这个在函数内部,它们两个是有个相等这么一个操作,也就是等效,那后面我们也会详细介绍一下任务控制块它是什么,这个大家不要着急)*/
)
注意:任务优先级的数值越大,任务就越优先。这是 FreeRTOS 它的一个特点
然后它有一个返回值,我们看一下它的类型是 BaseType_t 这个。
返回值 | 描述 |
---|---|
pdPASS | 任务创建成功 |
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY | 任务创建失败 |
大家看一下,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 有个 MEMORY,其实主要就是说你的堆栈可能太大了,然后 FreeRTOS 所管理堆栈可能就那么点,然后申请的这个堆栈,这个任务太大了。我们说过,动态创建它是在 FreeRTOS 所管理的一个堆空间里面去申请的,那如果堆空间已经不够了,然后还来申请那么大的,这时候就会申请失败了,就会返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 这么一个值。
那这个是它的返回值的描述。
那我们接着来看一下实现动态创建任务的流程:
- 将宏 configSUPPORT_DYNAMIC_ALLOCATION(支持动态申请内存) 配置为 1
config.h 这个配置文件我们是经常会调用到的。
- 定义函数入口参数。
它有 6 个入口参数,这些我们可以提前去定义一下,代入进去。
- 编写任务函数
那我们创建这个任务,这个任务要实现哪些功能?那这个功能就在这个任务函数去实现。
那我们只需要这三步,我们就可以用起来了,这三步就可以把动态创建任务实现。让大家如何简单用起来,就这么简单。
然后创建完之后会怎样?此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
比如说我们创建了很多个任务,它创建这么多任务都会第一时间进入就绪态,然后由任务调度器在这就绪态中找到优先级最高的去执行,那整个过程其实就这样。
好,那这个是比较简单的用起来,但它内部的一个操作肯定是没有这么简单了,只是有 API 函数给你,所以我们只要调用起来就可以了,就比较简单。但实际呢内部的一个操作是很麻烦的,它内部的操作,我们这里也是简单的跟大家先介绍一下,有一个基础的认识,在我们后面的实战环节的时候,我们再来详细介绍一下任务创建它内部的一个实现步骤。那我们来看一下:
- 申请堆栈内存&任务控制块内存
我们只要定义我们需要申请的大小就行了,之后是由 FreeRTOS 自动的去申请。
- TCB结构体成员赋值
TCB 其实就是任务控制块。那这个任务控制块它的结构体成员,我们会进行成员赋值,那大家肯定会问了,老是提到这个任务控制块,那这个任务控制块到底是什么呢?其实它就跟人的身份证是一个概念的,每个任务它都有自己的一个任务控制块,而这个任务控制块它的结构体成员存取的就是这个任务它的一些特征,比如任务名字,任务的优先级,此时任务的一个状态等等等等,这些都保留在这个任务控制块里面,所以说这个任务控制块是非常重要的,我们一会儿会来说。
- 添加新任务到就绪列表中(也就是进入就绪态)
这是它的一个内部操作,那简单我们分为这三步,那实际我们在后面的时候会进行再细化进行讲解,这里大家只要有个初步认识即可。
接着看一下任务控制块结构体成员的介绍了。
这里注意它是一个结构体。那这个结构体还有很多的成员变量,我们这里还省略了非常多的一些条件编译的一些成员,那大家看源码就知道它的结构体是非常非常,对于一些条件编译的,我们这里先省略,不看,我们就看这些最基本的。
typedef struct tskTaskControlBlock
{volatile StackType_t * pxTopOfStack; /* 任务栈栈顶,必须为TCB的第一个成员(这个非常关键,因为它后面跟任务切换、任务的上下文保存以及任务的恢复都息息相关,那这个非常重要,那它的内容我们会在后面的任务切换的时候再进行一个介绍,那大家这里就要有一个基本的了解:它非常关键,跟任务切换密切相关,就可以了)*/ListItem_t xStateListItem; /* 任务状态列表项(当前任务处于什么状态就保存在这里,就绪态、运行态、阻塞态、挂起态)*/ ListItem_t xEventListItem; /* 任务事件列表项(当前任务等待某个事件,就会使用到这个事件列表项,那关于列表以及列表项我们还没有介绍,大家只要有个了解就可以,在后面的一个列表专题我们会进行一个详细介绍)*/ UBaseType_t uxPriority; /* 任务优先级,数值越大,优先级越大(那这个就保存任务的一个优先级,也就是当前任务优先级多少是存在这个变量里面)*/StackType_t * pxStack; /* 任务栈起始地址(任务的堆栈首地址保存在这里)*/char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名字(任务的名字保存在这里)*/ …省略很多条件编译的成员
} tskTCB;
所以说简单讲任务控制块,它其实就是任务的一个身份证,它保留了任务的一些特征。
好,总结一下:
-
任务栈栈顶,在任务切换时的任务上下文保存、任务恢复息息相关。(任务切换章节讲解)
-
注意:每个任务都有属于自己的任务控制块,类似身份证。
那这是任务控制块的介绍了。那么接着看一下静态创建任务 API 函数的介绍。
1.2 静态创建任务
函数原型:
大家看到它这个函数有 7 个的入口参数,同样也是比较多的,而且大部分是跟动态创建任务那个函数是很类似的。那么我们来看一下这些入口参数有哪些含义。
TaskHandle_t xTaskCreateStatic
(TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */const char * const pcName, /* 任务函数名 */const uint32_t ulStackDepth, /* 任务堆栈大小,注意字为单位 */void * const pvParameters, /* 传递的任务函数参数(我们这里一般都是传入参数为空 NULL */UBaseType_t uxPriority, /* 任务优先级 */StackType_t * const puxStackBuffer, /* 任务堆栈,一般为数组,由用户分配(那这个跟动态是不是有区别,动态的话我们只需要给它一个堆栈大小,剩下我们不用管,剩下是 FreeRTOS 它根据我们给的堆栈大小自己去申请这段内存;而静态就不是了,给它一个我们的一个数组的地址)*/StaticTask_t * const pxTaskBuffer /* 任务控制块指针,由用户分配(控制块保留了很多信息,那这些信息它也是要用内存来保存的,那这个内存同样的也是由用户自己分配)*/
);
那大家看到前面 5 个跟动态创建任务是很类似的,几乎可以说是一模一样的;那后面就开始不一样了。
那这个是静态创建任务它的入口参数的介绍。我们来看一下它同样的也有返回值。
返回值 | 描述 |
---|---|
NULL | 用户没有提供相应的内存,任务创建失败 |
其他值 | 任务句柄,任务创建成功 |
比如说你这些内存你并没有提供,那这时候它就会创建任务失败。
这个任务句柄在我们后面也是要操作的,比如说我们任务删除,都是通过这个句柄来实现的,那这个也是它跟动态创建的一个区别了。
接着我们来看一下静态创建任务的使用流程:
- 需将宏 configSUPPORT_STATIC_ALLOCATION(支持静态申请内存) 配置为 1
- 定义空闲任务&定时器任务的任务堆栈及 TCB
注意:空闲任务是必须的,因为我们说了在 RTOS 中,CPU 是不能停止的,在空闲的时候,是要去执行这个空闲任务的,所以空闲任务是必须要有的。软件定时器任务是可选的,当使能了软件定时器功能,这个任务就需要创建;当没有使能的话,那就不需要创建这个软件定时器任务。那这个使能同样也是在 config.h 中进行配置的,当使能了,那么它也会创建一个定时器任务。那这两个任务都是使用的静态创建的时候,你都得分配任务堆栈以及任务控制块的内存给它,那这部分都是由用户自己去定义的,所以相对来说静态创建任务是比较麻烦的。
- 实现两个接口函数
vApplicationGetIdleTaskMemory()
和vApplicationGetTimerTaskMemory()
第一个就是空闲任务的内存复制,第二个就是软件定时器的内存复制,在我们前面定义的一些任务堆栈以及任务控制块复制给结构体成员,就通过这两个函数来实现。当然 vApplicationGetTimerTaskMemory 这个是可选的,而 vApplicationGetIdleTaskMemory 这个它是必须要有的。
- 定义函数入口参数
- 编写任务函数
这两步就跟动态创建很类似的。
那我们要用起来就是这 5 步,这是静态创建任务的使用流程。同样的此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
那么接着来看一下静态创建任务的内部实现逻辑,同样的也是简单给大家列一下:
- TCB结构体成员赋值
将任务的一些特征赋值给任务控制块。
- 添加新任务到就绪列表中。(也就是进入就绪态)
这两步看着比较简单,实际上也是比较复杂的,在后面的实战编程才会来详细的一个介绍和内部的一个创建逻辑。
接着,就有一个函数:任务删除函数,当然就比较简单了。
1.3 任务删除函数
void vTaskDelete(TaskHandle_t xTaskToDelete);
它只有一个入口参数。那我们看一下这个入口参数如何介绍。
形参 | 描述 |
---|---|
xTaskToDelete | 待删除任务的任务句柄 |
比如说我们创建了一个函数,那这个函数它是有个任务句柄的。那我们删除这个任务,就是把它的任务句柄给代进来就可以删掉它了。
注意:用于删除已被创建的任务。
如果你还没被创建,那是不能删除的,是一定要创建成功的这个任务它才能被删除。
被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除。也就是说我不管你这个任务它处于什么状态,只要你调用了这个函数,然后代入它的一个句柄,那么你都会被删除。
注意:
- 当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)
比如说我创建了两个任务 task1、task2,那么我在 task1 的任务函数中调用了一个任务删除,然后这个任务删除它的入口参数如果是 task2 句柄,然后就删除 task2 这个任务;如果参数是 NULL,就删除 task1 任务自身。
- 空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存, 则需要由用户在任务被删除前提前释放,否则将导致内存泄露
像我们动态创建任务,它的内存就是由系统自动分配的;而静态创建任务,它是由用户自己定义的,所以这里是针对动态的,也就是说当你要删除这个任务它是动态所创建的一个任务的时候,它是由空闲任务负责释放这份内存的,因为把这个任务删除了,那么这个任务刚刚所申请的一些内存、堆栈空间以及任务控制块都需要释放掉,而这个释放它是空闲任务中释放。但这里有个条件,就是说删除任务的时候,代入的是 NULL,就删除任务本身的时候,才是会在空闲任务中执行这个释放;如果是删除的其他任务,比如 task1 运行的删除函数,然后句柄是 task2 的,当然这里 task1、task2 都是动态创建的,然后这时候 task2 删除了之后,它的一个堆栈内存以及控制块一些内存在哪里释放,它是在删除函数中,就不是在空闲任务了。在空闲任务是什么情况,就是说 task1 运行的删除函数,然后句柄是 NULL,就删除 task1 本身,这时候是不能在删除函数里删除了,因为任务自身现在正在运行,不能立马就把任务自身的堆栈空间给释放掉,而是在空闲任务这里进行释放。那这是动态的一个删除情况。
那静态就是有用户自己申请的内存必须自己去提前释放,不然就会导致内存泄露了。那这个是静态的,也是动态跟静态它们任务删除的一个区别。
接着,看一下删除任务的流程:
-
使用删除任务函数,需将宏INCLUDE_vTaskDelete 配置为 1
-
入口参数输入需要删除的任务句柄(NULL代表删除本身)
那这个就是用起来了,只需要这两步。非常简单,但它内部操作实在不简单,我们可以看一下它内部:
- 获取所要删除任务的控制块。通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身
我们通过它句柄找到它的一个任务控制块。
- 将被删除任务,移除所在列表。将该任务在所在列表中移除,包括:就绪、阻塞、挂起、事件等列表
就不管任务在就绪、还是阻塞、还是挂起,都得移除,什么状态,只要删除了都得移除掉。
- 判断所需要删除的任务
- 删除任务自身,需先添加到等待删除列表。因为我要删除自己,我当前是正在运行的,那我要删除自己,我得运行完之后去切换到删除自己,我不能说我在运行的时候,我立马把自己的堆栈空间给释放掉了,这是不允许的。所以内存释放将在空闲任务执行。
- 删除其他任务,那么就直接在删除函数里面去进行释放内存,以及任务总数量–
- 更新下个任务的阻塞时间。更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务。
因为有可能你删除的这个任务,它刚好就是下一个阻塞超时的任务。就比如说我们有三个任务 task1、task2、task3 三个任务,那 task2、task3 它们进入阻塞态,比如说 task2 阻塞延时 20s,调用我们的一个系统延时;然后 task3 是 50ms,那么这两个就会挂载到我们的一个阻塞列表中,然后把时间短的我们会挂前面,先是 20ms 的,然后 50ms 的在后面,那要解的话肯定是 20ms 时间先到了,20ms 到的时候,就把 task2 从这个阻塞态给解除出去,解到我们的一个就绪列表中。那如果此时你删除了任务,刚好就是这个 task2,比如说我在 task1 里执行删除任务函数,删除 task2,就把 task2 的句柄代进来,那此时 task2 被删除了,那么此时不管你是就绪、阻塞、挂起、什么都得给移除掉,那么此时阻塞列表就没有 task2 了,那你这里阻塞时间还是 20ms,我们是不是得更新一下,更新成 50ms,那这个就是删除任务它的一个内部操作了,那这个我们在后面实现编程的时候也会进行一个详细的介绍。
那么本节就介绍到这里。
2. 实战编程-动态方法
本节我们就来到了实战环节了,我们来实现一下 FreeRTOS 的任务创建以及任务删除了。那我们要注意,我们本节需要实现的是动态创建的方式,然后静态创建我们留在下一节再来介绍。
我们先来看一下,本节是如何实现任务创建以及任务删除的。
实验目的:学会 xTaskCreate()
和 vTaskDelete()
这两个 API 函数的使用
实验设计:将设计四个任务:start_task、task1、task2、task3
四个任务的功能如下:
- start_task:用来创建其他的三个任务
- task1:实现LED0每500ms闪烁一次
- task2:实现LED1每500ms闪烁一次
- task3:判断按键KEY0是否按下,按下则删掉task1
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/SDRAM/sdram.h"
#include "./MALLOC/malloc.h"
#include "freertos_demo.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */delay_init(180); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */key_init(); /* 初始化按键 */sdram_init(); /* SRAM初始化 */lcd_init(); /* 初始化LCD */my_mem_init(SRAMIN); /* 初始化内部内存池 */my_mem_init(SRAMEX); /* 初始化外部内存池 */my_mem_init(SRAMCCM); /* 初始化CCM内存池 */freertos_demo(); /* FreeRTOS例程入口函数 */
}
freertos_demo();
是 FreeRTOS 例程的入口函数,我们 FreeRTOS 的全部内容都在这里实现。
2.1 宏定义 configSUPPORT_DYNAMIC_ALLOCATION 置 1
在 FreeRTOSConfig.h 中。
2.2 定义函数入口参数
freertos_demo 函数
/* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_TASK_STACK_SIZE 128 /* 堆栈大小,单位字,乘以 4 变成字节 */
TaskHandle_t start_task_handler; /* 任务句柄 */
void start_task( void * pvParameters ); /* 任务函数声明 */void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task, /* 任务函数 */(char * ) "start_task", /* 任务名字 */(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, /* 任务堆栈大小 */(void * ) NULL, /* 任务函数的入口参数 */(UBaseType_t ) START_TASK_PRIO, /* 任务优先级 */(TaskHandle_t * ) &start_task_handler ); /* 任务句柄 */vTaskStartScheduler(); /* 开启任务调度器 */
}
- 强转(建议):防止编译器报一些警告。
- 堆栈大小为什么定义 128 字?
其实我们这里是偏大定义的,因为你保证不了你后续可能在任务里面要操作些什么。你后续可能要定义一个比较大的数组,或者说你调用很多的嵌套函数,那这些都是要使用到我们的堆栈的,因为后续你要任务保存,上下文保存,寄存器信息,这些它都是要保存到你的任务堆栈,所以我们这里要尽量的定义大一点。- 那每个任务假设都定义偏大很多,那总堆栈就那么点,那我可能定义不了几个任务,我就创建不了了。那怎么办?
Freertos 它官方是有一个 API 函数的(uxTaskGetStackHighWaterMark()
),它是可以查询任务所剩余的历史最小堆栈。我们可以根据它剩余的大小决定我们定义的堆栈的大小。比如说你剩余的很小了,那我们可以定义偏大一点;那如果你剩余很大,那么代表你这么大就没用到,我可以定义偏小一点。- 我们创建完任务之后,任务调度器必须要开启,不然任务是没法调度的。
start_task 函数
/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );/* TASK3 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );void start_task( void * pvParameters )
{//taskENTER_CRITICAL(); /* 进入临界区 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler );xTaskCreate((TaskFunction_t ) task3,(char * ) "task3",(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(TaskHandle_t * ) &task3_handler );vTaskDelete(NULL); /* 删除任务自身(当前正在运行的任务) *///taskEXIT_CRITICAL(); /* 退出临界区 */
}
我们这里的任务 task1、task2、task3 它们任务优先级分别是 2、3、4,那按道理说哪个任务先运行?大家肯定会说任务优先级高的先运行。
实际上,它第一个运行的是 task1,然后 task2,接着 task3。这就很奇怪了,因为明明 task1 优先级是最低的,反而你是最先执行了,而 task3 优先级最高的反而是后面执行,那大家想一下为什么?
我们看一下逻辑:
-
首先这个入口函数(
freertos_demo
),我们一开始创建了开始任务(start_task
),这个开始任务它优先级是这四个任务中最低的,它是 1;这开始任务我们一创建好之后我们就调用了开启任务调度器(vTaskStartScheduler
),我们开启之后,我们的开始任务就要开始执行了。 -
开始任务这里面,它创建了三个任务:它一开始创建了
task1
,大家注意,它创建好 task1 之后,task1 的优先级是比开始任务的高的,那么它执行完创建 task1 的时候,task1 的优先级更高,cpu 会执行 task1 的任务。 -
然后 task1 任务阻塞之后,它就释放了它的 cpu 使用权,给低优先级开始任务继续执行。那这时候开始任务开始创建
task2
,那 task2 的优先级也比开启任务高,所以它创建完之后,cpu 就来到 task2 运行了。 -
接着 task2 也阻塞了,这时候又回到开始任务,创建
task3
,那 task3 的优先级是最高的,所以此时它也会抢占开始任务,执行到 task3。 -
然后 task3 阻塞了,这时候才会来到
vTaskDelete(NULL);
,删除开始任务自己。
整个过程就是这样。所以才会导致 task1、task2、task3 这样的一个执行顺序。那我肯定是要一开始高优先级的任务优先执行,我不想这样怎么办?
当你进入开始任务的时候,关闭任务调度器就可以了。
那我们的任务切换其实它是在中断中执行的,FreeRTOS 它提供了一个 API 函数,叫做进入临界区(
taskENTER_CRITICAL();
),它就是用来关闭中断的。然后退出临界区(taskEXIT_CRITICAL();
)之后才会开始我们的任务切换。
2.3 编写任务函数
task1、task2、task3 任务函数
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{while(1){printf("task1正在运行!!!\r\n");LED0_TOGGLE(); /* LED0 翻转函数 */vTaskDelay(500); /* 系统自带延时函数 */}
}/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{while(1){printf("task2正在运行!!!\r\n");LED1_TOGGLE();vTaskDelay(500);}
}/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{uint8_t key = 0; /* 保存键值 */while(1){printf("task3正在运行!!!\r\n");key = key_scan(0); /* 扫描, 0:不支持连按 */if(key == KEY0_PRES) /* 是否为 key0 按下 */{if(task1_handler != NULL){printf("删除task1任务\r\n");vTaskDelete(task1_handler); /* 删除 task1 任务 */task1_handler = NULL;}}vTaskDelay(10);}
}
- 注意:任务不能重复删除。(跟内存管理算法有关:因为你删除之后已经把它的内存给释放掉了,你一直重复释放是不行的,所以它会打印一些错误)
- 任务删除方法类似于指针。
2.4 整体代码
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/SDRAM/sdram.h"
#include "./MALLOC/malloc.h"
#include "freertos_demo.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */delay_init(180); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */key_init(); /* 初始化按键 */sdram_init(); /* SRAM初始化 */lcd_init(); /* 初始化LCD */my_mem_init(SRAMIN); /* 初始化内部内存池 */my_mem_init(SRAMEX); /* 初始化外部内存池 */my_mem_init(SRAMCCM); /* 初始化CCM内存池 */freertos_demo();
}
freertos_demo.h
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_Hvoid freertos_demo(void);#endif
freertos_demo.c
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"/******************************************************************************************************/
/*FreeRTOS配置*//* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );/* TASK3 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );
/******************************************************************************************************//*** @brief FreeRTOS例程入口函数* @param 无* @retval 无*/
void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task,(char * ) "start_task",(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler );xTaskCreate((TaskFunction_t ) task3,(char * ) "task3",(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(TaskHandle_t * ) &task3_handler );vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出临界区 */
}/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{while(1){printf("task1正在运行!!!\r\n");LED0_TOGGLE();vTaskDelay(500);}
}/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{while(1){printf("task2正在运行!!!\r\n");LED1_TOGGLE();vTaskDelay(500);}
}/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0_PRES){if(task1_handler != NULL){printf("删除task1任务\r\n");vTaskDelete(task1_handler);task1_handler = NULL;}}vTaskDelay(10);}
}
3. 实战编程-静态方法
实验目的:学会 xTaskCreate()
和 vTaskDelete()
这两个 API 函数的使用
实验设计:将设计四个任务:start_task、task1、task2、task3
四个任务的功能如下:
- start_task:用来创建其他的三个任务
- task1:实现LED0每500ms闪烁一次
- task2:实现LED1每500ms闪烁一次
- task3:判断按键KEY0是否按下,按下则删掉task1
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/SDRAM/sdram.h"
#include "./MALLOC/malloc.h"
#include "freertos_demo.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */delay_init(180); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */key_init(); /* 初始化按键 */sdram_init(); /* SRAM初始化 */lcd_init(); /* 初始化LCD */my_mem_init(SRAMIN); /* 初始化内部内存池 */my_mem_init(SRAMEX); /* 初始化外部内存池 */my_mem_init(SRAMCCM); /* 初始化CCM内存池 */freertos_demo(); /* FreeRTOS例程入口函数 */
}
3.1 宏定义 configSUPPORT_STATIC_ALLOCATION 置 1
在 FreeRTOSConfig.h 中。
3.2 定义空闲任务&定时器任务的任务堆栈及TCB
静态创建任务,空闲任务&定时器任务的任务堆栈和任务控制块这些内存是需要我们人为的手动提供给他的;像动态创建就不用了,动态创建是 FreeRTOS 自动分配的。
/* 空闲任务配置 */
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];/* 软件定时器任务配置 */
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
软件定时器由 configUSE_TIMERS 宏决定是否开启。
3.3 实现两个接口函数
静态创建任务环境下,FreeRTOS 只声明未实现。
vApplicationGetIdleTaskMemory & vApplicationGetTimerTaskMemory
/* 空闲任务内存分配 */
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, /* 任务控制块 */StackType_t ** ppxIdleTaskStackBuffer, /* 任务堆栈 */uint32_t * pulIdleTaskStackSize ) /* 任务堆栈的大小 */
{* ppxIdleTaskTCBBuffer = &idle_task_tcb;* ppxIdleTaskStackBuffer = idle_task_stack;* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}/* 软件定时器内存分配 */
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,StackType_t ** ppxTimerTaskStackBuffer,uint32_t * pulTimerTaskStackSize )
{* ppxTimerTaskTCBBuffer = &timer_task_tcb;* ppxTimerTaskStackBuffer = timer_task_stack;* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
3.4 定义函数入口参数
freertos_demo 函数
/* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_TASK_STACK_SIZE 128 /* 任务堆栈的大小 */
TaskHandle_t start_task_handler; /* 任务句柄 */
StackType_t start_task_stack[START_TASK_STACK_SIZE]; /* 任务堆栈 */
StaticTask_t start_task_tcb; /* 任务控制块 */
void start_task( void * pvParameters ); /* 任务函数 */void freertos_demo(void)
{ start_task_handler = xTaskCreateStatic( (TaskFunction_t ) start_task, /* 指向任务函数的指针 */(char * ) "start_task", /* 任务函数名 */(uint32_t ) START_TASK_STACK_SIZE, /* 任务堆栈大小注意字为单位 */(void * ) NULL, /* 传递的任务函数参数 */(UBaseType_t ) START_TASK_PRIO, /* 任务优先级 */(StackType_t * ) start_task_stack, /* 任务堆栈,一般为数组,由用户分配 */(StaticTask_t * ) &start_task_tcb ); /* 任务控制块指针,由用户分配 */vTaskStartScheduler();
- 前五个参数和动态创建任务一模一样,区别就是最后两个参数:我们要定义一个任务堆栈和一个任务控制块(手动分配)。
- 动态创建任务以参数的形式返回句柄;而静态创建任务以返回值的方式返回句柄,创建成功就会返回句柄,创建失败就返回 NULL。
start_task 函数
/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
StackType_t task1_stack[TASK1_STACK_SIZE];
StaticTask_t task1_tcb;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
StackType_t task2_stack[TASK2_STACK_SIZE];
StaticTask_t task2_tcb;
void task2( void * pvParameters );/* TASK3 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
StackType_t task3_stack[TASK3_STACK_SIZE];
StaticTask_t task3_tcb;
void task3( void * pvParameters );void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 */task1_handler = xTaskCreateStatic( (TaskFunction_t ) task1,(char * ) "task1", (uint32_t ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(StackType_t * ) task1_stack,(StaticTask_t * ) &task1_tcb );task2_handler = xTaskCreateStatic( (TaskFunction_t ) task2,(char * ) "task2", (uint32_t ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(StackType_t * ) task2_stack,(StaticTask_t * ) &task2_tcb );task3_handler = xTaskCreateStatic( (TaskFunction_t ) task3,(char * ) "task3", (uint32_t ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(StackType_t * ) task3_stack,(StaticTask_t * ) &task3_tcb );vTaskDelete(start_task_handler); /* 删除开始任务 */taskEXIT_CRITICAL(); /* 退出临界区 */
}
3.5 编写任务函数
task1、task2、task3 任务函数
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{while(1){printf("task1正在运行!!!\r\n");LED0_TOGGLE();vTaskDelay(500);}
}/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{while(1){printf("task2正在运行!!!\r\n");LED1_TOGGLE();vTaskDelay(500);}
}/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0_PRES){if(task1_handler != NULL){printf("删除task1任务\r\n");vTaskDelete(task1_handler);task1_handler = NULL;}}vTaskDelay(10);}
}
静态创建任务它相对于动态创建任务来说,步骤是比较多的,它又要给空闲任务分配内存,又给软件定时器分配内存,而且你每创建一个任务,第一个你要读取它的返回值,并且它的任务堆栈和任务控制块这些内存都是我们要手动去另外分配的,那这些呢其实是比较麻烦的。
那像动态创建任务就比较方便了,所以我们常用的是动态创建任务,在我们后续的例程,也都是用的动态创建任务,那静态创建任务呢大家了解一下、理解一下就可以了。
3.6 整体代码
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/SDRAM/sdram.h"
#include "./MALLOC/malloc.h"
#include "freertos_demo.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(360, 25, 2, 8); /* 设置时钟,180Mhz */delay_init(180); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */key_init(); /* 初始化按键 */sdram_init(); /* SRAM初始化 */lcd_init(); /* 初始化LCD */my_mem_init(SRAMIN); /* 初始化内部内存池 */my_mem_init(SRAMEX); /* 初始化外部内存池 */my_mem_init(SRAMCCM); /* 初始化CCM内存池 */freertos_demo();
}
freertos_demo.h
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_Hvoid freertos_demo(void);#endif
freertos_demo.c
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"/******************************************************************************************************/
/*FreeRTOS配置*//* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
StackType_t start_task_stack[START_TASK_STACK_SIZE];
StaticTask_t start_task_tcb;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
StackType_t task1_stack[TASK1_STACK_SIZE];
StaticTask_t task1_tcb;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
StackType_t task2_stack[TASK2_STACK_SIZE];
StaticTask_t task2_tcb;
void task2( void * pvParameters );/* TASK3 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
StackType_t task3_stack[TASK3_STACK_SIZE];
StaticTask_t task3_tcb;
void task3( void * pvParameters );
/******************************************************************************************************/
/* 空闲任务配置 */
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];/* 软件定时器任务配置 */
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];/* 空闲任务内存分配 */
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,StackType_t ** ppxIdleTaskStackBuffer,uint32_t * pulIdleTaskStackSize )
{* ppxIdleTaskTCBBuffer = &idle_task_tcb;* ppxIdleTaskStackBuffer = idle_task_stack;* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}/* 软件定时器内存分配 */
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,StackType_t ** ppxTimerTaskStackBuffer,uint32_t * pulTimerTaskStackSize )
{* ppxTimerTaskTCBBuffer = &timer_task_tcb;* ppxTimerTaskStackBuffer = timer_task_stack;* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}/*** @brief FreeRTOS例程入口函数* @param 无* @retval 无*/
void freertos_demo(void)
{ start_task_handler = xTaskCreateStatic( (TaskFunction_t ) start_task,(char * ) "start_task", (uint32_t ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(StackType_t * ) start_task_stack,(StaticTask_t * ) &start_task_tcb );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 */task1_handler = xTaskCreateStatic( (TaskFunction_t ) task1,(char * ) "task1", (uint32_t ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(StackType_t * ) task1_stack,(StaticTask_t * ) &task1_tcb );task2_handler = xTaskCreateStatic( (TaskFunction_t ) task2,(char * ) "task2", (uint32_t ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(StackType_t * ) task2_stack,(StaticTask_t * ) &task2_tcb );task3_handler = xTaskCreateStatic( (TaskFunction_t ) task3,(char * ) "task3", (uint32_t ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(StackType_t * ) task3_stack,(StaticTask_t * ) &task3_tcb );vTaskDelete(start_task_handler);taskEXIT_CRITICAL(); /* 退出临界区 */
}/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{while(1){printf("task1正在运行!!!\r\n");LED0_TOGGLE();vTaskDelay(500);}
}/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{while(1){printf("task2正在运行!!!\r\n");LED1_TOGGLE();vTaskDelay(500);}
}/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0_PRES){if(task1_handler != NULL){printf("删除task1任务\r\n");vTaskDelete(task1_handler);task1_handler = NULL;}}vTaskDelay(10);}
}