在 FreeRTOS 中,队列的读取函数用于从队列中获取数据,根据是否需要移除数据项或仅查看数据,分为以下两种核心函数:
1. 从队列中移除并读取数据:xQueueReceive
函数原型
BaseType_t xQueueReceive(
QueueHandle_t xQueue, // 队列句柄(目标队列)
void *pvBuffer, // 接收数据的缓冲区指针
TickType_t xTicksToWait // 最大阻塞时间(单位:Tick)
);
功能
- 从队列头部(队首)读取一个数据项,并移除该数据。
- 如果队列为空,任务会进入阻塞状态,直到队列中有数据或超时。
参数说明
xQueue:目标队列的句柄,由xQueueCreate()创建。pvBuffer:指向接收数据的缓冲区,需确保缓冲区大小与队列数据项一致。xTicksToWait:阻塞超时时间:0:立即返回,不等待。portMAX_DELAY:无限阻塞,直到队列中有数据。- 其他值:阻塞指定的 Tick 数(例如
pdMS_TO_TICKS(100)表示阻塞 100ms)。
返回值
pdPASS:成功读取数据。pdFAIL:超时或队列句柄无效。
示例
// 定义数据格式
typedef struct {
uint8_t sensor_id;
float value;
} SensorData;
// 任务中读取队列
SensorData data;
if (xQueueReceive(xSensorQueue, &data, portMAX_DELAY) == pdPASS) {
process_sensor_data(&data); // 处理数据
}
2. 查看队列数据但不移除:xQueuePeek
函数原型
BaseType_t xQueuePeek(
QueueHandle_t xQueue, // 队列句柄
void *pvBuffer, // 接收数据的缓冲区指针
TickType_t xTicksToWait // 最大阻塞时间
);
功能
- 从队列头部读取数据项,但不将其从队列中移除。
- 行为与
xQueueReceive类似,但数据保留在队列中供其他任务读取。
使用场景
- 需要多个任务查看同一数据(例如广播监控)。
- 数据需被多次处理(例如日志记录和实时显示同时进行)。
示例
SensorData data;
if (xQueuePeek(xSensorQueue, &data, 0) == pdPASS) {
display_data(&data); // 显示数据(数据仍保留在队列中)
}
3. 中断安全版本
在中断服务程序(ISR)中,需使用以下函数以避免阻塞:
3.1 xQueueReceiveFromISR
BaseType_t xQueueReceiveFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxHigherPriorityTaskWoken
);
3.2 xQueuePeekFromISR
BaseType_t xQueuePeekFromISR(
QueueHandle_t xQueue,
void *pvBuffer
);
参数说明
pxHigherPriorityTaskWoken:标记是否有高优先级任务被唤醒,退出中断后需调用portYIELD_FROM_ISR()触发任务切换。
示例(在 ISR 中读取队列)
void vSensorISR() {
SensorData data;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueReceiveFromISR(xSensorQueue, &data, &xHigherPriorityTaskWoken) == pdPASS) {
process_in_isr(&data); // 中断中快速处理
}
// 如果有任务被唤醒,触发上下文切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
4. 多任务竞争与调度策略
- 优先级决定顺序:
如果多个任务阻塞在同一个队列上,当数据到达时,最高优先级任务优先接收数据。 - 同优先级任务:
若多个任务优先级相同,则等待时间最长的任务(按 FIFO)获得数据。
5. 设计注意事项
-
数据所有权
- 使用
xQueueReceive后,数据所有权转移到接收任务,需确保发送方不再修改数据。 - 使用
xQueuePeek时,数据仍属于队列,可能被后续的xQueueReceive移除。
- 使用
-
队列深度与性能
- 队列深度不足可能导致数据丢失(发送方阻塞或覆盖)。
- 频繁的小数据传递建议使用任务通知(更高效)。
-
超时处理
- 避免在关键任务中使用
portMAX_DELAY,防止系统死锁。 - 超时后应检查返回值并处理异常(例如重试或错误日志)。
- 避免在关键任务中使用
6. 典型应用场景
场景 1:生产者-消费者模型
// 生产者任务(发送数据)
void vProducerTask(void *pvParameters) {
SensorData data;
while (1) {
read_sensor(&data);
xQueueSend(xSensorQueue, &data, portMAX_DELAY); // 阻塞直到发送成功
}
}
// 消费者任务(接收数据)
void vConsumerTask(void *pvParameters) {
SensorData data;
while (1) {
if (xQueueReceive(xSensorQueue, &data, pdMS_TO_TICKS(100)) == pdPASS) {
process_data(&data);
} else {
log_error("Queue timeout!"); // 处理超时
}
}
}
场景 2:数据广播(多任务查看)
// 任务1:实时显示数据
void vDisplayTask(void *pvParameters) {
SensorData data;
while (1) {
xQueuePeek(xSensorQueue, &data, portMAX_DELAY);
update_display(&data); // 不移除数据,供其他任务使用
}
}
// 任务2:记录数据到存储
void vLoggerTask(void *pvParameters) {
SensorData data;
while (1) {
xQueueReceive(xSensorQueue, &data, portMAX_DELAY);
save_to_flash(&data); // 移除数据,确保只记录一次
}
}
7. 常见问题
Q1:队列为空时,xQueueReceive 和 xQueuePeek 的行为?
- 若指定
xTicksToWait > 0,任务进入阻塞状态,直到数据到达或超时。 - 若
xTicksToWait = 0,立即返回pdFAIL。
Q2:如何在中断中安全读取队列?
- 必须使用
xQueueReceiveFromISR或xQueuePeekFromISR,并处理任务切换标记。
Q3:多个任务等待同一队列时,如何保证特定任务接收数据?
- 无法直接指定接收者,需通过设计解决:
- 为每个任务分配独立队列(点对点通信)。
- 在消息中添加目标标识符,非目标任务重新放回队列(需谨慎处理竞争)。
总结
xQueueReceive用于消费数据(移除队列项),xQueuePeek用于查看数据(保留队列项)。- 中断中必须使用
FromISR后缀的函数,并处理任务切换。 - 通过合理设计队列所有权和超时机制,可以构建高效可靠的多任务通信系统。
1万+

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



