上一章我们学习了 FreeRTOS 的任务基础知识,本章就正式学习如何使用 FreeRTOS 中有关任务的 API 函数。本来本章想讲解 FreeRTOS 的任务原理知识的,但是很多初学者还没使用过 FreeRTOS,甚至其他的 RTOS 系统都没有使用过,所以一上来就是苦涩的原理很可能会吓跑一大批初学者。 所以本章做了调整,先学习怎么用,先知其然,后面在知其所以然。使用过以后再学习原理、看源码就会轻松很多。
目录
6.1 任务创建和删除 API 函数
FreeRTOS 最基本的功能就是任务管理,而任务管理最基本的操作就是创建和删除任务,FreeRTOS 的任务创建和删除 API 函数如表 6.1.1.1 所示:
表 6.1.1.1 任务创建和删除 API 函数
1、 函数 xTaxkCreate()
此函数用来创建一个任务,任务需要 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: 传递给任务函数的参数。
- uxPriotiry: 任务优先级,范围 0~ configMAX_PRIORITIES-1。
- pxCreatedTask: 任务句柄,任务创建成功以后会返回此任务的任务句柄, 这个句柄其实就是任务的任务堆栈。 此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。
返回值:
- pdPASS: 任务创建成功。
- errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足!
2、 函数 xTaskCreateStatic()
此函数和 xTaskCreate()的功能相同, 也是用来创建任务的,但是使用此函数创建的任务所需 的 RAM 需 要 用 用 户 来 提 供 。 如 果 要 使 用 此 函 数 的 话 需 要 将 宏configSUPPORT_STATIC_ALLOCATION 定义为 1。函数原型如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
参数:
- pxTaskCode: 任务函数。
- pcName: 任务名字,一般用于追踪和调试,任务名字长度不能超过。configMAX_TASK_NAME_LEN。
- usStackDepth: 任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。
- pvParameters: 传递给任务函数的参数。
- uxPriotiry: 任务优先级,范围 0~ configMAX_PRIORITIES-1。
- puxStackBuffer: 任务堆栈,一般为数组,数组类型要为 StackType_t 类型。
- pxTaskBuffer: 任务控制块。
返回值:
- NULL: 任务创建失败, puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生。
- 其他值: 任务创建成功,返回任务的任务句柄。
3、 函数 xTaskCreateRestricted()
此函数也是用来创建任务的, 只不过此函数要求所使用的 MCU 有 MPU(内存保护单元)用此函数创建的任务会受到 MPU 的保护。 其他的功能和函数 xTaxkCreate()一样。
BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition,
TaskHandle_t * pxCreatedTask )
参数:
- pxTaskDefinition: 指向一个结构体 TaskParameters_t,这个结构体描述了任务的任务函数、堆栈大小、优先级等。此结构体在文件 task.h 中有定义。
- pxCreatedTask: 任务句柄。
返回值:
- pdPASS: 任务创建成功。
- 其他值: 任务未创建成功, 很有可能是因为 FreeRTOS 的堆太小了。
4、 函数 vTaskDelete()
删除一个用函数 xTaskCreate()或者 xTaskCreateStatic()创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。 任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了 500 字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这 500 字节的内存释放掉,否则会导致内存泄露。此函数原型如下:
vTaskDelete( TaskHandle_t xTaskToDelete )
参数:
- xTaskToDelete: 要删除的任务的任务句柄。
返回值:
- 无
6.2 任务创建和删除实验(动态方法)
6.2.1 实验程序设计
1、实验目的
上一小节讲解了 FreeRTOS 的任务创建和删除的 API 函数,本小节就来学习如何使用这些API 函数,本小节学习 xTaskCreate()和 vTaskDelete()这两个函数的使用
2、实验设计
本实验设计三个任务: start_task、 task1_task 和 task2_task ,这三个任务的任务功能如下:
start_task:用来创建其他两个任务。
task1_task :当此任务运行 5 此以后就会调用函数 vTaskDelete()删除任务 task2_task,此任务也会控制 LED0 的闪烁,并且周期性的刷新 LCD 指定区域的背景颜色。
task2_task : 此任务普通的应用任务,此任务也会控制 LED1 的闪烁,并且周期性的刷新LCD 指定区域的背景颜色。
3、实验工程
FreeRTOS 实验 6-1 FreeRTOS 任务创建和删除实验(动态方法)。
4、实验程序与分析
● 任务设置
#define START_TASK_PRIO 1 //任务优先级 (1)
#define START_STK_SIZE 128 //任务堆栈大小 (2)
TaskHandle_t StartTask_Handler; //任务句柄 (3)
void start_task(void *pvParameters); //任务函数 (4)
#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 }
(1) 、 start_task 任务的任务优先级,此处用宏来表示,以后所有的任务优先级都用宏来表
示。创建任务设置优先级的时候就用这个宏,当然了也可以直接在创建任务的时候指定任务优
先级。
(2)、 start_task 任务的任务堆栈大小。
(3)、 start_task 任务的任务句柄。
(4)、 start_task 任务的任务函数声明。
● main()函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
LCD_Init(); //初始化 LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F103/F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 6-1");
LCD_ShowString(30,50,200,16,16,"Task Creat and Del");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2016/11/25");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数 (1)
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度 (2)
}
(1)、调用函数 xTaskCreate()创建 tart_task 任务,函数中的各个参数就是上面的任务设置中
定义的,其他任务的创建也用这种方法。
(2)、调用函数 vTaskStartScheduler()开启 FreeRTOS 的任务调度器, FreeRTOS 开始运行。
● 任务函数
//开始任务任务函数
void start_task(void *pvParameters) (1)
{
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); //删除开始任务 (2)
taskEXIT_CRITICAL(); //退出临界区
}
//task1 任务函数
void task1_task(void *pvParameters) (3)
{
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_num1 加到 255 的时候会清零!!
LED0=!LED0;
printf("任务 1 已经执行: %d 次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler);//任务 1 执行 5 次删除任务 2 (4)
printf("任务 1 删除了任务 2!\r\n");
}
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) (5)
{
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 注意 task1_num2 加到 255 的时候会清零!!
LED1=!LED1;
printf("任务 2 已经执行: %d 次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务执行次数
LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); //填充区域
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
(1)、 start_task 任务的任务函数,在此任务函数中我们创建了另外两个任务 task1_task 和task2_task。 start_task 任务的职责就是用来创建其他的任务或者信号量、 消息队列等的,当创建完成以后就可以删除掉 start_task 任务。
(2)、删除 start_task 任务,注意函数 vTaskDelete()的参数就是 start_task 任务的任务句柄StartTask_Handler。
(3)、 task1_task 任务函数(任务 1),任务比较简单,每隔 1 秒钟 task1_num 加一并且 LED0反转,串口输出任务运行的次数,其实就是 task1_num 的值。当 task1_task 运行 5 次以后就调用函数 vTaskDelete()删除任务 task2_task。
(4)、任务 task1_task 运行了 5 次,调用函数 vTaskDelete()删除任务 task2_task。
(5)、 task2_task 任务函数(任务 2),和 task1_task 差不多。
简单的总结分析一下此例程的流程,因为这是我们使用 FreeRTOS 写的第一个程序,很多习惯是我们后面要用到的。比如使用任务宏定义任务优先级,堆栈大小等,一般有关一个任务的东西我们的放到一起,比如任务堆栈、任务句柄、任务函数声明等,这样方便修改。这些东西可以放到一个.h 头文件里面去,只是例程里面任务数比较少,所以就直接放到 main.c 文件里面了,要是工程比较大的话最好做一个专用的头文件来管理。
在 main 函数中一开始肯定是初始化各种硬件外设,初始化完外设以后调用函数xTaskCreate()创建一个开始任务,注意创建开始任务是在调用函数 vTaskStartScheduler()开启任务调度器之前,这样当后面开启任务调度器以后就会直接运行开始任务了。其他任务的创建就放到开始任务的任务函数中,由于开始任务的职责就是创建其他应用任务和信号量、队列等这些内核对象的,所以它只需要执行一次,当这些东西创建完成以后就可以删除掉开始任务了。看过我们的 UCOS 教程的话就会发现这个过程和 UCOS 里面一样的。
6.2.2 程序运行结果分析
编译程序并下载到开发板中,查看任务 1 和任务 2 的运行情况,下载完成以后以后 LCD 显示如图 6.2.2.1 所示:
图 6.2.2.1 LCD 默认界面
图中左边的框为任务 1 的运行区域,右边的框为任务 2 的运行区域,可以看出任务 2 运行了 5 次就停止了,而任务 1 运行了 12 次了。打开串口调试助手,显示如图 6.2.2.2 所示:
图 6.2.2.2 窗口调试助手输出信息
从图 6.2.2.2 中可以看出,一开始任务 1 和任务 2 是同时运行的,由于任务 2 的优先级比任务 1 的优先级高,所以任务 2 先输出信息。当任务 1 运行了 5 次以后任务 1 就删除了任务 2,最后只剩下了任务 1 在运行了。
6.3 任务创建和删除实验(静态方法)
6.3.1 实验程序设计
1、实验目的
上一小节我们讲了使用函数 xTaskCreate()来创建任务,本节在上一小节的基础上做简单的修改,使用函数 xTaskCreateStatic()来创建任务,也就是静态方法,任务的堆栈、任务控制块就需要由用户来指定了。
2、实验设计
参考实验: FreeRTOS 实验 6-1 FreeRTOS 任务创建和删除实验(动态方法)。
3、实验工程
FreeRTOS 实验 6-2 FreeRTOS 任务创建和删除实验(动态方法)。
4、实验程序与分析
● 系统设置
使用静态方法创建任务的时候需要将宏 configSUPPORT_STATIC_ALLOCATION 设置为 1,在文件 FreeRTOSConfig.h 中设置,如下所示:
#define configSUPPORT_STATIC_ALLOCATION 1 //静态内存
宏 configSUPPORT_STATIC_ALLOCATION 定义为 1 以后编译一次,会提示我们有两个函
数未定义,如图 6.3.1.1 所示:
图 6.3.3.1 错误提示
这个在我们讲 FreeRTOS 的配置文件 FreeRTOSConfig.h 的时候就说过了,如果使用静态方法 的 话 需 要 用 户 实 现 两 个 函 数 vApplicationGetIdleTaskMemory() 和vApplicationGetTimerTaskMemory()。 通过这两个函数来给空闲任务和定时器服务任务的任务堆栈和任务控制块分配内存,这两个函数我们在 mainc.c 中定义,定义如下:
//空闲任务任务堆栈
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
//空闲任务控制块
static StaticTask_t IdleTaskTCB;
//定时器服务任务堆栈
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
//定时器服务任务控制块
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;
}
可以看出这两个函数很简单,用户定义静态的任务堆栈和任务控制块内存,然后将这些内存 传 递 给 函 数 参 数 。 最 后 创 建 空 闲 任 务 和 定 时 器 服 务 任 务 的 API 函 数 会 调 用vApplicationGetIdleTaskMemory()和 vApplicationGetTimerTaskMemory()来获取这些内存。
● 任务设置
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
StackType_t StartTaskStack[START_STK_SIZE]; //任务堆栈 (1)
StaticTask_t StartTaskTCB; //任务控制块 (2)
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 };
(1)、静态创建任务需要用户提供任务堆栈,这里定义一个数组作为任务堆栈,堆栈数组为
StackType_t 类型。
(2)、定义任务控制块,注意任务控制块类型要用 StaticTask_t,而不是 TCB_t 或 tskTCB!
这里已经要切记!
● main()函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
LCD_Init(); //初始化 LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F103/407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 6-2");
LCD_ShowString(30,50,200,16,16,"Task Creat and Del");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2016/11/25");
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t)start_task, //任务函数 (1)
(const char* )"start_task", //任务名称
(uint32_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(StackType_t* )StartTaskStack, //任务堆栈 (2)
(StaticTask_t* )&StartTaskTCB); //任务控制块(3)
vTaskStartScheduler(); //开启任务调度
}
(1)、调用函数 xTaskCreateStatic()创建任务。
(2)、将定义的任务堆栈数组传递给函数。
(3)、将定义的任务控制块传递给函数。
可以看出在用法上 xTaskCreateStatic()和 xTaskCreate()没有太大的区别,大多数的参数都相同。学习过 UCOS 的同学应该会对函数 xTaskCreateStatic()感到熟悉,因为 UCOS 中创建任务的函数和 xTaskCreateStatic()类似,也需要用户来指定任务堆栈和任务控制块的内存的,然后将其作为参数传递给任务创建函数。 不过我们后面所有的例程不管是创建任务、信号量还是队列都使用动态方法。
● 任务函数
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建 TASK1 任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t )task1_task, (1)
(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, (2)
(const char* )"task2_task",
(uint32_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(StackType_t* )Task2TaskStack,
(StackType_t* )Task2TaskStack,
(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//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_num1 加到 255 的时候会清零!!
LED0=!LED0;
printf("任务 1 已经执行: %d 次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler);//任务 1 执行 5 次删除任务 2
printf("任务 1 删除了任务 2!\r\n");
}
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;(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++; //任务 2 执行次数加 1 注意 task1_num2 加到 255 的时候会清零!!
LED1=!LED1;
printf("任务 2 已经执行: %d 次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务执行次数
LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); //填充区域
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
(1)、使用静态任务创建函数 xTaskCreateStatic()来创建任务 task1_task。
(2)、使用静态任务创建函数 xTaskCreateStatic()来创建任务 task2_task。
6.3.2 程序运行结果分析
参考 6.2.2 小节。
6.4 任务挂起和恢复 API 函数
有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了! FreeRTOS 给我们提供了解决这种问题的方法, 那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。 FreeRTOS 的任务挂起和恢复 API 函数如表 6.2.1.1 所示:
1、 函数 vTaskSuspend()
此函数用于将某个任务设置为挂起态, 进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数 vTaskResume()或 xTaskResumeFromISR()。,函数原型如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend)
参数:
xTaskToSuspend: 要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。如果使用函数 xTaskCreate()创建任务的话那么函数的参数
pxCreatedTask 就是此任务的任务句柄,如果使用函数 xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数 xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。
注意! 如果参数为 NULL 的话表示挂起任务自己
返回值:
无。
2、 函数 vTaskResume()
将一个任务从挂起态恢复到就绪态, 只有通过函数 vTaskSuspend()设置为挂起态的任务才可以使用 vTaskRexume()恢复!函数原型如下:
void vTaskResume( TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复的任务的任务句柄。
返回值:
无。
3、 函数 xTaskResumeFromISR()
此函数是 vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。函数原型如下:
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复的任务的任务句柄。
返回值:
pdTRUE: 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务), 这意味着在退出中断服务函数以后必须进行一次上下文切换。
pdFALSE: 恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。
6.5 任务挂起和恢复实验
6.5.1 实验程序设计
1、实验目的
学习使用 FreeRTOS 的任务挂起和恢复相关 API 函数,包括 vTaskSuspend()、 vTaskResume()
和 xTaskResumeFromISR()。
2、实验设计
本实验设计 4 个任务: start_task、 key_task、 task1_task 和 task2_task,这四个任务的任务功
能如下:
start_task:用来创建其他 3 个任务。
key_task: 按键服务任务,检测按键的按下结果,根据不同的按键结果执行不同的操作。
task1_task:应用任务 1。
task2_task: 应用任务 2。
实验需要四个按键, KEY0、 KEY1、 KEY2 和 KEY_UP,这四个按键的功能如下:
KEY0: 此按键为中断模式,在中断服务函数中恢复任务 2 的运行。
KEY1: 此按键为输入模式,用于恢复任务 1 的运行。
KEY2: 此按键为输入模式,用于挂起任务 2 的运行。
KEY_UP: 此按键为输入模式,用于挂起任务 1 的运行。
3、实验工程
FreeRTOS 实验 6-3 FreeRTOS 任务挂起和恢复实验。
4、实验程序与分析
● 任务设置
实验中任务优先级、堆栈大小和任务句柄等的设置如下:
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define KEY_TASK_PRIO 2 //任务优先级
#define KEY_STK_SIZE 128 //任务堆栈大小
TaskHandle_t KeyTask_Handler; //任务句柄
void key_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define TASK2_TASK_PRIO 4 //任务优先级
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler; //任务句柄
void task2_task(void *pvParameters); //任务函数
● main()函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
EXTIX_Init(); //初始化外部中断
LCD_Init(); //初始化 LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F103/407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 6-3");
LCD_ShowString(30,50,200,16,16,"Task Susp and Resum");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2016/11/25");
//创建开始任务
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(); //开启任务调度
}
在 main 函数中我们主要完成硬件的初始化,在硬件初始化完成以后创建了任务 start_task()
并且开启了 FreeRTOS 的任务调度。
//开始任务任务函数
void start_task(void *pvParameters) (1)
{
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)
{
case WKUP_PRES:
vTaskSuspend(Task1Task_Handler); //挂起任务 1 (2)
printf("挂起任务 1 的运行!\r\n");
break;
case KEY1_PRES:
vTaskResume(Task1Task_Handler); //恢复任务 1 (3)
printf("恢复任务 1 的运行!\r\n");
break;
case KEY2_PRES:
vTaskSuspend(Task2Task_Handler);//挂起任务 2 (4)
printf("挂起任务 2 的运行!\r\n");
break;
}
vTaskDelay(10); //延时 10ms
}
}
//task1 任务函数
void task1_task(void *pvParameters) (5)
{
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_num1 加到 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) (6)
{
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 注意 task1_num2 加到 255 的时候会清零!!
LED1=!LED1;
printf("任务 2 已经执行: %d 次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); //显示任务执行次数
LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); //填充区域
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
(1)、 start_task 任务,用于创建其他 3 个任务。
(2)、在 key_tssk 任务里面, KEY_UP 被按下,调用函数 vTaskSuspend()挂起任务 1。
(3)、 KEY1 被按下,调用函数 vTaskResume()恢复任务 1 的运行。
(4)、 KEY2 被按下,调用函数 vTaskSuspend()挂起任务 2。
(5)、任务 1 的任务函数,用于观察任务挂起和恢复的过程。
(6)、任务 2 的任务函数, 用于观察任务挂起和恢复的过程(中断方式)。
● 中断初始化及处理过程
//外部中断初始化程序
//初始化 PE4 为中断输入.
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
KEY_Init(); // 按键端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟
//GPIOE4 中断线以及中断初始化配置 下降沿触发
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //初始化外设 EXTI 寄存器
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06; //抢占优先级 6 (1)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //初始化外设 NVIC 寄存器
}
//任务句柄
extern TaskHandle_t Task2Task_Handler;
//外部中断 4 服务程序
void EXTI4_IRQHandler(void)
{
BaseType_t YieldRequired;
delay_xms(20); //消抖
if(KEY0==0)
{
YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务 2
printf("恢复任务 2 的运行!\r\n");
if(YieldRequired==pdTRUE)
{
/*如果函数 xTaskResumeFromISR()返回值为 pdTRUE,那么说明要恢复的这个
任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
退出中断的时候一定要进行上下文切换! */
portYIELD_FROM_ISR(YieldRequired);
}
}
EXTI_ClearITPendingBit(EXTI_Line4);//清除 LINE4 上的中断标志位
}
//任务句柄
extern TaskHandle_t Task2Task_Handler;
//外部中断 4 服务程序
void EXTI4_IRQHandler(void)
{
BaseType_t YieldRequired;
delay_xms(20); //消抖
if(KEY0==0)
{
YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务 2 (2)
printf("恢复任务 2 的运行!\r\n");
if(YieldRequired==pdTRUE)
{
/*如果函数 xTaskResumeFromISR()返回值为 pdTRUE,那么说明要恢复的这个
任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
退出中断的时候一定要进行上下文切换! */
portYIELD_FROM_ISR(YieldRequired); (3)
}
}
EXTI_ClearITPendingBit(EXTI_Line4);//清除 LINE4 上的中断标志位
}
(1)、设置中断优先级,前面在讲解 FreeRTOS 中断的时候就讲过,如果中断服务函数要使
用 FreeRTOS 的 API 函 数 的 话 那 么 中 断 优 先 级 一 定 要 低 于
configMAX_SYSCALL_INTERRUPT_PRIORITY!这里设置为 6。
(2)、 调用函数 xTaskResumeFromISR()来恢复任务 2 的运行。
(3)、根据函数 xTaskResumeFromISR()的返回值来确定是否需要进行上下文切换。当返回值
为 pdTRUE 的时候就需要调用函数 portYIELD_FROM_ISR()进行上下文切换,否则的话不需要。
6.5.2 程序运行结果分析
编译并下载程序到开发板中,通过按不同的按键来观察任务的挂起和恢复的过程,如图
6.5.2.1 所示
从图 6.5.2.1 可以看出,一开始任务 1 和任务 2 都正常运行,当挂起任务 1 或者任务 2 以后
任务 1 或者任务 2 就会停止运行, 直到下一次重新恢复任务 1 或者任务 2 的运行。 重点是,保
存任务运行次数的变量都没有发生数据丢失,如果用任务删除和重建的方法这些数据必然会丢
失掉的!