FreeRTOS 的任务挂起和恢复

本文详细介绍了FreeRTOS中的任务挂起(vTaskSuspend)、恢复(vTaskResume和xTaskResumeFromISR)API,特别是在中断服务函数中的使用,以及任务优先级与中断优先级的关系。通过实例演示了如何在实际项目中运用这些功能,确保中断安全并正确管理任务调度。
摘要由CSDN通过智能技术生成

1. 任务挂起和恢复的 API 函数

API函数描述
vTaskSuspend()挂起任务
vTaskResume()恢复被挂起的任务
xTaskResumeFromISR()在中断中恢复被挂起的任务
  • 挂起:挂起任务类似暂停,可恢复; 删除任务,堆栈都给释放掉了,无法恢复,类似“人死两清”
  • 恢复:恢复被挂起的任务
  • “FromISR”:带FromISR后缀是在中断函数中专用的 API 函数。(不管你是 FreeRTOS 的哪一个 API 函数,只要你在中断服务函数中要调用,那么你肯定是有一个 FromISR 后缀的。)

挂起任务和删除任务本质上的区别:能否恢复。

1.1 任务挂起

函数原型:

void vTaskSuspend(TaskHandle_t xTaskToSuspend) 
形参描述
xTaskToSuspend待挂起任务的任务句柄
  • 此函数用于挂起任务,使用时需将宏 INCLUDE_vTaskSuspend 配置为 1。
  • 无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复 。

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

1.2 任务恢复

1.2.1 任务中恢复(在任务函数中调用)

函数原型:

void vTaskResume(TaskHandle_t xTaskToResume) 
形参描述
xTaskToResume待恢复任务的任务句柄
  • 使用该函数注意宏:INCLUDE_vTaskSuspend必须定义为 1

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

1.2.2 中断中恢复(在中断服务函数中调用)

函数原型:

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)  
形参描述
xTaskToResume待恢复任务的任务句柄
返回值描述
pdTRUE任务恢复后需要进行任务切换
pdFALSE任务恢复后不需要进行任务切换

什么情况下需要进行任务切换?

  • 就是你要恢复的这个任务它的优先级大于我们当前正在执行的这个任务,这时候由于抢占式调度,优先级高的任务要抢占当前正在执行的任务,那这时候就得进行一个任务切换了。
  • 如果你的优先级比较低呢?那么此时它就返回 pdFALSE,那这时候你就不需要进行任务切换。

所以我们如果在中断服务函数中调用这个函数,那么你就要判断它的一个返回值了,来确定是否需要手动执行一次任务切换(portYIELD_FROM_ISR)。

  • 使用该函数注意宏:INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 必须定义为 1
  • 该函数专用于中断服务函数中,用于解挂被挂起任务

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

  • 我们的代码 FreeRTOS 所管理的中断优先级是 5~15,也就是说中断服务函数的中断优先级必须在这个范围内,那如果你在 0~4,比 5~15 优先级要高,它就不属于 FreeRTOS 所管理的一个范围了。

注意:任务优先级跟中断优先级的区别:任务优先级是数值越大优先级越高;中断优先级是数值越小优先级越高。

2. 实战编程

实验目的:学会使用FreeRTOS中的任务挂起与恢复相关API函数:vTaskSuspend( )、vTaskResume( )、xTaskResumeFromISR( )
实验设计:将设计四个任务:start_task、task1、task2、task3

四个任务的功能如下:

  • start_task:用来创建其他的三个任务
  • task1:实现LED0每500ms闪烁一次
  • task2:实现LED1每500ms闪烁一次
  • task3:判断按键按下逻辑,KEY0按下,挂起task1;按下KEY1,在任务中恢复task1;按下KEY2,在中断中恢复task1(外部中断线实现)

2.1 任务中恢复

2.1.1 宏 INCLUDE_vTaskSuspend 置 1

2.1.2 编写任务函数

task1、task2、task3 任务函数
/* 任务一,实现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);
    }
}

/* 任务三,判断按键按下逻辑 */
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);		/* 挂起 task1 任务 */
        }else if(key == KEY1_PRES)
        {
            printf("在任务中恢复task1\r\n");
            vTaskResume(task1_handler);			/* 恢复 task1 任务 */
        }
        vTaskDelay(10);
    }
}

task3 的延时时间是比较快的,那它的运行次数肯定很多很多,如果打印的话你很难看到 task1 和 task2 的运行次数的打印信息。

2.2 中断中恢复

2.2.1 宏 INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 置 1

2.2.2 编写中断服务函数

exti.c
#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;			/* 外部声明 task1 句柄 */
/**
 * @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 */

}

因为我们要在中断服务函数里面调用 FreeRTOS 的 API 函数,而 FreeRTOS 要求我们:

  1. 把优先级分组的所有位都用做抢占式,而不能用作我们的一个子优先级;

FreeRTOS 官网:

  • 建议将所有优先级位指定为抢占优先级位,不留下任何优先级位作为子优先级位,其他的任何配置都会使 configMAX_SYSCALL_INTERRUPT_PRIORITY 宏设置与分配给各个外设中断的优先级之间的直接关系复杂化。

也就是说为什么都要设置为抢占式优先级,就是为了 FreeRTOS 方便管理,比如它非常复杂,官网明确表态了。

那怎么设置它全部用作抢占式优先级?通过调用:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

来确保将所有优先级位分配为抢占优先级位。

stm32f4xx_hal.c
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  1. 中断的优先级不能高于 FreeRTOS 所管理的优先级。

FreeRTOS 官网:

  • 以 “FromISR” 结尾的 FreeRTOS 函数是中断安全的,但前提是调用这些函数的中断的逻辑优先级不高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 定义的优先级(FreeRTOS 可管理的最高中断优先级)。

所以我们一定要确保中断的逻辑优先级等于或小于 configMAX_SYSCALL_INTERRUPT_PRIORITY。

HAL_NVIC_SetPriority(KEY2_INT_IRQn, 5, 0);               /* 抢占5,子优先级0 */

如果不满足以上两点要求,就不能在中断服务函数中调用 FreeRTOS 的 API 函数。

exti.h
#ifndef __EXTI_H
#define __EXTI_H

#include "./SYSTEM/sys/sys.h"

/******************************************************************************************/
/* 引脚 和 中断编号 & 中断服务函数 定义 */ 

#define KEY0_INT_GPIO_PORT              GPIOH
#define KEY0_INT_GPIO_PIN               GPIO_PIN_3
#define KEY0_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0)   /* PH口时钟使能 */
#define KEY0_INT_IRQn                   EXTI3_IRQn
#define KEY0_INT_IRQHandler             EXTI3_IRQHandler

#define KEY1_INT_GPIO_PORT              GPIOH
#define KEY1_INT_GPIO_PIN               GPIO_PIN_2
#define KEY1_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0)   /* PH口时钟使能 */
#define KEY1_INT_IRQn                   EXTI2_IRQn
#define KEY1_INT_IRQHandler             EXTI2_IRQHandler

#define KEY2_INT_GPIO_PORT              GPIOC
#define KEY2_INT_GPIO_PIN               GPIO_PIN_13
#define KEY2_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */
#define KEY2_INT_IRQn                   EXTI15_10_IRQn
#define KEY2_INT_IRQHandler             EXTI15_10_IRQHandler

#define WKUP_INT_GPIO_PORT              GPIOA
#define WKUP_INT_GPIO_PIN               GPIO_PIN_0
#define WKUP_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
#define WKUP_INT_IRQn                   EXTI0_IRQn
#define WKUP_INT_IRQHandler             EXTI0_IRQHandler

/******************************************************************************************/

void extix_init(void);  /* 外部中断初始化 */

#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/SDRAM/sdram.h"
#include "./MALLOC/malloc.h"
#include "freertos_demo.h"
#include "./BSP/EXTI/exti.h"

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);        /* 设置时钟,180Mhz */
    delay_init(180);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
    sdram_init();                               /* SRAM初始化 */
    lcd_init();                                 /* 初始化LCD */
    extix_init();								/* 外部中断初始化 */
    
    my_mem_init(SRAMIN);                        /* 初始化内部内存池 */
    my_mem_init(SRAMEX);                        /* 初始化外部内存池 */
    my_mem_init(SRAMCCM);                       /* 初始化CCM内存池 */
    
    freertos_demo();
}

3. 课堂总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值