目录
1.3 函数 xTaskCreateRestricted()
1. 任务创建和删除 API 函数
FreeRTOS 最基本的功能就是任务管理,而任务管理最基本的操作就是创建和删除任务。
FreeRTOS 的任务创建和删除 API 函数如下所示:
xTaskCreate() 使用动态的方法创建一个任务。
xTaskCreateStatic() 使用静态的方法创建一个任务。
xTaskCreateRestricted() 创建一个使用 MPU 进行限制的任务,相关内存使用动态内存分配。
vTaskDelete() 删除一个任务。
1.1 函数 xTaskCreate()
此函数用来创建一个任务,任务需要 RAM 来保存与任务有关的状态信息(任务控制块),任务也需要一定的 RAM 来作为任务堆栈。
如果使用函数 xTaskCreate() 来创建任务的话那么这些所需的 RAM 就会自动的从 FreeRTOS 的堆中分配,因此必须提供内存管理文件,默认我们使用 heap_4.c 这个内存管理文件,而且宏 configSUPPORT_DYNAMIC_ALLOCATION 必须为 1。
如果使用函数 xTaskCreateStatic() 创建的话这些 RAM 就需要用户来提供了。
新创建的任务默认就是就绪态的,如果当前没有比它更高优先级的任务运行,那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, //任务函数
const char *const pcName, //任务名字
const uint16_t usStackDepth, //任务堆栈大小
void *const pvParameters, //传递给任务函数的参数
UBaseType_t uxPriority, //任务优先级
TaskHandle_t *const pxCreatedTask //任务句柄
)
参数:
pxTaskCode: 任务函数。
pcName: 任务名字,一般用于追踪和调试,任务名字长度不能超过 configMAX_TASK_NAME_LEN。
usStackDepth: 任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE。
pvParameters: 传递给任务函数的参数。
uxPriority: 任务优先级,范围是 0 ~ configMAX_PRIORITIES - 1。
pxCreatedTask: 任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。
返回值:
pdPASS: 任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足!
1.2 函数 xTaskCreateStatic()
此函数和 xTaskCreate() 的功能相同,也是用来创建任务的,但是使用此函数创建的任务所需的 RAM 需要由用户来提供。如果要使用此函数的话需要将宏 configSUPPORT_STATIC_ALLOCATION 定义为 1。
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, //任务函数
const char *const pcName, //任务名字
const uint16_t usStackDepth, //任务堆栈大小
void *const pvParameters, //传递给任务函数的参数
UBaseType_t uxPriority, //任务优先级
StackType_t *const puxStackBuffer, //任务堆栈
StaticTask_t *const pxTaskBuffer //任务控制块
)
参数:
pxTaskCode: 任务函数。
pcName: 任务名字,一般用于追踪和调试,任务名字长度不能超过 configMAX_TASK_NAME_LEN。
usStackDepth: 任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。
pvParameters: 传递给任务函数的参数。
uxPriority: 任务优先级,范围是 0 ~ configMAX_PRIORITIES - 1。
puxStackBuffer: 任务堆栈,一般为数组,数组类型要为 StackType_t 类型。
pxTaskBuffer: 任务控制块。
返回值:
NULL: 任务创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误发生。
其他值: 任务创建成功,返回任务的任务句柄。
1.3 函数 xTaskCreateRestricted()
此函数也是用来创建任务的,只不过此函数要求所使用的 MCU 有 MPU(内存保护单元),用此函数创建的任务会受到 MPU 的保护。其他的功能和函数 xTaskCreate() 一样。
BaseType_t xTaskCreateRestricted(const TaskParameters_t *const pxTaskDefinition,
TaskHandle_t* pxCreatedTask)
参数:
pxTaskDefinition: 指向一个结构体 TaskParameters_t,这个结构体描述了任务的任务函数、堆栈大小、优先级等。此结构体在文件 task.h 中有定义。
pxCreatedTask: 任务句柄。
返回值:
pdPASS: 任务创建成功。
其他值: 任务未创建成功,很有可能是因为 FreeRTOS 的堆太小了。
1.4 函数 vTaskDelete()
删除一个用函数 xTaskCreate() 或者 xTaskCreateStatic() 创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!
如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate() 创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete() 删除任务以后必须给空闲任务一定的运行时间。
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc() 分配了 500 字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree() 将这 500 字节的内存释放掉,否则会导致内存泄露。
vTaskDelete(TaskHandle_t xTaskToDelete)
参数:
xTaskToDelete: 要删除的任务的任务句柄。
返回值:
无
2. 任务创建和删除实验(动态方法)
经过上面的学习,我们已经了解了 FreeRTOS 的任务创建和删除的 API 函数,紧接着我们就来学习如何去使用这些 API 函数;
首先先学习 xTaskCreate() 和 vTaskDelete() 这两个函数的使用:
在本实验中设计了三个任务:start_task、task1_task 和 task2_task,这三个任务的任务功能如下:
start_task:用来创建其他两个任务。
task1_task:当此任务运行 5 次以后就会调用函数 vTaskDelete() 删除任务 task2_task,此任务也会控制 LED0 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色。
task2_task:此任务为普通的应用任务,此任务也会控制 LED1 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色。
2.1 实验程序与分析
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lcd.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
//LCD刷屏时使用的颜色
int LCD_Discolor[14]={WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY};
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //首先设置系统中断优先级分组4
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
POINT_COLOR=RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Experiment");
LCD_ShowString(30,50,200,16,16,"Task Create and Delete");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2023/06/06");
//创建开始任务
xTaskCreate((TaskFunction_t)start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
//调用函数 xTaskCreate() 创建 start_task 任务,函数中的各个参数就是上面的任务设置中定义的,其他任务的创建也用这种方法。
vTaskStartScheduler(); //开启任务调度
//调用函数 vTaskStartScheduler() 开启 FreeRTOS 的任务调度器,FreeRTOS 开始运行。
}
//开始任务函数,用于创建其他两个任务
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t)task1_task, //任务函数
(const char* )"task1_task", //任务名称
(uint16_t )TASK1_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK1_TASK_PRIO,//任务优先级
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t)task2_task, //任务函数
(const char* )"task2_task", //任务名称
(uint16_t )TASK2_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK2_TASK_PRIO,//任务优先级
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务,参数为开始任务的句柄
//开始任务的任务函数,在此任务函数中我们创建了另外两个任务 task1_task 和 task2_task。
//start_task 任务的职责就是用来创建其他的任务或者信号量、消息队列等。
//当创建完成以后就可以删除掉 start_task 任务。
taskEXIT_CRITICAL(); //离开临界区
}
//task1任务函数
//当此任务运行 5 次以后就会调用函数 vTaskDelete() 删除任务 task2_task,此任务也会控制 LED0 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色。
void task1_task(void *pvParameters)
{
u8 task1_num=0; //定义一个局部变量来保存该函数运行的次数
POINT_COLOR=BLACK;
LCD_DrawRectangle(5,110,115,314); //(x1,y1),(x2,y2):矩形的对角坐标
LCD_DrawLine(5,130,115,130); x1,y1:起点坐标 x2,y2:终点坐标
POINT_COLOR=BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
while(1) //任务内部的程序都是无限的循环,也可以使用for(;;)
{
task1_num++; //注意task1_num加到255的时候会清零!!
//while循环延时了1000ms,也就是1s,所以每隔 1 秒钟 task1_num 加 1 并且 LED0翻转。
LED0=!LED0;
printf("任务 1 已经执行:%d 次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler); //当此任务运行 5 次以后就会调用函数 vTaskDelete() 删除任务 task2_task
printf("任务 1 删除了任务 2 !\r\n");
}
LCD_Fill(6,131,114,313,LCD_Discolor[task1_num%14]); //填充区域
//前四个参数是填充的x1,y1;x2,y2坐标,最后一个参数是要填充的颜色
//task1_num数组大小为14,所以对14取余,task1_task函数运行,task1_num++;始终得到的是0~13的循环,也就是LCD_Discolor中颜色依次变化
LCD_ShowxNum(86,111,task1_num,3,16,0x80);
//x,y:起点坐标
//num:数值(0~999999999);
//len:长度(即要显示的位数)
//size:字体大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非叠加显示;1,叠加显示.
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//task2任务函数
//此任务为普通的应用任务,此任务也会控制 LED1 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色
void task2_task(void *pvParameters)
{
u8 task2_num=0;
POINT_COLOR=BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++; //任务2执行次数加1
LED1=!LED1;
printf("任务 2 已经执行:%d 次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务2执行次数
LCD_Fill(126,131,233,313,LCD_Discolor[13-task2_num%14]); //填充区域 13-task2_num%14 该颜色是LCD_Discolor数组逆序显示颜色
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
实验中串口的现象如上所示:一开始任务1和任务2是同时开始运行的,由于任务2的优先级比任务1的优先级高,所以任务2先输出信息。当任务1运行了5次以后任务1就删除了任务2,最后只剩下任务1在运行了;注意task1_num加到255的时候会清零!!
总结:
这是我学习 FreeRTOS 以来写的第一个程序,很多习惯后面的学习中都是要用到的。比如说使用任务宏定义任务优先级,堆栈大小等,一般有关一个任务的东西我们放到一起,比如说任务堆栈、任务句柄、任务函数声明等,这样方便修改。要是工程比较大的话最好用一个专用的头文件来管理。
在 main 函数中一开始肯定是初始化各种外设硬件,初始化完外设以后调用函数 xTaskCreate() 创建一个开始任务,注意创建开始任务是在调用函数 vTaskStartScheduler() 开启任务调度器之前,这样当后面开启任务调度器以后就会直接运行开始任务了。其他任务的创建就放在开始任务的任务函数中,由于开始任务的职责就是创建其他应用任务和信号量、队列等这些内核对象的,所以它只需要执行一次,当这些东西创建完成以后就可以删除掉开始任务了。
3. 任务创建和删除实验(静态方法)
本次实验我们使用函数 xTaskCreateStatic() 来创建任务,也就是静态的方法实现任务的创建和删除,任务的堆栈、任务控制块就需要由用户来指定了。
注意:
使用静态方法创建任务的时候需要将 宏configSUPPORT_STATIC_ALLOCATION 设置为 1 ,在文件 FreeRTOSConfig.h 中设置,如下所示:
#define configSUPPORT_STATIC_ALLOCATION 1 //静态内存
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) extern void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize ); #endif
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) /* 如果支持静态分配,那么应用程序必须提供以下回调函数-这使得应用程序可以选择性地提供将被计时器任务用作任务堆栈和TCB的内存。*/ extern void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize ); #endif
如果默认设置 #define configSUPPORT_STATIC_ALLOCATION 1 ,那么就需要在主函数中定义 vApplicationGetIdleTaskMemory 和 vApplicationGetTimerTaskMemory ,否则程序就会报错!!!
//空闲任务任务堆栈 static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; //#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 ) //空闲任务控制块 static StaticTask_t IdleTaskTCB; //定时器服务任务堆栈 static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH]; //#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) //定时器服务任务控制块 static StaticTask_t TimerTaskTCB; //获取空闲任务的任务堆栈和任务控制块内存,本次使用的是静态内存, //因此空闲任务的任务堆栈和任务控制块的内存就应该由用户来提供, //FreeRTOS提供了接口函数 vApplicationGetIdleTaskMemory() 实现此函数即可 //ppxIdleTaskTCBBuffer:任务控制块内存 //ppxIdleTaskStackBuffer:任务堆栈内存 //pulIdleTaskStackSize:任务堆栈大小 void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { *ppxIdleTaskTCBBuffer=&IdleTaskTCB; *ppxIdleTaskStackBuffer=IdleTaskStack; //数组名代表首元素地址 *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE; } //获取定时器服务任务的任务堆栈和任务控制块内存 //ppxTimerTaskTCBBuffer:任务控制块内存 //ppxTimerTaskStackBuffer:任务堆栈内存 //pulTimerTaskStackSize:任务堆栈大小 void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) { *ppxTimerTaskTCBBuffer=&TimerTaskTCB; *ppxTimerTaskStackBuffer=TimerTaskStack; *pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH; }
如果使用静态方法的话需要用户实现两个函数 :vApplicationGetIdleTaskMemory() 和 vApplicationGetTimerTaskMemory()。通过这两个函数来给空闲任务和定时器服务任务的任务堆栈和任务控制块分配内存。
首先,用户自己定义静态的任务堆栈和任务控制块内存,然后将这些内存传递给函数参数。最后创建空闲任务和定时器服务任务的 API 函数会调用 vApplicationGetIdleTaskMemory() 和 vApplicationGetTimerTaskMemory() 来获取这些内存。
//任务优先级 #define START_TASK_PRIO 1 //任务堆栈大小 #define START_STK_SIZE 128 //任务堆栈 StackType_t StartTaskStack[START_STK_SIZE]; //任务控制块 StaticTask_t StartTaskTCB; //任务句柄 TaskHandle_t StartTask_Handler; //任务函数 void start_task(void *pvParameters); //任务优先级 #define TASK1_TASK_PRIO 2 //任务堆栈大小 #define TASK1_STK_SIZE 128 //任务堆栈 StackType_t Task1TaskStack[TASK1_STK_SIZE]; //任务控制块 StaticTask_t Task1TaskTCB; //任务句柄 TaskHandle_t Task1Task_Handler; //任务函数 void task1_task(void *pvParameters); //任务优先级 #define TASK2_TASK_PRIO 3 //任务堆栈大小 #define TASK2_STK_SIZE 128 //任务堆栈 StackType_t Task2TaskStack[TASK2_STK_SIZE]; //任务控制块 StaticTask_t Task2TaskTCB; //任务句柄 TaskHandle_t Task2Task_Handler; //任务函数 void task2_task(void *pvParameters);
1. 静态创建任务需要用户提供任务堆栈,这里定义一个数组作为任务堆栈,堆栈数组为 StackType_t 类型。
2. 定义任务控制块,注意任务控制块的类型要用 StaticTask_t,切记!!!
3.1 完整实验代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lcd.h"
//空闲任务任务堆栈
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; //#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 )
//空闲任务控制块
static StaticTask_t IdleTaskTCB;
//定时器服务任务堆栈
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH]; //#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
//定时器服务任务控制块
static StaticTask_t TimerTaskTCB;
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务堆栈
StackType_t StartTaskStack[START_STK_SIZE];
//任务控制块
StaticTask_t StartTaskTCB;
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务控制块
StaticTask_t Task1TaskTCB;
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
//任务控制块
StaticTask_t Task2TaskTCB;
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
//LCD刷屏时使用的颜色
int LCD_Discolor[14]={WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY};
//获取空闲任务的任务堆栈和任务控制块内存,本次使用的是静态内存,
//因此空闲任务的任务堆栈和任务控制块的内存就应该由用户来提供,
//FreeRTOS提供了接口函数 vApplicationGetIdleTaskMemory() 实现此函数即可
//ppxIdleTaskTCBBuffer:任务控制块内存
//ppxIdleTaskStackBuffer:任务堆栈内存
//pulIdleTaskStackSize:任务堆栈大小
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
//获取定时器服务任务的任务堆栈和任务控制块内存
//ppxTimerTaskTCBBuffer:任务控制块内存
//ppxTimerTaskStackBuffer:任务堆栈内存
//pulTimerTaskStackSize:任务堆栈大小
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
*ppxTimerTaskStackBuffer=TimerTaskStack;
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //首先设置系统中断优先级分组4
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
POINT_COLOR=RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Experiment");
LCD_ShowString(30,50,200,16,16,"Task Static Create and Delete");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2023/06/06");
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t)start_task, //任务函数
(const char* )"start_task", //任务名称
(uint32_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(StackType_t* )StartTaskStack, //任务堆栈
(StaticTask_t* )&StartTaskTCB); //任务控制块
//调用函数 xTaskCreate() 创建 start_task 任务,函数中的各个参数就是上面的任务设置中定义的,其他任务的创建也用这种方法。
vTaskStartScheduler(); //开启任务调度
//调用函数 vTaskStartScheduler() 开启 FreeRTOS 的任务调度器,FreeRTOS 开始运行。
}
//开始任务函数,用于创建其他两个任务
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t)task1_task, //任务函数
(const char* )"task1_task", //任务名称
(uint32_t )TASK1_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK1_TASK_PRIO,//任务优先级
(StackType_t* )Task1TaskStack, //任务堆栈
(StaticTask_t* )&Task1TaskTCB); //任务控制块
//创建TASK2任务
Task2Task_Handler=xTaskCreateStatic((TaskFunction_t)task2_task, //任务函数
(const char* )"task2_task", //任务名称
(uint32_t )TASK2_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK2_TASK_PRIO,//任务优先级
(StackType_t* )Task2TaskStack, //任务堆栈
(StaticTask_t* )&Task2TaskTCB); //任务控制块
vTaskDelete(StartTask_Handler); //删除开始任务,参数为开始任务的句柄
//开始任务的任务函数,在此任务函数中我们创建了另外两个任务 task1_task 和 task2_task。
//start_task 任务的职责就是用来创建其他的任务或者信号量、消息队列等。
//当创建完成以后就可以删除掉 start_task 任务。
taskEXIT_CRITICAL(); //离开临界区
}
//task1任务函数
//当此任务运行 5 次以后就会调用函数 vTaskDelete() 删除任务 task2_task,此任务也会控制 LED0 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色。
void task1_task(void *pvParameters)
{
u8 task1_num=0; //定义一个局部变量来保存该函数运行的次数
POINT_COLOR=BLACK;
LCD_DrawRectangle(5,110,115,314); //(x1,y1),(x2,y2):矩形的对角坐标
LCD_DrawLine(5,130,115,130); x1,y1:起点坐标 x2,y2:终点坐标
POINT_COLOR=BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
while(1) //任务内部的程序都是无限的循环,也可以使用for(;;)
{
task1_num++; //注意task1_num加到255的时候会清零!!
//while循环延时了1000ms,也就是1s,所以每隔 1 秒钟 task1_num 加 1 并且 LED0翻转。
LED0=!LED0;
printf("任务 1 已经执行:%d 次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler); //当此任务运行 5 次以后就会调用函数 vTaskDelete() 删除任务 task2_task
printf("任务 1 删除了任务 2 !\r\n");
}
LCD_Fill(6,131,114,313,LCD_Discolor[task1_num%14]); //填充区域
//前四个参数是填充的x1,y1;x2,y2坐标,最后一个参数是要填充的颜色
//task1_num数组大小为14,所以对14取余,task1_task函数运行,task1_num++;始终得到的是0~13的循环,也就是LCD_Discolor中颜色依次变化
LCD_ShowxNum(86,111,task1_num,3,16,0x80);
//x,y:起点坐标
//num:数值(0~999999999);
//len:长度(即要显示的位数)
//size:字体大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非叠加显示;1,叠加显示.
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//task2任务函数
//此任务为普通的应用任务,此任务也会控制 LED1 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色
void task2_task(void *pvParameters)
{
u8 task2_num=0;
POINT_COLOR=BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++; //任务2执行次数加1
LED1=!LED1;
printf("任务 2 已经执行:%d 次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务2执行次数
LCD_Fill(126,131,233,313,LCD_Discolor[13-task2_num%14]); //填充区域 13-task2_num%14 该颜色是LCD_Discolor数组逆序显示颜色
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
静态创建和删除任务和动态创建和删除任务的串口输出结果一致,本质区别在于需要我们手动提供任务控制块和任务栈区的内存。
4. 任务挂起和恢复 API 函数
有时候我们需要暂停某任务的运行,过一段时间以后再重新运行。这个时候要是使用了删除和重建的方法那么任务中变量保存的值肯定丢失了!FreeRTOS 给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。
FreeRTOS 的任务挂起和恢复 API 函数如下所示:
vTaskSuspend() 挂起一个任务
vTaskResume() 恢复一个任务的运行
xTaskResumeFromISR() 中断服务函数中恢复一个函数的运行
4.1 函数 vTaskSuspend()
此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数 vTaskResume() 或 xTaskResumeFromISR()。
函数原型如下:
void vTaskSuspend(TaskHandle_t xTaskToSuspend)
参数:
xTaskToSuspend: 要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。
如果使用函数 xTaskCreate() 创建任务的话那么函数的参数 pxCreatedTask 就是此任务的任务句柄,
如果使用函数 xTaskCreateStatic() 创建任务的话那么函数的返回值就是此任务的任务句柄。
也可以通过函数 xTaskGetHandler() 来根据任务名字来获取某个任务的任务句柄。
注意:
如果参数为 NULL 的话表示挂起任务自己!!!
返回值:
无
4.2 函数 vTaskResume()
将一个任务从挂起态恢复到就绪态,任务必须处于挂起态才能调用该函数,只有通过函数 vTaskSuspend() 设置为挂起态的任务才可以使用 vTaskResume() 恢复!
函数原型如下:
void vTaskResume(TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复的任务的任务句柄
返回值:
无
4.3 函数 xTaskResumeFromISR()
此函数是 vTaskResume() 的中断版本,用于在中断服务函数中恢复一个任务。
函数原型如下:
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复任务的任务句柄
返回值:
pdTRUE: 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。
pdFLASE: 恢复运行的任务的任务优先级低于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后不需要进行一次上下文切换。
5. 任务挂起与恢复实验
本次实验学习使用 FreeRTOS 的任务挂起和恢复相关的 API 函数,包括 vTaskSuspend()、vTaskResume() 和 xTaskResumeFromISR()。
本实验设计 4 个任务:start_task、key_task、task1_task 和 task2_task,这四个任务的任务功能如下:
start_task:用来创建其他三个任务
key_task:按键服务任务,检测按键按下的结果,根据不同的按键结果执行不同的操作
task1_task:应用任务 1
task2_task:应用任务 2
本实验需要四个按键,KEY0、KEY1、KEY2 和 KEY_UP,这四个按键的功能如下:
KEY0:此按键为中断模式,在中断服务函数中恢复任务 2 的运行
KEY1:此按键为输入模式,用于恢复任务 1 的运行
KEY2:此按键为输入模式,用于挂起任务 2 的运行
KEY_UP:此按键为输入模式,用于挂起任务 1 的运行
5.1 实验程序
5.1.1 main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lcd.h"
#include "key.h"
#include "EXTI.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 168
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define KEY_TASK_PRIO 2
//任务堆栈大小
#define KEY_STK_SIZE 168
//任务句柄
TaskHandle_t KeyTask_Handler;
//任务函数
void key_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 3
//任务堆栈大小
#define TASK1_STK_SIZE 168
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 4
//任务堆栈大小
#define TASK2_STK_SIZE 168
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
//LCD刷屏时使用的颜色
int LCD_discolor[14]={WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //系统中断优先级分组 4
delay_init(168);
uart_init(115200);
KEY_Init();
LED_Init();
LCD_Init();
EXTIX_Init();
POINT_COLOR=RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Experiment");
LCD_ShowString(30,50,200,16,16,"Task Suspend and Resume");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2023/06/07");
//创建开始任务
xTaskCreate((TaskFunction_t)start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务函数
void start_task(void* pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建KEY函数
xTaskCreate((TaskFunction_t)key_task, //任务函数
(const char* )"key_task", //任务名称
(uint16_t )KEY_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )KEY_TASK_PRIO, //任务优先级
(TaskHandle_t* )&KeyTask_Handler); //任务句柄
//创建TASK1函数
xTaskCreate((TaskFunction_t)task1_task, //任务函数
(const char* )"task1_task", //任务名称
(uint16_t )TASK1_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK1_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Task1Task_Handler); //任务句柄
//创建TASK2函数
xTaskCreate((TaskFunction_t)task2_task, //任务函数
(const char* )"task2_task", //任务名称
(uint16_t )TASK2_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK2_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Task2Task_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退入临界区
}
//key任务函数
void key_task(void *pvParameters)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
switch(key)
//KEY0 中断恢复任务2
//KEY1 恢复任务1
//KEY2 挂起任务2
//KEY_UP 挂起任务1
{
case 4: //Key.h中定义:key=4 KEY_UP按键按下
vTaskSuspend(Task1Task_Handler); //挂起任务1
printf("挂起任务1的运行!\r\n");
break;
case 2: //Key.h中定义:key=2 KEY1按键按下
vTaskResume(Task1Task_Handler); //恢复任务1
printf("恢复任务1的运行!\r\n");
break;
case 3: //Key.h中定义:key=3 KEY2按键按下
vTaskSuspend(Task2Task_Handler); //挂起任务2
printf("挂起任务2的运行!\r\n");
break;
}
vTaskDelay(10); //延时10ms
}
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
POINT_COLOR=BLACK;
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR=BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num加到255的时候会清零!!
LED0=!LED0;
printf("任务1已经执行:%d次\r\n",task1_num);
LCD_Fill(6,131,114,313,LCD_discolor[task1_num%14]); //要填充区域的颜色,设置颜色数组依次循环
LCD_ShowxNum(86,111,task1_num,3,16,0x80); //显示任务执行次数
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++; //任务2执1行次数加1
LED1=!LED1;
printf("任务2已经执行:%d次\r\n",task2_num);
LCD_Fill(126,131,233,313,LCD_discolor[13-task2_num%14]); //填充区域
LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务执行次数
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
5.1.2 EXTI.c
#include "stm32f4xx.h"
#include "EXTI.h"
#include "delay.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
//初始化外部中断
//初始化PE4引脚,PE4引脚对应于KEY0按键,KEY0按键低电平触发按键,所以设置该引脚为下降沿触发
//PE4引脚对应外部中断线4
void EXTIX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
KEY_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE); //使用外部中断需要使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4); //PE4 连接到中断线 4
EXTI_InitStructure.EXTI_Line=EXTI_Line4; //外部中断线4
EXTI_InitStructure.EXTI_LineCmd=ENABLE; //使能
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; //中断事件
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发,因为KEY0按键按下就表示引脚输入下降沿
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0x06; //抢占优先级 6
//如果中断服务函数要使用 FreeRTOS 的 API 函数的话,那么中断优先级一定要低于
//configMAX_SYSCALL_INTERRUPT_PRIORITY! 这里设置为6
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00;
NVIC_Init(&NVIC_InitStructure);
}
//任务句柄
extern TaskHandle_t Task2Task_Handler;
//外部中断线4服务程序
void EXTI4_IRQHandler(void)
{
//该程序的意思就是设置一个变量
BaseType_t YieldRequired; //typedef long BaseType_t;
delay_xms(20); //消抖
if(KEY0==0) //KEY0按键按下
{
YieldRequired=xTaskResumeFromISR(Task2Task_Handler); //中断服务函数中恢复任务2的运行
printf("恢复任务2的运行!\r\n");
if(YieldRequired==pdTRUE) //设置变量接收中断服务函数中恢复任务2的返回值
//如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
//任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
//退出中断的时候一定要进行上下文切换!
{
portYIELD_FROM_ISR(YieldRequired); //调用该函数进行上下文切换
}
}
EXTI_ClearITPendingBit(EXTI_Line4); //清除外部中断线4上的中断标志位
}
5.1.3 EXTI.h
#ifndef _EXTI__H_
#define _EXTI__H_
#include "sys.h"
void EXTIX_Init(void);
#endif
5.1.4 实验结果
从上图中可以看到,一开始任务1和任务2都正常运行,当挂起任务1或者任务2以后,任务1或者任务2就会停止运行,直到下一次重新恢复任务1或者任务2的运行。
重点是:保存任务运行次数的变量都没有发生数据丢失,如果用任务删除和重建的方法这些数据必然会丢失掉的!