【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
英文地址,請注明出處:http://en.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)的數據結構。元素的插入在隊尾進行,刪除在隊…