4. 队列(Queue):任务间通信的利器

大家好,我是你们的嵌入式系统讲师。今天我们来聊聊FreeRTOS里一个非常核心的机制——队列。

队列是什么?说白了,它就是任务之间传递数据的“邮局”。一个任务可以把数据“寄”到队列里,另一个任务再从队列里“收”到数据。这个过程是线程安全的,你不用操心互斥锁那些烦心事。

4.1 队列的创建

在ESP32上用FreeRTOS,创建队列的API是xQueueCreate()。我个人习惯在系统初始化阶段就把队列建好,省得后面手忙脚乱。

// 创建一个队列,能存放5个int类型的数据
QueueHandle_t xQueue;
xQueue = xQueueCreate(5, sizeof(int));

if (xQueue == NULL) {
    // 创建失败,内存不够了
    ESP_LOGE("QUEUE", "Failed to create queue");
}

这里有两个参数:队列长度和每个数据项的大小。我建议你根据实际需求来定,别贪多。我在项目中遇到过,有人把队列长度设得特别大,结果内存直接爆了。

注意:队列创建会从堆中分配内存。ESP32的堆空间有限,尤其是当你的项目里有很多队列、信号量、任务时,一定要算好内存用量。

4.2 队列的发送

发送数据到队列,用xQueueSend()。这个函数会把数据拷贝到队列内部,所以不用担心原数据被修改。

int data_to_send = 42;
BaseType_t ret = xQueueSend(xQueue, &data_to_send, portMAX_DELAY);

if (ret != pdPASS) {
    // 发送失败,队列满了
    ESP_LOGE("QUEUE", "Send failed");
}

第三个参数是等待时间。如果你设成portMAX_DELAY,任务会一直等,直到队列有空位。我曾经在一个项目中,因为队列满了,发送任务被阻塞了整整几秒钟,导致整个系统响应变慢。后来我改成pdMS_TO_TICKS(100),超时了就丢包,系统反而更稳定了。

小技巧:如果你发送的是结构体,记得结构体要尽量小。队列内部是值拷贝,太大的结构体会占用大量内存和拷贝时间。

4.3 队列的接收

接收数据用xQueueReceive()。它会从队列头部取出一个数据,然后拷贝到你提供的缓冲区里。

int received_data;
BaseType_t ret = xQueueReceive(xQueue, &received_data, portMAX_DELAY);

if (ret == pdPASS) {
    ESP_LOGI("QUEUE", "Received: %d", received_data);
} else {
    ESP_LOGE("QUEUE", "Receive failed");
}

这里要注意:接收缓冲区的大小必须和队列创建时指定的数据项大小一致。否则数据会截断或溢出,后果很严重。

为什么接收也要设超时?你想想看,如果队列是空的,接收任务就会一直等。有时候你希望任务在等待期间干点别的事,比如检查其他标志位,那就设一个较短的超时时间。

4.4 队列在任务间通信的应用

队列最常见的用法,就是让一个任务产生数据,另一个任务消费数据。比如传感器采集任务把数据发给处理任务。

// 传感器采集任务
void sensor_task(void *pvParameters) {
    int sensor_value;
    while (1) {
        sensor_value = read_sensor();
        xQueueSend(sensor_queue, &sensor_value, pdMS_TO_TICKS(10));
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// 数据处理任务
void process_task(void *pvParameters) {
    int value;
    while (1) {
        if (xQueueReceive(sensor_queue, &value, pdMS_TO_TICKS(50)) == pdPASS) {
            process_sensor_data(value);
        } else {
            // 没收到数据,做点别的
            check_other_stuff();
        }
    }
}

这种模式的好处是:两个任务可以跑在不同的核心上,互不干扰。ESP32是双核的,你可以把采集任务放在Core 0,处理任务放在Core 1,效率杠杠的。

核心要点:队列是FreeRTOS任务间通信的基石。它解耦了生产者和消费者,让系统更模块化、更易维护。

4.5 避坑指南

我曾经在一个项目中,队列接收任务优先级设得太低,结果数据一直积压在队列里,等到处理时已经过时了。嗯,这里要注意:接收任务的优先级最好比发送任务高,或者至少相等,否则数据会“堵车”。

还有一次,我忘了检查xQueueSend()的返回值,结果队列满了,数据丢了,系统还浑然不知。从那以后,我每次发送和接收都会检查返回值,哪怕只是打印个日志。

函数 作用 返回值
xQueueCreate() 创建队列 队列句柄,失败返回NULL
xQueueSend() 发送数据到队列尾部 pdPASS 或 errQUEUE_FULL
xQueueReceive() 从队列头部接收数据 pdPASS 或 errQUEUE_EMPTY

最后,记住一点:队列是实时系统的“血管”。设计好队列的大小、超时时间、优先级,你的系统才能流畅运行。别问我怎么知道的,都是踩坑踩出来的经验。