参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、任务创建和删除的API函数
1、概述
(1)任务的创建和删除本质就是调用FreeRTOS的API函数。
API函数 | 描述 |
xTaskCreate() | 动态方式创建任务 |
xTaskCreateStatic() | 静态方式创建任务 |
vTaskDelete() | 删除任务 |
(2)动态创建任务与静态创建任务:
①动态创建任务——任务的任务控制块以及任务的栈空间所需的内存,均由FreeRTOS从FreeRTOS管理的堆中分配。
②静态创建任务——任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供。
(3)任务控制块:
①任务控制块结构体成员:
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;
①每个任务都有属于自己的任务控制块,用于保存任务的相关信息,类似每个人都有属于自己的身份证。
②任务栈栈顶,在任务切换时与任务上下文保存、任务恢复息息相关。
2、动态创建任务
(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 /* 任务句柄,就是任务的任务控制块 */
)
返回值 | 描述 |
pdPASS | 任务创建成功 |
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY | 任务创建失败 |
(2)实现动态创建任务的流程:
①在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1。
②定义函数入口参数。
③编写任务函数。
(3)动态创建任务函数的内部实现(大致流程):
①申请堆栈内存或任务控制块内存。
②TCB(任务控制块)结构体成员赋值。
③添加新任务到就绪列表中。
3、静态创建任务
(1)静态创建任务函数的接口定义:
TaskHandle_t xTaskCreateStatic
(
TaskFunction_t pxTaskCode, /* 指向任务函数的指针 */
const char * const pcName, /* 任务名字 */
const uint32_t usStackDepth, /* 任务堆栈大小,单位为字 */
void * const pvParameters, /* 传递给任务函数的参数 */
UBaseType_t uxPriority, /* 任务优先级 */
StaticTask_t * const pxStackBuffer, /* 任务堆栈,一般为数组,由用户分配 */
StaticTask_t * const pxTaskBuffer /* 任务控制块指针,由用户分配 */
)
返回值 | 描述 |
NULL | 用户没有提供相应的内存,任务创建失败 |
其它值 | 任务句柄,任务创建成功 |
(2)实现静态创建任务的流程:
①在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION配置为1。
②定义空闲任务和定时器任务(可选,如果软件定时器失能则忽视)的任务堆栈及TCB。
③实现接口函数vApplicationGetIdleTaskMemory( )和vApplicationGetTimerTaskMemory ( )。
④定义函数入口参数。
⑤编写任务函数。
(3)静态创建任务函数的内部实现(大致流程):
①TCB(任务控制块)结构体成员赋值。
②添加新任务到就绪列表中。
4、任务删除
(1)任务删除函数接口定义:
void vTaskDelete
(
TaskHandle_t xTaskToDelete /* 待删除任务的任务句柄 */
)
①该函数用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除。
②当传入的参数为NULL,则代表删除任务自身,即当前正在运行的任务。
③空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露。
(2)实现删除任务的流程:
①在FreeRTOSConfig.h文件中将宏INCLUDE_vTaskDelete配置为1。
②入口参数输入需要删除的任务句柄。
(3)任务删除函数的内部实现:
①通过传入的任务句柄,判断所需要删除哪个任务。
②将该任务从其所在列表中移除,包括就绪、阻塞、挂起、事件等列表。
③判断所需要删除的任务:
[1]如果是删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行。
[2]如果是删除其它任务,则直接释放内存,任务数量减一。
④更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务。
二、任务创建和删除的动态方法实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计4个任务——start_task、task1、task2、task3:
[1]start_task:用于创建其它三个任务,然后删除自身。
[2]task1:实现LED1状态反转。
[3]task2:实现LED2状态反转。
[4]task3:按下按键1,删除task1、task2;按下按键2,创建task1、task2。
②预期实验现象:
[1]程序下载到板子上后,两个LED灯闪烁。
[2]按下按键1后,LED灯停止闪烁,接着按下按键2,LED灯恢复闪烁。
2、实验步骤
(1)将上一章中移植了FreeRTOS的工程文件夹复制一份,在拷贝版中进行实验。
(2)在User分组下添加源文件FreeRTOS_experiment.c和头文件FreeRTOS_experiment.h,并在main.c文件中包含FreeRTOS_experiment.h。
(3)在FreeRTOSConfig.h文件中将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1,注意代码的添加位置。
#define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态申请内存
(4)核对Key.c文件和LED.c文件,其中出现的延时函数都需要更换为vTaskDelay函数。
①Key.c文件:
#include "stm32f10x.h" // Device header
#include "FreeRTOS.h"
#include "Task.h"
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
vTaskDelay(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
vTaskDelay(20); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
vTaskDelay(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
vTaskDelay(20); //延时消抖
KeyNum = 2; //置键码为2
}
return KeyNum;//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
②LED.c文件:
#include "stm32f10x.h" // Device header
/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平
}
/**
* 函 数:LED1开启
* 参 数:无
* 返 回 值:无
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平
}
/**
* 函 数:LED1关闭
* 参 数:无
* 返 回 值:无
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平
}
/**
* 函 数:LED1状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}
/**
* 函 数:LED2开启
* 参 数:无
* 返 回 值:无
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平
}
/**
* 函 数:LED2关闭
* 参 数:无
* 返 回 值:无
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平
}
/**
* 函 数:LED2状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为低电平
}
}
(5)在FreeRTOS_experiment.c文件中添加如下内容。
①包含头文件、宏定义、任务函数声明及任务句柄:
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"
//宏定义
#define START_TASK_STACK_SIZE 128 //start_task任务的堆栈大小
#define START_TASK_PRIO 1 //start_task任务的优先级
#define TASK1_STACK_SIZE 128 //task1任务的堆栈大小
#define TASK1_PRIO 2 //task1任务的优先级
#define TASK2_STACK_SIZE 128 //task2任务的堆栈大小
#define TASK2_PRIO 3 //task2任务的优先级
#define TASK3_STACK_SIZE 128 //task3任务的堆栈大小
#define TASK3_PRIO 4 //task3任务的优先级
//任务函数声明
void start_task(void);
void task1(void);
void task2(void);
void task3(void);
//任务句柄
TaskHandle_t start_task_handler; //start_task任务的句柄
TaskHandle_t task1_handler; //task1任务的句柄
TaskHandle_t task2_handler; //task2任务的句柄
TaskHandle_t task3_handler; //task3任务的句柄
②供主函数调用的实验函数:
void FreeRTOS_Test(void)
{
//创建任务start_task
xTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针
"start_task", //任务名字
START_TASK_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
START_TASK_PRIO, //任务优先级
(TaskHandle_t *) &start_task_handler//任务句柄,就是任务的任务控制块
);
//开启任务调度器(开启任务调度器后,任务开始按抢占式算法进行调度)
vTaskStartScheduler();
}
③start_task任务函数:
void start_task(void)
{
//进入临界区(临界区保护,就是保护那些不想被打断的程序段)
taskENTER_CRITICAL();
//创建任务task1
xTaskCreate((TaskFunction_t)task1, //指向任务函数的指针
"task1", //任务名字
TASK1_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK1_PRIO, //任务优先级
(TaskHandle_t *) &task1_handler //任务句柄,就是任务的任务控制块
);
//创建任务task2
xTaskCreate((TaskFunction_t)task2, //指向任务函数的指针
"task2", //任务名字
TASK2_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK2_PRIO, //任务优先级
(TaskHandle_t *) &task2_handler //任务句柄,就是任务的任务控制块
);
//创建任务task3
xTaskCreate((TaskFunction_t)task3, //指向任务函数的指针
"task3", //任务名字
TASK3_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK3_PRIO, //任务优先级
(TaskHandle_t *) &task3_handler //任务句柄,就是任务的任务控制块
);
//删除任务自身
vTaskDelete(NULL);
//退出临界区
taskEXIT_CRITICAL();
}
④task1任务函数:
void task1(void)
{
while(1) //task1永远不停歇(除非被其它任务删除)
{
LED1_Turn(); //LED1状态翻转
vTaskDelay(500); //延时(自我阻塞)500ms
}
}
⑤task2任务函数:
void task2(void)
{
while(1) //task2永远不停歇(除非被其它任务删除)
{
LED2_Turn(); //LED2状态翻转
vTaskDelay(500); //延时(自我阻塞)500ms
}
}
⑥task3任务函数:
void task3(void)
{
uint8_t key = 0;
while(1)
{
key = Key_GetNum(); //读取按键键值
if(key == 1)
{
if(task1_handler != NULL) //防止重复删除
{
vTaskDelete(task1_handler);
task1_handler = NULL;
}
if(task2_handler != NULL) //防止重复删除
{
vTaskDelete(task2_handler);
task2_handler = NULL;
}
}
if(key == 2)
{
taskENTER_CRITICAL(); //进入临界区
//创建任务task1
xTaskCreate((TaskFunction_t)task1, //指向任务函数的指针
"task1", //任务名字
TASK1_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK1_PRIO, //任务优先级
(TaskHandle_t *) &task1_handler //任务句柄,就是任务的任务控制块
);
//创建任务task2
xTaskCreate((TaskFunction_t)task2, //指向任务函数的指针
"task2", //任务名字
TASK2_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK2_PRIO, //任务优先级
(TaskHandle_t *) &task2_handler //任务句柄,就是任务的任务控制块
);
taskEXIT_CRITICAL(); //退出临界区
}
vTaskDelay(10); //延时(自我阻塞)10ms
}
}
(6)在FreeRTOS_experiment.h文件中添加如下代码。
#ifndef __FREERTOS_EXPERIMENT
#define __FREERTOS_EXPERIMENT
void FreeRTOS_Test(void);
#endif
(7)在main.c文件中添加如下代码。
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_experiment.h"
#include "Key.h"
#include "LED.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //Key初始化
LED_Init(); //LED初始化
FreeRTOS_Test();
while (1)
{
}
}
(8)程序完善好后点击“编译”,然后将程序下载到开发板上。
3、程序执行流程
(1)main函数全流程:
①初始化OLED模块、按键模块、LED模块。
②调用FreeRTOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。
②三个任务中task3的优先级最高,故优先执行task3,假设先不按下任何按键,task3未接收到任何键码,不做出其它行为,直接自我阻塞10ms,此时task2的优先级最高(除开已阻塞的task3),故执行task2,LED2的状态翻转后,task2自我阻塞500ms,此时未阻塞的就绪任务仅剩task1,故执行task1,LED1的状态翻转后,task1自我阻塞500ms。待task3的自我阻塞时间结束,task3重新抢占CPU,task3自我阻塞后,待task2的自我阻塞时间结束,task2重新抢占CPU,待task1的自我阻塞时间结束,task1重新抢占CPU,以此往复,实现双LED灯闪烁。
③在某次执行task3时,按下按键1,task1和task2将会被删除,LED1和LED2会停止在任务删除前的状态,不再闪烁。(按下按键1的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)
④在只有任务task3的情况下,按下按键2,task1和task2将会重新恢复,LED1和LED2将继续闪烁。(按下按键2的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)
三、任务创建和删除的静态方法实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计4个任务——start_task、task1、task2、task3:
[1]start_task:用于创建其它三个任务,然后删除自身。
[2]task1:实现LED1状态反转。
[3]task2:实现LED2状态反转。
[4]task3:按下按键1,删除task1、task2;按下按键2,创建task1、task2。
②预期实验现象:
[1]程序下载到板子上后,两个LED灯闪烁。
[2]按下按键1后,LED灯停止闪烁,接着按下按键2,LED灯恢复闪烁。
2、实验步骤
(1)将上一节“任务创建和删除的动态方法实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)在FreeRTOSConfig.h文件中将宏configSUPPORT_STATIC_ALLOCATION配置为1,将宏configMINMAL_STACK_SIZE配置为128,同时添加软件定时器相关配置,注意代码的添加位置(理论上保证能编译即可,不要被一些条件编译覆盖掉)。
#define configSUPPORT_STATIC_ALLOCATION 1 //支持静态申请内存
#define configMINMAL_STACK_SIZE 128 //空闲任务的栈空间大小
/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 //使能软件定时器
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
//定时器任务优先级
#define configTIMER_QUEUE_LENGTH 5 //定时器任务队列长度
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
//定时器任务栈深度
(3)在FreeRTOS_experiment.c中定义空闲任务和定时器任务的任务堆栈及TCB。
StaticTask_t idle_task_tcb; //定义空闲任务控制块
StackType_t idle_task_stack[configMINMAL_STACK_SIZE]; //定义空闲任务堆栈
StaticTask_t timer_task_tcb; //定义定时器任务控制块
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH]; //定义定时器任务堆栈
(4)在FreeRTOS_experiment.c中实现接口函数vApplicationGetIdleTaskMemory( )以及vApplicationGetTimerTaskMemory ( )。
//空闲任务内存分配
void vApplicationGetIdleTaskMemory
( StaticTask_t ** ppxIdleTaskTCBBuffer, //空闲任务控制块
StackType_t ** ppxIdleTaskStackBuffer, //空闲任务堆栈
uint32_t * pulIdleTaskStackSize) //空闲任务堆栈大小
{
* ppxIdleTaskTCBBuffer = &idle_task_tcb;
* ppxIdleTaskStackBuffer = idle_task_stack;
* pulIdleTaskStackSize = configMINMAL_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;
}
(5)在FreeRTOS_experiment.c中增加如下定义。
//任务堆栈
StackType_t start_task_stack[START_TASK_STACK_SIZE]; //start_task任务的堆栈
StackType_t task1_stack[TASK1_STACK_SIZE]; //task1任务的堆栈
StackType_t task2_stack[TASK2_STACK_SIZE]; //task2任务的堆栈
StackType_t task3_stack[TASK3_STACK_SIZE]; //task3任务的堆栈
//任务控制块
StaticTask_t start_task_tcb; //start_task任务的控制块
StaticTask_t task1_tcb; //task1任务的控制块
StaticTask_t task2_tcb; //task2任务的控制块
StaticTask_t task3_tcb; //task3任务的控制块
(6)将FreeRTOS_Test函数的实现做如下替换,主要是将动态任务创建函数替换为静态任务创建函数。
void FreeRTOS_Test(void)
{
//创建任务start_task
start_task_handler = //获取start_task的任务句柄
xTaskCreateStatic((TaskFunction_t)start_task, //指向任务函数的指针
"start_task", //任务名字
START_TASK_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
START_TASK_PRIO, //任务优先级
start_task_stack, //任务堆栈,一般为数组,由用户分配
&start_task_tcb); //任务控制块指针,由用户分配
//开启任务调度器
vTaskStartScheduler();
}
(7)将start_task函数和task3函数的实现做如下替换,主要是将动态任务创建函数替换为静态任务创建函数。
void start_task(void)
{
//进入临界区(临界区保护,就是保护那些不想被打断的程序段)
taskENTER_CRITICAL();
//创建任务task1
task1_handler = //获取task1的任务句柄
xTaskCreateStatic((TaskFunction_t)task1, //指向任务函数的指针
"task1", //任务名字
TASK1_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK1_PRIO, //任务优先级
task1_stack, //任务堆栈,一般为数组,由用户分配
&task1_tcb); //任务控制块指针,由用户分配
//创建任务task2
task2_handler = //获取task2的任务句柄
xTaskCreateStatic((TaskFunction_t)task2, //指向任务函数的指针
"task2", //任务名字
TASK2_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK2_PRIO, //任务优先级
task2_stack, //任务堆栈,一般为数组,由用户分配
&task2_tcb); //任务控制块指针,由用户分配
//创建任务task3
task3_handler = //获取task3的任务句柄
xTaskCreateStatic((TaskFunction_t)task3, //指向任务函数的指针
"task3", //任务名字
TASK3_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK3_PRIO, //任务优先级
task3_stack, //任务堆栈,一般为数组,由用户分配
&task3_tcb); //任务控制块指针,由用户分配
//删除任务自身
vTaskDelete(NULL);
//退出临界区
taskEXIT_CRITICAL();
}
void task3(void)
{
uint8_t key = 0;
while(1)
{
key = Key_GetNum(); //读取按键键值
if(key == 1)
{
if(task1_handler != NULL) //防止重复删除
{ vTaskDelete(task1_handler); task1_handler = NULL; }
if(task2_handler != NULL) //防止重复删除
{ vTaskDelete(task2_handler); task2_handler = NULL; }
}
if(key == 2)
{
//进入临界区
taskENTER_CRITICAL();
//创建任务task1
task1_handler = //获取task1的任务句柄
xTaskCreateStatic((TaskFunction_t)task1, //指向任务函数的指针
"task1", //任务名字
TASK1_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK1_PRIO, //任务优先级
task1_stack, //任务堆栈,一般为数组,由用户分配
&task1_tcb); //任务控制块指针,由用户分配
//创建任务task2
task2_handler = //获取task2的任务句柄
xTaskCreateStatic((TaskFunction_t)task2, //指向任务函数的指针
"task2", //任务名字
TASK2_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK2_PRIO, //任务优先级
task2_stack, //任务堆栈,一般为数组,由用户分配
&task2_tcb); //任务控制块指针,由用户分配
//退出临界区
taskEXIT_CRITICAL();
}
vTaskDelay(10); //延时(自我阻塞)10ms
}
}
(8)程序完善好后点击“编译”,然后将程序下载到开发板上。
3、程序执行流程
(1)main函数全流程:
①初始化OLED模块、按键模块、LED模块。
②调用FreeRTOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断(否则创建task1后,由于task1的优先级较高,start_task任务会被打断),接着start_task任务依次创建任务task1、task2、task3,然后删除自身,接着退出临界区,让出CPU资源。
②三个任务中task3的优先级最高,故优先执行task3,假设先不按下任何按键,task3未接收到任何键码,不做出其它行为,直接自我阻塞10ms,此时task2的优先级最高(除开已阻塞的task3),故执行task2,LED2的状态翻转后,task2自我阻塞500ms,此时未阻塞的就绪任务仅剩task1,故执行task1,LED1的状态翻转后,task1自我阻塞500ms。待task3的自我阻塞时间结束,task3重新抢占CPU,task3自我阻塞后,待task2的自我阻塞时间结束,task2重新抢占CPU,待task1的自我阻塞时间结束,task1重新抢占CPU,以此往复,实现双LED灯闪烁。
③在某次执行task3时,按下按键1,task1和task2将会被删除,LED1和LED2会停止在任务删除前的状态,不再闪烁。(按下按键1的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)
④在只有任务task3的情况下,按下按键2,task1和task2将会重新恢复,LED1和LED2将继续闪烁。(按下按键2的过程中因为按键消抖使用了vTaskDelay函数,task3会进行短暂的自我阻塞,图中未示出,不过由于task3的优先级最高,阻塞时间结束后task3可重新抢占CPU)
四、动态任务创建和删除详细过程(源码剖析)
1、xTaskCreate函数及其调用到的函数
(1)xTaskCreate函数本体——prvCreateTask函数:
static TCB_t * prvCreateTask( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t * pxNewTCB;
#if ( portSTACK_GROWTH > 0 ) //判断栈的生长方向,分别做处理
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
( void ) memset( ( void * ) pxNewTCB, 0x00, sizeof( TCB_t ) );
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) ) );
if( pxNewTCB->pxStack == NULL )
{
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /* STM32中的栈都是向下生长的(堆是向上生长的) */
{
StackType_t * pxStack; //定义任务堆栈
pxStack = pvPortMallocStack( ( ( ( size_t ) uxStackDepth ) * sizeof( StackType_t ) ) ); //FreeRTOS自动为任务申请栈空间
if( pxStack != NULL ) //判断栈空间是否申请成功
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); //申请任务控制块专属内存
if( pxNewTCB != NULL ) //判断任务控制块内存是否申请成功
{
( void ) memset( ( void * ) pxNewTCB, 0x00, sizeof( TCB_t ) );
pxNewTCB->pxStack = pxStack; //初始化任务控制块中的pxStack成员
}
else
{
vPortFreeStack(pxStack); //任务控制块申请失败,释放之前申请的栈空间
}
}
else
{
pxNewTCB = NULL; //栈空间申请失败,那么任务控制块也创建失败
}
}
#endif /* portSTACK_GROWTH */
if( pxNewTCB != NULL ) //判断任务控制块内存是否申请成功
{
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
//初始化任务控制块中的所有成员
prvInitialiseNewTask( pxTaskCode, pcName, uxStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
}
return pxNewTCB;
}
(2)prvInitialiseNewTask函数:
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
{
StackType_t * pxTopOfStack;
UBaseType_t x;
#if ( portUSING_MPU_WRAPPERS == 1 )
BaseType_t xRunPrivileged;
if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */
#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
//用一个值将堆栈填满,用于调试
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) uxStackDepth * sizeof( StackType_t ) );
}
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
#if ( portSTACK_GROWTH < 0 ) //判断栈的生长方向,分别做处理
{
pxTopOfStack = &( pxNewTCB->pxStack[ uxStackDepth - ( configSTACK_DEPTH_TYPE ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); //栈顶指针
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0U ) );
#if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
{
pxNewTCB->pxEndOfStack = pxTopOfStack;
}
#endif /* configRECORD_STACK_HIGH_ADDRESS */
}
#else /* STM32中的栈都是向下生长的(堆是向上生长的),编译上面的代码块 */
{
pxTopOfStack = pxNewTCB->pxStack;
pxTopOfStack = ( StackType_t * ) ( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) + portBYTE_ALIGNMENT_MASK ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0U ) );
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( uxStackDepth - ( configSTACK_DEPTH_TYPE ) 1 );
}
#endif /* portSTACK_GROWTH */
if( pcName != NULL )
{
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ]; //任务名初始化(任务名有最大长度限制)
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1U ] = '\0';
}
else
{
mtCOVERAGE_TEST_MARKER();
}
configASSERT( uxPriority < configMAX_PRIORITIES );
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority; //任务优先级初始化
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
}
#endif /* configUSE_MUTEXES */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); //初始化状态列表项
vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); //初始化事件列表项
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB ); //状态列表项的归属为该任务
//如果几个任务都需要同一个事件唤醒,那么事件发生时会按照任务优先级选择首先唤醒的任务
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); //事件列表项的归属为该任务
#if ( portUSING_MPU_WRAPPERS == 1 )
{
vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, uxStackDepth );
}
#else
{
( void ) xRegions;
}
#endif
#if ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 )
{
configINIT_TLS_BLOCK( pxNewTCB->xTLSBlock, pxTopOfStack );
}
#endif
#if ( portUSING_MPU_WRAPPERS == 1 )
{
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{
#if ( portSTACK_GROWTH < 0 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged, &( pxNewTCB->xMPUSettings ) );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged, &( pxNewTCB->xMPUSettings ) );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged, &( pxNewTCB->xMPUSettings ) );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#else /* portUSING_MPU_WRAPPERS */
{
//栈空间初始化
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{
#if ( portSTACK_GROWTH < 0 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#endif /* portUSING_MPU_WRAPPERS */
#if ( configNUMBER_OF_CORES > 1 )
{
pxNewTCB->xTaskRunState = taskTASK_NOT_RUNNING;
if( ( ( TaskFunction_t ) pxTaskCode == ( TaskFunction_t ) prvIdleTask ) || ( ( TaskFunction_t ) pxTaskCode == ( TaskFunction_t ) prvPassiveIdleTask ) )
{
pxNewTCB->uxTaskAttributes |= taskATTRIBUTE_IS_IDLE;
}
}
#endif /* #if ( configNUMBER_OF_CORES > 1 ) */
if( pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB; //初始化任务句柄
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(3)pxPortInitialiseStack函数:
①该函数用于初始化栈空间中的内容,如下图所示(图示中的“寄存器”并不是说申请的内存等于寄存器,而是指后续数据出栈时会传入相应的寄存器),pxTopOfStack指针从高地址的初始位置开始往下移动,同时往栈空间中写入相应的数据,然后将移动后的栈指针返回。
[1]寄存器xPSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令。
[2]寄存器PC被初始化为任务函数指针,假设是vTask_A(对应任务A),当某次任务切换后,任务A获得CPU控制权,任务函数vTask_A被出栈到PC寄存器,之后会执行任务A的代码。
[3]LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。
[4]子函数的调用通过寄存器R0~R3传递参数,创建任务时,传入的参数被保存到R0中,用来向任务传递参数。
②源码剖析:
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
/* Simulate the stack frame as it would be created by a context switch interrupt. */
pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR的bit24位置1 */
pxTopOfStack--;
*pxTopOfStack = ((StackType_t) pxCode) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = (StackType_t) prvTaskExitError; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = (StackType_t) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
(4)prvAddNewTaskToReadyList函数:
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks = ( UBaseType_t ) ( uxCurrentNumberOfTasks + 1U ); //任务数量+1
if( pxCurrentTCB == NULL ) //判断新创建的任务是否是第一个任务
{
pxCurrentTCB = pxNewTCB; //pxCurrentTCB指向新创建的任务
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) //再次确认新创建的任务是否是第一个任务
{
//初始化32个就绪列表(每个优先级各1个)、2个延时列表、1个等待就绪列表、1个等待删除列表、1个挂起列表
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
if( xSchedulerRunning == pdFALSE ) //判断调度器是否未启动
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB; //如果新创建的任务优先级较高,pxCurrentTCB要指向优先级更高的任务
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++; //任务标号自增
#if ( configUSE_TRACE_FACILITY == 1 )
{
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
prvAddTaskToReadyList( pxNewTCB ); //将新创建的任务添加进就绪列表中
portSETUP_TCB( pxNewTCB );
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE ) //判断任务调度器是否开启
{
//如果当前任务的优先级非最高,执行一次任务切换,调度优先级最高的任务执行
taskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxNewTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
2、vTaskDelete函数本体
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t * pxTCB;
BaseType_t xDeleteTCBInIdleTask = pdFALSE;
BaseType_t xTaskIsRunningOrYielding;
traceENTER_vTaskDelete( xTaskToDelete );
taskENTER_CRITICAL(); //进入临界区(关中断)
{
pxTCB = prvGetTCBFromHandle( xTaskToDelete ); //获取要删除的任务控制块
//将任务移出状态列表,并判断移出后列表是否为空,是则将列表对应的位清零
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
else
mtCOVERAGE_TEST_MARKER();
//判断该任务是否在等待某个事件,是则将其事件列表项移除
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
else
mtCOVERAGE_TEST_MARKER();
uxTaskNumber++;
xTaskIsRunningOrYielding = taskTASK_IS_RUNNING_OR_SCHEDULED_TO_YIELD( pxTCB );
//判断当前运行的任务是否是需要删除的任务(以及任务调度器是否已开启)
if((xSchedulerRunning !=pdFALSE) && (xTaskIsRunningOrYielding !=pdFALSE))
{
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) ); //将任务移动至等待删除列表
++uxDeletedTasksWaitingCleanUp; //等待删除列表任务数自增
traceTASK_DELETE( pxTCB );
xDeleteTCBInIdleTask = pdTRUE;
#if ( configNUMBER_OF_CORES == 1 )
portPRE_TASK_DELETE_HOOK( pxTCB, &( xYieldPendings[ 0 ] ) );
#else
portPRE_TASK_DELETE_HOOK( pxTCB, &( xYieldPendings[ pxTCB->xTaskRunState ] ) );
#endif
#if ( configNUMBER_OF_CORES > 1 )
{
if( taskTASK_IS_RUNNING( pxTCB ) == pdTRUE )
{
if(pxTCB->xTaskRunState == ( BaseType_t )portGET_CORE_ID() )
{
configASSERT( uxSchedulerSuspended == 0 );
taskYIELD_WITHIN_API();
}
else
{
prvYieldCore( pxTCB->xTaskRunState );
}
}
}
#endif /* #if ( configNUMBER_OF_CORES > 1 ) */
}
else
{
--uxCurrentNumberOfTasks; //现有的任务总数自减
traceTASK_DELETE( pxTCB );
prvResetNextTaskUnblockTime(); //更新下一个任务的阻塞超时时间
}
}
taskEXIT_CRITICAL(); //退出临界区(开中断)
if( xDeleteTCBInIdleTask != pdTRUE ) //如果要删除的任务不是当前正在运行的任务
{
prvDeleteTCB( pxTCB ); //释放任务的堆栈内存
}
#if ( configNUMBER_OF_CORES == 1 )
{
if( xSchedulerRunning != pdFALSE ) //判断任务调度器是否已开启
{
if( pxTCB == pxCurrentTCB ) //判断需要删除的任务是否正在运行
{
configASSERT( uxSchedulerSuspended == 0 );
taskYIELD_WITHIN_API(); //执行一次任务切换
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
traceRETURN_vTaskDelete();
}