【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 总览

队列创建函数如下表:
在这里插入图片描述

入队函数(发送数据):
在这里插入图片描述

出队函数(接收数据):
在这里插入图片描述

三、队列操作试验

实验一:
实验目标:使用一个队列在中断中传递按键事件任务中读取队列信息并控制 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.** <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>*******************************************************************************/  /* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdio.h>
#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  1
TaskHandle_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口速度为50MHzGPIO_Init(GPIOC, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5GPIO_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_Queueif(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;	 //速度为50MHzGPIO_Init(GPIOC, &GPIO_InitStructure);	 //根据参数初始化GPIOB.8GPIO_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_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		256  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define TASK1_TASK_PRIO		2
//任务堆栈大小	
#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_QueueMessage_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控制LED1LED1=!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);elseGPIO_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 TDA1[任务 A: 发送数据] -->|xQueueSend() / xQueueSendToBack()| Q1[消息队列]A2[中断: EXTI0_IRQHandler] -->|xQueueSendFromISR()| Q1Q1 -- 队列满 --> F1[等待 or 覆盖 or 丢弃]Q1 -->|xQueueReceive()| T1[任务 B: 接收数据]T1 -->|处理数据| D1[执行对应操作,如控制 LED]Q1 -.->|xQueuePeek()| T2[任务 C: 查看但不取出]style A1 fill:#E6F7FF,stroke:#007ACCstyle A2 fill:#FFF1F0,stroke:#CF1322style Q1 fill:#F6FFED,stroke:#389E0Dstyle T1 fill:#FFFBE6,stroke:#FAAD14style D1 fill:#F9F0FF,stroke:#722ED1
FreeRTOS 队列结构体通信模拟:

一个一个发送数据:
在这里插入图片描述
一个一个接收数据(阻塞接收):
在这里插入图片描述
最大(设置为)五个,当队列中满员时不可继续添加数据:
在这里插入图片描述

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91082.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【笔记】加速 uv 安装:系统环境变量配置国内镜像源

使用 Conda 工具链创建 UV 本地虚拟环境全记录——基于《Python 多版本与开发环境治理架构设计》-CSDN博客 命令行创建 UV 环境及本地化实战演示—— 基于《Python 多版本与开发环境治理架构设计》的最佳实践-CSDN博客 加速 uv 包安装&#xff1a;Windows 系统环境变量配置国内…

Three.js 渲染优化处理

基于项目经验和最佳实践&#xff0c;以下是渲染优化的具体处理方法&#xff1a; 1. 几何体与材质优化 使用 BufferGeometry // 推荐&#xff1a;使用 BufferGeometry 替代 Geometry const geometry new THREE.BufferGeometry();合并几何体 // 将多个几何体合并为一个以减少绘制…

Kafka——Kafka控制器

引言在Kafka集群中&#xff0c;有一个组件堪称"隐形的指挥官"——它默默协调着Broker的加入与退出&#xff0c;管理着主题的创建与删除&#xff0c;掌控着分区领导者的选举&#xff0c;它就是控制器&#xff08;Controller&#xff09;。想象一个拥有100台Broker的大…

编程与数学 03-002 计算机网络 11_域名系统(DNS)

编程与数学 03-002 计算机网络 11_域名系统&#xff08;DNS&#xff09;一、DNS的作用与功能&#xff08;一&#xff09;域名与IP地址的映射关系&#xff08;二&#xff09;DNS的层次结构二、DNS查询过程&#xff08;一&#xff09;递归查询与迭代查询&#xff08;二&#xff0…

影翎Antigravity将发布全球首款全景无人机,8月开启公测招募

7月28日&#xff0c;消费级无人机品牌「影翎Antigravity」及品牌标识官宣亮相&#xff0c;计划推出全新品类——全球首款「全景无人机」。这一消息引发行业震动&#xff0c;消费级航拍无人机市场或将迎来颠覆性飞行体验。影翎Antigravity官方介绍&#xff0c;引力不仅是束缚双脚…

SpringBoot集成Quzrtz实现定时任务

一 定时任务介绍 自律是很多人都想拥有的一种能力&#xff0c;或者说素质&#xff0c;但是理想往往很美好&#xff0c;现实却是无比残酷的。在现实生活中&#xff0c;我们很难做到自律&#xff0c;或者说做到持续自律。例如&#xff0c;我们经常会做各种学习计划、储蓄计划或减…

Java中的异常判断以及文件中的常用方法及功能

目录 异常 作用 异常的处理方式 JVM&#xff08;虚拟机&#xff09;默认的处理方式 自己处理&#xff08;捕获异常&#xff09; 抛出异常&#xff08;也就是交给调用者处理&#xff09; 自定义异常 file File中常见成员方法 判断和获取 创建和删除 获取并遍历 异常…

【C++算法】74.优先级队列_最后一块石头的重量

文章目录题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;题目链接&#xff1a; 1046. 最后一块石头的重量 题目描述&#xff1a; 解法 每次取出最重的两块石头进行碰撞&#xff0c;将剩余的石头重新放入堆中。 C 算法代码&#xff1a; class Solution …

中兴云电脑W101D2-晶晨S905L3A-2G+8G-安卓9-线刷固件包

中兴云电脑W101D2-晶晨S905L3A-2G8G-WIFI-蓝牙5.0-3个USB2.0-线刷包线刷方法&#xff1a;1、准备好一根双公头USB线刷刷机线&#xff0c;长度30-50CM长度最佳&#xff0c;同时准备一台电脑&#xff1b;2、电脑上安装好刷机工具Amlogic USB Burning Tool 软件 →打开软件 →文件…

Android OkHttp 底层原理和实战完全教程(责任链模式详解)

目录 1. OkHttp 入门:从一个请求开始 1.1 基本 GET 请求:三步走 1.2 同步 vs 异步:选择你的风格 1.3 为什么选 OkHttp? 2. 配置 OkHttpClient:打造你的专属网络引擎 2.1 超时设置:别让请求卡死 2.2 添加拦截器:窥探请求全过程 2.3 缓存:让请求更快更省流量 3. …

【RK3588部署yolo】算法篇

简历描述收集并制作军事伪装目标数据集&#xff0c;包含真实与伪装各种类型军事目标共计60余类。其中&#xff0c;包含最新战场充气伪装军事装备30余类&#xff0c;并为每一张图片制作了详细的标注。针对军事伪装目标的特点&#xff0c;在YOLOv8的Backbone与Neck部分分别加…

【Spring Boot 快速入门】一、入门

目录Spring Boot 简介Web 入门Spring Boot 快速入门HTTP 协议概述请求协议响应协议解析协议TomcatSpring Boot 简介 Spring Boot 是由 Pivotal 团队&#xff08;后被 VMware 收购&#xff09;开发的基于 Spring 框架的开源项目&#xff0c;于 2014 年首次发布。其核心目标是简…

如何调整服务器的内核参数?-哈尔滨云前沿

调整服务器内核参数是一项较为专业的操作&#xff0c;不同的操作系统调整方式略有不同&#xff0c;以下以常见的 Linux 系统为例&#xff0c;介绍一些调整服务器内核参数的一般步骤和常用参数&#xff1a;一般步骤 备份当前配置&#xff1a;在修改内核参数之前&#xff0c;先备…

C++基础:模拟实现queue和stack。底层:适配器

引言模拟实现queue和stack&#xff0c;理解适配器&#xff0c;实现起来非常简单。一、适配器 适配器是一种能让原本不兼容的接口协同工作的设计模式或者组件。它的主要作用是对一个类的接口进行转换&#xff0c;使其符合另一个类的期望接口&#xff0c;进而实现适配和复用。&am…

OI 杂题

OI 杂题字符串括号匹配例 1&#xff1a;与之前的类似&#xff0c;就是讲一点技巧&#xff0c;但是比较乱&#xff0c;凑合着看吧。 字符串 括号匹配 几何意义&#xff1a;考虑令 ( 为 111 变换&#xff0c;令 ) 为 −1-1−1 变换&#xff0c;然后对这个 1/−11/-11/−1 构成…

【论文阅读】Safety Alignment Should Be Made More Than Just a Few Tokens Deep

Safety Alignment Should Be Made More Than Just a Few Tokens Deep原文摘要问题提出现状与漏洞&#xff1a;当前LLMs的安全对齐机制容易被攻破&#xff0c;即使是简单的攻击&#xff08;如对抗性后缀攻击&#xff09;或良性的微调也可能导致模型越狱。核心论点&#xff1a; 作…

Generative AI in Game Development

如有侵权或其他问题&#xff0c;欢迎留言联系更正或删除。 出处&#xff1a;CHI 20241. 一段话总结本研究通过对来自 Reddit 和 Facebook 群组的 3,091 条独立游戏开发者的在线帖子和评论进行定性分析&#xff0c;探讨了他们对生成式 AI在游戏开发中多方面作用的认知与设想。研…

【C++算法】72.队列+宽搜_二叉树的最大宽度

文章目录题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;题目链接&#xff1a; 662. 二叉树最大宽度 题目描述&#xff1a; 解法 这里的宽度指的是一层的最右边的非空节点到一层的最左边的非空节点&#xff0c;一共的节点数。 解法一&#xff1a;硬来&am…

什么是3DVR?VR技术有哪些应用场景?

VR与3D技术解析及应用在高科技领域&#xff0c;VR和3D是两个常被提及的名词。那么&#xff0c;这两者之间究竟存在着怎样的区别与联系呢&#xff1f;简而来说&#xff0c;VR技术是3D技术的一种高级延展和深化应用。3D技术&#xff0c;即将二维设计图转化为立体、逼真的视觉效果…

栈与队列:数据结构核心解密

栈和队列的基本 栈(Stack)是一种后进先出(LIFO, Last In First Out)的数据结构。元素的插入和删除操作只能在栈顶进行。常见的操作包括压栈(push)和弹栈(pop)。 队列(Queue)是一种先进先出(FIFO, First In First Out)的数据结构。元素的插入在队尾进行,删除在队…