STM32F1+HAL库+FreeTOTS学习16——队列集
上期我们介绍了互斥信号量。这一期我们来学习队列集
1. 队列集
1.1 队列集引入
前面我们介绍了队列和各种信号量,他们的作用都是解决FreeRTOS中任务之间的消息传递和信号同步问题而产生的,在实际的应用场景中,会存在有队列和信号量同时使用的情况,那么就有可能存在同一个任务不仅需要接收队列的消息,同时也需要等待信号量的释放。
按照之前学习的内容,我们会这样做:
这样做会有一个弊端,就是要求队列的消息必须比信号量释放提前发生,否则会造成信号量被队列阻塞。再者,也可能导致任务因为队列的消息未到来或者信号量未释放而阻塞,不管怎么样,这样现象都是我们不希望发生的,该如何解决呢,这个时候我们就需要引入队列集这个概念。
1.2 队列集简介
在使用队列进行任务之间的“沟通交流”时,一个队列只允许任务间传递的消息为同一种数据类型,如果需要传递不同数据类型的时候,就可以使用队列集。
FreeRTOS中的队列集可以对多个队列(信号量本质上也是队列)进行“监听”,只要被监听的队列中有一个队列有有效的消息,那么队列集的读取人都可以读取到消息。
使用队列集的好处在于,队列集可以在一个人任务中同时读取多个队列的消息,无需遍历所有待读取的队列,以确定具体读取哪一个队列。
使用队列集功能,需要在 FreeRTOSConfig.h 文件中将配置项 configUSE_QUEUE_SETS 配置为 1,来启用队列集功能。
2. 相关API函数
队列集的使用过程:创建队列集>创建队列或信号量-> 往队列集中添加信号量或者队列 -> 往队列发生消息或释放信号量->获取队列集消息,下面我们围绕几个步骤介绍队列集的相关API函数
常用的队列集API函数如下表:
函数 | 描述 |
---|---|
xQueueCreateSet() | 创建队列集 |
xQueueAddToSet() | 队列添加到队列集中 |
xQueueRemoveFromSet() | 从队列集中移除队列 |
xQueueSelectFromSet() | 获取队列集中有有效消息的队列 |
xQueueSelectFromSetFromISR() | 在中断中获取队列集中有有效消息的队列 |
2. 1 xQueueCreateSet()
此函数用于创建队列集,该函数在 queue.c 文件中有定义,函数的原型如下所示:
/**
* @brief xQueueCreateSet:创建队列集
* @param uxEventQueueLength:队列集可容纳的队列数量
* @retval 返回值为NULL,表示创建失败,其他值表示为创建队列集的句柄
*/
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);
2.2 xQueueAddToSet()
此函数用于往队列集中添加队列,要注意的时,队列在被添加到队列集之前,队列中不能有有效的消息,该函数在 queue.c 文件中有定义,函数的原型如下所示:
/**
* @brief xQueueAddToSet:往队列集中添加队列
* @param xQueueOrSemaphore:需要被添加的队列
* @param xQueueSet:被添加的队列集
* @retval 返回值为pdPASS,表示添加成功,返回值为pdFALL,表示添加失败
*/
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
2.3 xQueueRemoveFromSet()
此函数用于从队列集中移除队列,要注意的时,队列在被队列集移除之前,队列中不能有有效的消息,该函数在 queue.c 文件中有定义,函数的原型如下所示:
/**
* @brief xQueueRemoveFromSet:从队列集中移除队列
* @param xQueueOrSemaphore:需要被移除的队列
* @param xQueueSet:被移除的队列集
* @retval 返回值为pdPASS,表示移除成功,返回值为pdFALL,表示移除失败
*/
BaseType_t xQueueRemoveFromSet(QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet);
2.4 xQueueSelectFromSet()
此函数用于在任务中获取队列集中有有效消息的队列,该函数在 queue.c 文件中有定义,函数的原型如下所示:
/**
* @brief xQueueSelectFromSet:从队列集中获取有效信息
* @param xQueueSet:获取有效消息的队列集
* @param xTicksToWait:阻塞超时时间
* @retval 返回值为NULL,表示获取消息失败,返回值为其他值,表示获取到消息的队列
*/
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait);
2.4 xQueueSelectFromSetFromISR()
此函数用于在中断中获取队列集中有有效消息的队列,该函数在 queue.c 文件中有定义,函数的原型如下所示:
/**
* @brief xQueueSelectFromSetFromISR:在中断中获取队列集的有效消息
* @param xQueueSet :获取有效消息的队列集
* @retval 返回值为NULL,表示获取消息失败,返回值为其他值,表示获取到消息的队列
*/
QueueSetMemberHandle_t xQueueSelectFromSetFromISR( QueueSetHandle_t xQueueSet );
3. 操作实验
3.1. 实验内容
在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的队列集操作,具体要求如下:
- 定义一个队列集,一个队列、一个信号量。
- 定义任务1:每次按下按键0,向队列发生消息,按下按键1,释放信号量。
- 定义任务2:读取队列集中的消息。
- 每完成一个步骤,通过串口打印相关信息。
3.2 代码实现
- 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
- freertos_demo.c
#include "freertos_demo.h"
#include "main.h"
#include "queue.h" //需要包含队列和任务相关的头文件
#include "task.h"
#include "key.h" //包含按键相关头文件
/*FreeRTOS*********************************************************************************************/
/******************************************************************************************************/
/*FreeRTOS配置*/
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK1_PRIO 1 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /*任务函数*/
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小
*/
#define TASK2_PRIO 2 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /*任务函数*/
QueueHandle_t xQueueHandle_t; /* 队列句柄 */
#define xQueueLenth 2 /* 队列长度 */
#define xQueueType uint8_t /* 队列元素类型 */
#define xQueueSize sizeof(xQueueType) /* 队列元素大小 */
SemaphoreHandle_t SemaphoreCountHandle_t; /* 数值信号量句柄 */
#define SemaphoreCountMaxCount 3 /* 数值信号量的最大值 */
#define SemaphoreCountInitCount 0 /* 数值信号量的初始值 */
QueueSetHandle_t xQueueSetHandle_t; /* 队列集句柄 */
/* 为确保事件不会丢失,uxEventQueueLength 必须设置为 添加到队列集中的所有队列长度的总和,其中二进制信号量和互斥锁的长度为 1, 而计数信号量的长度由其最大计数值确定。 */
#define xQueueSetLenth ( xQueueLenth + SemaphoreCountMaxCount ) /* 队列集长度:可容纳的队列或信号量数量 */
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
BaseType_t errMessage;
taskENTER_CRITICAL(); /* 进入临界区,关闭中断,此时停止任务调度*/
/* 创建队列集 */
xQueueSetHandle_t = xQueueCreateSet(xQueueSetLenth);
if(xQueueSetHandle_t == NULL)
{
printf("队列集创建失败\r\n");
}
else
{
printf("队列集创建成功,长度为:%d\r\n",xQueueSetLenth);
}
/* 创建队列 */
xQueueHandle_t = xQueueCreate( xQueueLenth,xQueueSize );
if(xQueueHandle_t == NULL)
{
printf("队列创建失败\r\n");
}
else
{
printf("队列创建成功,长度为:%d,队列元素大小为:%d\r\n",xQueueLenth,xQueueSize);
}
/* 创建数值信号量 */
SemaphoreCountHandle_t = xSemaphoreCreateCounting( SemaphoreCountMaxCount,SemaphoreCountInitCount);
if(SemaphoreCountHandle_t == NULL)
{
printf("数值信号量创建失败\r\n");
}
else
{
printf("数值信号量创建成功,信号量最大值为:%d,初始值为:%d\r\n",SemaphoreCountMaxCount,SemaphoreCountInitCount);
}
/* 往队列集里面添加队列 */
errMessage = xQueueAddToSet( xQueueHandle_t,xQueueSetHandle_t );
if(errMessage == pdPASS)
{
printf("往队列集添加队列成功\r\n");
}
else
{
printf("往队列集添加队列失败\r\n");
}
/* 往队列集里面添加数值信号量 */
errMessage = xQueueAddToSet( SemaphoreCountHandle_t,xQueueSetHandle_t );
if(errMessage == pdPASS)
{
printf("往队列集添加数值信号量成功\r\n");
}
else
{
printf("往队列集添加数值信号量失败\r\n");
}
/* 创建任务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);
taskEXIT_CRITICAL(); /* 退出临界区,重新开启中断,开启任务调度 */
vTaskStartScheduler(); //开启任务调度
}
/**
* @brief task1:用于按键扫描,检测按键0按下时,释放计数型信号量(资源数+1)
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
while(1)
{
Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);
Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);
}
}
/**
* @brief task2:每1s获取计数信号量(资源数-1),当获取成功后,打印信号量的计数值(资源数)
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task2(void *pvParameters)
{
QueueSetMemberHandle_t activeMember;
uint8_t buffer;
while(1)
{
activeMember = xQueueSelectFromSet(xQueueSetHandle_t,portMAX_DELAY);
if(activeMember == xQueueHandle_t)
{
xQueueReceive(activeMember,&buffer,portMAX_DELAY);
printf("来自队列接收到的消息为:%d\r\n",buffer);
}
if(activeMember == SemaphoreCountHandle_t)
{
xSemaphoreTake(activeMember,portMAX_DELAY);
printf("获取到数值信号量\r\n");
}
}
}
- key.c
/* USER CODE BEGIN 2 */
#include "freertos_demo.h"
#include "key.h"
#include "usart.h"
extern QueueHandle_t xQueueHandle_t; /* 队列句柄 */
extern SemaphoreHandle_t SemaphoreCountHandle_t; /* 数值信号量句柄 */
uint8_t key0Num = 0x00; /* 写入队列的数据 */
void Key0_Down_Task(void)
{
BaseType_t errMessage;
/* 向队列发送消息 */
errMessage = xQueueSend(xQueueHandle_t,&key0Num,1000);
if(errMessage == pdTRUE)
{
printf("队列发送消息成功\r\n");
}
else
{
printf("队列发送消息失败\r\n");
}
}
void Key0_Up_Task(void)
{
}
void Key1_Down_Task(void)
{
BaseType_t errMessage;
/* 释放数值信号量 */
errMessage = xSemaphoreGive(SemaphoreCountHandle_t);
if(errMessage == pdTRUE)
{
printf("数值信号量释放成功\r\n");
}
else
{
printf("数值信号量释放失败\r\n");
}
}
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))
{
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 */
以上就是本期内容的核心代码,下面我们来看一下实验结果:
3.2 实验结果
以上就是本期的所有内容,创造不易,点个关注再走呗。