在 FreeRTOS 中,任務消息隊列(Message Queue) 是一種非常關鍵的通信機制,用于在任務之間 傳遞數據、同步事件。 它是實現任務 解耦、異步通信 的核心工具之一,FreeRTOS 的消息隊列是任務之間通信的橋梁。
簡單點說,在 FreeRTOS 中,隊列 是任務之間、任務與中斷之間常用的通信機制。 每個隊列都維護一個先進先出的緩沖區,用于存儲“消息項”(數據項)。用于:
- 任務與任務之間傳遞消息
- 中斷與任務之間傳遞數據
- 實現事件驅動系統
這篇文章梳理出了一份完整的 《FreeRTOS 隊列及隊列操作實驗文檔》,內容包含:
- ? 隊列簡介
- ? 隊列 API 總覽(結合圖表)
- ? 隊列操作實驗(實戰代碼)
- ? FreeRTOS 列表與列表項試驗簡述
- ? 實驗總結與擴展建議
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>© 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
- 定義隊列句柄:
QueueHandle_t xQueue;
- 創建隊列(在
main()
或啟動任務中):
xQueue = xQueueCreate(10, sizeof(uint8_t));
// 創建一個可容納 10 個 uint8_t 數據的隊列
- 發送任務(發送者):
void vSenderTask(void *pvParameters)
{uint8_t value = 1;while (1){xQueueSend(xQueue, &value, portMAX_DELAY); // 發送數據到隊列printf("Sent value: %d\n", value);value++;vTaskDelay(1000);}
}
- 接收任務(接收者):
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);}}
}
- 創建任務(在
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 內部使用 List
和 ListItem
實現了許多高級特性(如就緒列表、延時鏈表、定時器鏈表等),是內核調度器的核心結構。 這些內容相對比較復雜,不展開是因為此部分更多用于內核源碼分析,一般不在應用層直接使用。
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 的消息隊列 的使用方法),如果你需要寫代碼請不要直接復制粘貼我寫的,請根據自己的實際情況編程。
以上,歡迎有從事同行業的電子信息工程、互聯網通信、嵌入式開發的朋友共同探討與提問,我可以提供實戰演示或模板庫。希望內容能夠對你產生幫助!