当我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!FreeRTOS 给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。FreeRTOS 的任务挂起和恢复 API 函数如下表所示:
此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数 vTaskResume()或 xTaskResumeFromISR()。函数原型如
下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend)
参数:
xTaskToSuspend: 要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。如果使用函数 xTaskCreate()创建任务的话那么函数的参数
pxCreatedTask 就是此任务的任务句柄,如果使用函数 xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数 xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。
注意!如果参数为 NULL 的话表示挂起任务自己。
返回值:无。
将一个任务从挂起态恢复到就绪态,只有通过函数 vTaskSuspend()设置为挂起态的任务才可以使用 vTaskRexume()恢复!函数原型如下:
void vTaskResume( TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复的任务的任务句柄。
返回值:无。
此函数是 vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。函数原型如下:
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume)
参数:
xTaskToResume: 要恢复的任务的任务句柄。
返回值:
pdTRUE: 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。
pdFALSE: 恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。
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、实验程序与分析
● 任务设置
实验中任务优先级、堆栈大小和任务句柄等的设置如下:
#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()进行上下文切换,否则的话不需要。
4. 实验结果分析
从上图可以看出,一开始任务 1 和任务 2 都正常运行,当挂起任务 1 或者任务 2 以后任务 1 或者任务 2 就会停止运行,直到下一次重新恢复任务 1 或者任务 2 的运行。重点是,保存任务运行次数的变量都没有发生数据丢失,如果用任务删除和重建的方法这些数据必然会丢失掉的!