STM32F1+HAL库+FreeTOTS学习4——任务挂起与恢复
- 任务挂起和恢复的API介绍
- 代码实现
上一期我们学习了FreeRTOS中任务创建的两种方法,这一期我们学习任务的挂起和恢复。
任务挂起和恢复的API介绍
在 :STM32F1+HAL库+FreeTOTS学习1——FreeRTOS入门 的学习中,我们有了解到FreeRTOS的状态分为就绪、阻塞、挂起和运行四种状态,那如何将处于就绪态的任务挂起呢?我们有对应的函数:
- 挂起任务函数
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
/*
参数是需要挂起的任务句柄,当传入的参数是NULL时,代表任务自身挂起。
*/
使用前需要将宏 : INCLUDE_vTaskSuspend 配置为1,且无论优先级如何,被挂起的任务都不会被执行,知道任务恢复。
内部实现如下:
- 任务恢复函数(任务中恢复)
void vTaskResume(TaskHandle_t xTaskToResume);
/*
传入参数是需要恢复的任务句柄。
*/
使用前需要将宏 : INCLUDE_vTaskSuspend 配置为1 ,且无论该任务被挂起多少次,只需要调用一次vTaskResume函数即可恢复该任务,且该任务立即进入就绪态。
内部实现如下:
- 任务恢复函数(中断中恢复)
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
/*
参数依旧是需要恢复的任务句柄,但是该函数存在返回值
当返回值为 pdTRUE时,表示改任务恢复后需要进行任务切换, 需要调用portYIELD_FROM_ISR函数切换到更高优先级的任务。
当返回值为 pdFALSE 时,表示任务恢复后不需要进行任务切换。
*/
- 使用前需要将宏 : INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 定义为1
- 该函数专门用在中断服务函数中,用于解挂(恢复)被挂起的任务
- 中断服务程序中要调用freeRTOS的API函数则该中断的优先级不能高于FreeRTOS所管理的最高优先级:简单理解就是中断的优先级必须在 5 到 15之间 。
- 并且中断的优先级分组必须是4位全用做抢占优先级,无子优先级。
代码实现
再看代码实现之前,我们先明确以下实现的功能,不然有的时候看的云里雾里的:
- 创建四任务,一个开始任务,三个运行任务。
- 再开始任务中创建三个运行任务,创建完成后删除开始任务。
- 任务1控制LED0闪烁,1s闪一次
- 任务2负责串口打印,打印系统运行时间。
- 按键3负责按键扫描,按键0按下挂起任务1,按键1按下恢复任务1,打印相关信息。
- WK_UP按键用作外部中断,在中断里面恢复任务1,并打印相关信息
以上就是需要实现的内容,由于本期内容使用到按键,所有这里展示以下一些按键的CubeMX配置:
- 按键部分:这里只展示按键0,按键1和0一样即可
- WK_UP按键:配置外部输入,优先级为5(必须要设置优先级再5到15之前,且优先级分组为4)
其他部分的配置这里就不展示了,在直接在配好的工程上该即可。下面来看代码
- main.c :须在main函数里面调用 freertos_demo()函数进入FreRTOS系统。
freertos_demo();
- freertos_demo.c
#include "freertos_demo.h"
#include "main.h"
/*FreeRTOS*********************************************************************************************/
#include "key.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 1 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 1 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 1 /* 任务优先级 */
#define TASK3_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task3Task_Handler; /* 任务句柄 */
void task3(void *pvParameters); /* 任务函数 */
/******************************************************************************************************/
/* LCD刷屏时使用的颜色 */
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler(); //开启任务调度
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区,关闭中断,此时停止任务调度*/
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区,重新开启中断,开启任务调度 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
printf("task1 start\r\n");
/* LED0闪烁 */
vTaskDelay(1000); /* 延时1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
float float_num = 0.0;
while(1)
{
float_num += 0.01f; /* 更新数值 */
printf("float_num: %0.4f\r\n", float_num); /* 打印数值 */
printf("task2 start\r\n");
vTaskDelay(1000); /* 延时1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task3(void *pvParameters)
{
vTaskDelay(100);
while(1)
{
Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task); //扫描Key0状态
Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task); //扫描Key1状态
Key_One_Scan(Key_Name_Key2,Key2_Up_Task,Key2_Down_Task); //扫描Key2状态
}
}
- key.c (这里面用来实现软件方式按键扫描)
/* USER CODE BEGIN 2 */
#include "freertos_demo.h"
#include "key.h"
#include "usart.h"
void Key0_Down_Task(void)
{
vTaskSuspend(Task1Task_Handler);
printf("任务1挂起\r\n");
}
void Key0_Up_Task(void)
{
}
void Key1_Down_Task(void)
{
printf("任务1解挂\r\n");
}
void Key1_Up_Task(void)
{
vTaskResume(Task1Task_Handler);
}
void Key2_Down_Task(void)
{
}
void Key2_Up_Task(void)
{
}
void WKUP_Down_Task(void)
{
}
void WWKUP_Up_Task(void)
{
}
void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{
static uint8_t Key_Val[Key_Name_Max]; //按键值的存放位置
static uint8_t Key_Flag[Key_Name_Max]; //KEY0~2为0时表示按下,为1表示松开,WKUP反之
Key_Val[KeyName] = Key_Val[KeyName] <<1; //每次扫描完,将上一次扫描的结果左移保存
switch(KeyName)
{
case Key_Name_Key0: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)); //读取Key0按键值
break;
case Key_Name_Key1: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)); //读取Key1按键值
break;
case Key_Name_Key2: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin)); //读取Key2按键值
break;
// case Key_Name_WKUP: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin)); //读取WKUP按键值
// break;
default:
break;
}
// if(KeyName == Key_Name_WKUP) //WKUP的电路图与其他按键不同,所以需要特殊处理
// {
// //WKUP特殊情况
// //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
// if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
// {
// (*OnKeyOneDown)();
// Key_Flag[KeyName] = 0;
// }
// //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
// if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
// {
// (*OnKeyOneUp)();
// Key_Flag[KeyName] = 1;
// }
// }
// else //Key0~2按键逻辑判断
// {
//Key0~2常规判断
//当按键标志为1(松开)是,判断是否按下
if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1)
{
(*OnKeyOneDown)();
Key_Flag[KeyName] = 0;
}
//当按键标志位为0(按下),判断按键是否松开
if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0)
{
(*OnKeyOneUp)();
Key_Flag[KeyName] = 1;
}
}
//}
/* USER CODE END 2 */
- key.h (lkey.c搭配使用)
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
typedef enum
{
Key_Name_Key0 = 0,
Key_Name_Key1,
Key_Name_Key2,
Key_Name_WKUP,
Key_Name_Max
}EnumKeyOneName;
/* USER CODE END Private defines */
void MX_GPIO_Init(void);
/* USER CODE BEGIN Prototypes */
void Key0_Down_Task(void);
void Key0_Up_Task(void);
void Key1_Down_Task(void);
void Key1_Up_Task(void);
void Key2_Down_Task(void);
void Key2_Up_Task(void);
void WKUP_Down_Task(void);
void WWKUP_Up_Task(void);
void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void));
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif /*__ GPIO_H__ */
值得注意的是,按键扫描部分代码取自 : STM32框架之按键扫描新思路 并稍作修改,具体实现原理可以自行了解。
- gpio.c (这里用来完成中断方式的恢复任务。)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
BaseType_t xYieldRequired;
if(GPIO_Pin == WK_UP_Pin ) //判断中断源为WKUP中断
{
xYieldRequired = xTaskResumeFromISR(Task1Task_Handler);
if(xYieldRequired == pdTRUE)
{
portYIELD_FROM_ISR(xYieldRequired);
printf("任务1解挂中断方式\r\n");
}
}
}