FreeRTOS中队列是任务间通信(任务之间,中断与任务之间通信)的主要形式。

FreeRTOS 队列特点

FreeRTOS 队列有以下特点:
消息以副本方式发送: 发送的消息或数据会被整个复制到队列中

  • 发送方将消息或数据发出后,即可重用或销毁该数据,而接收方接收完成后,消息或数据副本会自动销毁,相较于消息以副本方式发送,内存管理更为方便
    • 若消息非副本方式发送,而以引用方式发送,发送方发送数据后,需等接收方接收并处理数据后,才能重用或销毁数据(或者接收方接收并处理数据后,确认发送方不再使用该数据后,方可销毁)
  • 由于消息或数据会被整个复制到队列中,因此,若发送的数据较大时,可使用指针的方式传送;

队列可阻塞:

  • 当从空队列中读取数据时,该队列将进入阻塞状态(不占用CPU时间),直到队列中的数据变得可用,或阻塞时间过期;
    • 同理,当试图往满队列中写数据时,该队列也会进入阻塞状态,直至队列中的数据变得可用,或阻塞时间过期;
  • 如果同一队列上有多个处于阻塞状态的任务,那么具有最高优先级的任务最先解除阻塞;

Queue 常用API 接口

 /* 创建队列并返回可引用该队列的句柄:创建队列时,用于保存队列状态及数据的RAM会自动从FreeRTOS堆中分配 */
 /* uxQueueLength: 队列可存储的最大项目数(消息数目)*/
 /* uxItemSize: 每个项目所需的大小(消息占用的 byte num)*/
 /* 若创建成功,则返回QueueHandle_t 类型的队列句柄,否则为NULL */
 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                             UBaseType_t uxItemSize );


/* 发送消息或数据入队列 */
/* pvItemToQueue: 指向要放入队列中的项目的指针(虽然是指针,但该指针指向的整个数据都会被copy进队列,而不是仅copy 该指针) */
/* xTicksToWait: 队列已满时,任务处于阻塞状态等待可用的最大时间 */
/* 同理,在ISR 中可用的接口,以及与xQueueSend等价的xQueueSendToBack, 以及插入队列头部的xQueueSendToFront */
 BaseType_t xQueueSend(
                        QueueHandle_t xQueue,
                        const void * pvItemToQueue,
                        TickType_t xTicksToWait
                      );
/* pxHigherPriorityTaskWoken: 返回值,为true时,表明接收该发送消息的接收方的任务优先级比当前运行任务优先级更高(ISR前正在运行的任务),可以手动申请切换到高优先级任务:portYIELD_FROM_ISR(xHigherPriorityTaskWoken) */
/* 也可直接传递NULL,让API自动处理切换 */
 BaseType_t xQueueSendFromISR
           (
               QueueHandle_t xQueue,
               const void *pvItemToQueue,
               BaseType_t *pxHigherPriorityTaskWoken
           );
 BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
 BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
 BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
 BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);


/* 从xQueue 中接收项目:pvBuffer (项目指针) */
/* xTicksToWait: 若队列为空,则任务阻塞等待项目接收的最长时间,成功返回true,否则返回false */
BaseType_t xQueueReceive(
                          QueueHandle_t xQueue,
                          void *pvBuffer,
                          TickType_t xTicksToWait
);
/* 在ISR 中调用该接口,而不是对方在ISR中发送,就要用该接口接收*/
 BaseType_t xQueueReceiveFromISR
           (
               QueueHandle_t xQueue,
               void *pvBuffer,
               BaseType_t *pxHigherPriorityTaskWoken
           );

Queue 使用实例

/* 消息结构体定义 */
typedef struct {
    uint8_t data;               // 接收到的数据
    uint32_t timestamp;         // 时间戳(节拍数)
    uint8_t error_flag;         // 错误标志(奇偶校验错等)
} UartRxMessage_t;

/* 全局变量 */
static QueueHandle_t xUartRxQueue = NULL;      // UART接收队列
static TaskHandle_t xUartRxTaskHandle = NULL;  // UART接收任务句柄

/*
 * UART中断服务程序
 */
void vUartIrqHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    static uint32_t ulInterruptCount = 0;
    UartRxMessage_t newMessage;
  
    /* 中断简单处理 */
  	  .....
  
    /* 包装数据/消息 发送到队列中 */
    newMessage.data = UART_DR;
    newMessage.timestamp = xTaskGetTickCountFromISR();
    newMessage.error_flag = 0;  // 这里可以检查奇偶校验错等
	 // 发送到队列(从ISR调用专用API)
    if (xQueueSendFromISR(xUartRxQueue, &newMessage, &xHigherPriorityTaskWoken) != pdPASS) 
    {
        // 队列已满!数据丢失
        printf("[ISR WARNING] Queue full! Data lost: 0x%02X\n", newMessage.data);
        
        // 可以在这里增加错误计数、触发错误处理等
        // xErrorCount++;
    }
    else 
    {
        printf("[ISR] Message queued successfully\n");
        printf("[ISR] Queue space remaining: %lu\n", 
               (UART_RX_QUEUE_LENGTH - uxQueueMessagesWaitingFromISR(xUartRxQueue)));
    }
      
    // 若当前发送消息队列此前为空,且消息唤醒的任务优先级更高(相比当前运行任务的优先级),则如果需要,请求上下文切换
    if (xHigherPriorityTaskWoken == pdTRUE) 
    {
        printf("[ISR] Requesting context switch...\n");
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
    else 
    {
        printf("[ISR] No context switch needed\n");
    }
}


/*
 * UART接收任务
 * 从队列接收数据并处理
 */
void vUartRxTask(void *pvParameters)
{
    UartRxMessage_t rxMsg;
    BaseType_t xStatus;
    uint32_t ulMessagesReceived = 0;
    uint32_t ulLastProcessTime = 0;
    
    printf("[TASK] Task priority: %d, Stack size: %d\n", 
           UART_RX_TASK_PRIORITY, UART_RX_TASK_STACK_SIZE);
    
    // 任务主循环
    for (;;) 
    {
        // 1. 从队列接收数据(无限等待)
        xStatus = xQueueReceive(xUartRxQueue, &rxMsg, portMAX_DELAY);
        
        if (xStatus == pdPASS) 
        {
            /* 接收数据处理:rxMsg */
        }
        else 
        {
            // 这不应该发生,因为我们是无限等待
            printf("[TASK ERROR] Failed to receive from queue!\n");
        }
    }
}       


/*
 * 队列及接收task创建
 */
int main(void)
{    
    // 1. 创建UART接收队列:队列最多可容纳10个消息
    printf("[MAIN] Creating UART receive queue...\n");
    xUartRxQueue = xQueueCreate(10, sizeof(UartRxMessage_t));
    
    if (xUartRxQueue == NULL) 
    {
        printf("[MAIN ERROR] Failed to create queue!\n");
        return -1}
    
    // 2. 创建UART接收任务
    printf("[MAIN] Creating UART receive task...\n");
    xTaskCreate(vUartRxTask,               // 任务函数
                "UART Rx",                 // 任务名称
                UART_RX_TASK_STACK_SIZE,   // 栈大小
                NULL,                      // 参数
                UART_RX_TASK_PRIORITY,     // 优先级
                &xUartRxTaskHandle);       // 任务句柄

    return 0;
}

队列集

    队列和信号量被分成集合,即队列集,之后任务并非对从单个队列或信号量的数据接收进行阻塞,而是对集合中任意队列和信号量的接收都进行阻塞(同理集合中的任意队列和信号量的数据发送都会唤醒队列集)。除非必要,一般通过单个队列,以较少的代码,更小的RAM,更短的运行时间实现相同功能。

  • 队列集通常用在需要等待多个事件源/数据源,并统一根据来源做特定处理的场景(多对1,但需要区分来源),但可以通过把所有数据(统一成同一个数据结构体)统一发送到一个队列的方式来替代,单个队列占用更少内存,调度路径更短,性能更优,故一般不推荐使用较为复杂的队列集
    • 队列集只是同时监听多个对象的工具,但大多数情况下,用一个队列加类型标志就能实现同样功能,而且更简单,更省资源,更高效;
  • 但当多个数据没办法合并时,就必须要使用队列集了,如:
    • 多个独立模块,无法改变数据结构:各个模块相对独立,实现有差异,且无法统一数据结构时,就需要用队列集;
    • 需要同时等待Queue + Semaphore
    • 不能或不想copy 数据: 单队列方案需要复制数据到统一结构,而Queue Set则可以避免copy;
/* 核心数据结构设计:分别处理来自以下4个事件的任务 */
typedef enum
{
    EVT_DMA_DONE,
    EVT_UART_RX,
    EVT_CTRL_CMD,
    EVT_ERROR,
} EventType;

typedef struct
{
    EventType type;
    uint8_t priority;   // 自定义优先级(可选)

    union
    {
        struct {
            uint32_t addr;
            uint32_t len;
        } dma;

        struct {
            uint8_t data[32];
            uint32_t len;
        } uart;

        struct {
            int cmd;
        } ctrl;

        struct {
            int err_code;
        } error;
    };
} EventMsg;


/* 不同task 发送对应事件 */
void DMA_ISR(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    EventMsg msg;
    msg.type = EVT_DMA_DONE;
    msg.priority = 2;
    msg.dma.addr = DMA_ADDR;
    msg.dma.len  = DMA_LEN;

    xQueueSendFromISR(eventQueue, &msg, &xHigherPriorityTaskWoken);

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

void ControlTask(void *arg)
{
    EventMsg msg;

    for(;;)
    {
        msg.type = EVT_CTRL_CMD;
        msg.priority = 0;
        msg.ctrl.cmd = get_cmd();

        xQueueSend(eventQueue, &msg, portMAX_DELAY);
    }
}

/* ERROR task优先级最高,不用队列,直接task notification */
TaskHandle_t workerTask;

void ERROR_ISR(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    vTaskNotifyGiveFromISR(workerTask, &xHigherPriorityTaskWoken);

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* 统一处理Task */
void WorkerTask(void *arg)
{
    EventMsg msg;

    for(;;)
    {
        // 优先处理 error(最高优先级)
        /* 无阻等待:直达通知时没有take, 不会错过这次通知,但多次通知不会累积 */
        if(ulTaskNotifyTake(pdTRUE, 0) > 0)
        {
            handle_error();
            continue;
        }

        // 从队列取事件
        if(xQueueReceive(eventQueue, &msg, portMAX_DELAY))
        {
            switch(msg.type)
            {
                case EVT_DMA_DONE:
                    handle_dma(msg.dma.addr, msg.dma.len);
                    break;

                case EVT_UART_RX:
                    handle_uart(msg.uart.data, msg.uart.len);
                    break;

                case EVT_CTRL_CMD:
                    handle_ctrl(msg.ctrl.cmd);
                    break;

                default:
                    break;
            }
        }
    }
}


Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐