09. FreeRTOS的队列集
一、FreeRTOS的队列集
在使用队列进行任务之间的“沟通交流”时,一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集。FreeRTOS 提供的队列集功能可以对多个队列进行“监听”,只要被监听的队列中有一个队列有有效的消息,那么队列集的读取任务都可以读取到消息,如果读取任务因读取队列集而被阻塞,那么队列集将解除读取任务的阻塞。使用队列集的好处在于,队列集可以是的任务可以读取多个队列中的消息,而无需遍历所有待读取的队列,以确定具体读取哪一个队列。
使用队列集功能,需要在 FreeRTOSConfig.h 文件中将配置项
configUSE_QUEUE_SETS
配置为 1,来启用队列集功能。
二、FreeRTOS队列集相关API函数
2.1、创建队列集
xQueueCreateSet()
函数用于创建队列集,该函数在 queue.c 文件中有定义,其的具体代码如下所示:
// 返回值: NULL: 队列集创建失败; 其它值: 队列集创建成功,返回队列集;
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ) // uxEventQueueLength为队列集可容纳的队列数量
{
QueueSetHandle_t pxQueue;
traceENTER_xQueueCreateSet( uxEventQueueLength );
// 创建一个队列作为队列集,队列长度为队列集可容纳的队列数量,队列项目的大小为队列控制块的大小,队列的类型为队列集
pxQueue = xQueueGenericCreate( uxEventQueueLength, ( UBaseType_t ) sizeof( Queue_t * ), queueQUEUE_TYPE_SET );
traceRETURN_xQueueCreateSet( pxQueue );
return pxQueue;
}
2.2、队列添加到队列集中
xQueueAddToSet()
函数用于往队列集中添加队列,要注意的时,队列在被添加到队列集之前,队列中不能有有效的消息,该函数在 queue.c 文件中有定义,其具体代码如下所示:
// 返回值: pdPASS: 队列集添加队列成功; pdFAIL: 队列集添加队列失败;
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, // 待添加的队列
QueueSetHandle_t xQueueSet ) // 队列集
{
BaseType_t xReturn;
traceENTER_xQueueAddToSet( xQueueOrSemaphore, xQueueSet );
taskENTER_CRITICAL(); // 进入临界区
{
if( ( ( Queue_t * ) xQueueOrSemaphore )->pxQueueSetContainer != NULL )
{
xReturn = pdFAIL;
}
// 队列中要求没有有效消息
else if( ( ( Queue_t * ) xQueueOrSemaphore )->uxMessagesWaiting != ( UBaseType_t ) 0 )
{
xReturn = pdFAIL;
}
else
{
// 将队列所在队列集设为队列集
( ( Queue_t * ) xQueueOrSemaphore )->pxQueueSetContainer = xQueueSet;
xReturn = pdPASS;
}
}
taskEXIT_CRITICAL(); // 退出临界区
traceRETURN_xQueueAddToSet( xReturn );
return xReturn;
}
2.3、从队列集中移除队列
xQueueRemoveFromSet()
此函数用于从队列集中移除队列,要注意的时,队列在从队列集移除之前,必须没有有效的消息,该函数在 queue.c 文件中有定义,其具体代码如下所示:
// 返回值: pdPASS: 队列集移除队列成功; pdFAIL: 队列集移除队列失败;
BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore, // 待移除的队列
QueueSetHandle_t xQueueSet ) // 队列集
{
BaseType_t xReturn;
Queue_t * const pxQueueOrSemaphore = ( Queue_t * ) xQueueOrSemaphore;
traceENTER_xQueueRemoveFromSet( xQueueOrSemaphore, xQueueSet );
if( pxQueueOrSemaphore->pxQueueSetContainer != xQueueSet ) // 队列需在队列集中,才能移除
{
xReturn = pdFAIL;
}
else if( pxQueueOrSemaphore->uxMessagesWaiting != ( UBaseType_t ) 0 ) // 队列中没有有效消息时,才能移除
{
xReturn = pdFAIL;
}
else
{
taskENTER_CRITICAL(); // 进入临界区
{
pxQueueOrSemaphore->pxQueueSetContainer = NULL; // 将队列所在队列集设为空
}
taskEXIT_CRITICAL(); // 退出临界区
xReturn = pdPASS;
}
traceRETURN_xQueueRemoveFromSet( xReturn );
return xReturn;
}
2.4、获取队列集中有有效消息的队列
xQueueSelectFromSet()
函数用于在任务中获取队列集中有有效消息的队列,该函数在 queue.c 文件中有定义,其具体代码如下所示:
// 返回值: NULL: 获取消息失败; 其他值: 获取到消息的队列;
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, // 队列集
TickType_t const xTicksToWait ) // 阻塞超时时间
{
QueueSetMemberHandle_t xReturn = NULL;
traceENTER_xQueueSelectFromSet( xQueueSet, xTicksToWait );
// 读取队列集的消息,读取到的消息,即为队列集中有空闲消息的队列
( void ) xQueueReceive( ( QueueHandle_t ) xQueueSet, &xReturn, xTicksToWait );
traceRETURN_xQueueSelectFromSet( xReturn );
return xReturn;
}
2.5、在中断中获取队列集中有有效消息的队列
xQueueSelectFromSetFromISR()
函数用于在中断中获取队列集中有有效消息的队列,该函数在 queue.c 文件中有定义,其具体代码如下所示:
// 返回值: NULL: 获取消息失败; 其他值: 获取到消息的队列;
QueueSetMemberHandle_t xQueueSelectFromSetFromISR( QueueSetHandle_t xQueueSet ) // xQueueSet为队列集
{
QueueSetMemberHandle_t xReturn = NULL;
traceENTER_xQueueSelectFromSetFromISR( xQueueSet );
// 在中断中读取队列集的消息,读取到的消息, 即为队列集中有空闲消息的队列
( void ) xQueueReceiveFromISR( ( QueueHandle_t ) xQueueSet, &xReturn, NULL );
traceRETURN_xQueueSelectFromSetFromISR( xReturn );
return xReturn;
}
三、实验例程
main() 函数内容如下:
int main(void)
{
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
UART_Init(&g_usart1_handle, USART1, 115200);
Key_Init();
freertos_demo();
return 0;
}
FreeRTOS 例程入口函数:
/**
* @brief FreeRTOS的入口函数
*
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t ) start_task, // 任务函数
(char * ) "start_task", // 任务名
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE, // 任务栈大小
(void * ) NULL, // 入口参数
(UBaseType_t ) START_TASK_PRIORITY, // 任务优先级
(TaskHandle_t * ) start_task_handle); // 任务句柄
vTaskStartScheduler(); // 开启任务调度器
}
START_TASK 任务配置:
EventGroupHandle_t event_group_handle;
/**
* START_TASK 任务配置
* 包括: 任务优先级 任务栈大小 任务句柄 任务函数
*/
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handle;
void start_task(void *pvParameters );
/**
* @brief 开始任务的任务函数
*
* @param pvParameters 任务函数的入口参数
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); // 进入临界区,关闭中断
event_group_handle = xEventGroupCreate(); // 创建事件标志组
if (event_group_handle != NULL)
{
printf("事件标志组创建成功\r\n", event_group_handle);
}
xTaskCreate((TaskFunction_t ) task1, // 任务函数
(char * ) "task1", // 任务名
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE, // 任务栈大小
(void * ) NULL, // 入口参数
(UBaseType_t ) TASK1_PRIORITY, // 任务优先级
(TaskHandle_t * ) &task1_handle); // 任务句柄
xTaskCreate((TaskFunction_t ) task2, // 任务函数
(char * ) "task2", // 任务名
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE, // 任务栈大小
(void * ) NULL, // 入口参数
(UBaseType_t ) TASK2_PRIORITY, // 任务优先级
(TaskHandle_t * ) &task2_handle); // 任务句柄
vTaskDelete(NULL); // 删除任务自身
taskEXIT_CRITICAL(); // 退出临界区,重新开启中断
}
TASK1 任务配置:
/**
* TASK1 任务配置
* 包括: 任务优先级 任务栈大小 任务句柄 任务函数
*/
#define TASK1_PRIORITY 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handle;
void task1(void *pvParameters);
/**
* @brief 任务1的任务函数
*
* @param pvParameters 任务函数的入口参数
*/
void task1(void *pvParameters)
{
BaseType_t result = 0;
uint8_t key = 0;
char *data1 = "你好啊,我是小樱,请多多关照!";
char *data2 = "隐藏在黑暗里的钥匙啊,和你定下约定的小樱命令你:封印解除!";
while (1)
{
key = Key_Scan(0);
switch (key)
{
case WKUP_PRESS:
result = xQueueSend(queue1_handle, &data1, portMAX_DELAY);
if (result != pdTRUE)
{
printf("队列queue1发送失败\r\n");
}
break;
case KEY2_PRESS:
result = xQueueSend(queue1_handle, &data2, portMAX_DELAY);
if (result != pdTRUE)
{
printf("队列queue1发送失败\r\n");
}
break;
case KEY1_PRESS:
case KEY3_PRESS:
result = xQueueSend(queue2_handle, &key, portMAX_DELAY);
if (result != pdTRUE)
{
printf("队列queue2发送失败\r\n");
}
break;
default:
break;
}
vTaskDelay(10);
}
}
TASK2 任务配置:
/**
* TASK2 任务配置
* 包括: 任务优先级 任务栈大小 任务句柄 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIORITY 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handle;
void task2(void *pvParameters);
/**
* @brief 任务2的任务函数
*
* @param pvParameters 任务函数的入口参数
*/
void task2(void *pvParameters )
{
BaseType_t result = 0;
uint8_t key = 0;
char *data;
QueueSetMemberHandle_t member_handle;
while (1)
{
member_handle = xQueueSelectFromSet(queue_set_handle, portMAX_DELAY);
if (member_handle == queue1_handle)
{
result = xQueueReceive(member_handle, &data, portMAX_DELAY);
if (result != pdTRUE)
{
printf("queue1队列读取失败\r\n");
}
else
{
printf("queue1队列读取成功,数据: %s\r\n", data);
}
}
else if (member_handle == queue2_handle)
{
result = xQueueReceive(member_handle, &key, portMAX_DELAY);
if (result != pdTRUE)
{
printf("queue2队列读取失败\r\n");
}
else
{
printf("queue2队列读取成功,数据: %d\r\n", key);
}
}
}
}