FreeRTOS队列通信

c 复制代码
QueueHandle_t xQueue;
void vSenderTask(void *pvParameters) {
    int data = 0;
    while(1) {
        data++;
        xQueueSend(xQueue, &data, portMAX_DELAY);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void vReceiverTask(void *pvParameters) {
    int receivedData;
    while(1) {
        if(xQueueReceive(xQueue, &receivedData, portMAX_DELAY) == pdPASS) {
            printf("Received: %d\r\n", receivedData);
        }
    }
}

下面将结合上述代码示例,详细讲解 FreeRTOS 中的队列(Queue)通信机制 。队列是 FreeRTOS 中任务间(或任务与中断间)传递数据的主要手段,它提供了线程安全 的、基于拷贝的数据交换方式,并支持阻塞等待。


一、队列的基本概念

  • 队列是一个"先入先出"(FIFO)的线性表,用于存储固定大小的数据单元。
  • 每个队列内部维护一段连续的内存缓冲区,以及读指针、写指针和记录当前消息个数的计数器。
  • 核心特点
    • 数据拷贝:发送时将数据拷贝到队列内部缓冲区,接收时将数据从队列缓冲区拷贝到用户变量。因此队列中保存的是数据的副本,而非指针或引用。
    • 多对多通信:多个任务可以向同一个队列发送消息,多个任务也可以从同一个队列接收消息。
    • 阻塞机制:当队列满时,发送任务可选择阻塞等待空间;当队列空时,接收任务可选择阻塞等待消息。阻塞超时时间可配置。
    • 线程安全:所有队列 API 内部均使用临界区或关中断保护,无需用户额外加锁。

二、代码结构分析

代码片段包含了两个任务函数 vSenderTaskvReceiverTask,以及一个全局的队列句柄 xQueue。为了完整运行,还需要在 main 函数中创建队列和任务。下面逐部分讲解。

2.1 队列的创建(代码中未显示,但必不可少)

c 复制代码
// 创建队列:每个消息大小为 sizeof(int),队列深度为 5(举例)
xQueue = xQueueCreate(5, sizeof(int));
if (xQueue == NULL) {
    // 创建失败处理
}
  • xQueueCreate(5, sizeof(int)):创建能存放 5 个 int 类型数据的队列。
  • 返回值:成功返回队列句柄(QueueHandle_t),失败返回 NULL(通常因内存不足)。

2.2 发送任务 vSenderTask

c 复制代码
void vSenderTask(void *pvParameters) {
    int data = 0;
    while(1) {
        data++;
        xQueueSend(xQueue, &data, portMAX_DELAY);   // 发送数据
        vTaskDelay(1000 / portTICK_PERIOD_MS);      // 延时 1 秒
    }
}
  • xQueueSend 等价于 xQueueSendToBack(向队列尾部写入)。
    • 参数1 xQueue:目标队列句柄。
    • 参数2 &data:待发送数据的指针。队列会将这 sizeof(int) 字节的数据拷贝到其内部缓冲区。
    • 参数3 portMAX_DELAY:阻塞最大时间。这里表示若队列已满,任务将无限期等待(直到队列有空位)。
  • vTaskDelay :使任务进入阻塞态 1000 ms,让出 CPU。这样发送频率为每秒一次。

2.3 接收任务 vReceiverTask

c 复制代码
void vReceiverTask(void *pvParameters) {
    int receivedData;
    while(1) {
        if(xQueueReceive(xQueue, &receivedData, portMAX_DELAY) == pdPASS) {
            printf("Received: %d\r\n", receivedData);
        }
    }
}
  • xQueueReceive :从队列头部取出一个消息,并删除该消息。
    • 参数2 &receivedData:接收缓冲区的指针,用于存放拷贝出的数据。
    • 参数3 portMAX_DELAY:若队列为空,任务无限期等待直到有消息到达。
  • 返回值 pdPASS 表示成功接收到数据;pdFALSE(或 errQUEUE_EMPTY)表示超时未收到。

三、工作流程与调度细节

假设创建队列深度为 5,以下展示典型运行流程:

  1. 初始状态:队列为空。
  2. 接收任务 :调用 xQueueReceive,因为队列空,接收任务进入阻塞态(等待消息)。
  3. 发送任务 :运行 → data++(变成 1)→ xQueueSend 成功(队列空 → 变为 1 个消息)→ vTaskDelay 进入阻塞 1 秒。
  4. 此时接收任务 :因为有消息到达,从阻塞态被唤醒,取出数据 1 并打印。然后再次调用 xQueueReceive,队列再度变空,重新阻塞。
  5. 1 秒后 :发送任务恢复运行,发送 2,再次延时,接收任务收到 2......循环往复。

如果发送速度超过接收速度,队列会逐渐填满。一旦队列满,发送任务调用 xQueueSend 时就会阻塞(参数 portMAX_DELAY 导致永久阻塞),直到接收任务取走一条消息,腾出空间。


四、队列的关键 API 说明

函数 作用 中断安全版本
xQueueCreate 创建队列 -
xQueueSend / xQueueSendToBack 从队尾发送数据 xQueueSendToBackFromISR
xQueueSendToFront 从队首发送数据(LIFO 行为) xQueueSendToFrontFromISR
xQueueReceive 从队首接收并删除数据 xQueueReceiveFromISR
xQueuePeek 从队首接收但不删除数据 xQueuePeekFromISR
uxQueueMessagesWaiting 查询队列中当前消息个数 uxQueueMessagesWaitingFromISR
  • 阻塞时间 :单位是 tick,可以使用 pdMS_TO_TICKS(ms) 转换毫秒。portMAX_DELAY 表示无限等待(前提是 INCLUDE_vTaskSuspend 为 1)。
  • 中断中使用 :必须调用带 FromISR 后缀的版本,并传递 pxHigherPriorityTaskWoken 参数以决定是否在中断退出后执行上下文切换。

五、队列通信的优势与适用场景

优势

  • 解耦:发送方和接收方不必知道彼此的存在,只需共享队列句柄。
  • 数据完整性:通过拷贝方式避免共享内存的竞态条件。
  • 自然实现阻塞同步:可以用队列空/满来实现任务间的同步(例如生产者-消费者模型)。
  • 灵活的超时控制:既可以通过超时实现轮询,也可以通过无限等待实现精准同步。

适用场景

  • 生产者-消费者模型(如传感器数据采集、日志打印)。
  • 命令传递(如 GUI 任务向工作任务发送指令)。
  • 多源数据汇聚(多个任务向同一队列发送,由一个任务集中处理)。

六、注意事项

  1. 队列大小与内存:队列内部缓冲区在创建时分配,若队列中消息长度较大(如结构体),需权衡内存占用。
  2. 任务优先级设定
    • 如果生产者优先级高于消费者,且队列深度有限,生产者可能长期阻塞导致高优先级任务无法运行(反直觉)。
    • 通常让消费者优先级稍高于生产者,确保数据被及时处理。
  3. 不要在中断中阻塞 :中断服务程序中只能使用非阻塞版本(超时参数为 0)或 FromISR 版本。
  4. 数据拷贝开销:对于大数据(如几 KB 的数组),频繁拷贝会占用 CPU。此时可考虑用队列传递指向缓冲区的指针(但需自行管理内存和互斥)。
  5. 队列重置xQueueReset 可以清空队列所有消息,但要注意正在阻塞的任务处理。

七、扩展:队列集(Queue Set)

FreeRTOS 还提供了队列集 ,允许一个任务同时等待多个队列(或信号量)上的消息,类似 select/poll 机制。当任意一个队列有数据时,任务被唤醒并得知是哪个队列就绪。


总结

代码示例展示了 FreeRTOS 队列通信的基本模式:

  1. 创建队列。
  2. 发送任务周期性地调用 xQueueSend 入队数据(可能阻塞)。
  3. 接收任务调用 xQueueReceive 取出数据(可能阻塞)。

队列作为 FreeRTOS 的核心组件,使用简单、功能强大,是嵌入式多任务编程中不可或缺的通信工具。掌握其原理和 API,可以构建出高内聚、低耦合的实时系统。

附录

工程架构参考

user_program.c
  • 可以在自己的工程目录main.c调用该函数
c 复制代码
/**
 * @brief  主应用函数:
 * 
 * @note   None
 * @param  None
 * @return None
 * @warning 此函数不会返回,内部包含无限循环
 * @remark  通过函数指针从main()调用,支持灵活的启动架构
 */
void Current_Program3(void)
{	
	
void vSenderTask(void *pvParameters);
void vReceiverTask(void *pvParameters);	
	
	LED_Init();			//	初始化LED设备
	UART1_Config();		//	UART1串口初始化
	
	// 创建队列:每个消息大小为 sizeof(int),队列深度为 5(举例)
	xQueue = xQueueCreate(5, sizeof(int));
	if (xQueue == NULL) {
		// 队列创建失败处理(例如死循环、点亮错误指示灯等)
		while(1);
    }
	
	xTaskCreate(vSenderTask, "vSenderTask", 128, NULL, 1, NULL);
	xTaskCreate(vReceiverTask, "vReceiverTask", 128, NULL, 1, NULL);
	
	// 启动调度器(永远不会返回)
	vTaskStartScheduler();	
	
	// 理论上程序不会执行到这里,但为了安全,可以加一个死循环
	while(1){

	}
}
user_task.c
c 复制代码
/* 队列通信 */
QueueHandle_t xQueue;

// 发送任务 vSenderTask
void vSenderTask(void *pvParameters) {
    int data = 0;
    while(1) {
        data++;
        xQueueSend(xQueue, &data, portMAX_DELAY);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

// 接收任务 vReceiverTask
void vReceiverTask(void *pvParameters) {
    int receivedData;
    while(1) {
        if(xQueueReceive(xQueue, &receivedData, portMAX_DELAY) == pdPASS) {
            printf("Received: %d\r\n", receivedData);
        }
    }
}
user_task.h
c 复制代码
/**
  * @file    user_task.c
  * @brief   user task module
  * @author  xyx-3v
  * @date    2026-04-28
  * @version 1.0
  * @note
  */
  
#ifndef __USER_TASK_H
#define __USER_TASK_H


#include <stdint.h>

/* FreeRTOS三方库 */
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"					//	vTaskDelay等
#include "queue.h"  				//  QueueHandle_t

/* 队列通信 */
extern QueueHandle_t xQueue;

// 发送任务 vSenderTask
void vSenderTask(void *pvParameters);

// 接收任务 vReceiverTask
void vReceiverTask(void *pvParameters);

#endif /* __USER_TASK_H */ 

实验现象演示

相关推荐
存在的五月雨1 小时前
uniapp 一些组件的使用
java·前端·uni-app
weixin_451431561 小时前
HLS加密流解码异常导致视频花屏?通用技术解析及合规指引
网络·音视频
wanhengidc1 小时前
云手机是什么黑科技?
运维·网络·科技·安全·web安全·智能手机
我命由我123451 小时前
Kotlin 开发 - 双冒号操作符(引用顶层函数、引用成员函数、引用构造函数、引用属性、引用类)
android·java·开发语言·kotlin·android studio·android jetpack·android-studio
佳xuan1 小时前
QA与RAG检索
java·服务器·前端
2401_873479401 小时前
遭遇DDoS攻击后如何快速分析攻击源?用IP查询+离线库定位异常IP
服务器·开发语言·tcp/ip·php
Brookty1 小时前
网络标识关系、Socket通信与UDP数据报首部介绍
网络·udp
半瓶榴莲奶^_^4 小时前
jvm java虚拟机
java·jvm
lifewange5 小时前
RPC 是什么
网络·网络协议·rpc