3.5 任务挂起与恢复

任务挂起与恢复API

挂起任务和删除任务有什么不同?

挂起任务类似暂停,可恢复; 删除任务,无法恢复,类似“人死两清。

有很多API函数都带有FromISR后缀,带FromISR后缀是在中断函数中专用的API函数 。

任务挂起函数

void vTaskSuspend(TaskHandle_t xTaskToSuspend) ;

此函数用于挂起任务,使用时需将宏 INCLUDE_vTaskSuspend 配置为 1。

无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复。

注意:当传入的参数为NULL,则代表挂起任务自身(当前正在运行的任务)。

任务恢复函数

void vTaskResume(TaskHandle_t xTaskToResume);

使用该函数注意宏:INCLUDE_vTaskSuspend必须定义为 1 。

注意:任务无论被 vTaskSuspend() 挂起多少次,只需在任务中调用 vTakResume() 恢复一次,就可以继续运行。且被恢复的任务会进入就绪态!

我们的任务挂起,是不支持嵌套的,不管挂起多少次,其意义上和挂起一次是一样的,只需恢复一次即可。

任务恢复函数(中断)

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume);

函数返回值xTaskResumeFromISR:

任务恢复后需要进行任务切换,大家想一下,什么情况下需要任务切换?

就是你正在恢复的这个任务,优先级大于我们当前正在执行的任务。所以,优先级高需要任务切换,就返回TRUE;反之,就会返回FALSE。注意要手动切换,当然这也是API函数,不需要太麻烦。任务切换的API函数时portYIELD_FROM_ISR( x )

使用该函数注意宏:INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 必须定义为 1。

该函数专用于中断服务函数中,用于解挂被挂起任务。

不要混淆中断优先级和任务优先级哦,中断优先级是数值越小越高,任务优先级是数值越大越高。

注意:中断服务程序中要调用FreeRTOS的API函数则中断优先级不能高于FreeRTOS所管理的最高优先级,FreeRTOS所管理的中断优先级是5~15。

任务挂起与恢复

学习目的:学会 使用FreeRTOS中的任务挂起与恢复相关API函数:vTaskSuspend( )、 vTaskResume( )、xTaskResumeFromISR( )。

目标设计:将设计四个任务:start_task、task1、task2、task3。

其实,前三个任务我们前面的课程都已经完成,我们只需要写task3即可:

我们按照要求,先把宏给配置成1:

#define INCLUDE_vTaskSuspend     1        /* 挂起任务 */

然后写task3任务:

void task3(void *pvParameters)
{
	uint8_t key = 0;
	while(1)
	{
		key = Key_Scan();
		if(key == 1)//KEY0按下
		{
			vTaskSuspend(task1_handler);
		}
		else if(key == 2)//KEY1按下
		{
			vTaskResume(task1_handler);
		}
		vTaskDelay(10);//按键延时
	}
}

我们还需要完成剩下的一个功能,就是按下KEY2,在中断中恢复task1:

首先,按键是外部中断,所以我们需要初始化EXTI,下面是初始化EXTI的代码:

EXTI初始化代码

注意优先级的设置,FreeRTOS是看抢占式中断优先级,必须是5~15之间,即NVIC的分组方式要选择NVIC_PriorityGroup_4,代表都是抢占式,没有子优先级,而且抢占式要选择在5~15之间。

void Key2_EXTI_Init(void)
{
	//开启GPIOE时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
	//配置GPIOE
	GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//KEY2
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
  GPIO_Init(GPIOE, &GPIO_InitStructure);	
	
  //创建出一个EXTI结构体。
	EXTI_InitTypeDef EXTI_InitStructure;	
  //指定要使能或禁用的EXTI线路。此参数可以是 @ref EXTI_Lines 的任意组合。
 	EXTI_InitStructure.EXTI_Line=EXTI_Line2;
  //指定所选EXTI线路的新状态。此参数可以设置为 ENABLE 或 DISABLE 。
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
  // 指定EXTI线路的模式。此参数可以是 @ref EXTIMod_TypeDef 的一个值 。
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
  //指定EXTI线路的触发信号有效边沿。此参数可以是 @ref EXTITrigger_TypeDef 的一个值 。
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
  //根据 EXTI_InitStruct 中指定的参数初始化 EXTI 外设。
	EXTI_Init(&EXTI_InitStructure);

	//设置本程序的分组方式
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); 
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=5;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_Init(&NVIC_InitStructure);
}

然后我们需要把FreeRTOSConfig中的宏配置一下:

#define INCLUDE_xTaskResumeFromISR  1      /* 恢复在中断中挂起的任务 */

然后写我们的中断函数EXTI2_IRQHandler:

EXTI2_IRQHandler(void) 
{
  if (EXTI_GetITStatus(EXTI_Line2) != RESET) { // 检查指定的 EXTI 线路中断是否发生
        if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2) == 0) 
					{
            BaseType_t xYieldRequired;
            xYieldRequired = xTaskResumeFromISR(task1_handler);

            if (xYieldRequired == pdTRUE) {
                portYIELD_FROM_ISR(xYieldRequired);
           }
      }  
        EXTI_ClearITPendingBit(EXTI_Line2); // 清除 EXTI 线路挂起位
    }
}

这样,就完美实现了我们的目标功能,不过记得在主逻辑函数中初始化EXTI哦。