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);
            }
        }
    }
}
posted @ 2024-03-15 21:40  星光映梦  阅读(194)  评论(0编辑  收藏  举报