說明
本文僅作為學習FreeRTOS的記錄文檔,作為初學者肯定很多理解不對甚至錯誤的地方,望網友指正。
FreeRTOS是一個RTOS(實時操作系統)系統,支持搶占式、合作式和時間片調度。適用於微處理器或小型微處理器的實時應用。
本文檔使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
參考文檔:《FreeRTOS_Reference_Manual_V10.0.0.pdf》《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》《STM32F4 FreeRTOS開發手冊_V1.1.pdf》
參考視頻:正點原子FreeRTOS手把手教學-基於STM32_嗶哩嗶哩_bilibili
8 隊列
8.1 簡介
隊列是為了任務與任務、任務與中斷之間的通信而准備的,可以在任務與任務、任務與中斷之間傳遞消息,隊列中可以存儲有限的、大小固定的數據項目。隊列所能保存的最大數據項目數量叫做隊列的長度,創建隊列的時候會指定數據項目的大小和隊列的長度。隊列是用來傳遞消息的,所以也稱為消息隊列。信號量也是依據隊列實現的。
8.1.1 數據存儲
隊列采用先進先出(FIFO)的存儲緩沖機制,往隊列中放數據叫做入隊(放隊尾),從隊列中取數據叫做出隊(從隊頭)。也可以使用LIFO的存儲緩沖,也就是后進先出,FreeRTOS也提供了LIFO的存儲緩沖機制。
數據存放到隊列中會導致值拷貝,也就是放數據到隊列中,而不是數據的指針,這叫值傳遞。采用值傳遞,當消息放到隊列中后原始的數據緩沖區就可以刪除,緩沖區就可以重復使用。FreeRTOS使用的是數據拷貝,但是也可以采用引用(指針)來傳遞消息,直接往隊列中放入發送消息緩沖區的地址的指針。
8.1.2 多任務訪問
隊列不屬於某個特定的任務,任何任務都可以向隊列中發送消息,或者從隊列中提取消息。
8.1.3 出隊阻塞
當任務從一個隊列中提取消息的時候可以指定一個阻塞時間,這個阻塞時間就是當任務從隊列中提取消息無效的時候任務阻塞的時間。出隊就是從隊列中提取消息,出隊阻塞是針對從隊列中提取消息的任務而言的。阻塞時間的單位是時鍾節拍數,阻塞時間為0就是不阻塞。如果阻塞時間是0~portMAX_DELAY,當任務沒有從隊列中獲取到消息的話就會進入阻塞態,阻塞時間指定了任務進入阻塞態的時間,當阻塞時間到了以后還沒有接收到消息就立即退出阻塞態;如果在阻塞時間收到了數據就立即返回。當阻塞時間設置為portMAX_DELAY,任務就會一直進入阻塞態等待,直到接收到數據為止。
8.1.4 入隊阻塞
入隊就是往隊列中發送消息,將消息加入到隊列中。入隊也可以設置阻塞時間。比如入隊時,隊列已經滿了。
8.1.5 隊列操作過程
創建隊列:創建一個隊列,用於任務A和任務B之間通信,隊列數據項目的個數為5,創建時隊列為空。
往隊列中發送第一個消息:任務A往隊列中發送了一個消息,值為10。
往隊列中再發送一個消息:任務A往隊列中再發送了一個消息,值為20,這時隊列剩余空間大小為3。
從隊列中取一個消息:任務B從隊列中取一個消息,從隊列頭開始取(值為10),隊列中剩下一個消息,剩余空間大小為4。
下圖演示了一次完整的任務A和任務B通過隊列傳遞消息的過程:
8.2 隊列結構體
隊列結構體為Queue_t,在queue.c中定義:
typedef struct QueuePointers
{
int8_t * pcTail; //指向隊列存儲區最后一個字節
int8_t * pcReadFrom; //作為隊列使用時指向最后一個出隊的隊列項首地址
} QueuePointers_t;
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder;
UBaseType_t uxRecursiveCallCount; //作為遞歸互斥量的時候用來記錄遞歸互斥量被調用的次數
} SemaphoreData_t;
typedef struct QueueDefinition
{
int8_t * pcHead; //指向隊列存儲區開始地址
int8_t * pcWriteTo; //指向存儲區中下一個空閑區域
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u;
List_t xTasksWaitingToSend; //等待發送任務列表,因隊滿導致入隊失敗而阻塞的任務掛在這個列表上
List_t xTasksWaitingToReceive; //等待接收任務列表,因隊空導致出隊失敗而阻塞的任務掛在這個列表上
volatile UBaseType_t uxMessagesWaiting; //隊列中當前數據項數量,也就是消息數
UBaseType_t uxLength; //隊列創建時指定的隊列長度,隊列允許的最大隊列項個數
UBaseType_t uxItemSize; //隊列創建時指定的每個隊列項最大長度,單位字節
volatile int8_t cRxLock; //當隊列上鎖后用來統計接收到的隊列項個數,也就是出隊的隊列項個數
volatile int8_t cTxLock; //當隊列上鎖后用來統計發送到隊列的隊列項個數,也就是入隊的隊列項個數
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; //如果使用靜態存儲,則設置字段值為pdTURE
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
typedef xQUEUE Queue_t;
8.3 隊列創建
8.3.1 動態隊列創建
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
函數描述:創建一個新的隊列,並返回指向隊列的句柄。使用這個函數需要將宏configSUPPORT_DYNAMIC_ALLOCATION置為1。每一個隊列需要存儲空間來存放隊列的狀態和隊列項,如果使用xQueueCreate()創建隊列,則存儲空間由系統自動分配。如果使用xQueueCreateStatic()創建,存儲空間由用戶指定。
函數參數:uxQueueLength:要創建的隊列項個數。uxItemSize:單個隊列項的大小,單位為字節。
返回值:NULL:表示分配存儲空間失敗。其它值:隊列創建成功,返回值是指向創建隊列的句柄。
8.3.2 靜態隊列創建
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
QueueHandle_t xQueueCreateStatic( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer );
函數描述:創建一個新的隊列,並返回指向隊列的句柄。存儲空間由用戶指定。
函數參數:uxQueueLength:要創建的隊列項個數。
uxItemSize:單個隊列項的大小,單位為字節。
pucQueueStorageBuffer:指向隊列項目的存儲區,也就是消息存儲區,存儲區要大於等於uxQueueLength * uxItemSizede 字節。
pxQueueBuffer:指向用來存放隊列結構體的空間。
返回值:NULL:表示分配存儲空間失敗。其它值:隊列創建成功,返回值是指向創建隊列的句柄。
隊列創建的具體實現過程可以參考源碼,這里給出創建一個有4個隊列項,每個隊列項長度為32字節的隊列成功的示意圖。
8.4 向隊列發送消息
向隊列中發送消息分為任務級入隊函數和中斷級入隊函數。
8.4.1 任務級入隊函數
任務級入隊函數有3個,分別為xQueueSend()、xQueueSendToFront()、xQueueSendToBack() 、xQueueOverwrite()。
先介紹xQueueSend()、xQueueSendToFront()、xQueueSendToBack() 。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
函數描述:發送一個隊列項到隊列頭或者隊列尾。xQueueSend()和xQueueSendToBack()是同樣的操作,都是發送數據到隊列的尾部,xQueueSend()是原始版本,當前使用xQueueSendToBack()替換它。xQueueSendToFront()是發送數據到隊列項的首部。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvItemToQueue:指向待發送的隊列項的指針,隊列項是拷貝到隊列中的。隊列項的大小由創建隊列的時候指定。
xTicksToWait:阻塞時間,此參數表示隊列滿的時候任務進入阻塞態等待隊列空閑的最大時間。如果為0,任務立即返回;如果為portMAX_DELAY,任務會一直死等,宏portMAX_DELAY需要置為1;其余值為等到的時鍾計數值,可使用宏pdMS_TO_TICKS()轉換時鍾計數值為毫秒。
返回值:pdPASS:數據成功發送到隊列;errQUEUE_FULL:隊列滿,消息發送失敗。
另外還有xQueueOverwrite()函數為消息覆蓋寫函數。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void *pvItemToQueue );
函數描述:向隊列中發送數據,當隊列滿了以后會覆蓋掉舊的數據,不管這個舊的數據有沒有被其它任務或中斷取走。這個函數常用於向那些長度為1的隊列發送消息。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvItemToQueue:指向待發送的隊列項的指針,隊列項是拷貝到隊列中的。隊列項的大小由創建隊列的時候指定。
返回值:pdPASS:數據成功發送到隊列,因為隊列滿了之后會覆蓋寫,所以不存在失敗的情況。
8.4.2 中斷級入隊函數
任務級入隊函數有3個,分別為xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR() 、xQueueOverwriteFromISR ()。
先介紹xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
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 );
函數描述:發送一個隊列項到隊列頭或者隊列尾,用於中斷服務函數中。xQueueSendFromISR()和xQueueSendToBackFromISR()是同樣的操作,都是發送數據到隊列的尾部。xQueueSendToFrontFromISR()是發送數據到隊列項的首部。和任務級入隊函數不同的是中斷級入隊函數不允許指定任務的阻塞時間。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvItemToQueue:指向待發送的隊列項的指針,隊列項是拷貝到隊列中的。隊列項的大小由創建隊列的時候指定。
pxHigherPriorityTaskWoken:標記退出此函數以后是否進行任務切換,這個變量的值由函數指定,用戶不進行設置,用戶僅需要提供一個變量來保存值。當此值為pdTRUE的時候在退出中斷服務函數之前一定要進行一次任務切換。
返回值:pdPASS:數據成功發送到隊列;errQUEUE_FULL:隊列滿,消息發送失敗。
另外xQueueOverwriteFromISR()函數為消息覆蓋寫函數。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueueOverwriteFromISR( QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken );
函數描述:向隊列中發送數據,當隊列滿了以后會覆蓋掉舊的數據,從隊尾寫入。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvItemToQueue:指向待發送的隊列項的指針,隊列項是拷貝到隊列中的。隊列項的大小由創建隊列的時候指定。
pxHigherPriorityTaskWoken:標記退出此函數以后是否進行任務切換,這個變量的值由函數指定,用戶不進行設置,用戶僅需要提供一個變量來保存值。當此值為pdTRUE的時候在退出中斷服務函數之前一定要進行一次任務切換。
返回值:pdPASS:數據成功發送到隊列,因為隊列滿了之后會覆蓋寫,所以不存在失敗的情況。
8.5 從隊列中讀取消息
出隊就是從隊列中獲取隊列項。也分為任務級出隊函數和中斷級出隊函數。
8.4.1 任務級出隊函數
任務級出隊函數有2個,分別為xQueueReceive()、xQueuePeek()。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait );
函數描述:從隊列中讀取一個隊列項,讀取成功以后就會將隊列中的這條消息刪除。讀取消息時是采用的拷貝方式,所以用戶需要提供一個數組或緩沖區來保存讀取到的數據,所讀取的數據長度是隊列創建的時候設定的每個隊列項的長度。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvBuffer:保存數據的緩沖區,讀取隊列的過程中會將讀取到的數據拷貝到這個緩沖區中。
xTicksToWait:阻塞時間,此參數表示隊列空的時候任務進入阻塞態等待隊列空閑的最大時間。如果為0,任務立即返回;如果為portMAX_DELAY,任務會一直死等,宏portMAX_DELAY需要置為1;其余值為等到的時鍾計數值,可使用宏pdMS_TO_TICKS()轉換時鍾計數值為毫秒。
返回值:pdPASS:成功從隊列中讀取到數據;errQUEUE_EMPTY:隊列空,消息讀取失敗。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait );
函數描述:從隊列中讀取一個隊列項,讀取成功以后不會將隊列中的這條消息刪除。下一次讀取的時候仍然可以從這個隊列中讀取這個消息。讀取消息時是采用的拷貝方式,所以用戶需要提供一個數組或緩沖區來保存讀取到的數據,所讀取的數據長度是隊列創建的時候設定的每個隊列項的長度。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvBuffer:保存數據的緩沖區,讀取隊列的過程中會將讀取到的數據拷貝到這個緩沖區中。
xTicksToWait:阻塞時間,此參數表示隊列空的時候任務進入阻塞態等待隊列空閑的最大時間。如果為0,任務立即返回;如果為portMAX_DELAY,任務會一直死等,宏portMAX_DELAY需要置為1;其余值為等到的時鍾計數值,可使用宏pdMS_TO_TICKS()轉換時鍾計數值為毫秒。
返回值:pdPASS:成功從隊列中讀取到數據;errQUEUE_EMPTY:隊列空,消息讀取失敗。
8.4.2 中斷級出隊函數
中斷級出隊函數xQueueReceiveFromISR()、xQueuePeekFromISR ()。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxHigherPriorityTaskWoken );
函數描述:用於在中斷服務函數中從隊列中讀取一條消息,讀取成功后就會將隊列中的這條數據刪除。讀取消息的方式采用值拷貝方式,需要用戶提供一個數組或者緩沖區來保存讀取到的數據,所讀取的數據長度是創建隊列的時候指定的每個隊列項的長度。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvBuffer:保存數據的緩沖區,讀取隊列的過程中會將讀取到的數據拷貝到這個緩沖區中。
pxHigherPriorityTaskWoken:標記退出此函數以后是否進行任務切換,這個變量的值由函數指定,用戶不進行設置,用戶僅需要提供一個變量來保存值。當此值為pdTRUE的時候在退出中斷服務函數之前一定要進行一次任務切換。
返回值:pdPASS:成功從隊列中讀取到數據;pdFAIL:隊列空,消息讀取失敗。
函數原型:
#include “FreeRTOS.h”
#include “queue.h”
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue, void *pvBuffer );
函數描述:xQueuePeek()的中斷版本,函數讀取成功以后不會將消息刪除。
函數參數:xQueue:隊列句柄,由創建隊列的函數返回。
pvBuffer:保存數據的緩沖區,讀取隊列的過程中會將讀取到的數據拷貝到這個緩沖區中。
返回值:pdPASS:成功從隊列中讀取到數據;pdFAIL:隊列空,消息讀取失敗。
8.6 隊列操作實驗
目的:熟悉隊列的使用
設計:創建兩個任務,任務task0每隔2秒發送1個數據到隊列中,並每隔4秒檢查隊列余量;task1讀取隊列中的消息,阻塞時間為5秒。隊列的容量為4,每個隊列項的大小為4字節。
測試代碼:
/* task00 info */
configSTACK_DEPTH_TYPE Task00_STACK_SIZE = 5;
UBaseType_t Task00_Priority = 1;
TaskHandle_t Task00_xHandle;
/* task01 info */
configSTACK_DEPTH_TYPE Task01_STACK_SIZE = 5;
UBaseType_t Task01_Priority = 1;
TaskHandle_t Task01_xHandle;
#define MSG_QUEUE_NUM 4
#define MSG_QUEUE_ITEM_SIZE 4
QueueHandle_t Message_Queue;
void check_msg_queue(void)
{
unsigned char *p = NULL;
unsigned char msgq_remain_size = 0;
unsigned char msgq_total_size = 0;
taskENTER_CRITICAL();
msgq_remain_size = uxQueueSpacesAvailable(Message_Queue);
msgq_total_size = uxQueueMessagesWaiting(Message_Queue) + msgq_remain_size;
PRINT(" total size:%d remain size:%d", msgq_total_size, msgq_remain_size);
taskEXIT_CRITICAL();
}
void vTask00_Code(void *para)
{
static unsigned int cnt = 0;
BaseType_t err;
unsigned int item = 0;
for (;;)
{
PRINT(" task00 cnt %u...", cnt);
if (cnt%2 == 0) {
item = cnt*100 + 1;
err = xQueueSend(Message_Queue, &item, pdMS_TO_TICKS(5000));
if (err == errQUEUE_FULL)
PRINT("queue full!");
}
if (cnt%4 == 0)
check_msg_queue();
cnt++;
vTaskDelay(1000);
}
}
void vTask01_Code(void *para)
{
static unsigned int cnt = 0;
unsigned int item = 0;
BaseType_t err;
for (;;)
{
PRINT(" task01 cnt %u...", cnt);
err = xQueueReceive(Message_Queue, &item, pdMS_TO_TICKS(5000));
if (err == errQUEUE_EMPTY)
PRINT("queue empty!");
else
PRINT(" item: %d", item);
cnt++;
vTaskDelay(1000);
}
}
void test_queue()
{
Message_Queue = xQueueCreate(MSG_QUEUE_NUM, MSG_QUEUE_ITEM_SIZE);
if (xTaskCreate(vTask00_Code, "task00 task",
Task00_STACK_SIZE, NULL, Task00_Priority,
&Task00_xHandle) != pdPASS)
{
PRINT("creat task00 failed!\n");
}
if (xTaskCreate(vTask01_Code, "task01 task",
Task01_STACK_SIZE, NULL, Task01_Priority,
&Task01_xHandle) != pdPASS)
{
PRINT("creat task01 failed!\n");
}
}
void creat_task(void)
{
test_queue();
}
編譯、運行,結果如下:
$ ./build/freertos-simulator
task00 cnt 0...
total size:4 remain size:3
task01 cnt 0...
item: 1
task00 cnt 1...
task01 cnt 1...
task00 cnt 2...
item: 201
task00 cnt 3...
task01 cnt 2...
task00 cnt 4...
total size:4 remain size:3
item: 401
task00 cnt 5...
task01 cnt 3...
task00 cnt 6...
item: 601
task00 cnt 7...
task01 cnt 4...
可以看出,task01會阻塞性的等待隊列中的值(見item:201)。
下面測試隊列空的情況,task00不往隊列中放入數據:
void vTask00_Code(void *para)
{
static unsigned int cnt = 0;
BaseType_t err;
unsigned int item = 0;
for (;;)
{
PRINT(" task00 cnt %u...", cnt);
if (cnt%4 == 0)
check_msg_queue();
cnt++;
vTaskDelay(1000);
}
}
void vTask01_Code(void *para)
{
static unsigned int cnt = 0;
unsigned int item = 0;
BaseType_t err;
for (;;)
{
PRINT(" task01 cnt %u...", cnt);
err = xQueueReceive(Message_Queue, &item, pdMS_TO_TICKS(5000));
if (err == errQUEUE_EMPTY)
PRINT("queue empty!");
else
PRINT(" item: %d", item);
cnt++;
vTaskDelay(1000);
}
}
編譯、運行,結果如下,task01阻塞了5秒:
$ ./build/freertos-simulator
task00 cnt 0...
total size:4 remain size:4
task01 cnt 0...
task00 cnt 1...
task00 cnt 2...
task00 cnt 3...
task00 cnt 4...
total size:4 remain size:4
queue empty!
task00 cnt 5...
task01 cnt 1...
task00 cnt 6...
下面測試隊列空的情況,task01不從隊列中取數據:
void vTask00_Code(void *para)
{
static unsigned int cnt = 0;
BaseType_t err;
unsigned int item = 0;
for (;;)
{
PRINT(" task00 cnt %u...", cnt);
item = cnt*100 + 1;
err = xQueueSend(Message_Queue, &item, pdMS_TO_TICKS(5000));
if (err == errQUEUE_FULL)
PRINT("queue full!");
if (cnt%4 == 0)
check_msg_queue();
cnt++;
vTaskDelay(1000);
}
}
void vTask01_Code(void *para)
{
static unsigned int cnt = 0;
unsigned int item = 0;
BaseType_t err;
for (;;)
{
PRINT(" task01 cnt %u...", cnt);
cnt++;
vTaskDelay(1000);
}
}
編譯、運行,結果如下,task00往隊列中放了4個數據項之后,就阻塞了,每次阻塞時間為5秒:
$ ./build/freertos-simulator
task00 cnt 0...
total size:4 remain size:3
task01 cnt 0...
task00 cnt 1...
task01 cnt 1...
task00 cnt 2...
task01 cnt 2...
task00 cnt 3...
task01 cnt 3...
task00 cnt 4...
task01 cnt 4...
task01 cnt 5...
task01 cnt 6...
task01 cnt 7...
task01 cnt 8...
queue full!
total size:4 remain size:0
task01 cnt 9...
task00 cnt 5...
task01 cnt 10...
task01 cnt 11...
task01 cnt 12...
task01 cnt 13...
task01 cnt 14...
queue full!
task01 cnt 15...
task00 cnt 6...