在實際應用中,我們會遇到一個任務或者中斷服務需要和另一個任務進行消息傳遞,FreeRTOS提供了隊列的機制來完成任務與任務、任務與中斷之間的消息傳遞。
0x01 隊列簡介
隊列是為了任務與任務、任務與中斷之間的通信而準備的,可以在任務與任務、任務與中斷之間傳遞消息,隊列中可以存儲有限的、大小固定的數據項目。隊列中能保存的最大數據項目數量叫做隊列的長度。
1. 數據存儲
隊列提供了FIFO、LIFO的存儲緩沖機制,數據發送到隊列中會導致數據拷貝,數據拷貝是值傳遞,在隊列中存儲的是數據的原始值,而不是原始值的引用(即值傳遞數據的引用),FreeRTOS中使用隊列傳遞消息的話雖然使用的是數據拷貝,但是也可以使用引用來傳遞消息,直接往隊列中發送指向這個消息的地址指針就可以了。
2. 多任務訪問
隊列不屬于某個特別指定的任務,任何任務都可以向隊列中發送消息,提取消息
0x02 隊列結構體
隊列的類型是Queue_t,定義在queue.c文件中
typedef struct QueueDefinition
{int8_t *pcHead; /*< Points to the beginning of the queue storage area. */int8_t *pcTail; /*< Points to the byte at the end of the queue storage area. Once more byte is allocated than necessary to store the queue items, this is used as a marker. */int8_t *pcWriteTo; /*< Points to the free next place in the storage area. */union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */{int8_t *pcReadFrom; /*< Points to the last place that a queued item was read from when the structure is used as a queue. */UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */} u;List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */List_t xTasksWaitingToReceive; /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition *pxQueueSetContainer;#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;uint8_t ucQueueType;#endif} xQUEUE;/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;
- pcHead:指向隊列存儲區首地址
- pcTail:指向隊列存儲區最后一個字節地址
- pcWriteTo:指向下一個可以存儲的地址
- pcReadFrom:當用作隊列的時候,指向最后一個出隊的隊列項首地址
- uxRecursiveCallCount:當用作遞歸互斥量的時候用來記錄遞歸互斥量被調用的次數
- xTasksWaitingToSend:等待發送任務列表,那些因為隊列滿導致入隊失敗而進入阻塞態的任務就會掛到此列表上
- xTasksWaitingToReceive:等待接受任務列表,那些因為隊列空導致出隊失敗而進入阻塞態的任務就會掛到此列表上
- uxMessagesWaiting:隊列中當前消息數
- uxLength:隊列中最大允許的消息數量
- uxItemSize:每個消息最大長度
- cRxLock:當列表上鎖后,統計出隊的消息數量
- cTxLock:當列表上鎖后,統計入隊的消息數量
0x03 隊列創建
隊列創建有兩種方法:
- 方法1:使用xQueueCreate函數動態創建
- 方法2:使用xQueueCreateStatic動態創建
1. xQueueCreate
從源碼可以看出,使用xQueueCreate,configSUPPORT_DYNAMIC_ALLOCATION 要等于1,支持動態分配內存
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize)
- uxQueueLength:要創建的隊列的隊列長度,即最多可以接受多少個消息
- uxItemSize :隊列中每個消息的長度
創建成功返回隊列句柄,失敗返回NULL
2.xQueueCreateStatic
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer);
- uxQueueLength:要創建的隊列的隊列長度,即最多可以接受多少個消息
- uxItemSize :隊列中每個消息的長度
- pucQueueStorage:指向消息的存儲區,是一個uint8_t類型的數組,數組的大小要大于等于(uxQueueLength*uxItemSize )
- pxQueueBuffer :保存隊列結構體
創建成功返回隊列句柄,失敗返回NULL
0x04 向隊列發送消息
1. 函數原型
創建好隊列就可以向隊列發送消息了,FreeRTOS提供了8個向對列發送消息的API函數。
1.1 xQueueSend、xQueueSendToBack、xQueueSendToToFront
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);BaseType_t xQueueSendToBack(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
- QueueHandle_t xQueue:任務句柄,指明要向那個隊列發送數據
- const void *pvItemToQueue:指向要發送的數據,發送的時候會將這個消息拷貝到隊列中
- TickType_t xTicksToWait:阻塞時間,此參數指示隊列滿的時候任務進入阻塞態等待隊列空閑的最大時間,如果是0,隊列滿會立即返回,當為portMAX_DELAY就會一直等待。
成功返回pdPASS,失敗返回errQUEUE_FULL
1.2 xQueueOverwrite
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);
- QueueHandle_t xQueue:隊列句柄,指明要向那個隊列發送數據
- const void * pvItemToQueue:要發送的數據
1.3 xQueueSendFromISR、xQueueSendToBackFromISR、xQueueSendToFrontFromISR
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
- QueueHandle_t xQueue:隊列句柄,指明要向那個隊列發送數據
- const void *pvItemToQueue:要發送的數據
- BaseType_t *pxHigherPriorityTaskWoken:標記退出此函數以后是否進行任務切換,這個變量由三個函數來設置,用戶不用進行設置,用戶只需提供一個變量來保存這個值就行了。當此值為pdTRUE的時候在退出中斷服務函數之前一定要進行一次任務切換。
成功返回pdTURE,失敗返回errQUEUE_FULL
0x05 從隊列讀取消息
從隊列中獲取消息,FreeRTOS相關API函數如下
1. xQueueReceive、xQueuePeek
BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait)BaseType_t xQueuePeek(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait);
- QueueHandle_t xQueue:指明讀取那個隊列的數據
- void *pvBuffer:讀取隊列的過程中將讀取的數據拷貝到這個緩沖區
- TickType_t xTicksToWait:阻塞時間,如果為0,隊列為空立即返回,當為portMAX_DELAY的話就一直等待,直到隊列有數據。
2. xQueueReceiveFromISR
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxTaskWoken);BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer,);
- QueueHandle_t xQueue:指明讀取那個隊列的數據
- void *pvBuffer:讀取隊列的過程中將讀取的數據拷貝到這個緩沖區
- BaseType_t *pxTaskWoken:標記退出此函數以后是否進行任務切換
返回值:
返回pdTRUE,從隊列中讀取數據成功,返回pdFALSE,從隊列中讀取數據失敗。
驗證
實驗設計三個任務,start_task、task1_task、Keyprocess_task這三個任務。
start_task:用來創建其他兩個任務
task1_task:讀取按鍵的鍵值,然后將鍵值發送到KEY_Queue隊列中,并且檢查隊列的剩余容量等信息
Keyprocess_task:按鍵處理任務,讀取隊列Key_Queue中的信息,根據不同的消息值做相應的處理。
實驗需要是哪個按鍵KEY_UP、KEY2、KEY0,不同的按鍵對應不同的按鍵值,任務task1_task會將這些值發送到隊列Key_Queue中。
實驗中創建兩個隊列Key_Queue和Message_Queue,隊列Key_Queue用于傳遞按鍵值,隊列Message_Queue用于傳遞串口發送過來的消息。
實驗還需要兩個中斷,一個是串口1接收中斷,一個是定時器9中斷,他們的作用如下:串口1接收中斷,接收串口發送過來的數據,并將接收到的數據發送到隊列Message_Queue中,定時器9中斷,定時周期為500ms,在定時器中斷中讀取隊列Message_Queue中的消息,并將其顯示在LCD上。
start_task代碼:
//開始任務任務函數
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(); //退出臨界區
}
在start_task中創建了兩個隊列,分別是Key_Queue和Message_Queue,Key_Queue用于和按鍵通信,Message_Queue用于和串口通信。
task1_task代碼
//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個時鐘節拍 }
}
task1_task獲取按鍵值,并將按鍵值發送到Key_Queue中
Keyprocess_task代碼:
//Keyprocess_task函數
void Keyprocess_task(void *pvParameters)
{u8 num,key,beepsta=1;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控制蜂鳴器beepsta=!beepsta;BEEP=beepsta;break;case KEY0_PRES: //KEY0刷新LCD背景num++;LCD_Fill(126,111,233,313,lcd_discolor[num%14]);break;}}} vTaskDelay(10); //延時10ms,也就是10個時鐘節拍 }
}
Keyprocess_task獲取Key_Queue隊列中按鍵值
整個main.c代碼如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "sdram.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "string.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/************************************************ALIENTEK 水星STM32F429開發板 FreeRTOS實驗13-1FreeRTOS隊列操作實驗-HAL庫版本技術支持:www.openedv.com淘寶店鋪:http://eboard.taobao.com 關注微信公眾平臺微信號:"正點原子",免費獲取STM32資料。廣州市星翼電子科技有限公司 作者:正點原子 @ALIENTEK
************************************************///任務優先級
#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; //信息隊列句柄//LCD刷屏時使用的顏色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED, GRED, GBLUE, RED, MAGENTA, GREEN, CYAN, YELLOW,BROWN, BRRED, GRAY };//用于在LCD上顯示接收到的隊列的消息
//str: 要顯示的字符串(接收到的消息)
void disp_str(u8* str)
{LCD_Fill(5,230,110,245,WHITE); //先清除顯示區域LCD_ShowString(5,230,100,16,16,str);
}//加載主界面
void freertos_load_main_ui(void)
{POINT_COLOR = RED;LCD_ShowString(10,10,200,16,16,"Apollo STM32F4/F7"); LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 13-1");LCD_ShowString(10,50,200,16,16,"Message Queue");LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1 KEY0:Refresh LCD");LCD_ShowString(10,90,200,16,16,"KEY1:SendMsg KEY2:BEEP");POINT_COLOR = BLACK;LCD_DrawLine(0,107,239,107); //畫線LCD_DrawLine(119,107,119,319); //畫線LCD_DrawRectangle(125,110,234,314); //畫矩形POINT_COLOR = RED;LCD_ShowString(0,130,120,16,16,"DATA_Msg Size:");LCD_ShowString(0,170,120,16,16,"DATA_Msg rema:");LCD_ShowString(0,210,100,16,16,"DATA_Msg:");POINT_COLOR = BLUE;
}//查詢Message_Queue隊列中的總隊列數量和剩余隊列數量
void check_msg_queue(void)
{u8 *p;u8 msgq_remain_size; //消息隊列剩余大小u8 msgq_total_size; //消息隊列總大小taskENTER_CRITICAL(); //進入臨界區msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到隊列項剩余大小msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到隊列總大小,總大小=使用+剩余的。p=mymalloc(SRAMIN,20); //申請內存sprintf((char*)p,"Total Size:%d",msgq_total_size); //顯示DATA_Msg消息隊列總的大小LCD_ShowString(10,150,100,16,16,p);sprintf((char*)p,"Remain Size:%d",msgq_remain_size); //顯示DATA_Msg剩余大小LCD_ShowString(10,190,100,16,16,p);myfree(SRAMIN,p); //釋放內存taskEXIT_CRITICAL(); //退出臨界區
}int main(void)
{HAL_Init(); //初始化HAL庫 Stm32_Clock_Init(360,25,2,8); //設置時鐘,180Mhzdelay_init(180); //初始化延時函數uart_init(115200); //初始化串口LED_Init(); //初始化LED KEY_Init(); //初始化按鍵BEEP_Init(); //初始化蜂鳴器SDRAM_Init(); //初始化SDRAMLCD_Init(); //初始化LCDTIM9_Init(5000,18000-1); //初始化定時器9,周期500msmy_mem_init(SRAMIN); //初始化內部內存池freertos_load_main_ui(); //加載主UI//創建開始任務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 num,key,beepsta=1;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控制蜂鳴器beepsta=!beepsta;BEEP=beepsta;break;case KEY0_PRES: //KEY0刷新LCD背景num++;LCD_Fill(126,111,233,313,lcd_discolor[num%14]);break;}}} vTaskDelay(10); //延時10ms,也就是10個時鐘節拍 }
}