200字
RTOS任务挂起和恢复
2023-12-19
2026-03-23

理论

任务的挂起与恢复的API函数

API函数 描述
vTaskSuspend() 挂起任务
vTaskResume() 恢复被挂起的任务
xTaskResumeFromISR() 在中断中恢复被挂起的任务
  • 挂起:挂起任务类似暂停,可恢复; 删除任务,无法恢复,类似“人死两清”
  • 恢复:恢复被挂起的任务
  • "FromISR":带FromISR后缀是在中断函数中专用的API函数

任务挂起函数

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

形参 描述
xTaskToSuspend 待挂起任务的任务句柄

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

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

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

任务恢复函数(任务中恢复)

void vTaskResume(TaskHandle_t xTaskToResume)

形参 描述
xTaskToResume 待恢复任务的任务句柄

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

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

任务恢复函数(中断中恢复)

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

形参 描述
xTaskToResume 待恢复任务的任务句柄

函数:xTaskResumeFromISR返回值描述如下:

返回值 描述
pdTRUE 任务恢复后需要进行任务切换
pdFALSE 任务恢复后不需要进行任务切换

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

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

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

  1. 不能高于FreeRTOS所管理的最高优先级
  2. FreeRTOS建议优先级分组为全抢占优先级,不分配响应优先级(子优先级)
  3. 中断中使用FreeRTOS的delay会报错
  4. 不符合1、2会报错但不影响使用(task.c port.c 报错)

内部实现

任务挂起函数内部实现

  1. 获取所要挂起任务的控制块:通过传入的任务句柄,判断所需要挂起哪个任务,NULL代表挂起自身

  2. 移除所在列表:将要挂起的任务从相应的状态列表和事件列表中移除(就绪或阻塞列表)

  3. 插入挂起任务列表:将待挂起任务的任务状态列表向插入到挂起态任务列表末尾

  4. 判断任务调度器是否运行:在运行,更新下一次阻塞时间,防止被挂起任务为下一次阻塞超时任务

  5. 判断待挂起任务是否为当前任务:如果挂起的是任务自身,且调度器正在运行,需要进行一次任务切换

    调度器没有运行,判断挂起任务数是否等于任务总数,是:当前控制块赋值为NULL,否:寻找下一个最高优先级任务

任务恢复函数内部实现

  1. 恢复任务不能是正在运行任务
  2. 判断任务是否在挂起列表中:若是 就会将该任务在挂起列表中移除, 将该任务添加到就绪列表中
  3. 判断恢复任务优先级:判断恢复的任务优先级是否大于当前正在运行的 是的话执行任务切换

实践

任务挂起与恢复实验

image

freeRTOS_demo()

  • 初始化变量 作用是储存触发按键

  • 执行按键扫描

  • 判断触发按键

    • 挂起 vTaskSuspend(TASK1_Handler);

    • 恢复vTaskResume(TASK1_Handler);

      任务中恢复

void TASK3(void *pvParameters)
{
    uint8_t key=0;
    while(1)
    {
        key=key_scan(0);
        if(key==KEY0_PRES)
        {
            printf("挂起task1\n");
             vTaskSuspend(TASK1_Handler);
        }
        else if(key==KEY1_PRES)
        {
           printf("在任务中恢复task1\r\n");
           vTaskResume(TASK1_Handler); 
        }
        vTaskDelay(50);
    }
}

exti.c

  • 调用中断处理公用函数(函数中会判断触发引脚、清除中断标志位和执行回调函数HAL_GPIO_EXTI_Callback(GPIO_Pin); 入口参数附带触发引脚)
  • 再次清除中断标志
//WK_UP 外部中断服务程序
void WKUP_INT_IRQHandler(void)
{ 
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);        /* 调用中断处理公用函数 清除KEY_UP所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);        /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

exit回调函数 在HAL库中所有的外部中断服务函数都会调用此函数

  • 延时消抖(使用vtaskdelay 会导致任务切换报错?)

  • 判断外部中断触发引脚

  • 执行对应处理函数

    • 引用全局变量任务句柄

    • 定义 从中断中恢复任务 函数 的 返回值

    • 执行从中断中恢复任务并接受返回值

    • 判断返回值

      • pdTRUE 则执行 任务切换portYIELD_FROM_ISR(xYieldRequired);
extern TaskHandle_t   TASK1_Handler;
BaseType_t xYieldRequired;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(20);
    switch(GPIO_Pin)
    {
  
     case GPIO_PIN_0:
       if (WK_UP == 1)
            {
                xYieldRequired = xTaskResumeFromISR(TASK1_Handler);
                printf("在中断中恢复task1\r\n");
            }
            if(xYieldRequired == pdTRUE)
            {
                portYIELD_FROM_ISR(xYieldRequired);
            }
            break;
    }
}

外部中断初始化程序

  • 使能中断引脚的时钟
  • 配置引脚初始化参数
  • 引脚初始化
  • 设置优先级,以符合RTOS的要求(注意修改中断优先级分组,无需响应优先级)
  • 使能中断
void extix_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    WKUP_GPIO_CLK_ENABLE();                                  /* WKUP时钟使能 */
 
    gpio_init_struct.Pin = GPIO_PIN_0;
    gpio_init_struct.Mode = GPIO_MODE_IT_RISING;             /* 上升沿触发 */
    gpio_init_struct.Pull = GPIO_PULLDOWN;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);        /* WKUP配置为下降沿触发中断 */

    HAL_NVIC_SetPriority(WKUP_INT_IRQn, 5, 0);               /* 抢占2,子优先级2 */
    HAL_NVIC_EnableIRQ(WKUP_INT_IRQn);                       /* 使能中断线0 */
}
```

评论