news 2026/3/13 8:48:27

FreeRTOS 入门(二十六):队列创建与读写 API 实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS 入门(二十六):队列创建与读写 API 实战解析

目录

  • 一、前言
  • 二、队列的两种创建方式:动态与静态
  • 三、实验平台说明与实战准备
  • 四、实战三步:队列在 IR 中断中的应用
  • 五、写队列 API:任务 / ISR 场景专用函数
  • 六、读队列 API:数据接收核心函数
  • 七、中断场景写队列实现
  • 八、总结
  • 九、下一篇预告
  • 十、结尾

一、前言

大家好,我是Hello_Embed。上一篇我们解析了队列的本质的核心流程,知道它是 “带互斥 + 阻塞机制” 的环形缓冲区,是多任务数据传输的可靠方案。本次笔记将聚焦队列的核心操作:从创建(动态 / 静态两种方式),到写队列、读队列的 API 详解,再结合 IR 中断场景实战队列应用,为后续编码器控制挡球板的实战打下基础。所有 API 讲解均基于百问网资料,确保知识点准确可靠。

二、队列的两种创建方式:动态与静态

队列创建与任务创建逻辑一致,支持动态分配内存静态分配内存两种方式,分别适配不同内存管理需求,核心是通过函数创建并获取队列句柄(操作队列的唯一标识)。

补充:以下创建函数相关资料来源百问网,API 参数与返回值解析均基于 FreeRTOS 标准实现。

2.1 动态创建:xQueueCreate(推荐入门使用)

动态创建无需手动分配内存,队列的结构体和数据缓冲区由 FreeRTOS 内核自动分配,操作简单,适合快速开发。

函数原型
QueueHandle_txQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);
参数与返回值说明
参数说明
uxQueueLength队列长度:最多可存放的数据个数(item)
uxItemSize单个数据大小:以字节为单位(如sizeof(uint32_t)
返回值非 NULL:创建成功,返回队列句柄;NULL:内存不足,创建失败

2.2 静态创建:xQueueCreateStatic(内存可控场景)

静态创建需手动分配 “队列结构体缓冲区” 和 “数据存储缓冲区”,内存分配更可控,适合对内存使用有严格要求的场景(如裸机移植、内存紧张的设备)。

函数原型
QueueHandle_txQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t*pucQueueStorageBuffer,StaticQueue_t*pxQueueBuffer);
参数与返回值说明
参数说明
uxQueueLength队列长度:最多可存放的数据个数(item)
uxItemSize单个数据大小:以字节为单位
pucQueueStorageBuffer数据存储缓冲区:指向一个uint8_t数组,大小需≥uxQueueLength * uxItemSize
pxQueueBuffer队列结构体缓冲区:指向StaticQueue_t类型变量,用于保存队列控制信息
返回值非 NULL:创建成功,返回队列句柄;NULL:pxQueueBuffer为 NULL,创建失败
静态创建示例代码
// 示例代码:静态创建队列#defineQUEUE_LENGTH10// 队列长度:最多存10个数据#defineITEM_SIZEsizeof(uint32_t)// 单个数据大小:4字节(uint32_t)// 1. 队列结构体缓冲区:保存队列控制信息StaticQueue_t xQueueBuffer;// 2. 数据存储缓冲区:保存队列实际数据(长度×单个数据大小)uint8_tucQueueStorage[QUEUE_LENGTH*ITEM_SIZE];voidvATask(void*pvParameters){QueueHandle_t xQueue1;// 队列句柄// 3. 静态创建队列xQueue1=xQueueCreateStatic(QUEUE_LENGTH,// 队列长度ITEM_SIZE,// 单个数据大小ucQueueStorage,// 数据存储缓冲区&xQueueBuffer// 队列结构体缓冲区);}

2.3 核心区别总结

创建方式优点缺点适用场景
动态创建操作简单,无需手动分配内存内存分配由内核管理,不可控入门开发、快速验证功能
静态创建内存分配可控,无内存泄漏风险需手动计算并分配缓冲区大小内存紧张设备、工业项目

三、实验平台说明与实战准备

由于此前尝试将图库移植到 TFT-LCD 屏幕未成功,为不耽误 FreeRTOS 学习进度,实验平台调整为:STM32F103C8T6 核心板 + 面包板 + 0.96 寸 OLED 屏。经 OLED 屏幕测试,烧录程序后显示正常,确保实验环境可靠。

另外,因缺少红外遥控器,本次暂不展示实际运行结果,重点讲解 “IR 中断 + 队列” 的实现原理;后续将替换为编码器控制,核心逻辑一致。

核心目标:将原 “环形缓冲区读取 IR 中断数据” 的方案,改为 “队列读写”,解决 CPU 轮询浪费资源的问题。

四、实战三步:队列在 IR 中断中的应用

本次实战核心是 “IR 中断触发写队列,任务读取队列数据”,分三步实现,完美契合多任务数据传输的典型场景:

步骤 1:定义数据结构体(适配 IIC 通信需求)

OLED 通过 IIC 通信,需传输 “设备标识” 和 “数据值”,因此定义输入数据结构体,统一数据传输格式:

// 输入数据结构体:适配IIC通信的设备+数据传输需求structinput_data{uint32_tdev;// 设备标识(区分不同外设)uint32_tval;// 传输的数据值};

步骤 2:创建队列(动态创建方式)

与任务句柄类似,队列句柄是操作队列的核心,本质是 “指向队列控制块的指针别名”。通过动态创建函数创建队列:

QueueHandle_t g_xQueuePlatform;// 全局队列句柄,供中断和任务访问// 在初始化函数中创建队列voidQueue_Init(void){// 创建队列:长度10(最多存10组数据),单个数据大小为input_data结构体大小g_xQueuePlatform=xQueueCreate(10,sizeof(structinput_data));}

步骤 3:绑定 IR 中断与任务(核心流程)

核心逻辑:中断仅负责 “采集数据并写入队列”,任务负责 “读取数据并处理”,解耦中断与业务逻辑,提升系统稳定性。

五、写队列 API:任务 / ISR 场景专用函数

写队列支持 “写尾部”“写头部” 两种方式,且区分 “任务中使用” 和 “ISR(中断)中使用” 的版本 —— 中断中函数不可阻塞,需特别注意。

补充:以下写队列 API 资料来源百问网,函数功能与参数解析均符合 FreeRTOS 标准。

5.1 任务中使用的写队列函数(支持阻塞)

函数名核心功能适用场景
xQueueSend等同于 xQueueSendToBack,写队列尾部任务中,需默认写尾部场景
xQueueSendToBack写队列尾部(FIFO,先进先出)任务中,常规数据传输(推荐)
xQueueSendToFront写队列头部(LIFO,后进先出)任务中,紧急数据优先处理
函数原型(以 xQueueSendToBack 为例)
BaseType_txQueueSendToBack(QueueHandle_t xQueue,constvoid*pvItemToQueue,TickType_t xTicksToWait);

5.2 中断中使用的写队列函数(不可阻塞)

函数名核心功能适用场景
xQueueSendToBackFromISR写队列尾部,中断专用IR、编码器等中断场景
xQueueSendToFrontFromISR写队列头部,中断专用中断中紧急数据传输
函数原型(以 xQueueSendToBackFromISR 为例)
BaseType_txQueueSendToBackFromISR(QueueHandle_t xQueue,constvoid*pvItemToQueue,BaseType_t*pxHigherPriorityTaskWoken);

5.3 通用参数与返回值说明

所有写队列函数参数逻辑一致,统一说明:

参数说明
xQueue队列句柄:指定要写入的队列
pvItemToQueue数据指针:待写入的数据地址,内核会自动复制 “创建时指定大小” 的数据到队列
xTicksToWait阻塞时间(仅任务函数支持):队列满时,任务阻塞的最大 Tick 数;0 = 不阻塞;portMAX_DELAY = 永久阻塞
pxHigherPriorityTaskWoken高优先级任务唤醒标志(仅 ISR 函数支持):NULL = 无需关注
返回值pdPASS = 写入成功;errQUEUE_FULL = 队列满,写入失败

六、读队列 API:数据接收核心函数

读队列的核心是 “从队列中取出数据并移除”,同样区分 “任务中使用” 和 “ISR 中使用” 的版本,任务版本支持阻塞等待数据。

6.1 任务中使用的读队列函数(支持阻塞)

函数原型
BaseType_txQueueReceive(QueueHandle_t xQueue,void*constpvBuffer,TickType_t xTicksToWait);

6.2 中断中使用的读队列函数(不可阻塞)

函数原型
BaseType_txQueueReceiveFromISR(QueueHandle_t xQueue,void*pvBuffer,BaseType_t*pxTaskWoken);

6.3 参数与返回值说明

参数说明
xQueue队列句柄:指定要读取的队列
pvBuffer缓冲区指针:接收数据的缓冲区地址,内核会自动复制数据到该缓冲区
xTicksToWait阻塞时间(仅任务函数支持):队列空时,任务阻塞的最大 Tick 数;0 = 不阻塞;portMAX_DELAY = 永久阻塞
pxTaskWoken高优先级任务唤醒标志(仅 ISR 函数支持):NULL = 无需关注
返回值pdPASS = 读取成功;errQUEUE_EMPTY = 队列空,读取失败

6.4 读队列示例代码(任务中使用)

structinput_dataidata;// 定义接收数据的结构体变量// 在platform_task任务中持续读队列voidvPlatformTask(void*pvParameters){while(1){// 永久阻塞等待队列数据(portMAX_DELAY),读取成功则执行后续逻辑if(pdPASS==xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY)){// 读取成功:根据idata.dev和idata.val控制OLED// (此处省略OLED控制逻辑,后续实战补充)}}}

七、中断场景写队列实现

在 IR 中断服务函数中,使用 “ISR 专用写队列函数” 将数据写入队列,确保中断中操作的安全性(不可阻塞)。

实现代码

// IR中断服务函数voidIR_IRQHandler(void){structinput_dataidata;// 定义要写入队列的数据结构体// 1. 中断处理:读取IR按键值(此处省略硬件读取逻辑)idata.dev=0;// 设备标识:0代表IR遥控器idata.val=0;// 按键值:根据实际读取结果赋值(如左移=1,右移=2)// 2. 中断中写队列:写尾部,无需关注高优先级任务唤醒(传NULL)xQueueSendToBackFromISR(g_xQueuePlatform,&idata,NULL);// 3. 清除中断标志位(根据硬件手册补充,此处省略)}

关键注意:中断中必须使用xQueueSendToBackFromISR(或 FromISR 结尾的函数),不可使用任务中的xQueueSend,否则会导致系统异常。

八、总结

本次笔记聚焦队列的核心操作,关键要点如下:

  1. 创建方式:动态创建(xQueueCreate)简单高效,适合入门;静态创建(xQueueCreateStatic)内存可控,适合复杂场景;
  2. API 分类:写队列分 “任务 / ISR”“写头部 / 尾部”,读队列分 “任务 / ISR”,中断函数必须用 FromISR 结尾的版本;
  3. 实战逻辑:IR 中断写队列→任务读队列,解耦中断与业务,避免 CPU 轮询浪费;
  4. 核心原则:队列句柄是唯一操作标识,数据传输通过结构体统一格式,中断中操作不可阻塞。

掌握这些基础操作,就能应对大部分多任务数据传输场景,为后续编码器控制挡球板的实战做好了准备。

九、下一篇预告

本次我们完成了队列创建与读写 API 的学习,下一篇将进入实战环节:用编码器替代红外遥控器,实现挡球板的控制。核心逻辑是:旋转编码器中断写队列 B→创建解析任务处理队列 B 数据→将处理后的数据写入队列 A→任务读队列 A 控制挡球板。这样设计的原因是编码器数据需额外运算,单独创建任务处理可避免中断阻塞,提升系统稳定性。

十、结尾

从队列本质到 API 实战,我们逐步掌握了 FreeRTOS 数据传输的核心工具。队列的价值在于 “封装复杂逻辑,简化多任务协作”—— 无需关注底层互斥与阻塞,仅通过 API 即可实现安全高效的数据传输。

下一篇的编码器实战,将是对队列 API 的综合应用,同时融入中断、任务创建等此前所学知识,形成完整的技术闭环。我是Hello_Embed,感谢大家的持续关注,让我们在实战中巩固技能,逐步推进 FreeRTOS 的学习之旅!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!