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 代码实现

  1. 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");
		}

    }

}
  1. 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 实验结果

在这里插入图片描述

以上就是本期的所有内容,创造不易,点个关注再走呗。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想写代码的我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值