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 要求我们:
- 把优先级分组的所有位都用做抢占式,而不能用作我们的一个子优先级;
FreeRTOS 官网:
- 建议将所有优先级位指定为抢占优先级位,不留下任何优先级位作为子优先级位,其他的任何配置都会使 configMAX_SYSCALL_INTERRUPT_PRIORITY 宏设置与分配给各个外设中断的优先级之间的直接关系复杂化。
也就是说为什么都要设置为抢占式优先级,就是为了 FreeRTOS 方便管理,比如它非常复杂,官网明确表态了。
那怎么设置它全部用作抢占式优先级?通过调用:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
来确保将所有优先级位分配为抢占优先级位。
stm32f4xx_hal.c
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
- 中断的优先级不能高于 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();
}