news 2026/6/4 0:32:04

FreeRTOS 手动移植教程(四):队列 —— 任务间通信的最佳起点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FreeRTOS 手动移植教程(四):队列 —— 任务间通信的最佳起点

前几篇文章中,我们学会了任务创建与延时管理,但任务之间仍无法传递数据。本篇将引入 FreeRTOS 最基础、最常用的 IPC(进程间通信)机制——队列。通过队列,任务与任务、中断与任务之间可以安全、高效地传递数据,彻底告别裸机全局变量带来的隐患。我们将以按键中断通知 LED 闪烁模式切换为例,完整演示队列的创建、发送、接收及阻塞等待的全过程。


一、为什么需要队列?

在裸机程序中,任务间的数据共享通常靠全局变量实现,但在 RTOS 下这种做法极易引发问题:

  • 竞争条件:多个任务同时读写同一个变量,可能造成数据错乱;
  • 非阻塞需求:接收方任务需要等待数据,又不希望浪费 CPU 做轮询;
  • 中断中传递数据:中断需要快速退出,不能等待任务处理完数据。

队列提供了一种线程安全的机制:它自带互斥访问和阻塞/唤醒逻辑,可以方便地在不同任务(或中断)之间传递消息,而无需开发者自行实现复杂的锁机制。


二、队列的核心概念与 API

2.1 队列的基本模型

队列可以看作一个先进先出(FIFO)的环形缓冲区,每个队列项的大小在创建时固定。任务可以向队列尾部发送数据,也可以从队列头部接收数据。当队列为空时,接收任务可以选择阻塞等待,直到有数据进入队列;当队列满时,发送任务也可以阻塞等待,直到队列有空位。

2.2 关键 API

功能API 名称说明
创建队列xQueueCreate返回队列句柄,后续操作均基于此句柄
发送数据(任务)xQueueSend类似xQueueSendToBack,将数据放到队尾
发送数据(中断)xQueueSendFromISR中断服务函数中使用的版本
接收数据(任务)xQueueReceive从队首取出数据,可设置阻塞超时
接收数据(中断)xQueueReceiveFromISR中断中使用的版本(但不常用)
查询队列中项数uxQueueMessagesWaiting返回当前队列中的有效数据项数量
删除队列vQueueDelete释放队列占用的内存

三、硬件准备与优先级分组设置

3.1 硬件连接

本章实验使用一个按键(PA0)和一个 LED(PC13),要求按一次按键切换一次 LED 的闪烁模式。

3.2 中断优先级分组(必须)

为了让 FreeRTOS 的中断管理策略正确生效,必须在系统初始化早期设置 NVIC 优先级分组为4位抢占优先级。在main()函数开头调用:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

这样,每个中断的 4 位优先级都用于抢占,子优先级不再使用,与我们FreeRTOSConfig.h中的配置完全匹配。


四、扩展板级驱动

4.1 按键 BSP

BSP目录下新建bsp_key.hbsp_key.c

// bsp_key.h#ifndefBSP_KEY_H#defineBSP_KEY_H#include"stm32f10x.h"voidKEY_Init(void);uint8_tKEY_Read(void);// 返回 1 表示按下#endif
// bsp_key.c#include"bsp_key.h"voidKEY_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;// 上拉输入,按下为低电平GPIO_Init(GPIOA,&GPIO_InitStructure);}uint8_tKEY_Read(void){// 返回 1 表示按键按下(PA0 为低电平)return(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET)?1:0;}

4.2 LED BSP

沿用之前文章的bsp_led.hbsp_led.c,确保已包含LED_InitAll()LED3_Toggle()函数。LED3_Toggle翻转的是GPIOC->ODR的 Pin_13。


五、按键中断配置

5.1 EXTI 初始化(标准库)

BSP目录下新建bsp_exti.hbsp_exti.c

// bsp_exti.h#ifndefBSP_EXTI_H#defineBSP_EXTI_H#include"stm32f10x.h"voidEXTI0_Init(void);#endif
// bsp_exti.c#include"bsp_exti.h"#include"bsp_key.h"voidEXTI0_Init(void){EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;KEY_Init();// 初始化 PA0 引脚// 将 PA0 连接到 EXTI 线 0GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);EXTI_InitStructure.EXTI_Line=EXTI_Line0;EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;// 下降沿触发EXTI_InitStructure.EXTI_LineCmd=ENABLE;EXTI_Init(&EXTI_InitStructure);NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=5;// 优先级 5,可安全调用 RTOS APINVIC_InitStructure.NVIC_IRQChannelSubPriority=0;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);}

说明:中断优先级设为 5,与我们配置的configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY一致,表示该中断可以安全调用 FreeRTOS 的 FromISR 系列函数。优先级 0~4 的中断完全不被打扰,但绝对不能在其内部调用任何 RTOS API。

5.2 中断服务函数

stm32f10x_it.c中编写EXTI0_IRQHandler(如果该文件已有弱定义的空函数,直接覆盖即可)。

// stm32f10x_it.c#include"stm32f10x_it.h"#include"FreeRTOS.h"#include"queue.h"externQueueHandle_t xKeyQueue;// 队列句柄,在 main.c 中定义voidEXTI0_IRQHandler(void){BaseType_t xHigherPriorityTaskWoken=pdFALSE;if(EXTI_GetITStatus(EXTI_Line0)!=RESET){// 发送按键消息到队列,仅发送一个字节(内容无关紧要,只表示事件)uint8_tkey_event=1;xQueueSendFromISR(xKeyQueue,&key_event,&xHigherPriorityTaskWoken);EXTI_ClearITPendingBit(EXTI_Line0);}portYIELD_FROM_ISR(xHigherPriorityTaskWoken);// 如有更高优先级任务被唤醒,则触发切换}

六、实现队列通信与 LED 模式切换

6.1 任务设计

  • KeyProcessTask:阻塞等待队列中的按键事件,收到后切换 LED 模式;
  • LedTask:根据当前模式以不同频率闪烁 LED。

6.2 main.c 完整代码

#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"#include"queue.h"#include"bsp_led.h"#include"bsp_exti.h"/* 队列句柄,需在中断服务函数中引用 */QueueHandle_t xKeyQueue=NULL;/* 闪烁模式(0 = 慢闪,1 = 快闪) */volatileuint8_tled_mode=0;/* 按键处理任务 */voidvKeyProcessTask(void*pvParameters){uint8_tkey_val;while(1){// 阻塞等待队列数据(portMAX_DELAY 表示无限等待)if(xQueueReceive(xKeyQueue,&key_val,portMAX_DELAY)==pdTRUE){// 收到按键事件,切换模式led_mode=!led_mode;}}}/* LED 闪烁任务 */voidvLedTask(void*pvParameters){while(1){if(led_mode==0){LED3_Toggle();vTaskDelay(pdMS_TO_TICKS(500));// 慢闪:周期 1s}else{LED3_Toggle();vTaskDelay(pdMS_TO_TICKS(100));// 快闪:周期 200ms}}}intmain(void){/* 设置中断优先级分组为 4 位抢占优先级(必须) */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);LED_InitAll();// 初始化 LEDEXTI0_Init();// 初始化按键中断/* 创建队列,每个消息为 1 个 uint8_t,队列长度 5(足够缓冲按键事件) */xKeyQueue=xQueueCreate(5,sizeof(uint8_t));if(xKeyQueue==NULL){// 创建失败,阻塞程序(实际项目中可重启或报错)while(1);}xTaskCreate(vKeyProcessTask,"KeyProc",128,NULL,2,NULL);xTaskCreate(vLedTask,"Led",128,NULL,1,NULL);vTaskStartScheduler();while(1);}

七、实验现象与验证

  1. 上电后,LED 以 1Hz 频率闪烁(慢闪模式);
  2. 每按一次 PA0 按键,LED 在慢闪(周期 1s)和快闪(周期 200ms)之间切换;
  3. 由于在中断中使用了xQueueSendFromISR,按键响应实时且不丢事件;
  4. KeyProcessTask使用portMAX_DELAY阻塞等待,完全不占用 CPU,仅在按键按下时才被唤醒执行。

如果需要传递更复杂的数据(如按键次数、按键类型),可以扩大队列项大小,或传递结构体。但请注意:队列存储的是数据副本,而非指针,因此不必担心悬挂指针问题。


八、队列使用的注意事项

  • 阻塞超时portMAX_DELAY表示无限等待;0表示不等待;其他值为等待的 tick 数。
  • 中断中必须使用 FromISR 版本xQueueSendFromISRxQueueReceiveFromISR,它们的最后一个参数pxHigherPriorityTaskWoken必须传递给portYIELD_FROM_ISR
  • 队列拷贝语义:所有通过队列传递的数据都会进行字节级拷贝,接收方修改拷贝的数据不会影响队列内部状态。
  • 大小估算:队列长度应能容纳最坏情况下的突发数据,接收任务要及时处理以免队列溢出。

九、总结

通过本篇的实战,你应该掌握了:

  • 队列的创建、发送、接收与阻塞等待;
  • 如何在标准库外部中断中使用xQueueSendFromISR安全地传递事件;
  • 队列天然解决了中断与任务间的同步问题,实现了硬件事件与业务逻辑的解耦。

队列是 FreeRTOS 中使用最频繁的 IPC 工具,所有稍复杂的 RTOS 应用都会用到它。下一篇文章我们将学习二值信号量与计数信号量,解决任务同步、中断通知以及资源管理的常见场景。


下一篇:FreeRTOS 信号量 —— 任务同步与中断通知的优雅解决方案。

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

风控逻辑回归建模:L1、L2正则化、早停与评分卡细节

风控逻辑回归建模:L1、L2正则化、早停与评分卡细节在金融风控领域,信用风险评估、欺诈检测、贷后预警等核心任务本质上都是二分类问题。逻辑回归凭借其可解释性强、训练高效、输出概率校准良好等优势,长期以来一直是风控建模的首选算法。然而…

作者头像 李华
网站建设 2026/6/4 0:31:05

LangChain 框架大项目用起来有多痛苦?本文整理了一套工程化方案

LangChain 框架大项目用起来有多痛苦?本文整理了一套工程化方案前言 "老王,为什么本文们的 LangChain 服务一重启就丢记忆?" 全栈工程师小李一脸无奈。 本文看了看他的代码,发现他用的是默认的 BufferMemory。"你这…

作者头像 李华
网站建设 2026/6/4 0:27:01

Python为何成为TVA的神经与感官系统(9)

重磅预告:本专栏将独家连载系列丛书《AI智能体视觉技术与应用》部分精华内容,该书是世界首套系统阐述“因式智能体”视觉理论与实践的专著,特邀美国 TypeOne 公司首席科学家、斯坦福大学博士 Bohan 担任技术顾问。Bohan先生师从美国三院院士、…

作者头像 李华
网站建设 2026/6/4 0:27:01

Python为何成为TVA的神经与感官系统(10)

重磅预告:本专栏将独家连载系列丛书《AI智能体视觉技术与应用》部分精华内容,该书是世界首套系统阐述“因式智能体”视觉理论与实践的专著,特邀美国 TypeOne 公司首席科学家、斯坦福大学博士 Bohan 担任技术顾问。Bohan先生师从美国三院院士、…

作者头像 李华
网站建设 2026/6/4 0:18:24

边缘计算实战:无人工厂多楼层AGV物理隔离梯控状态机设计

摘要: 在无人工厂的多机协同配送业务中,如果上位机调度系统要求实施团队强行读取底层老旧货梯的协议来获取平层状态,往往会面临极大的联调阻力与特种设备违规风险。面对协议封闭与合规性紧迫的双重限制,架构师亟需一种高度物理隔离…

作者头像 李华