基于esp32s3学习freertos---队列集

队列集(Queue Set)是 FreeRTOS 中的一种数据结构,用于管理多个队列。它提供了一种有效的方式,通过单个 API 调用来操作和访问一组相关的队列。

在多任务系统中,任务之间可能需要共享数据,而这些数据可能存储在不同的队列中。队列集的作用就是为了更方便地管理这些相关队列,使得任务能够轻松地访问和处理多个队列的数据。

队列集的特点和用法:

  • 集中管理多个队列:队列集允许你将多个相关联的队列组织在一起,方便集中管理。
  • 单一 API 调用:通过单一的 API 调用,任务可以同时操作多个队列,而无需分别处理每个队列。
  • 简化任务代码:对于需要处理多个相关队列的任务,使用队列集可以简化代码,提高可读性和维护性。
  • 提高系统效率:在需要协同工作的任务之间共享和传递数据时,队列集可以提高系统的效率。
  • 协同工作:任务可以更方便地协同工作,共享数据,实现更复杂的任务间通信和同步。

使用队列集时,你需要了解如何创建、添加和访问队列集,以及如何使用队列集 API 进行数据的发送和接收。队列集是 FreeRTOS 提供的一个强大工具,用于更灵活地组织和处理任务之间的数据流。

想象一下你有一个智能家居系统,有一个任务负责处理温度信息,另一个任务负责光照信息。你可能有两个队列,一个用于温度,一个用于光照。现在,通过队列集,你可以方便地管理这两个队列,让控制任务能够在需要时从这两个队列中获取信息,从而更智能地控制环境。

  1. 队列集相关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]...
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值