寫在前面:杰杰這個月很忙~所以并沒有時間更新,現在健身房閉館裝修,晚上有空就更新一下!其實在公眾號沒更新的這段日子,每天都有兄弟在來關注我的公眾號,這讓我受寵若驚,在這里謝謝大家的支持啦!!謝謝^
在這里我們就跟著火哥的書來學習一下FreeRTOS的訊息佇列,這本書我覺得寫得很好,基本都講解到了,關于什么是訊息佇列,就請大家去看書,基礎知識我暫時不說了,
宣告:本書絕大部分內容來自《FreeRTOS 內核實作與應用開發實戰指南—基于野火 STM32 全系列(M3/4/7)開發板》,如涉及侵權請聯系杰杰洗掉
FreeRTOS的訊息佇列支持
- FreeRTOS 中使用佇列資料結構實作任務異步通信作業,具有如下特性:
- 訊息支持先進先出方式排隊,支持異步讀寫作業方式,
- 讀寫佇列均支持超時機制,
- 訊息支持后進先出方式排隊, 往隊首發送訊息(LIFO) ,
- 可以允許不同長度(不超過佇列節點最大值)的任意型別訊息,
- 一個任務能夠從任意一個訊息佇列接收和發送訊息,
- 多個任務能夠從同一個訊息佇列接收和發送訊息,
- 當佇列使用結束后,可以通過洗掉佇列函式進行洗掉,
FreeRTOS佇列的特點
一般來說,魚與熊掌不可兼得,如果資料太多,那資料傳輸的速度必然是會慢下來,而如果采用參考傳遞的方式,當原始資料被修改的時候,資料有變得不安全,但是FreeRTOS支持拷貝與參考的方式進行資料的傳輸,變得更加靈活,
佇列是通過拷貝傳遞資料的,但這并不妨礙佇列通過參考來傳遞資料,當資訊的大小到達一個臨界點后,逐位元組拷貝整個資訊是不實際的,可以定義一個指向資料區域的指標,將指標傳遞即可,這種方法在物聯網中是非常常用的,
訊息佇列控制塊
其實訊息佇列不僅僅是用于當做訊息佇列,FreeRTOS還把他當做信號量的資料結構來使用
typedef struct QueueDefinition
{
int8_t *pcHead; /* 指向佇列存盤區起始位置,即第一個佇列項 */
int8_t *pcTail; /* 指向佇列存盤區結束后的下一個位元組 */
int8_t *pcWriteTo; /* 指向下佇列存盤區的下一個空閑位置 */
union /* 使用聯合體用來確保兩個互斥的結構體成員不會同時出現 */
{
int8_t *pcReadFrom; /* 當結構體用于佇列時,這個欄位指向出隊專案中的最后一個. */
UBaseType_t uxRecursiveCallCount;/* 當結構體用于互斥量時,用作計數器,保存遞回互斥量被"獲取"的次數. */
} u;
List_t xTasksWaitingToSend; /* 因為等待入隊而阻塞的任務串列,按照優先級順序存盤 */
List_t xTasksWaitingToReceive; /* 因為等待佇列項而阻塞的任務串列,按照優先級順序存盤 */
volatile UBaseType_t uxMessagesWaiting;/*< 當前佇列的佇列項數目 */
UBaseType_t uxLength; /* 佇列項的數目 */
UBaseType_t uxItemSize; /* 每個佇列項的大小 */
volatile BaseType_t xRxLock; /* 佇列上鎖后,存盤從佇列收到的串列項數目,如果佇列沒有上鎖,設定為queueUNLOCKED */
volatile BaseType_t xTxLock; /* 佇列上鎖后,存盤發送到佇列的串列項數目,如果佇列沒有上鎖,設定為queueUNLOCKED */
/* 洗掉部分原始碼 */
} xQUEUE;
typedef xQUEUE Queue_t;
先過一遍訊息佇列的資料結構,其實沒啥東西的,記不住也沒啥大問題,下面會用到就行了,
創建訊息佇列
FreeRTOS創建佇列API函式是xQueueCreate(),但其實這是一個宏,真正被執行的函式是xQueueGenericCreate(),我們稱這個函式為通用佇列創建函式,
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
size_t xQueueSizeInBytes;
uint8_t *pucQueueStorage;
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
if( uxItemSize == ( UBaseType_t ) 0 )
{
/* 如果 uxItemSize 為 0,也就是單個訊息空間大小為 0,這樣子就不
需要申請記憶體了,那么 xQueueSizeInBytes 也設定為 0 即可,設定為 0 是可以的,用作信號
量的時候這個就可以設定為 0,*/
xQueueSizeInBytes = ( size_t ) 0;
}
else
{
/* 分配足夠訊息存盤空間,空間的大小為佇列長度*單個訊息大小 */
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}
/* FreeRTOS 呼叫 pvPortMalloc()函式向系統申請記憶體空間,記憶體大
小為訊息佇列控制塊大小加上訊息存盤空間大小,因為這段記憶體空間是需要保證連續的 */
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
if( pxNewQueue != NULL )
{
/* 計算出訊息存盤空間的起始地址 */
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
return pxNewQueue;
}
真正的初始化在下面這個函式中:
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
taskENTER_CRITICAL();
{
/* 訊息佇列資料結構的相關初始化 */
pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
pxQueue->pcWriteTo = pxQueue->pcHead;
pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
pxQueue->cRxLock = queueUNLOCKED;
pxQueue->cTxLock = queueUNLOCKED;
if( xNewQueue == pdFALSE )
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Ensure the event queues start in the correct state. */
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
初始化完成之后,為了讓大家理解,訊息佇列是怎么樣的,就給出一個示意圖,黃色部分是訊息佇列的控制塊,而綠色部分則是訊息佇列的存放訊息的地方,在創建的時候,我們知道的訊息佇列長度與單個訊息空間大小,

訊息佇列發送
任務或者中斷服務程式都可以給訊息佇列發送訊息,當發送訊息時,如果佇列未滿或者允許覆寫入隊, FreeRTOS 會將訊息拷貝到訊息佇列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果佇列一直不允許入隊,該任務將保持阻塞狀態以等待佇列允許入隊,當其它任務從其等待的佇列中讀取入了資料(佇列未滿),該任務將自動由阻塞態轉為就緒態,當任務等待的時間超過了指定的阻塞時間,即使佇列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送訊息的任務或者中斷程式會收到一個錯誤碼 errQUEUE_FULL,
發送緊急訊息的程序與發送訊息幾乎一樣,唯一的不同是,當發送緊急訊息時,發送的位置是訊息佇列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急訊息,從而及時進行訊息處理,
下面是訊息佇列的發送API介面,函式中有FromISR則表明在中斷中使用的,

1 /*-----------------------------------------------------------*/
2 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, (1)
3 const void * const pvItemToQueue, (2)
4 TickType_t xTicksToWait, (3)
5 const BaseType_t xCopyPosition ) (4)
6 {
7 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
8 TimeOut_t xTimeOut;
9 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
10
11 /* 已洗掉一些斷言操作 */
12
13 for ( ;; ) {
14 taskENTER_CRITICAL(); (5)
15 {
16 /* 佇列未滿 */
17 if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
18 || ( xCopyPosition == queueOVERWRITE ) ) { (6)
19 traceQUEUE_SEND( pxQueue );
20 xYieldRequired =
21 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (7)
22
23 /* 已洗掉使用佇列集部分代碼 */
24 /* 如果有任務在等待獲取此訊息佇列 */
25 if ( listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive))==pdFALSE){ (8)
26 /* 將任務從阻塞中恢復 */
27 if ( xTaskRemoveFromEventList(
28 &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE) { (9)
29 /* 如果恢復的任務優先級比當前運行任務優先級還高,
30 那么需要進行一次任務切換 */
31 queueYIELD_IF_USING_PREEMPTION(); (10)
32 } else {
33 mtCOVERAGE_TEST_MARKER();
34 }
35 } else if ( xYieldRequired != pdFALSE ) {
36 /* 如果沒有等待的任務,拷貝成功也需要任務切換 */
37 queueYIELD_IF_USING_PREEMPTION(); (11)
38 } else {
39 mtCOVERAGE_TEST_MARKER();
40 }
41
42 taskEXIT_CRITICAL(); (12)
43 return pdPASS;
44 }
45 /* 佇列已滿 */
46 else { (13)
47 if ( xTicksToWait == ( TickType_t ) 0 ) {
48 /* 如果用戶不指定阻塞超時時間,退出 */
49 taskEXIT_CRITICAL(); (14)
50 traceQUEUE_SEND_FAILED( pxQueue );
51 return errQUEUE_FULL;
52 } else if ( xEntryTimeSet == pdFALSE ) {
53 /* 初始化阻塞超時結構體變數,初始化進入
54 阻塞的時間xTickCount和溢位次數xNumOfOverflows */
55 vTaskSetTimeOutState( &xTimeOut ); (15)
56 xEntryTimeSet = pdTRUE;
57 } else {
58 mtCOVERAGE_TEST_MARKER();
59 }
60 }
61 }
62 taskEXIT_CRITICAL(); (16)
63 /* 掛起調度器 */
64 vTaskSuspendAll();
65 /* 佇列上鎖 */
66 prvLockQueue( pxQueue );
67
68 /* 檢查超時時間是否已經過去了 */
69 if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)==pdFALSE){ (17)
70 /* 如果佇列還是滿的 */
71 if ( prvIsQueueFull( pxQueue ) != pdFALSE ) { (18)
72 traceBLOCKING_ON_QUEUE_SEND( pxQueue );
73 /* 將當前任務添加到佇列的等待發送串列中
74 以及阻塞延時串列,延時時間為用戶指定的超時時間xTicksToWait */
75 vTaskPlaceOnEventList(
76 &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(19)
77 /* 佇列解鎖 */
78 prvUnlockQueue( pxQueue ); (20)
79
80 /* 恢復調度器 */
81 if ( xTaskResumeAll() == pdFALSE ) {
82 portYIELD_WITHIN_API();
83 }
84 } else {
85 /* 佇列有空閑訊息空間,允許入隊 */
86 prvUnlockQueue( pxQueue ); (21)
87 ( void ) xTaskResumeAll();
88 }
89 } else {
90 /* 超時時間已過,退出 */
91 prvUnlockQueue( pxQueue ); (22)
92 ( void ) xTaskResumeAll();
93
94 traceQUEUE_SEND_FAILED( pxQueue );
95 return errQUEUE_FULL;
96 }
97 }
98 }
99 /*-----------------------------------------------------------*/
如果阻塞時間不為 0,任務會因為等待入隊而進入阻塞, 在將任務設定為阻塞的程序中, 系統不希望有其它任務和中斷操作這個佇列的 xTasksWaitingToReceive 串列和 xTasksWaitingToSend 串列,因為可能引起其它任務解除阻塞,這可能會發生優先級翻轉,比如任務 A 的優先級低于當前任務,但是在當前任務進入阻塞的程序中,任務 A 卻因為其它原因解除阻塞了,這顯然是要絕對禁止的,因此FreeRTOS 使用掛起調度器禁止其它任務操作佇列,因為掛起調度器意味著任務不能切換并且不準呼叫可能引起任務切換的 API 函式,但掛起調度器并不會禁止中斷,中斷服務函式仍然可以操作佇列事件串列,可能會解除任務阻塞、可能會進行背景關系切換,這也是不允許的,于是,解決辦法是不但掛起調度器,還要給佇列上鎖,禁止任何中斷來操作佇列,
再借用朱工精心制作的流程圖加以理解:圖片出自:https://blog.csdn.net/zhzht19861011/article/details/51510384

訊息佇列出隊的API函式介面:

訊息佇列出隊程序分析,其實跟入隊差不多,請看注釋:
1 /*-----------------------------------------------------------*/
2 BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, (1)
3 void * const pvBuffer, (2)
4 TickType_t xTicksToWait, (3)
5 const BaseType_t xJustPeeking ) (4)
6 {
7 BaseType_t xEntryTimeSet = pdFALSE;
8 TimeOut_t xTimeOut;
9 int8_t *pcOriginalReadPosition;
10 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
11
12 /* 已洗掉一些斷言 */
13 for ( ;; ) {
14 taskENTER_CRITICAL(); (5)
15 {
16 const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
17
18 /* 看看佇列中有沒有訊息 */
19 if ( uxMessagesWaiting > ( UBaseType_t ) 0 ) { (6)
20 /*防止僅僅是讀取訊息,而不進行訊息出隊操作*/
21 pcOriginalReadPosition = pxQueue->u.pcReadFrom; (7)
22 /* 拷貝訊息到用戶指定存放區域pvBuffer */
23 prvCopyDataFromQueue( pxQueue, pvBuffer ); (8)
24
25 if ( xJustPeeking == pdFALSE ) { (9)
26 /* 讀取訊息并且訊息出隊 */
27 traceQUEUE_RECEIVE( pxQueue );
28
29 /* 獲取了訊息,當前訊息佇列的訊息個數需要減一 */
30 pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (10)
31 /* 判斷一下訊息佇列中是否有等待發送訊息的任務 */
32 if ( listLIST_IS_EMPTY( (11)
33 &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) {
34 /* 將任務從阻塞中恢復 */
35 if ( xTaskRemoveFromEventList( (12)
36 &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) {
37 /* 如果被恢復的任務優先級比當前任務高,會進行一次任務切換 */
38 queueYIELD_IF_USING_PREEMPTION(); (13)
39 } else {
40 mtCOVERAGE_TEST_MARKER();
41 }
42 } else {
43 mtCOVERAGE_TEST_MARKER();
44 }
45 } else { (14)
46 /* 任務只是看一下訊息(peek),并不出隊 */
47 traceQUEUE_PEEK( pxQueue );
48
49 /* 因為是只讀訊息 所以還要還原讀訊息位置指標 */
50 pxQueue->u.pcReadFrom = pcOriginalReadPosition; (15)
51
52 /* 判斷一下訊息佇列中是否還有等待獲取訊息的任務 */
53 if ( listLIST_IS_EMPTY( (16)
54 &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) {
55 /* 將任務從阻塞中恢復 */
56 if ( xTaskRemoveFromEventList(
57 &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) {
58 /* 如果被恢復的任務優先級比當前任務高,會進行一次任務切換 */
59 queueYIELD_IF_USING_PREEMPTION();
60 } else {
61 mtCOVERAGE_TEST_MARKER();
62 }
63 } else {
64 mtCOVERAGE_TEST_MARKER();
65 }
66 }
67
68 taskEXIT_CRITICAL(); (17)
69 return pdPASS;
70 } else { (18)
71 /* 訊息佇列中沒有訊息可讀 */
72 if ( xTicksToWait == ( TickType_t ) 0 ) { (19)
73 /* 不等待,直接回傳 */
74 taskEXIT_CRITICAL();
75 traceQUEUE_RECEIVE_FAILED( pxQueue );
76 return errQUEUE_EMPTY;
77 } else if ( xEntryTimeSet == pdFALSE ) {
78 /* 初始化阻塞超時結構體變數,初始化進入
79 阻塞的時間xTickCount和溢位次數xNumOfOverflows */
80 vTaskSetTimeOutState( &xTimeOut ); (20)
81 xEntryTimeSet = pdTRUE;
82 } else {
83 mtCOVERAGE_TEST_MARKER();
84 }
85 }
86 }
87 taskEXIT_CRITICAL();
88
89 vTaskSuspendAll();
90 prvLockQueue( pxQueue ); (21)
91
92 /* 檢查超時時間是否已經過去了*/
93 if ( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) {(22)
94 /* 如果佇列還是空的 */
95 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
96 traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue ); (23)
97 /* 將當前任務添加到佇列的等待接收串列中
98 以及阻塞延時串列,阻塞時間為用戶指定的超時時間xTicksToWait */
99 vTaskPlaceOnEventList(
100 &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
101 prvUnlockQueue( pxQueue );
102 if ( xTaskResumeAll() == pdFALSE ) {
103 /* 如果有任務優先級比當前任務高,會進行一次任務切換 */
104 portYIELD_WITHIN_API();
105 } else {
106 mtCOVERAGE_TEST_MARKER();
107 }
108 } else {
109 /* 如果佇列有訊息了,就再試一次獲取訊息 */
110 prvUnlockQueue( pxQueue ); (24)
111 ( void ) xTaskResumeAll();
112 }
113 } else {
114 /* 超時時間已過,退出 */
115 prvUnlockQueue( pxQueue ); (25)
116 ( void ) xTaskResumeAll();
117
118 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
119 /* 如果佇列還是空的,回傳錯誤代碼errQUEUE_EMPTY */
120 traceQUEUE_RECEIVE_FAILED( pxQueue );
121 return errQUEUE_EMPTY; (26)
122 } else {
123 mtCOVERAGE_TEST_MARKER();
124 }
125 }
126 }
127 }
128 /*-----------------------------------------------------------*/
關注我

更多資料歡迎關注“物聯網IoT開發”公眾號!
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/33412.html
標籤:嵌入式
