> 技术文档 > 【STM32】FreeRTOS 消息队列(五)

【STM32】FreeRTOS 消息队列(五)

FreeRTOS 中,任务消息队列(Message Queue) 是一种非常关键的通信机制,用于在任务之间 传递数据、同步事件。 它是实现任务 解耦、异步通信 的核心工具之一,FreeRTOS 的消息队列是任务之间通信的桥梁。

简单点说,在 FreeRTOS 中,队列 是任务之间、任务与中断之间常用的通信机制。 每个队列都维护一个先进先出的缓冲区,用于存储“消息项”(数据项)。用于:

  • 任务与任务之间传递消息
  • 中断与任务之间传递数据
  • 实现事件驱动系统

这篇文章梳理出了一份完整的 《FreeRTOS 队列及队列操作实验文档》,内容包含:

  1. ✅ 队列简介
  2. ✅ 队列 API 总览(结合图表)
  3. ✅ 队列操作实验(实战代码)
  4. ✅ FreeRTOS 列表与列表项试验简述
  5. ✅ 实验总结与扩展建议

FreeRTOS 消息队列的核心 API

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/06-Queues/01-xQueueCreate

参考资料:STM32开发板
《FreeRTOS源码详解与应用开发》-第十三章 FreeRTOS队列
《STM32Fxxx FreeRTOS开发手册》-第十三章 FreeRTOS队列


FreeRTOS官方资料
《The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors, 3rd Edition》(资料中的Cortex-M3和M4权威指南)
《161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide》
《FreeRTOS_Reference_Manual_V9.0.0》
《Corex-M3权威指南》

一、队列简介

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。 由于队列用来传递消息的,所以也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!

二、队列 API 总览

队列创建函数如下表:
【STM32】FreeRTOS 消息队列(五)

入队函数(发送数据):
【STM32】FreeRTOS 消息队列(五)

出队函数(接收数据):
【STM32】FreeRTOS 消息队列(五)

三、队列操作试验

实验一:
实验目标:使用一个队列在中断中传递按键事件任务中读取队列信息并控制 LED 输出FreeRTOS 消息队列的工作机制:[Sender Task] ---xQueueSend---> [Queue] ---xQueueReceive---> [Receiver Task]代码思路流程:[用户按下按钮] ⇩[硬件中断触发(EXTI0)] ⇩[中断服务程序将事件发送到队列] ⇩[任务阻塞等待队列,收到事件后处理] ⇩[控制 LED 或其他操作]
1. 队列定义 & 创建:
QueueHandle_t xKeyEventQueue;void Init_Queue(){ // 创建一个可容纳 10 个 uint8_t 的队列 xKeyEventQueue = xQueueCreate(10, sizeof(uint8_t));}
2. 按键中断服务函数(中断 → 队列)
void EXTI0_IRQHandler(void){ BaseType_t xHigherPriorityTaskWoken = pdFALSE; uint8_t key_event = 1; if (EXTI_GetITStatus(EXTI_Line0) != RESET) { xQueueSendFromISR(xKeyEventQueue, &key_event, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); EXTI_ClearITPendingBit(EXTI_Line0); }}
3. 接收任务(队列 → 控制 LED)
void vKeyProcessTask(void *pvParameters){ uint8_t received_event; while (1) { if (xQueueReceive(xKeyEventQueue, &received_event, portMAX_DELAY) == pdTRUE) { // 控制 LED 翻转 GPIOC->ODR ^= GPIO_Pin_7; printf(\"Key Event Received: %d\\r\\n\", received_event); } }}
main.c 函数中: 创建任务和初始化
int main(void){ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); usart_init(115200); MX_GPIO_Init(); MX_EXTI_Init(); Init_Queue(); // 创建队列 xTaskCreate(vKeyProcessTask, \"KeyProc\", 128, NULL, 2, NULL); vTaskStartScheduler(); while (1); // 不会执行}

实验二:
实验目的:学习使用 FreeRTOS 的队列相关 API 函数,学会如何在任务或中断中向队列发送消息或者从队列中接收消息 。实验设计:本实验设计三个任务:start_task、task1_task 、Keyprocess_task 这三个任务的任务功能如下:start_task:用来创建其他 2 个任务。task1_task :读取按键的键值,然后将键值发送到队列 Key_Queue 中。Keyprocess_task : 按键处理任务,读取队列 Key_Queue 中的消息,根据不同的消息值做相应的处理。
/** ****************************************************************************** * @file Project/STM32F10x_StdPeriph_Template/main.c * @author MCD Application Team * @version V3.5.0 * @date 08-April-2011 * @brief Main program body ****************************************************************************** * @attention * * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. * * 

© COPYRIGHT 2011 STMicroelectronics

****************************************************************************** */
/* Includes ------------------------------------------------------------------*/#include \"stm32f10x.h\"#include #include \"FreeRTOS.h\"#include \"task.h\"#include \"usart.h\"#include \"gpio.h\"#include \"queue.h\"#include \"exti.h\"//----------------------Queue Create -------------------//QueueHandle_t Key_Queue;//---------------------- start task --------------------//void start_task( void *pvParameters ); //任务函数入口#define START_STK_SIZE 64 //任务堆栈大小 #define START_TASK_PRO 1TaskHandle_t StartTask_Handler ;//任务句柄//---------------------- Led task --------------------//void led_task( void *pvParameters ); //任务函数入口TaskHandle_t LED_Task_Handler ;//任务句柄int main(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);MX_USART_Init(115200);MX_GPIO_Init();EXTI0_Init(15, 0);printf(\"System Init OK! \\r\\n\");//--------------- start task ------------------//xTaskCreate((TaskFunction_t) start_task,(const char * )\"start_task\",(uint16_t ) START_STK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRO,(TaskHandle_t *) &StartTask_Handler );vTaskStartScheduler(); //开启任务调度while(1){}}void start_task( void *pvParameters ){printf(\"start Task Run! \\r\\n\");//--------------Create Led_task ------------------------//taskENTER_CRITICAL(); //进入临界区 Key_Queue = xQueueCreate(3,sizeof(uint8_t)); //创建消息队列xTaskCreate(led_task,\"led_blue_task\",128,NULL,3,&LED_Task_Handler );vTaskDelete(StartTask_Handler); //删除任务 start_task printf(\"start Task Delete! \\r\\n\"); //start_task 退出临界区之前可以运行taskEXIT_CRITICAL(); //退出临界区}void led_task( void *pvParameters ){BitAction BitVal = Bit_SET;uint8_t KeyRecv;printf(\"led_task Run! \\r\\n\");for(;;){if(Key_Queue != NULL) {if( xQueueReceive(Key_Queue,&KeyRecv,portMAX_DELAY)){if(KeyRecv == Bit_SET){BitVal = (BitAction) !BitVal;GPIO_WriteBit(GPIOC, GPIO_Pin_7, BitVal);KeyRecv = Bit_RESET;}}}}}/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

实验三:
实验目的:学习使用FreeRTOS的队列相关API函数。硬件资源:1,DS0(连接在PB5),DS1(连接在PE5上)2,串口1(波特率:115200,PA9/PA10连接在板载USB转串口芯片CH340上面) 3,ALIENTEK 2.8/3.5/4.3/7寸LCD模块(仅支持MCU屏)4,按键KEY0(PE4)/KEY1(PE3)/KEY2(PE2)/KEY_UP(PA0,也称之为WK_UP)5,定时器36,蜂鸣器(PB8)实验现象:通过串口调试助手给开发板发送字符串,开发板接收到字符串以后就会将字符串显示到LCD上。按下开发板上的按键可以实现不同的功能。不管是串口调试助手给开发板,发送数据还是通过按键控制不同的外设,这些都是使用FreeRTOS的队列实现的。

📄 led.h led灯

#ifndef __LED_H#define __LED_H #include \"sys.h\"#define LED0 PCout(6)// #define LED1 PCout(7)// void LED_Init(void);//初始化 #endif

📄 led.c

#include \"led.h\" //LED IO初始化void LED_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE); //使能PB,PE端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //LED0-->PB.5 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5 GPIO_SetBits(GPIOC,GPIO_Pin_6 | GPIO_Pin_7); //PB.5 输出高}

📄 timer.h 定时器

#ifndef __TIMER_H#define __TIMER_H#include \"sys.h\"////////////////////////////////////////////////////////////////////////////////// void TIM3_Int_Init(u16 arr,u16 psc);void TIM2_Int_Init(u16 arr,u16 psc);extern volatile unsigned long long FreeRTOSRunTimeTicks;void ConfigureTimeForRunTimeStats(void);#endif

📄 timer.c

#include \"timer.h\"#include \"led.h\"#include \"led.h\"#include \"usart.h\"#include \"malloc.h\"#include \"string.h\"#include \"FreeRTOS.h\"#include \"task.h\"#include \"queue.h\"////////////////////////////////////////////////////////////////////////////////// //FreeRTOS时间统计所用的节拍计数器volatile unsigned long long FreeRTOSRunTimeTicks;//初始化TIM3使其为FreeRTOS的时间统计提供时基void ConfigureTimeForRunTimeStats(void){//定时器3初始化,定时器时钟为 72M,分频系数为 72-1,所以定时器3的频率//为72M/72=1M,自动重装载为 50-1,那么定时器周期就是 50usFreeRTOSRunTimeTicks=0;TIM3_Int_Init(50-1,72-1);//初始化TIM3}//通用定时器3中断初始化//这里时钟选择为APB1的2倍,而APB1为36M//arr:自动重装值。//psc:时钟预分频数//这里使用的是定时器3!void TIM3_Int_Init(u16 arr,u16 psc){ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位 TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级4级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器TIM_Cmd(TIM3, ENABLE); //使能TIMx }//通用定时器2中断初始化//这里时钟选择为APB1的2倍,而APB1为36M//arr:自动重装值。//psc:时钟预分频数//这里使用的是定时器2!void TIM2_Int_Init(u16 arr,u16 psc){ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能//定时器TIM2初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //使能指定的TIM2中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8; //先占优先级4级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器TIM_Cmd(TIM2, ENABLE); //使能TIM2 }//定时器3中断服务函数void TIM3_IRQHandler(void){if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断{FreeRTOSRunTimeTicks++;}TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位}extern QueueHandle_t Message_Queue;//信息队列句柄extern void disp_str(u8* str);//定时器2中断服务函数void TIM2_IRQHandler(void){u8 *buffer;BaseType_t xTaskWokenByReceive=pdFALSE;BaseType_t err;if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断{buffer=mymalloc(SRAMIN,USART_REC_LEN); //申请内存 if(Message_Queue!=NULL) {memset(buffer,0,USART_REC_LEN);//清除缓冲区err=xQueueReceiveFromISR(Message_Queue,buffer,&xTaskWokenByReceive);//请求消息Message_Queue if(err==pdTRUE)//接收到消息 {//disp_str(buffer);//在LCD上显示接收到的消息printf(\"Recv Data:%s \\r\\n\",buffer ); } }myfree(SRAMIN,buffer);//释放内存portYIELD_FROM_ISR(xTaskWokenByReceive);//如果需要的话进行一次任务切换}TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位}

📄 key.h 按键

#ifndef __KEY_H#define __KEY_H #include \"sys.h\"////////////////////////////////////////////////////////////////////////////////// //#define KEY0 PEin(4) //PE4//#define KEY1 PEin(3)//PE3 //#define WK_UP PAin(0)//PA0 WK_UP#define KEY1 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)//读取按键1#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键3(WK_UP)  #define KEY0_PRES 1//KEY0按下#define KEY1_PRES 2//KEY1按下#define WKUP_PRES 3//KEY_UP按下(即WK_UP/KEY_UP)void KEY_Init(void);//IO初始化u8 KEY_Scan(u8); //按键扫描函数 #endif

📄 key.c

#include \"stm32f10x.h\"#include \"key.h\"#include \"sys.h\" #include \"delay.h\" //////////////////////////////////////////////////////////////////////////////////  //按键初始化函数void KEY_Init(void) //IO初始化{ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTE时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//KEY0-KEY1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入 GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化G//初始化 WK_UP-->GPIOA.0 下拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //PA0设置成输入,默认上拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0}//按键处理函数//返回按键值//mode:0,不支持连续按;1,支持连续按;//0,没有任何按键按下//1,KEY0按下//2,KEY1按下//3,KEY3按下 WK_UP//注意此函数有响应优先级,KEY0>KEY1>KEY_UP!!u8 KEY_Scan(u8 mode){ static u8 key_up=1;//按键按松开标志if(mode)key_up=1; //支持连按 if(key_up&&( KEY1==0||WK_UP==0)){delay_ms(10);//去抖动 key_up=0;if(KEY1==0)return KEY1_PRES;else if(KEY1==0)return KEY1_PRES;else if(WK_UP==0)return WKUP_PRES;}else if(KEY1==1&&WK_UP==1)key_up=1; return 0;// 无按键按下}

📄 beep.h 蜂鸣器

#ifndef __BEEP_H#define __BEEP_H #include \"sys.h\"////////////////////////////////////////////////////////////////////////////////// //蜂鸣器端口定义#define BEEP PCout(9)// BEEP,蜂鸣器接口 void BEEP_Init(void);//初始化 #endif

📄 beep.c

#include \"beep.h\" ////////////////////////////////////////////////////////////////////////////////// //初始化PB8为输出口.并使能这个口的时钟 //蜂鸣器初始化void BEEP_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能GPIOB端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //BEEP-->PB.8 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度为50MHz GPIO_Init(GPIOC, &GPIO_InitStructure); //根据参数初始化GPIOB.8 GPIO_SetBits(GPIOC,GPIO_Pin_9);//输出0,关闭蜂鸣器输出}

📄 main.c

#include \"sys.h\"#include \"delay.h\"#include \"usart.h\"#include \"led.h\"#include \"timer.h\"#include \"key.h\"#include \"beep.h\"#include \"malloc.h\"#include \"string.h\"#include \"FreeRTOS.h\"#include \"task.h\"#include \"queue.h\"/*************************************************///任务优先级#define START_TASK_PRIO1//任务堆栈大小#define START_STK_SIZE 256 //任务句柄TaskHandle_t StartTask_Handler;//任务函数void start_task(void *pvParameters);//任务优先级#define TASK1_TASK_PRIO2//任务堆栈大小#define TASK1_STK_SIZE 256 //任务句柄TaskHandle_t Task1Task_Handler;//任务函数void task1_task(void *pvParameters);//任务优先级#define KEYPROCESS_TASK_PRIO 3//任务堆栈大小 #define KEYPROCESS_STK_SIZE 256 //任务句柄TaskHandle_t Keyprocess_Handler;//任务函数void Keyprocess_task(void *pvParameters);//按键消息队列的数量#define KEYMSG_Q_NUM 1 //按键消息队列的数量 #define MESSAGE_Q_NUM 4 //发送数据的消息队列的数量 QueueHandle_t Key_Queue; //按键值消息队列句柄QueueHandle_t Message_Queue;//信息队列句柄//查询Message_Queue队列中的总队列数量和剩余队列数量void check_msg_queue(void){u8 msgq_remain_size;//消息队列剩余大小 u8 msgq_total_size; //消息队列总大小 static u8 last_msgq_remain_size;static u8 last_msgq_total_size; taskENTER_CRITICAL(); //进入临界区 msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列剩余大小 msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。if( (msgq_total_size != last_msgq_total_size) || ( msgq_remain_size != last_msgq_remain_size ) ){last_msgq_total_size = msgq_total_size;last_msgq_remain_size = msgq_remain_size;printf(\"Total Size:%d \\r\\n\",msgq_total_size);printf(\"Remain Size:%d \\r\\n\",msgq_remain_size);} taskEXIT_CRITICAL(); //退出临界区}/****************************************************************************************************************************************************************************************************************taskENTER_CRITICAL();用于在任务中,进入临界区。taskEXIT_CRITICAL();用于在任务中,退出临界区。什么是临界段?临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。特点:成对出现、快进快出:****************************************************************************************************************************************************************************************************************/int main(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 delay_init(); //延时函数初始化 uart_init(115200);//初始化串口LED_Init(); //初始化LEDKEY_Init();//初始化按键BEEP_Init();//初始化蜂鸣器TIM2_Int_Init(5000,7200-1);//初始化定时器2,周期500msmy_mem_init(SRAMIN); //初始化内部内存池//创建开始任务 xTaskCreate((TaskFunction_t )start_task, //任务函数 (const char* )\"start_task\", //任务名称 (uint16_t )START_STK_SIZE, //任务堆栈大小 (void* )NULL,  //传递给任务函数的参数 (UBaseType_t )START_TASK_PRIO, //任务优先级 (TaskHandle_t* )&StartTask_Handler); //任务句柄   vTaskStartScheduler(); //开启任务调度}//开始任务任务函数void start_task(void *pvParameters){ taskENTER_CRITICAL();  //进入临界区//创建消息队列 -- 申请分配内存 Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息Key_Queue Message_Queue=xQueueCreate(MESSAGE_Q_NUM,USART_REC_LEN); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度 //创建TASK1任务 xTaskCreate((TaskFunction_t )task1_task, (const char* )\"task1_task\", (uint16_t )TASK1_STK_SIZE, (void* )NULL, (UBaseType_t )TASK1_TASK_PRIO, (TaskHandle_t* )&Task1Task_Handler); //创建TASK2任务 xTaskCreate((TaskFunction_t )Keyprocess_task,  (const char* )\"keyprocess_task\",  (uint16_t )KEYPROCESS_STK_SIZE, (void* )NULL, (UBaseType_t )KEYPROCESS_TASK_PRIO, (TaskHandle_t* )&Keyprocess_Handler); vTaskDelete(StartTask_Handler); //删除开始任务 taskEXIT_CRITICAL(); //退出临界区}//task1任务函数void task1_task(void *pvParameters){u8 key,i=0; BaseType_t err;while(1){key=KEY_Scan(0); //扫描按键 if((Key_Queue!=NULL)&&(key)) //消息队列Key_Queue创建成功,并且按键被按下 { err=xQueueSend(Key_Queue,&key,10); if(err==errQUEUE_FULL) //发送按键值 { printf(\"队列Key_Queue已满,数据发送失败!\\r\\n\"); } } i++; if(i%10==0) check_msg_queue();//检Message_Queue队列的容量 if(i==50) { i=0; LED0=!LED0; } vTaskDelay(10); //延时10ms,也就是10个时钟节拍}}//Keyprocess_task函数void Keyprocess_task(void *pvParameters){u8 key;while(1){ if(Key_Queue!=NULL) { if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue { switch(key) {  case WKUP_PRES://KEY_UP控制LED1 LED1=!LED1; break;  case KEY1_PRES://KEY1控制蜂鸣器 BEEP=!BEEP; break; } } }vTaskDelay(10); //延时10ms,也就是10个时钟节拍}}

总而言之,FreeRTOS 消息队列的使用思路还是很简单的:

任务 A 发送消息 → 任务 B 接收消息 → 控制 LED

  1. 定义队列句柄
QueueHandle_t xQueue;
  1. 创建队列(在 main() 或启动任务中)
xQueue = xQueueCreate(10, sizeof(uint8_t));// 创建一个可容纳 10 个 uint8_t 数据的队列
  1. 发送任务(发送者)
void vSenderTask(void *pvParameters){ uint8_t value = 1; while (1) { xQueueSend(xQueue, &value, portMAX_DELAY); // 发送数据到队列 printf(\"Sent value: %d\\n\", value); value++; vTaskDelay(1000); }}
  1. 接收任务(接收者)
void vReceiverTask(void *pvParameters){ uint8_t receivedValue; while (1) { if (xQueueReceive(xQueue, &receivedValue, portMAX_DELAY) == pdTRUE) { printf(\"Received value: %d\\n\", receivedValue); // 控制 LED 根据值闪烁 if (receivedValue % 2 == 0) GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_RESET); else GPIO_WriteBit(GPIOC, GPIO_Pin_7, Bit_SET); } }}
  1. 创建任务(在 start_task()main() 中)
xTaskCreate(vSenderTask, \"Sender\", 128, NULL, 2, NULL);xTaskCreate(vReceiverTask, \"Receiver\", 128, NULL, 2, NULL);
参数解释:xQueue队列句柄*pvItemToQueue要发送的数据指针xTicksToWait如果队列满/空,最多等待多少 tick(portMAX_DELAY 表示永久等待)需要注意的是:队列内存队列使用的是 FreeRTOS 内部堆(heap_x.c),注意堆大小中断支持使用 xQueueSendFromISR() 从中断中发送数据类型只能发送固定大小的数据(如结构体、整数等)实时性使用队列时注意任务优先级和阻塞时间,避免死锁或延迟

结构体传输:(高级用法)

typedef struct { uint8_t led_id; uint16_t blink_time;} LedCmd_t;LedCmd_t cmd = {1, 500};xQueueSend(xQueue, &cmd, 0);
额外附加知识: FreeRTOS 列表与列表项试验(高级)(不展开讲解)

FreeRTOS 内部使用 ListListItem 实现了许多高级特性(如就绪列表、延时链表、定时器链表等),是内核调度器的核心结构。 这些内容相对比较复杂,不展开是因为此部分更多用于内核源码分析,一般不在应用层直接使用。

  • List_t:链表结构,例如延时任务链表
  • ListItem_t:链表节点,任务控制块(TCB)包含一个或多个
  • 用途包括:
    • 延时任务列表(vTaskDelay
    • 定时器结构
    • 优先级就绪任务表
关于FreeRTOS 队列操作流程的描述:
  • 发送端 可来自任务或中断
  • 接收端 一般在任务中使用 xQueueReceive()(阻塞式)
  • 中断中 不允许使用阻塞式函数,只能使用 FromISR() 版本
  • 队列满 时,可设置:
    • 阻塞等待(超时/永久)
    • 使用覆盖函数如 xQueueOverwrite()
    • 丢弃数据(默认行为)
flowchart TD A1[任务 A: 发送数据] -->|xQueueSend() / xQueueSendToBack()| Q1[消息队列] A2[中断: EXTI0_IRQHandler] -->|xQueueSendFromISR()| Q1 Q1 -- 队列满 --> F1[等待 or 覆盖 or 丢弃] Q1 -->|xQueueReceive()| T1[任务 B: 接收数据] T1 -->|处理数据| D1[执行对应操作,如控制 LED] Q1 -.->|xQueuePeek()| T2[任务 C: 查看但不取出] style A1 fill:#E6F7FF,stroke:#007ACC style A2 fill:#FFF1F0,stroke:#CF1322 style Q1 fill:#F6FFED,stroke:#389E0D style T1 fill:#FFFBE6,stroke:#FAAD14 style D1 fill:#F9F0FF,stroke:#722ED1
FreeRTOS 队列结构体通信模拟:

一个一个发送数据:
【STM32】FreeRTOS 消息队列(五)
一个一个接收数据(阻塞接收):
【STM32】FreeRTOS 消息队列(五)
最大(设置为)五个,当队列中满员时不可继续添加数据:
【STM32】FreeRTOS 消息队列(五)

说明:任务 A使用 xQueueSend() 发送数据中断 ISR使用 xQueueSendFromISR() 发送数据消息队列存储传递的数据项任务 B使用 xQueueReceive() 获取数据并处理任务 C使用 xQueuePeek() 查看数据但不移除队列满可选择等待、覆盖或丢弃策略

以上,便是 FreeRTOS 的消息队列,常用的两种:任务消息队列 和 中断消息队列,我也提供了FreeRTOS队列操作串口的代码示例。(代码主要提供实现思路,重点在于讲述 FreeRTOS 的消息队列 的使用方法),如果你需要写代码请不要直接复制粘贴我写的,请根据自己的实际情况编程。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!