【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. 队列定义 & 创建:
c 复制代码
QueueHandle_t xKeyEventQueue;

void Init_Queue()
{
    // 创建一个可容纳 10 个 uint8_t 的队列
    xKeyEventQueue = xQueueCreate(10, sizeof(uint8_t));
}
2. 按键中断服务函数(中断 → 队列)
c 复制代码
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)
c 复制代码
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 函数中: 创建任务和初始化
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 中的消息,根据不同的消息值做相应的处理。
c 复制代码
/**
  ******************************************************************************
  * @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,定时器3
	6,蜂鸣器(PB8)

实验现象:
	通过串口调试助手给开发板发送字符串,开发板接收到字符串以后就会将字符串显示到LCD上。
	按下开发板上的按键可以实现不同的功能。不管是串口调试助手给开发板,
	发送数据还是通过按键控制不同的外设,这些都是使用FreeRTOS的队列实现的。

📄 led.h led灯

c 复制代码
#ifndef __LED_H
#define __LED_H	 
#include "sys.h"

#define LED0 PCout(6)// 
#define LED1 PCout(7)// 

void LED_Init(void);//初始化
		 				    
#endif

📄 led.c

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 定时器

c 复制代码
#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

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,那么定时器周期就是 50us
	FreeRTOSRunTimeTicks=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_tim
	TIM_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_tim
	TIM_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 按键

c 复制代码
#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

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-KEY1
	GPIO_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 蜂鸣器

c 复制代码
#ifndef __BEEP_H
#define __BEEP_H	 
#include "sys.h"
//////////////////////////////////////////////////////////////////////////////////	 

//蜂鸣器端口定义
#define BEEP PCout(9)	// BEEP,蜂鸣器接口		   

void BEEP_Init(void);	//初始化
		 				    
#endif

📄 beep.c

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

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();		  					//初始化LED
	KEY_Init();							//初始化按键
	BEEP_Init();						//初始化蜂鸣器
	TIM2_Int_Init(5000,7200-1);			//初始化定时器2,周期500ms
	my_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. 定义队列句柄
c 复制代码
QueueHandle_t xQueue;
  1. 创建队列(在 main() 或启动任务中)
c 复制代码
xQueue = xQueueCreate(10, sizeof(uint8_t));
// 创建一个可容纳 10 个 uint8_t 数据的队列
  1. 发送任务(发送者)
c 复制代码
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. 接收任务(接收者)
c 复制代码
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() 中)
c 复制代码
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() 从中断中发送
数据类型			只能发送固定大小的数据(如结构体、整数等)
实时性			使用队列时注意任务优先级和阻塞时间,避免死锁或延迟

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

c 复制代码
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()
    • 丢弃数据(默认行为)
bash 复制代码
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 队列结构体通信模拟:

一个一个发送数据:

一个一个接收数据(阻塞接收):

最大(设置为)五个,当队列中满员时不可继续添加数据:

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

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

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

相关推荐
悠哉悠哉愿意1 小时前
【电赛学习笔记】MaixCAM 的OCR图片文字识别
笔记·python·嵌入式硬件·学习·视觉检测·ocr
一只小bit3 小时前
Linux网络:阿里云轻量级应用服务器配置防火墙模板开放端口
linux·网络·阿里云
嘉琪0013 小时前
实现视频实时马赛克
linux·前端·javascript
慕容白 MU4 小时前
新唐51单片机看门狗配置流程
c语言·单片机·嵌入式硬件·51单片机
帽儿山的枪手4 小时前
HVV期间,如何使用SSH隧道绕过内外网隔离限制?
linux·网络协议·安全
邹诗钰-电子信息工程5 小时前
嵌入式基础知识复习(C语言)
linux·c语言·vim
吴烦恼的博客5 小时前
FSMC的配置和应用
c语言·单片机·fsmc
瀚高PG实验室6 小时前
CentOS 8 安装HGDB V4.5 psql命令执行报错
linux·运维·centos·瀚高数据库
小醉你真好6 小时前
6、CentOS 9 安装 Docker
linux·docker·centos
jingjing~6 小时前
STM32 软件模拟 I2C 读写 MPU6050--实现加速度与陀螺仪数据采集
stm32·单片机·嵌入式硬件