上一章我们学习了 FreeRTOS 的任务基础知识,本章就正式学习如何使用 FreeRTOS 中有关任务的 API 函数。本来本章想讲解 FreeRTOS 的任务原理知识的,但是很多初学者还没使用过 FreeRTOS,甚至其他的 RTOS 系统都没有使用过,所以一上来就是苦涩的原理很可能会吓跑一大批初学者。 所以本章做了调整,先学习怎么用,先知其然,后面在知其所以然。使用过以后再学习原理、看源码就会轻松很多。

目录

6.1 任务创建和删除 API 函数

1、 函数 xTaxkCreate()

2、 函数 xTaskCreateStatic()

3、 函数 xTaskCreateRestricted()

4、 函数 vTaskDelete()

6.2 任务创建和删除实验(动态方法)

6.2.1 实验程序设计

6.2.2 程序运行结果分析

6.3 任务创建和删除实验(静态方法)

6.3.1 实验程序设计

6.3.2 程序运行结果分析

6.4 任务挂起和恢复 API 函数

6.5 任务挂起和恢复实验

6.5.1 实验程序设计


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);//设置系统中断优先级分组 4delay_init(); //延时函数初始化uart_init(115200); //初始化串口LED_Init(); //初始化 LEDLCD_Init(); //初始化 LCDPOINT_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);//设置系统中断优先级分组 4delay_init(); //延时函数初始化uart_init(115200); //初始化串口LED_Init(); //初始化 LEDLCD_Init(); //初始化 LCDPOINT_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 次删除任务 2printf("任务 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 的运行。 重点是,保
存任务运行次数的变量都没有发生数据丢失,如果用任务删除和重建的方法这些数据必然会丢
失掉的!

FreeRTOS学习笔记【六】——FreeRTOS 任务相关 API 函数相关推荐

  1. STM32F407霸天虎FreeRTOS学习笔记——移植FreeRTOS到开发板上

    STM32F407霸天虎FreeRTOS学习笔记--移植FreeRTOS到开发板上 FreeRTOS源码获取 移植第一步:创建文件夹 Keil main.c 实验效果 FreeRTOS源码获取 在移植 ...

  2. 【theano-windows】学习笔记六——theano中的循环函数scan

    前言 Scan是Theano中最基础的循环函数, 官方教程主要是通过大量的例子来说明用法. 不过在学习的时候我比较习惯先看看用途, 然后是参数说明, 最后再是研究实例. 国际惯例, 参考网址 官网关于 ...

  3. 【ESP32+freeRTOS学习笔记-开篇前言】

    目录 前言的前言 RTOS的选择 开发与实践环境 参考资料 笔记的形式 专题文章的链接(持续更新中......) 前言的前言 单片机的开发,也有两年多了,之前一直是做一些简单应用,因此以裸机开发的方式 ...

  4. FreeRTOS学习笔记

    FreeRTOS学习笔记 (这是我自己学习FreeRTOS整理的笔记,仅供参考) 第一部分:实现FreeRTOS内核 变量名: 定义变量时往往会把变量的类型当作前缀加在变量上 变量类型 前缀 char ...

  5. 1、野火freertos学习笔记

    野火freertos学习笔记 1.任务 1.1 栈 1.2 任务的切换 taskYIELD(); 1.3 临界段 2.空闲任务 3.任务优先级 4.任务延时的表现 5.时间片 5.1抢占式.协做式 6 ...

  6. FreeRTOS学习笔记20200526

    FreeRTOS学习笔记-Day1-20200526 nFlag = TRUE; 先立个Flag,是时候点亮嵌入式实时操作系统这个技能了.座右铭:坚持.认真.沉静.笃行. FreeRTOS优势 总结F ...

  7. freeRtos学习笔记 (9) 移植和CPU利用率统计

    freeRtos学习笔记 (9) 移植和CPU利用率统计 使用官方固件移植 首先准备一个能跑的裸机工程 注意,freertos需要使用systick定时器,而stm32HAL库默认使用systick作 ...

  8. freeRtos学习笔记 (6)软件定时器

    freeRtos学习笔记 freeRtos软件定时器 软件定时器需要注意事项 软件定时器的精度基于时钟节拍,例如系统时钟节拍为10ms, 软件定时器定时时间必须是10ms的整数倍,因此软件定时器一般用 ...

  9. FreeRTOS学习笔记——互斥型信号量

    来自:http://blog.csdn.net/xukai871105/article/details/43456985 0.前言 在嵌入式操作系统中互斥型信号量是任务间资源保护的重要手段.下面结合一 ...

最新文章

  1. 获得html元素自身的宽度
  2. 打开AD组策略编辑器提示“strings区段项目太长被截断”的解决
  3. 每日一皮:代码出现Bug的三种表情!
  4. 七天入门linux,一个Linux新手的七天
  5. JAVA之运算符优先级
  6. centos安装无线网卡驱动_CentOS下显卡驱动安装的相关思考
  7. GridView去掉边框
  8. mysql8参数方案_Mysql8.0及以上 only_full_group_by以及其他关于sql_mode原因报错详细解决方案...
  9. from_子句-内连接-左连接-右连接
  10. 给大忙人们看的 Java NIO 极简教程
  11. 【渝粤教育】电大中专跨境电子商务理论与实务 (30)作业 题库
  12. asp.net mvc 如何调用微信jssdk接口:分享到微信朋友(圈)| 分享到qq空间
  13. size ar objdump readelf binutils
  14. 复杂作业车间调度-单机调度问题研究
  15. STM32学习心得二十九:I2C通讯实验及相关代码解读
  16. 计算机管理界面截图,电脑网页页面太长怎么截图网页界面滚动截图的三种方法...
  17. python生成手写文字图片_使用PHP辅助 快速制作一套自己的手写字体实践
  18. 百度AI输入法发布全感官输入2.0版本,语音技术取得世界级突破
  19. HTML5工程师利用原生js开发百度搜索黑洞漩涡特效
  20. mysql 免安装 密码_mysql免安装版配置与修改密码

热门文章

  1. iphone3开发基础教程中文版高清PDF全集迅雷高速下载
  2. Oracle EBS AR关闭会计期间例外提示:APP-AR-294571: XXX-XXX 因一些未处理的会计事件或未传送的日记帐分录而无法关闭
  3. Ubuntu Linux调整CPU governor
  4. python爬取名言和商品信息
  5. 蓝眼睛和红眼睛的数学问题_在设计眼睛数学时
  6. elasticsearch (一): Centos7 RPM 方式安装最新单机版elasticsearch版本7.10.2 并配置中文(ik)、拼音(pinyin)插件
  7. 用友U8C U8Cloud U8 cloud u8c V2.1 2.2 V2.3 V2.4 V2.5 V2.6 V2.7 V3.0 3.1 3.2文件下载
  8. 由Instagram被收购引发的创业启示
  9. WIN电脑,固定到任务栏的快捷方式 实际存储在电脑的位置
  10. 镜头像差之四——像散