目录
1.任务的挂起与恢复的API函数
1.1任务挂起函数介绍
1.2任务恢复函数介绍(任务中恢复)
1.3任务恢复函数介绍(中断中恢复)
2.任务挂起与恢复实验
3.任务挂起和恢复API函数“内部实现”解析
3.1vTaskSuspend()
3.2(任务中调用)任务恢复函数vTaskResume()
3.3(中断中调用)任务恢复函数xTaskResumeFromISR()
4.总结
1.任务的挂起与恢复的API函数
API函数 | 描述 |
---|---|
vTaskSuspend() | 挂起任务 |
vTaskResume() | 恢复被挂起的任务 |
xTaskResumeFromISR() | 在中断中恢复被挂起的任务 |
挂起:挂起任务类似于暂停,可恢复;删除任务,无法恢复。
恢复:恢复被挂起的任务。
“FromISR”:带FromISR后缀是在中断函数中专用的API函数。
1.1任务挂起函数介绍
void vTaskSuspend(TaskHandle_t xTaskToSuspend)
参数介绍:
xTaskToSuspend:待挂起任务的任务句柄(想挂起哪个任务,就传入哪个任务的任务句柄)
此函数用于挂起任务,使用时需要将FreeRTOSCofig.h中的宏INCLUDE_vTaskSuspend配置为1。
无论优先级多高,被挂起的任务都将不再执行,直到任务被恢复。
注意:当传入的参数为NULL,则代表挂起任务自身(当前正在运行的任务),和删除任务类似。
1.2任务恢复函数介绍(任务中恢复)
任务中恢复挂起函数如下所示(在任务函数中被调用):
void vTaskResume(TaskHandle_t xTaskToResume)
参数介绍:
xTaskToResume:待恢复任务的任务句柄。
使用此函数需要将FreeRTOSConfig.h中的宏INCLUDE_vTaskSuspend配置为1,和挂起任务需要配置的宏是同一个。
注意:任务无论被vTaskSuspend()挂起多少次,只需在任务中调用vTaskResume()恢复一次,就可以继续运行,且被恢复的任务会进入就绪态。
1.3任务恢复函数介绍(中断中恢复)
中断中恢复挂起函数如下所示(在中断函数中被调用):
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
参数介绍:
xTaskToResume:待恢复任务的任务句柄。
函数xTaskResumeFromISR会有两个返回值:
返回值 描述 pdTrue 任务恢复后需要进行任务切换(恢复的任务优先级,大于当前正在执行的任务优先级) pdFALSE 任务恢复后不需要进行任务切换
使用该函数需要将FreeRTOSConfig.h中的宏INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR配置为1。
该函数专用于中断服务函数中,用于解挂(恢复)被挂起的任务。
注意:中断服务函数中要调用FreeRTOS中的API函数,则中断优先级不能高于FreeRTOS所管理的最高优先级。(FreeRTOS所管理的中断优先级是5~15,中断优先级必须在此范围内,如果中断优先级在0~4(FreeRTOS任务优先级是越大越高,而中断优先级是越小越高。),0~4数值比5~15小,优先级高,不属于FreeRTOS所管理的范围)。
2.任务挂起与恢复实验
1、实验目的:学会 使用FreeRTOS中的任务挂起与恢复相关API函数:vTaskSuspend( )、 vTaskResume( )、xTaskResumeFromISR( )
2、实验设计:将设计四个任务:start_task、task1、task2、task3
四个任务的功能如下:
start_task:用来创建其他的三个任务
task1:实现LED0每500ms闪烁一次
task2:实现LED1每500ms闪烁一次
task3:判断按键按下逻辑,KEY0按下,挂起task1,按下KEY1在任务中恢复task1
按下KEY2,在中断中恢复task1(外部中断线实现,并不在task3中)
本次实验基于本系列【03】FreeRTOS的任务创建(静态和动态)和删除中的动态创建任务工程实现。
在使用任务挂起相关函数之前,将FreeRTOSCofig.h中的宏配置为1(由于老版本的正点原子中无INCLUDE_xTaskResumeFromISR,所以复制新的FreeRTOSConfig.h程序,在文章【3】中有,并且以后文章都将用新的)。
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_xTaskResumeFromISR 1 /* 恢复在中断中挂起的任务 */
start_task、task1、task2并不用做修改。修改完的task3如下所示:
/* 任务三,判断按键KEY0,按下KEY0挂起task1,按下KEY1恢复task1*/
void task3( void * pvParameters )
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == KEY0_PRES)
{
printf("task1被挂起\r\n");
vTaskSuspend(task1_handler);
}
else if (key == KEY1_PRES)
{
printf("task1被恢复\r\n");
vTaskResume(task1_handler);
}
vTaskDelay(10);
}
}
为了方便展示效果,在task1和task2中打印运行次数,freertos_demo.h修改的部分代码如下:
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
uint32_t task1_num=0;
while(1)
{
printf("task1_num:%d\r\n",++task1_num);
LED0=~LED0;
vTaskDelay(500);
}
}
/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
uint32_t task2_num=0;
while(1)
{
printf("task2_num:%d\r\n",++task2_num);
LED1=~LED1;
vTaskDelay(500);
}
}
实验现象:
按下KEY0,task1被挂起后不再被执行,按下KEY1,task1被恢复,执行次数从被挂起之前继续累计。
在HAL库版本程序,“实验4 外部中断实验”中,复制外部中断程序到本项目中(添加.c文件到项目中,并添加.h文件路径),
此处用的程序是2015年版本程序,要删除KEY0中断服务函数,删除KEY0、KEY1、WEAKUP内容,并添加FreeRTOS相关函数,增加extern调用task2任务句柄,根据FreeRTOS官网API函数示例修改中断回调函数,修改完exti.c如下所示:
#include "exti.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"
extern TaskHandle_t task2_handler;
//外部中断初始化
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟
GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_IT_FALLING; //下降沿触发
GPIO_Initure.Pull=GPIO_PULLUP;
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
//中断线13-PC13
HAL_NVIC_SetPriority(EXTI15_10_IRQn,2,3); //抢占优先级为2,子优先级为3
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); //使能中断线13
}
//中断服务函数
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);//调用中断处理公用函数
}
//中断服务程序中需要做的事情
//在HAL库中所有的外部中断服务函数都会调用此函数
//GPIO_Pin:中断引脚号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(100); //消抖
switch(GPIO_Pin)
{
BaseType_t xYieldRequired;
case GPIO_PIN_13:
if(KEY2==0)
{
xYieldRequired=xTaskResumeFromISR(task2_handler);
printf("在中断中恢复task2\r\n");
}
if(xYieldRequired==pdTRUE)
{
portYIELD_FROM_ISR(xYieldRequired);
}
break;
}
}
运行程序,在中断中恢复task2,串口会报:Error: ..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c, 807 类型错误。 报错在port.c的807行,789行提供的网址有详细的报错信息。优先级分组必须全部用作抢占式,不能用作子优先级,所以修改HAL_Init()部分内容如下所示:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);//中断优先级分组4
修改完此处后,下载程序,发现串口会输出:Error: ..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c, 791 类型错误,从789行的网址中提示,需要将KEY2的中断优先级调整至FreeRTOS管理的优先级范围。修改EXTI_Init()部分内容如下:
HAL_NVIC_SetPriority(EXTI15_10_IRQn,6,0); //抢占优先级为6,子优先级为0
再次运行程序,串口将不再报错。
3.任务挂起和恢复API函数“内部实现”解析
此部分解析需根据task.c中对应的API函数查看。
3.1vTaskSuspend()
1、需将宏INCLUDE_vTaskSuspend 配置为 1
#if ( INCLUDE_vTaskSuspend == 1 )
将宏 INCLUDE_vTaskSuspend配置为1,才能使用vTaskSuspend()函数。
2、根据任务句柄获取任务控制块,如果任务句柄为NULL,表示挂起任务自身
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t * pxTCB;
taskENTER_CRITICAL();
{
/* If null is passed in here then it is the running task that is
* being suspended. */
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
传入 vTaskSuspend()的参数为要挂起的任务句柄,任务句柄通过调用prvGetTCBFromHandle()函数返回任务控制块。prvGetTCBFromHandle()函数的定义如下,如果是句柄等于NULL则返回当前正在运行的任务控制块,如果不等于NULL,传入什么则返回什么。
#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? pxCurrentTCB : ( pxHandle ) )
3、将要挂起的任务从相应的状态列表和事件列表中移除
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
移除状态列表项, 不管任务处于何种状态,都要移除。如果就绪列表项没有任务了,则将每一位的优先级标志位进行清0。
taskRESET_READY_PRIORITY()函数定义:
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
/* Is the task waiting on an event also? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
移除事件列表项。挂起任务时 ,不管是否在等待某种事件发生,还是什么,在挂起的同时将需要等待的事件移除,恢复时无需等待事件。
4、将待挂起任务的任务状态列表向插入到挂起态任务列表末尾
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
并清除消息通知, 如果要挂起的任务,正在等待消息通知,改为NOT不需要等待。
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
BaseType_t x;
for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
/* The task was blocked to wait for a notification, but is
* now suspended, so no notification was received. */
pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
}
}
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
}
taskEXIT_CRITICAL();
5、判断任务调度器是否运行,在运行,更新下一次阻塞时间,防止被挂起任务为下一次阻塞超时任务
if( xSchedulerRunning != pdFALSE )
{
/* Reset the next expected unblock time in case it referred to the
* task that is now in the Suspended state. */
taskENTER_CRITICAL();
{
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
6、如果挂起的是任务自身,且调度器正在运行,需要强制进行一次任务切换;调度器没有运行,判断挂起任务数是否等于任务总数,是:代表所有任务均被挂起,当前控制块赋值为NULL,否:通过函数vTaskSwitchContext()寻找下一个最高优先级任务。
if( pxTCB == pxCurrentTCB )
{
if( xSchedulerRunning != pdFALSE )
{
/* The current task has just been suspended. */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
/* The scheduler is not running, but the task that was pointed
* to by pxCurrentTCB has just been suspended and pxCurrentTCB
* must be adjusted to point to a different task. */
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
{
/* No other tasks are ready, so set pxCurrentTCB back to
* NULL so when the next task is created pxCurrentTCB will
* be set to point to it no matter what its relative priority
* is. */
pxCurrentTCB = NULL;
}
else
{
vTaskSwitchContext();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskSuspend */
3.2(任务中调用)任务恢复函数vTaskResume()
1、需将宏INCLUDE_vTaskSuspend 配置为 1
#if ( INCLUDE_vTaskSuspend == 1 )
函数入口参数只有待恢复的任务句柄,将任务句柄赋值给任务控制块,
void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = xTaskToResume;
/* It does not make sense to resume the calling task. */
configASSERT( xTaskToResume );
/* The parameter cannot be NULL as it is impossible to resume the
* currently executing task. */
2、恢复任务不能是正在运行任务
任务控制块不能是当前正在执行任务的任务控制块,只能恢复被挂起的任务。
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
{
taskENTER_CRITICAL();
{
3、判断任务是否在挂起列表中,是:就会将该任务在挂起列表中移除, 将该任务添加到就绪列表中
移除状态列表项(不管在什么列表,都会被移除)。
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
{
traceTASK_RESUME( pxTCB );
/* The ready list can be accessed even if the scheduler is
* suspended because this is inside a critical section. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
4、判断恢复的任务优先级是否大于当前正在运行的 是的话执行任务切换(任务切换是操作的PendSV中断)
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
/* This yield may not cause the task just resumed to run,
* but will leave the lists in the correct state for the
* next yield. */
taskYIELD_IF_USING_PREEMPTION();
}
3.3(中断中调用)任务恢复函数xTaskResumeFromISR()
1、 入口参数是待恢复的任务句柄,并存在返回值。 定义三个变量,第一个是返回值,初始化为pdFALSE(0);将待恢复的任务句柄赋值给任务控制块;定义保存当前中断状态的变量。
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
{
BaseType_t xYieldRequired = pdFALSE;
TCB_t * const pxTCB = xTaskToResume;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToResume );
2、函数portASSERT_IF_INTERRUPT_PRIORITY_INVALID(),用于检测:调用FreeRTOS的API函数的中断优先级是否在管理范围内以及全部设置为抢占式优先级。
使用uxSavedInterruptStatus保存当前中断的状态。
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
3、关闭freertos可管理中断,防止被其他的中断打断,并返回关闭前basepri寄存器的值
4、判断任务是否在挂起列表中,有:则检测调度器是否挂起,如果未挂起则判断恢复的任务优先级是否比当前正在运行的优先级大,大则xYieldRequired标记为pdTRUE,表示需要进行一次任务切换;将被恢复的任务从挂起列表删除;插入到就绪列表。如果任务调度器被挂起,将恢复的任务插入到就绪列表prvAddTaskToReadyList中,直到调度器被恢复时再进行任务的处理。
{
traceTASK_RESUME_FROM_ISR( pxTCB );
/* Check the ready lists can be accessed. */
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* Ready lists can be accessed so move the task from the
* suspended list to the ready list directly. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xYieldRequired = pdTRUE;
/* Mark that a yield is pending in case the user is not
* using the return value to initiate a context switch
* from the ISR using portYIELD_FROM_ISR. */
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
5、将前面保存的basepri的值,恢复回来
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
6、返回xYieldRequired的值 用于决定是否需要进行任务切换
return xYieldRequired;
4.总结