当前位置:首页 > 技术 > 正文内容

FreeRTOS 消息队列机制解析与 STM32 实战指南

访客 技术 2026年10月28日 1

嵌入式系统中的任务通信机制

在基于实时操作系统(RTOS)的嵌入式开发中,多任务协同工作是核心需求。任务之间、任务与中断服务程序之间需要可靠的数据交换与同步机制。FreeRTOS 内核提供了一套基于队列(Queue)的通信方案,它是系统内部所有通信原语的基础。

一、队列基本原理

队列本质上是一块共享的内存区域,用于存储特定类型的数据项。它可以被多个任务或中断同时访问,但内核保证了访问的原子性与安全性。

队列具有深度(Length)和项大小(Item Size)两个关键属性。深度决定了队列能缓存多少条消息,项大小定义了每条消息占用的字节数。数据通常遵循先进先出(FIFO)原则,即后写入的数据排在队尾,先写入的数据从队头读出。当然,开发者也可以配置为后进先出(LIFO)模式。

为了区分数据来源,通常建议将数据源标识与数据内容封装在结构体中一同发送,这样接收端可以根据标识符决定如何处理数据。

二、核心 API 函数解析

1. 队列的创建

队列必须在使用前初始化。FreeRTOS 支持动态内存分配和静态内存分配两种方式。动态创建由内核管理内存,静态创建则由开发者提供内存缓冲区,适合对内存确定性要求高的场景。


// 动态创建队列
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

// 静态创建队列
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
                                 UBaseType_t uxItemSize,
                                 uint8_t *pucQueueStorageBuffer,
                                 StaticQueue_t *pxQueueBuffer);

示例:创建一个能容纳 8 个 uint32_t 类型数据的队列。


QueueHandle_t g_msgQueue;
g_msgQueue = xQueueCreate(8, sizeof(uint32_t));

2. 数据发送

向队列写入数据被称为"发送"。根据写入位置不同,可分为发送到队尾(FIFO)或队头(LIFO)。此外,针对中断上下文,提供了后缀为 FromISR 的安全版本。


// 发送到队尾(阻塞模式)
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);

// 发送到队头(实现 LIFO)
BaseType_t xQueueSendToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);

// 中断安全版本
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);

对于长度仅为 1 的队列,若队列已满,可使用覆盖发送函数,新数据将直接替换旧数据。


BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);

3. 数据接收

从队列读取数据被称为"接收"。读取操作可以选择是否将数据项从队列中移除。


// 读取并移除数据
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);

// 仅读取不移除( Peek 操作)
BaseType_t xQueuePeek(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);

// 中断安全接收
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken);

4. 状态查询与管理

开发者可以查询队列的剩余空间或待处理消息数量,也可执行删除或复位操作。


UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue); // 剩余空间
UBaseType_t uxQueueMessagesWaiting(const QueueHandle_t xQueue); // 待处理消息数
void vQueueDelete(QueueHandle_t pxQueueToDelete); // 删除队列
BaseType_t xQueueReset(QueueHandle_t xQueue); // 复位队列

5. 阻塞机制

当任务向满队列发送数据,或从空队列接收数据时,若设置了等待时间,任务将进入阻塞状态。以下情况可解除阻塞:

  • 等待超时时间到达。
  • 队列状态发生变化(如有了新空间或新数据)。

若多个任务因同一队列阻塞,优先级最高的任务将优先被唤醒;若优先级相同,则等待时间最长的任务优先。

三、STM32 实战案例

1. 实验设计

本案例旨在验证任务间及中断与任务间的通信能力。

  • 创建一个深度为 20 的全局队列 g_msgQueue。
  • 创建生产者任务 vProducerTask:扫描三个按键,按下不同按键发送不同数值。
  • 创建消费者任务 vConsumerTask:从队列读取数据并通过串口打印。
  • 配置 RTC 唤醒中断:每秒向队列发送特定中断标识数据。

2. 硬件抽象层配置

使用 STM32CubeMX 工具进行初始化。首先配置 GPIO 引脚用于按键输入(KEY0, KEY1, KEY2),配置 USART1 用于日志输出。接着启用 RTC 模块并配置为周期性唤醒模式。

在 FreeRTOS 中间件配置中,添加两个任务并创建一个队列。注意 RTC 中断优先级需配置在 FreeRTOS 最大可调用中断优先级范围内(通常为 5-15 之间),此处设为 7。

3. 代码实现

在生成的工程中,需重定向 printf 函数至串口。并在 freertos.c 中包含必要头文件。


#include "stdio.h"
#include "queue.h"

实现 RTC 回调函数,在中断中发送数据。注意使用中断安全 API 并检查是否需要上下文切换。


void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
    uint32_t interruptData = 99;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    // 中断上下文发送数据
    xQueueSendToBackFromISR(g_msgQueue, &interruptData, &xHigherPriorityTaskWoken);
    
    // 触发上下文切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

生产者任务逻辑:检测按键状态,映射不同数值发送。


void vProducerTask(void *argument)
{
    uint32_t msgData = 0;
    for(;;)
    {
        msgData = 0;
        if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET)
            msgData = 30;
        else if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)
            msgData = 20;
        else if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
            msgData = 10;
            
        if(msgData != 0)
        {
            // 发送数据,阻塞等待 50ms
            if(xQueueSendToBack(g_msgQueue, &msgData, pdMS_TO_TICKS(50)) == errQUEUE_FULL)
            {
                xQueueReset(g_msgQueue);
            }
            osDelay(300); // 按键消抖
        }
        else
        {
            osDelay(10);
        }
    }
}

消费者任务逻辑:循环读取队列数据并打印状态。


void vConsumerTask(void *argument)
{
    UBaseType_t waitingMsg = 0;
    UBaseType_t spaceLeft = 0;
    uint32_t recvData = 0;
    
    for(;;)
    {
        waitingMsg = uxQueueMessagesWaiting(g_msgQueue);
        spaceLeft = uxQueueSpacesAvailable(g_msgQueue);
        
        if(xQueueReceive(g_msgQueue, &recvData, pdMS_TO_TICKS(50)) == pdPASS)
        {
            printf("Waiting: %d, Space: %d, Data: %d\r\n", 
                   (int)waitingMsg, (int)spaceLeft, (int)recvData);
        }
        osDelay(100);
    }
}

4. 验证结果

编译并下载程序至开发板。打开串口调试工具,观察到每秒自动输出一条数据为 99 的消息,代表 RTC 中断触发。当按下 KEY0 时,输出数据 10;按下 KEY1 时,输出数据 20;按下 KEY2 时,输出数据 30。串口终端实时显示队列当前的等待消息数与剩余空间,验证了队列通信机制正常工作。

标签: FreeRTOSSTM32

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。