队列集(Queue Set)是 FreeRTOS 中的一种数据结构,用于管理多个队列。它提供了一种有效的方式,通过单个 API 调用来操作和访问一组相关的队列。
在多任务系统中,任务之间可能需要共享数据,而这些数据可能存储在不同的队列中。队列集的作用就是为了更方便地管理这些相关队列,使得任务能够轻松地访问和处理多个队列的数据。
队列集的特点和用法:
- 集中管理多个队列:队列集允许你将多个相关联的队列组织在一起,方便集中管理。
- 单一 API 调用:通过单一的 API 调用,任务可以同时操作多个队列,而无需分别处理每个队列。
- 简化任务代码:对于需要处理多个相关队列的任务,使用队列集可以简化代码,提高可读性和维护性。
- 提高系统效率:在需要协同工作的任务之间共享和传递数据时,队列集可以提高系统的效率。
- 协同工作:任务可以更方便地协同工作,共享数据,实现更复杂的任务间通信和同步。
使用队列集时,你需要了解如何创建、添加和访问队列集,以及如何使用队列集 API 进行数据的发送和接收。队列集是 FreeRTOS 提供的一个强大工具,用于更灵活地组织和处理任务之间的数据流。
想象一下你有一个智能家居系统,有一个任务负责处理温度信息,另一个任务负责光照信息。你可能有两个队列,一个用于温度,一个用于光照。现在,通过队列集,你可以方便地管理这两个队列,让控制任务能够在需要时从这两个队列中获取信息,从而更智能地控制环境。
- 队列集相关API函数介绍(熟悉)
队列集相关函数:
|
函数 |
描述 |
|
xQueueCreateSet() |
创建队列集 |
|
xQueueAddToSet() |
队列添加到队列集中 |
|
xQueueRemoveFromSet() |
从队列集中移除队列 |
|
xQueueSelectFromSet() |
获取队列集中有有效消息的队列 |
|
xQueueSelectFromSetFromISR() |
在中断中获取队列集中有有效消息的队列 |
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include "esp_timer.h"
#include "freertos/semphr.h"
static const char* TAG = "LED_Task";
#define LED1_PIN GPIO_NUM_4
#define LED2_PIN GPIO_NUM_5
#define KEY1_PIN GPIO_NUM_15
#define KEY2_PIN GPIO_NUM_18
#define KEY3_PIN GPIO_NUM_46
#define KEY1 gpio_get_level(KEY1_PIN)
#define KEY2 gpio_get_level(KEY2_PIN)
#define KEY3 gpio_get_level(KEY3_PIN)
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
TaskHandle_t Task1_handle = NULL;
TaskHandle_t Task2_handle = NULL;
TaskHandle_t Task3_handle = NULL;
/* 队列句柄 */
QueueHandle_t queue1;
QueueHandle_t sem_handle;
QueueSetHandle_t queue_set_handle;
void led_flash_init()
{
gpio_config_t led_cfg =
{
.pin_bit_mask = (1ULL<<GPIO_NUM_4)|(1ULL<<GPIO_NUM_5),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&led_cfg);
gpio_config_t btn_cfg =
{
.pin_bit_mask = (1ULL << GPIO_NUM_15)|(1ULL<<GPIO_NUM_18)|(1ULL<<GPIO_NUM_46),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&btn_cfg);
}
uint8_t Key_Detect(void)
{
uint8_t res = 0;
uint8_t key1_state = gpio_get_level(KEY1_PIN);
uint8_t key2_state = gpio_get_level(KEY2_PIN);
uint8_t key3_state = gpio_get_level(KEY3_PIN);
if (key1_state == 0 || key2_state == 0 || key3_state == 0) {
vTaskDelay(pdMS_TO_TICKS(50));
key1_state = gpio_get_level(KEY1_PIN);
key2_state = gpio_get_level(KEY2_PIN);
key3_state = gpio_get_level(KEY3_PIN);
if (key1_state == 0) {
res = KEY1_PRESS;
} else if (key2_state == 0) {
res = KEY2_PRESS;
} else if (key3_state == 0) {
res = KEY3_PRESS;
}
}
return res;
}
void busy_delay_ms(uint32_t ms)
{
uint64_t start_time = esp_timer_get_time();
while ((esp_timer_get_time() - start_time) < (ms * 1000)) {
// 空循环,不交出CPU
}
}
/**
* @description: 任务一:用于扫描按键,当KEY1按下,往队列写入数据,当KEY2按下,释放二值信号量。
* @param {void} *pvParameters
* @return {*}
*/
void Task1(void *pvParameters)
{
uint8_t key = 0;
uint8_t res = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
/* 将key的编号写入queue1 */
res = xQueueSend(queue1, &key, portMAX_DELAY);
if (res == pdPASS)
{
printf("往queue1发送数据[%d]成功\r\n", key);
}
else
{
printf("往queue1发送数据失败\r\n");
}
}
else if (key == KEY2_PRESS)
{
/* 释放信号量 */
res = xSemaphoreGive(sem_handle);
if (res == pdPASS)
{
printf("释放信号量成功\r\n");
}
else
{
printf("释放信号量失败\r\n");
}
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/**
* @description: 任务二:读取队列集中的消息,并打印
* @param {void} *pvParameters
* @return {*}
*/
void Task2(void *pvParameters)
{
QueueSetMemberHandle_t member_handle;
uint8_t receive = 0;
uint8_t res = 0;
while (1)
{
/* 查看哪个队列有数据来了 */
member_handle = xQueueSelectFromSet(queue_set_handle, portMAX_DELAY);
/* 根据对应的队列,去获取数据 */
if (member_handle == queue1)
{
res = xQueueReceive(queue1, &receive, portMAX_DELAY);
if (res == pdPASS)
{
printf("task2从queue1成功读取数据[%d]\r\n", receive);
}
else
{
printf("task2从queue1读取数据失败\r\n");
}
}
else if (member_handle == sem_handle)
{
res = xSemaphoreTake(sem_handle,portMAX_DELAY);
if(res == pdPASS)
{
printf("获取信号量成功\r\n");
}
}
else
{
printf("从队列集获取信息失败\r\n");
}
}
}
void app_main(void)
{
led_flash_init();
BaseType_t res = 0;
/* 在创建任务之前,先创建好需要的队列 */
/* 创建一个存放KEY值编号的小队列queue1 */
queue1 = xQueueCreate(2, sizeof(uint8_t));
if (queue1 != NULL)
{
printf("queue1创建成功\r\n");
}
else
{
printf("queue1创建失败\r\n");
}
/* 创建二值信号量,创建完不主动释放 */
sem_handle = xSemaphoreCreateBinary();
if (sem_handle != NULL)
{
printf("二值信号量创建成功\r\n");
}
else
{
printf("二值信号量创建失败\r\n");
}
/* 创建队列集 */
queue_set_handle = xQueueCreateSet(2);
if (queue_set_handle != NULL)
{
printf("创建队列集成功\r\n");
}
//v开头会释放信号量,信号量释放了就是信号量不为0,不为空,加入队列集需要为空
/* 将队列、信号量添加到队列集中:添加时,队列/信号量 需要为空 */
res = xQueueAddToSet(queue1, queue_set_handle);
if (res == pdPASS)
{
printf("queue1添加到队列集成功\r\n");
}
res = xQueueAddToSet(sem_handle, queue_set_handle);
if (res == pdPASS)
{
printf("二值信号量添加到队列集成功\r\n");
}
BaseType_t ret1 = xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 2, &Task1_handle, 0);
BaseType_t ret2 = xTaskCreatePinnedToCore(Task2, "Task2", 2048, NULL, 1, &Task2_handle, 0);
}
下面是部分日志内容:
queue1创建成功
二值信号量创建成功
创建队列集成功
queue1添加到队列集成功
二值信号量添加到队列[?:e9]戛�成功
I (362) main_task: Returned from app_main()
往queue1发送数据[1]成功
task2从queue1成功读取数据[1]
释放信号量成功
获取信号量成功
然后我们再说一下部分源码:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait )
{
QueueSetMemberHandle_t xReturn = NULL;
// 这里的关键:队列集里存储的就是成员句柄!
( void ) xQueueReceive( ( QueueHandle_t ) xQueueSet, &xReturn, xTicksToWait );
return xReturn;
}
任务调用时:
member_handle = xQueueSelectFromSet(queue_set_handle, portMAX_DELAY);
// 内部执行:
xQueueReceive(队列集, &member_handle, portMAX_DELAY)
{
// prvCopyDataFromQueue 从队列集复制数据到 member_handle
// 复制的就是预先存储的 queue1 或 sem_handle 句柄!
}
在prvCopyDataFromQueue 中体现:
static void prvCopyDataFromQueue( Queue_t * const pxQueue, // 这是队列集!
void * const pvBuffer ) // 这是 &member_handle
{
if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
{
// 移动读指针
pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize;
// 处理环形缓冲区回绕
if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail )
{
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;
}
// 关键:从队列集的缓冲区复制数据到你的 member_handle
( void ) memcpy( ( void * ) pvBuffer, // 目标:member_handle
( void * ) pxQueue->u.xQueue.pcReadFrom, // 源:队列集中存储的句柄
( size_t ) pxQueue->uxItemSize ); // 大小:句柄的大小
}
}
场景:queue1 有数据
时间线: 1. 外部任务: xQueueSend(queue1, &data, 0) 2. queue1 内部: 发现自己是队列集成成员 3. queue1 调用: prvNotifyQueueSetContainer(queue1) 4. 将 queue1句柄 存入队列集的缓冲区 队列集缓冲区: [queue1句柄, NULL, NULL, ...] 5. 你的任务: member_handle = xQueueSelectFromSet(...) 6. xQueueReceive 从队列集缓冲区读取第一个数据 7. prvCopyDataFromQueue 将 queue1句柄 复制到 member_handle 8. 返回: member_handle = queue1句柄
场景:sem_handle 被 give
时间线:
1. 外部任务: xSemaphoreGive(sem_handle)
2. sem_handle 内部: 发现自己是队列集成成员
3. sem_handle 调用: prvNotifyQueueSetContainer(sem_handle)
4. 将 sem_handle句柄 存入队列集的缓冲区 队列集缓冲区: [sem_handle句柄, NULL, NULL, ...]
5. 你的任务: member_handle = xQueueSelectFromSet(...)
6. xQueueReceive 从队列集缓冲区读取第一个数据
7. prvCopyDataFromQueue 将 sem_handle句柄 复制到 member_handle
8. 返回: member_handle = sem_handle句柄
队列集内部数据结构
// 队列集实际上是这样的结构:
struct QueueSet {
// 标准的队列结构
UBaseType_t uxMessagesWaiting; // 当前就绪的成员数量
UBaseType_t uxLength; // 最大成员数量
UBaseType_t uxItemSize; // 句柄的大小(sizeof(QueueSetMemberHandle_t))
// 环形缓冲区,存储就绪成员的句柄
char *pcHead; // 缓冲区起始位置
char *pcTail; // 缓冲区结束位置
char *pcWriteTo; // 写指针位置
char *pcReadFrom; // 读指针位置
// 缓冲区内容示例:
// [queue1句柄][sem_handle句柄][queue2句柄][NULL]...
};

649

被折叠的 条评论
为什么被折叠?



