FreeRTOS队列使用三步走:创建、写入与读取完整指南

73 阅读5分钟

FreeRTOS队列的使用

队列是FreeRTOS任务间通信的核心机制。本文将带你掌握队列的创建、写入和读取三个核心操作,通过清晰的代码示例和关键要点。

1. 队列是什么?

在FreeRTOS中,队列是一种先进先出的数据结构,主要用于:

  • 任务间的数据传递
  • 中断与任务间的通信
  • 实现生产者-消费者模型

可以把它想象成一条传送带:生产者把物品放上去,消费者按顺序取走。

2. 第一步:创建队列

2.1 核心函数

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

2.2 完整创建流程

/* ---------- 第一步:创建队列 ---------- */

// 1. 定义要在队列中传递的数据类型
typedef struct {
    uint8_t  cmd;    // 命令类型
    uint32_t value;  // 数值参数
} message_t;         // 消息结构体

// 2. 声明队列句柄(类似文件指针)
QueueHandle_t myQueue;

// 3. 创建队列
myQueue = xQueueCreate(
    10,                  // 队列深度:最多能存10条消息
    sizeof(message_t)    // 消息大小:每条消息占用的字节数
);

// 4. 【重要】检查创建是否成功
if (myQueue == NULL) {
    // 创建失败!通常是内存不足
    // 应该进行错误处理,比如重启或报警
    while(1);  // 示例:死循环等待
}

2.3 创建要点总结

项目说明
队列深度根据实际需求设置,太小容易满,太大浪费内存
消息大小必须准确计算数据类型的大小
返回值检查必须检查,NULL表示创建失败
全局访问队列句柄要能被所有使用它的任务访问

3. 第二步:写入队列

3.1 在任务中写入

/* ---------- 第二步:向队列写入数据 ---------- */

// 准备要发送的数据
message_t msg_to_send = { 
    .cmd = 1,      // 命令编号1
    .value = 100   // 数值100
};

// 核心写入函数
BaseType_t result = xQueueSend(
    myQueue,                // 队列句柄
    &msg_to_send,           // 要发送数据的地址
    pdMS_TO_TICKS(100)      // 等待时间:最多等100ms
);

// 等待时间参数说明:
// pdMS_TO_TICKS(100)   -> 等待100毫秒
// 0                    -> 不等待,立即返回
// portMAX_DELAY        -> 一直等到队列有空位

// 检查写入结果
if (result != pdPASS) {
    // 写入失败:队列始终满,超时返回
    // 可以记录日志或采取其他措施
}

3.2 在中断中写入

/* ---------- 中断服务函数中写入 ---------- */

// 【注意】中断中必须使用特殊版本函数
void MyISR_Handler(void)
{
    message_t msg = { .cmd = 2, .value = 200 };
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    // 使用FromISR版本
    xQueueSendFromISR(
        myQueue,
        &msg,
        &xHigherPriorityTaskWoken  // 传出参数
    );
    
    // 检查是否需要立即进行任务切换
    if (xHigherPriorityTaskWoken == pdTRUE) {
        portYIELD_FROM_ISR();  // 执行一次任务切换
    }
}

3.3 写入要点总结

场景使用函数关键区别
任务中xQueueSend可以设置等待时间
中断中xQueueSendFromISR不能等待,需要处理唤醒标志

4. 第三步:读取队列

4.1 在任务中读取

/* ---------- 第三步:从队列读取数据 ---------- */

// 准备接收数据的变量
message_t received_msg;

// 核心读取函数
BaseType_t result = xQueueReceive(
    myQueue,                // 队列句柄
    &received_msg,          // 接收数据的缓冲区
    pdMS_TO_TICKS(200)      // 等待时间:最多等200ms
);

// 处理读取结果
if (result == pdPASS) {
    // 成功读取到数据
    printf("收到命令: %d, 数值: %d\n", 
           received_msg.cmd, 
           received_msg.value);
    
    // 根据命令执行相应操作
    process_command(&received_msg);
} else {
    // 读取失败:队列始终为空
    // 可能是没有数据或超时
}

4.2 在中断中读取(较少使用)

/* ---------- 中断中读取 ---------- */
void AnotherISR_Handler(void)
{
    message_t msg;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    xQueueReceiveFromISR(
        myQueue,
        &msg,
        &xHigherPriorityTaskWoken
    );
    
    if (xHigherPriorityTaskWoken == pdTRUE) {
        portYIELD_FROM_ISR();
    }
    
    // 注意:中断中处理要尽量简短
}

5. 关键对比与记忆要点

5.1 任务与中断API对比

操作任务中函数中断中函数核心区别
写入xQueueSendxQueueSendFromISR中断版不能等待
读取xQueueReceivexQueueReceiveFromISR中断版不能等待

5.2 一句话记住核心

创建得句柄,写入传地址,读取收数据,中断用FromISR。

6. 实际应用示例

6.1 按键消息处理

// 按键消息定义
typedef struct {
    uint8_t key_id;     // 按键编号
    uint8_t key_state;  // 按下/释放
} key_msg_t;

// 创建按键消息队列
QueueHandle_t key_queue = xQueueCreate(5, sizeof(key_msg_t));

// 按键扫描任务(生产者)
void KeyScan_Task(void *param)
{
    key_msg_t msg;
    while(1) {
        if (检测到按键按下) {
            msg.key_id = 1;
            msg.key_state = 1;  // 按下
            xQueueSend(key_queue, &msg, 0);
        }
        vTaskDelay(10);  // 10ms扫描一次
    }
}

// 按键处理任务(消费者)
void KeyProcess_Task(void *param)
{
    key_msg_t msg;
    while(1) {
        if (xQueueReceive(key_queue, &msg, portMAX_DELAY) == pdPASS) {
            if (msg.key_state == 1) {
                LED_Toggle();  // 按键按下时切换LED
            }
        }
    }
}

7. 常见问题与解决

7.1 队列满了怎么办?

  • 增加队列深度
  • 提高消费者任务优先级
  • 检查是否有任务没有及时读取

7.2 队列一直空怎么办?

  • 检查生产者任务是否正常运行
  • 确认数据是否正确发送
  • 使用uxQueueMessagesWaiting()查看队列中消息数量

7.3 内存不足怎么办?

  • 减少队列深度
  • 减小消息大小
  • 优化系统内存分配

8. 总结

FreeRTOS队列的使用可以总结为三个核心操作:

  1. 创建xQueueCreate(深度, 大小) → 得到队列句柄
  2. 写入xQueueSend(句柄, &数据, 等待时间) → 发送数据
  3. 读取xQueueReceive(句柄, &变量, 等待时间) → 接收数据

记住关键规则

  • 创建后一定要检查返回值
  • 任务和中断使用不同的API
  • 数据传输是复制,不是引用

掌握这三个步骤,就能够使用队列在FreeRTOS任务间进行可靠的数据通信了。从简单的消息传递开始,逐步应用到更复杂的场景中,队列会成为你嵌入式开发的重要工具。