一、简介
二、相关的API函数
1.任务挂起函数
(1)函数 vTaskSuspend()
函数 vTaskSuspend()在 task.c 文件中有定义,具体的代码如下所示:
void vTaskSuspend(
TaskHandle_t xTaskToSuspend) /* 待挂起任务的任务句柄 */
{
TCB_t * pxTCB;
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 如果传入的任务句柄为空(NULL)
* 此函数会将待挂起的任务设置为调用该函数的任务本身
* 因此,如果要在任务中挂起任务本身,
* 那么可以调用函数 vTaskSuspend(),并传入任务句柄,
* 或传入 NULL
*/
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
/* 调试使用,不用理会 */
traceTASK_SUSPEND( pxTCB );
/* 将任务从任务所在任务状态列表(就绪态任务列表或阻塞态任务列表)中移除
* 如果移除后列表中的列表项数量为 0
* 那么就需要更新任务优先级记录
* 因为此时系统中可能已经没有和被挂起任务相同优先级的任务了
*/
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 更新任务优先级记录 */
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 判断被挂起的任务是否还有等待的事件 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
/* 将被挂起任务的事件列表项,从所在事件列表中移除 */
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 将待挂起任务的任务状态列表向插入到挂起态任务列表末尾 */
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
/* 此宏用于启用任务通知功能 */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
BaseType_t x;
/* 遍历待挂起任务的所有任务通知状态 */
for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
/* 如果有正在等待的任务通知,则取消等待
* 因为此时,任务已经被挂起
*/
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
}
}
}
#endif
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 判断任务调度器是否正在运行
* 如果任务调度器正在运行,
* 则需要更新下一个任务的阻塞超时时间,
* 以防被挂起的任务就是下一个阻塞超时的任务
*/
if( xSchedulerRunning != pdFALSE )
{
taskENTER_CRITICAL();
{
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果待挂起任务就是任务本身 */
if( pxTCB == pxCurrentTCB )
{
/* 如果任务调度器正在运行,则需要切换任务 */
if( xSchedulerRunning != pdFALSE )
{
/* 此时任务调度器不能处于挂起状态 */
configASSERT( uxSchedulerSuspended == 0 );
/* 进行任务切换 */
portYIELD_WITHIN_API();
}
else
{
/* 如果任务调度器没有运行,并且 pxCurrentTCB 又指向了待挂起的任务,
* 那么就需要将 pxCurrentTCB 指向其他任务
*/
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) ==
uxCurrentNumberOfTasks )
{
/* 没有就绪的任务,则将 pxCurrentTCB 指向空(NULL) */
pxCurrentTCB = NULL;
}
else
{
/* 更新 pxCurrentTCB 为优先级最高的就绪态任务 */
vTaskSwitchContext();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
使用函数 vTaskSuspend()挂起任务时,如果任务调度器没有运行,并且待挂起的任务又是调用函数 vTaskSuspend()的任务,那么 pxCurrentTCB 需要指向其他优先级最高的就绪态任务,更新 pxCurrentTCB 的操作,时通过调用函数 vTaskSwitchContext()实现的。
(2)函数 vTaskSwitchContext()
该函数用于更新 pxCurrentTCB 指向就绪态任务列表中优先级最高的任务,该函数在 task.c
文件中有定义,具体代码如下所示:
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* 任务调度器没有运行,不允许切换上下文,直接退出函数 */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
/* 用于调试,不用理会 */
traceTASK_SWITCHED_OUT();
/* 此宏用于启用任务运行时间统计功能 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter +=
( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif
/* 未定义,不用理会 */
taskCHECK_FOR_STACK_OVERFLOW();
/* 与 POSIX 相关配置,不用理会 */
#if ( configUSE_POSIX_ERRNO == 1 )
{
pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
}
#endif
/* 此函数用于将 pxCurrentTCB 更新为指向优先级最高的就绪态任务 */
taskSELECT_HIGHEST_PRIORITY_TASK();
/* 用于调试,不用理会 */
traceTASK_SWITCHED_IN();
/* 与 POSIX 相关配置,不用理会 */
#if ( configUSE_POSIX_ERRNO == 1 )
{
FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
}
#endif
/* 与 Newlib 相关配置,不用理会 */
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif
}
}
此 函 数 的 重 点 在 于 调 用 了 函 数 taskSELETE_HIGHEST_PRIORITY_TASK() 更 新
pxCurrentTCB 指向优先级最高的就绪态任务,函数 taskSELETE_HIGHEST_PRIORITY_TASK()实际上是一个宏定义,task.c 文件中有定义,具体的代码如下所示:
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* 查找就绪态任务列表中最高的任务优先级 */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
/* 此任务优先级不能时最低的任务优先级 */ \
configASSERT( \
listCURRENT_LIST_LENGTH(&( pxReadyTasksLists[ uxTopPriority ] ) ) > \
0 \
); \
/* 让 pxCurrentTCB 指向该任务优先级就绪态任务列表中的任务 */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, \
&( pxReadyTasksLists[ uxTopPriority ] ) ); \
}
2.任务恢复函数
(1) 函数 vTaskResume()
函数 vTaskResume()在 task.c 文件中有定义,具体的代码如下所示:
void vTaskResume(
TaskHandle_t xTaskToResume) /* 待恢复的任务句柄 */
{
TCB_t * const pxTCB = xTaskToResume;
/* 确保有指定的待恢复任务 */
configASSERT( xTaskToResume );
/* 待恢复的任务不能时当前正在运行的任务,并且也不能为空(NULL) */
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
{
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 判断任务是否被挂起
* 只有被挂起的任务才需要恢复
*/
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
{
/* 用于调试,不用理会 */
traceTASK_RESUME( pxTCB );
/* 将待恢复任务的任务状态列表项
* 从所在任务状态列表(挂起态任务列表)中移除
*/
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 将待恢复任务的任务状态列表项
* 添加到就绪态任务列表中
*/
prvAddTaskToReadyList( pxTCB );
/* 如果待恢复任务的优先级比当前正在运行的任务的任务优先级高
* 则需要进行任务切换
*/
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
3.任务恢复函数(中断中恢复)
三、相关实验
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
xTaskCreate((TaskFunction_t ) task3,
(char * ) "task3",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(TaskHandle_t * ) &task3_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{
uint32_t task1_num = 0;
while(1)
{
printf("task1_num:%d\r\n",++task1_num);
LED0_TOGGLE();
vTaskDelay(500);
}
}
/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{
uint32_t task2_num = 0;
while(1)
{
printf("task2_num:%d\r\n",++task2_num);
LED1_TOGGLE();
vTaskDelay(500);
}
}
/* 任务三,判断按键KEY0,按下KEY0删除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);
}
}
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/EXTI/exti.h"
#include "FreeRTOS.h"
#include "task.h"
extern TaskHandle_t task1_handler;
/**
* @brief KEY2 外部中断服务程序
* @param 无
* @retval 无
*/
void KEY2_INT_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY2_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY2所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
__HAL_GPIO_EXTI_CLEAR_IT(KEY2_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}
/**
* @brief 中断服务程序中需要做的事情
在HAL库中所有的外部中断服务函数都会调用此函数
* @param GPIO_Pin:中断引脚号
* @retval 无
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(20); /* 消抖 */
switch(GPIO_Pin)
{
BaseType_t xYieldRequired;
case KEY2_INT_GPIO_PIN:
if (KEY2 == 0)
{
xYieldRequired = xTaskResumeFromISR(task1_handler);
printf("在中断中恢复task1\r\n");
}
if(xYieldRequired == pdTRUE)
{
portYIELD_FROM_ISR(xYieldRequired);
}
break;
default : break;
}
}
/**
* @brief 外部中断初始化程序
* @param 无
* @retval 无
*/
void extix_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
key_init();
gpio_init_struct.Pin = KEY2_INT_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下降沿触发 */
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &gpio_init_struct); /* KEY2配置为下降沿触发中断 */
HAL_NVIC_SetPriority(KEY2_INT_IRQn, 5, 0); /* 抢占5,子优先级0 */
HAL_NVIC_EnableIRQ(KEY2_INT_IRQn); /* 使能中断线2 */
}