目錄
1.? 基本概念
2.? 數據存儲
3.? 運作機制
4.? 阻塞機制
4.1 ?出隊阻塞
4.2 ?入隊阻塞
5.??操作示意圖
5.1? 創建隊列
5.2? 向隊列發送第一個消息
5.3? 向隊列發送第二個消息
5.4? 從隊列讀取消息
6.? 消息隊列控制塊
7.? 消息隊列常用函數
7.1? 消息隊列創建函數 xQueueCreate()
7.2? 消息隊列靜態創建函數? xQueueCreateStatic()
7.3??消息隊列刪除函數? vQueueDelete()
7.4??向消息隊列發送消息函數
7.4.1??xQueueSend()與 xQueueSendToBack()
7.4.2??xQueueSendFromISR()與 xQueueSendToBackFromISR()
7.4.3? xQueueSendToFront()
7.4.4??xQueueSendToFrontFromISR()
7.4.5? 通用消息隊列發送函數 xQueueGenericSend()
7.4.6? 消息隊列發送函數 xQueueGenericSendFromISR()
7.5??從消息隊列讀取消息函數
7.5.1??xQueueReceive()與 xQueuePeek()
7.5.2??xQueueReceiveFromISR()與 xQueuePeekFromISR()
1.? 基本概念
????????隊列又稱消息隊列,是一種常用于任務間通信的數據結構,消息隊列可以在任務與任務間、中斷和任務間傳遞信息,實現了任務接收來自其他任務或中斷的不固定長度的消息,任務能夠從隊列里面讀取消息,當隊列中的消息是空時,讀取消息的任務將被阻塞,用戶還可以指定阻塞的任務時間 xTicksToWait,在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當隊列中有新消息時,被阻塞的任務會被喚醒并處理新消息;當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉為就緒態。消息隊列是一種異步的通信方式。
????????FreeRTOS 中使用隊列數據結構實現任務異步通信工作,具有如下特性:
- 消息支持先進先出方式排隊,支持異步讀寫工作方式。
- 讀寫隊列均支持超時機制。
- 消息支持后進先出方式排隊,往隊首發送消息(LIFO)。
- 可以允許不同長度(不超過隊列節點最大值)的任意類型消息。
- 一個任務能夠從任意一個消息隊列接收和發送消息。
- 多個任務能夠從同一個消息隊列接收和發送消息。
- 當隊列使用結束后,可以通過刪除隊列函數進行刪除。
2.? 數據存儲
????????通過消息隊列服務,任務或中斷服務例程可以將一條或多條消息放入消息隊列中。同樣,一個或多個任務可以從消息隊列中獲得消息。當有多個消息發送到消息隊列時,通常是將先進入消息隊列的消息先傳給任務,也就是說,任務先得到的是最先進入消息隊列的消息,即先進先出原則(FIFO),但是也支持后進先出原則(LIFO)。
????????數據發送到隊列中會導致數據拷貝,也就是將要發送的數據拷貝到隊列中,這就意味著在隊列中存儲的是數據的原始值,而不是原數據的引用(即只傳遞數據的指針),這個也叫做值傳遞。
3.? 運作機制
????????創建消息隊列時 FreeRTOS 會先給消息隊列分配一塊內存空間,這塊內存的大小等于消息隊列控制塊大小加上(單個消息空間大小與消息隊列長度的乘積),接著再初始化消息隊列,此時消息隊列為空。
????????FreeRTOS 的消息隊列控制塊由多個元素組成,當消息隊列被創建時,系統會為控制塊分配對應的內存空間,用于保存消息隊列的一些信息如消息的存儲位置,頭指針 pcHead、尾指針 pcTail、消息大小 uxItemSize 以及隊列長度 uxLength 等。(先大概有個了解,后續講解代碼在著重講解)同時每個消息隊列都與消息空間在同一段連續的內存空間中,在創建成功的時候,這些內存就被占用了,只有刪除了消息隊列的時候,這段內存才會被釋放掉,創建成功的時候就已經分配好每個消息空間與消息隊列的容量,無法更改,每個消息空間可以存放不大于消息大小 uxItemSize 的任意類型的數據,所有消息隊列中的消息空間總數即是消息隊列的長度,這個長度可在消息隊列創建時指定。
????????任務或者中斷服務程序都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊,FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程序會收到一個錯誤碼 errQUEUE_FULL。
????????發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。
????????當某個任務試圖讀一個隊列時,其可以指定一個阻塞超時時間。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務程序往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
????????當消息隊列不再被使用時,應該刪除它以釋放系統資源,一旦操作完成,消息隊列將被永久性的刪除。
4.? 阻塞機制
4.1 ?出隊阻塞
當任務嘗試從一個隊列讀取消息的時候可以指定一個阻塞時間,這個阻塞時間就是當任務從隊列讀取消息無效的時候任務阻塞的時間。
????????假設有一個任務 A 對某個隊列進行讀操作的時候(也就是我們所說的出隊),發現它沒有消息,那么此時任務 A有3個選擇:
????????第一個選擇,任務 A 扭頭就走,既然隊列沒有消息,那我也不等了,干其它事情去,這樣子任務 A 不會進入阻塞態;
????????第二個選擇,任務 A 還是在這里等等吧,可能過一會隊列就有消息,此時任務 A 會進入阻塞狀態,在等待著消息的道來,而任務 A 的等待時間就由我們自己定義,比如設置 1000 個系統時鐘節拍 tick 的等待,在這 1000 個 tick 到來之前任務 A 都是處于阻塞態,當阻塞的這段時間任務 A 等到了隊列的消息,那么任務 A 就會從阻塞態變成就緒態,如果此時任務 A 比當前運行的任務優先級還高,那么,任務 A 就會得到消息并且運行;假如 1000 個 tick 都過去了,隊列還沒消息,那任務 A 就不等了,從阻塞態中喚醒,返回一個沒等到消息的錯誤代碼,然后繼續執行任務 A 的其他代碼;
????????第三個選擇,任務 A 死等,不等到消息就不走了,這樣子任務 A 就會進入阻塞態,直到完成讀取隊列的消息。
4.2 ?入隊阻塞
入隊說的是向隊列中發送消息,將消息加入到隊列中,和出隊阻塞一樣,當一個任務向隊列發送消息的話也可以設置阻塞時間。
????????而在發送消息操作的時候,為了保護數據,當且僅當隊列允許入隊的時候,發送者才能成功發送消息;隊列中無可用消息空間時,說明消息隊列已滿,此時,系統會根據用戶指定的阻塞超時時間將任務阻塞,在指定的超時時間內如果還不能完成入隊操作,發送消息的任務或者中斷服務程序會收到一個錯誤碼 errQUEUE_FULL,然后解除阻塞狀態;當然,只有在任務中發送消息才允許進行阻塞狀態,而在中斷中發送消息不允許帶有阻塞機制的,需要調用在中斷中發送消息的 API 函數接口,因為發送消息的上下文環境是在中斷中,不允許有阻塞的情況。假如有多個任務阻塞在一個消息隊列中,那么這些阻塞的任務將按照任務優先級進行排序,優先級高的任務將優先獲得隊列的訪問權。
5.??操作示意圖
5.1? 創建隊列
? ? ? ? 首先創建兩個任務A和B,任務A想向任務B發送消息,這個消息變量為x,我們在創建隊列的時候需要創建其對列的長度以及每個消息的長度,消息的長度需要看好其數據類型,此時x的數據類型為int,占據四個字節,因此消息的長度為4:
5.2? 向隊列發送第一個消息
????????任務 A 的變量 x 值為 10,將這個值發送到消息隊列中。此時隊列剩余長度就是 3 了。前面說了向隊列中發送消息是采用拷貝的方式,所以一旦消息發送完成變量 x 就可以再次被使用,賦其他的值:
5.3? 向隊列發送第二個消息
????????任務 A 又向隊列發送了一個消息,即新的 x 的值,這里是 20。緊跟在上一個數據的后面,此時隊列剩余長度為 2。
5.4? 從隊列讀取消息
? ? ? ? 任務 B 從隊列中讀取消息,并將讀取到的消息值賦值給 y,這樣 y 就等于 10 了。任務 B 從隊列中讀取消息完成以后可以選擇清除掉這個消息或者不清除。當選擇清除這個 消息的話其他任務或中斷就不能獲取這個消息了,而且隊列剩余大小就會加一,變成 3。如果 不清除的話其他任務或中斷也可以獲取這個消息,而隊列剩余大小依舊是 2。
6.? 消息隊列控制塊
????????FreeRTOS 的消息隊列控制塊由多個元素組成,當消息隊列被創建時,系統會為控制塊分配對應的內存空間,用于保存消息隊列的一些信息如消息的存儲位置,頭指針 pcHead、尾指針 pcTail、消息大小 uxItemSize 以及隊列長度 uxLength,以及當前隊列消息個數 uxMessagesWaiting 等:
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 int8_t cRxLock; /*< 存儲在隊列鎖定時從隊列中接收(移除)的項數。當隊列未鎖定時設置為 `queueUNLOCKED`。 */volatile int8_t cTxLock; /*< 存儲在隊列鎖定時發送到隊列(添加)的項數。當隊列未鎖定時設置為 `queueUNLOCKED`。 */#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*< 如果隊列使用的內存是靜態分配的,則設置為 `pdTRUE`,以確保不嘗試釋放內存。 */#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition *pxQueueSetContainer; /*< 指向包含此隊列的隊列集合的指針。 */#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber; /*< 隊列的唯一編號。 */uint8_t ucQueueType; /*< 隊列的類型。 */#endif} xQUEUE;
7.? 消息隊列常用函數
7.1? 消息隊列創建函數 xQueueCreate()
????????xQueueCreate()用于創建一個新的隊列并返回可用于訪問這個隊列的隊列句柄。隊列句柄其實就是一個指向隊列數據結構類型的指針。
????????隊列就是一個數據結構,用于任務間的數據的傳遞。每創建一個新的隊列都需要為其分配 RAM,一部分用于存儲隊列的狀態,剩下的作為隊列消息的存儲區域。使用xQueueCreate()創建隊列時,使用的是動態內存分配,所以要想使用該函數必須在FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定義為 1 來使能,這是個用于使能動態內存分配的宏,通常情況下,在 FreeRTOS 中,凡是創建任務,隊列,信號量和互斥量等內核對象都需要使用動態內存分配,所以這個宏默認在 FreeRTOS.h 頭文件中已經使能(即定義為 1)。如果想使用靜態內存,則可以使用 xQueueCreateStatic() 函數來創建一個隊列。使用靜態創建消息隊列函數創建隊列時需要的形參更多,需要的內存由編譯的時候預先分配好,一般很少使用這種方法。
函數原型:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize ) \
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
函數說明:
函數原型 | QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_tuxItemSize); | |
功能 | 用于創建一個新的隊列。 | |
參數 | uxQueueLength | 隊列能夠存儲的最大消息單元數目,即隊列長度。 |
uxltemSize | 隊列中消息單元的大小,以字節為單位。 | |
返回值 | 如果創建成功則返回一個隊列句柄,用于訪問創建的隊列。如果創建不成功則返回NULL,可能原因是創建隊列需要的RAM無法分配成功。 |
????????從函數原型中,我們可以看到,創建隊列真正使用的函數是 xQueueGenericCreate(),消息隊列創建函數,顧名思義,就是創建一個隊列,與任務一樣,都是需要先創建才能使用的東西,FreeRTOS 肯定不知道我們需要什么樣的隊列,比如隊列的長度,消息的大小這些信息都是需要我們自己定義的,FreeRTOS 提供給我們這個創建函數,愛怎么搞都是我們自己來實現,下面來看看 xQueueGenericCreate()函數源碼:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )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 ){/* There is not going to be a queue storage area. 消息空間大小為 0*/xQueueSizeInBytes = ( size_t ) 0;//如果 uxItemSize 為 0,也就是單個消息空間大小為 0,這樣子就不需要申請內存了,那么 xQueueSizeInBytes 也設置為 0 即可,設置為 0 是可以的,用作信號量的時候這個就可以設置為 0。}else{/* Allocate enough space to hold the maximum number of items thatcan be in the queue at any time. 分配足夠消息存儲空間,空間的大小為隊列長度*單個消息大小*/xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */}/* 向系統申請內存,內存大小為消息隊列控制塊大小+消息存儲空間大小 */pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );if( pxNewQueue != NULL ){/* Jump past the queue structure to find the location of the queuestorage area. 計算出消息存儲空間的起始地址*/pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );#if( configSUPPORT_STATIC_ALLOCATION == 1 ){/* Queues can be created either statically or dynamically, sonote this task was created dynamically in case it is laterdeleted. */pxNewQueue->ucStaticallyAllocated = pdFALSE;}#endif /* configSUPPORT_STATIC_ALLOCATION */prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );//①}return pxNewQueue;}#endif /* configSUPPORT_STATIC_ALLOCATION */
????????FreeRTOS 調用 pvPortMalloc()函數向系統申請內存空間,內存大小為消息隊列控制塊大小加上消息存儲空間大小,因為這段內存空間是需要保證連續的:
? ? ? ? ①:調用 prvInitialiseNewQueue()函數將消息隊列進行初始化。其實xQueueGenericCreate()主要是用于分配消息隊列內存的,消息隊列初始化函數源碼:
/*
const UBaseType_t uxQueueLength:消息隊列長度;
const UBaseType_t uxItemSize:單個消息大小;
uint8_t *pucQueueStorage:存儲消息起始地址;
const uint8_t ucQueueType:消息隊列類型● queueQUEUE_TYPE_BASE:表示隊列。● queueQUEUE_TYPE_SET:表示隊列集合 。● queueQUEUE_TYPE_MUTEX:表示互斥量。● queueQUEUE_TYPE_COUNTING_SEMAPHORE:表示計數信號量。● queueQUEUE_TYPE_BINARY_SEMAPHORE:表示二進制信號量。● queueQUEUE_TYPE_RECURSIVE_MUTEX :表示遞歸互斥量。
Queue_t *pxNewQueue:消息隊列控制塊
*/
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{/* Remove compiler warnings about unused parameters shouldconfigUSE_TRACE_FACILITY not be set to 1. */( void ) ucQueueType;if( uxItemSize == ( UBaseType_t ) 0 ){/* No RAM was allocated for the queue storage area, but PC head cannotbe set to NULL because NULL is used as a key to say the queue is used asa mutex. Therefore just set pcHead to point to the queue as a benignvalue that is known to be within the memory map. 沒有為消息存儲分配內存,但是 pcHead 指針不能設置為 NULL,因為隊列用作互斥量時,pcHead 要設置成 NULL。這里只是將 pcHead 指向一個已知的區域*/pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;}else{/* Set the head to the start of the queue storage area.設置 pcHead 指向存儲消息的起始地址 */pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;}/* Initialise the queue members as described where the queue type isdefined. 初始化消息隊列控制塊的其他成員*/pxNewQueue->uxLength = uxQueueLength;pxNewQueue->uxItemSize = uxItemSize;( void ) xQueueGenericReset( pxNewQueue, pdTRUE );//② 重置消息隊列,在消息隊列初始化的時候,需要重置一下相關參數#if ( configUSE_TRACE_FACILITY == 1 ){pxNewQueue->ucQueueType = ucQueueType;}#endif /* configUSE_TRACE_FACILITY */#if( configUSE_QUEUE_SETS == 1 ){pxNewQueue->pxQueueSetContainer = NULL;}#endif /* configUSE_QUEUE_SETS */traceQUEUE_CREATE( 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 );//重置消息隊列的成員變量,pcTail 指向存儲消息內存空間的結束地址。pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;//當前消息隊列中的消息個數 uxMessagesWaiting 為 0。pxQueue->pcWriteTo = pxQueue->pcHead;//pcWriteTo 指向隊列消息存儲區下一個可用消息空間,因為是重置消息隊列,就指向消息隊列的第一個消息空間,也就是 pcHead 指向的空間。pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );//pcReadFrom 指向消息隊列最后一個消息空間。pxQueue->cRxLock = queueUNLOCKED;//消息隊列沒有上鎖,設置為 queueUNLOCKED。pxQueue->cTxLock = queueUNLOCKED;if( xNewQueue == pdFALSE ){/* If there are tasks blocked waiting to read from the queue, thenthe tasks will remain blocked as after this function exits the queuewill still be empty. If there are tasks blocked waiting to write tothe queue, then one should be unblocked as after this function exitsit will be possible to write to it. 如果不是新建一個消息隊列,那么之前的消息隊列可能阻塞了一些任務,需要將其解除阻塞。如果有發送消息任務被阻塞,那么需要將它恢復,而如果任務是因為讀取消息而阻塞,那么重置之后的消息隊列也是空的,則無需被恢復。*/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();/* A value is returned for calling semantic consistency with previousversions. */return pdPASS;
}
消息隊列創建完成示意圖:
????????在創建消息隊列的時候,是需要用戶自己定義消息隊列的句柄的,但是注意了,定義了隊列的句柄并不等于創建了隊列,創建隊列必須是調用消息隊列創建函數進行創建(可以是靜態也可以是動態創建),否則,以后根據隊列句柄使用消息隊列的其它函數的時候會發生錯誤,創建完成會返回消息隊列的句柄,用戶通過句柄就可使用消息隊列進行發送與讀取消息隊列的操作,如果返回的是 NULL 則表示創建失敗,舉個例子:
在此模版上進行修改:
基于STM32F103ZET6的FreeRTOS移植任務創建-動態創建多個任務資源-CSDN文庫
????????創建一個內核對象句柄:
/********************************** 內核對象句柄 *********************************/
/** 信號量,消息隊列,事件標志組,軟件定時器這些都屬于內核的對象,要想使用這些內核* 對象,必須先創建,創建成功之后會返回一個相應的句柄。實際上就是一個指針,后續我* 們就可以通過這個句柄操作這些內核對象。** 內核對象說白了就是一種全局的數據結構,通過這些數據結構我們可以實現任務間的通信,* 任務間的事件同步等各種功能。至于這些功能的實現我們是通過調用這些內核對象的函數* 來完成的* */
QueueHandle_t Test_Queue =NULL;
? ? ? ? 我們知道要想創建一個消息隊列,需要對隊列長度,消息隊列的消息大小進行分配:
#define QUEUE_LEN 4 /* 隊列的長度,最大可包含多少個消息 */
#define QUEUE_SIZE 4 /* 隊列中每個消息大小(字節) */
????????消息隊列創建函數:
//任務創建函數
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue)printf("創建Test_Queue消息隊列成功!\r\n");vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
????????消息隊列創建函數默認的是動態創建的方法。通常情況下,在 FreeRTOS 中,凡是創建任務,隊列, 信號量和互斥量等內核對象都需要使用動態內存分配,使用靜態創建消息隊列函數創建隊列時需要的形參更多,需要的內存 由編譯的時候預先分配好,一般很少使用這種方法。
完整工程:
FreeRTOS消息隊列-創建消息隊列.zip資源-CSDN文庫
運行結果:
7.2? 消息隊列靜態創建函數? xQueueCreateStatic()
????????xQueueCreateStatic()用于創建一個新的隊列并返回可用于訪問這個隊列的隊列句柄。隊列句柄其實就是一個指向隊列數據結構類型的指針。
????????使用xQueueCreateStatic()創建隊列時,使用的是靜態內存分配,所以要想使用該函數必須在FreeRTOSConfig.h 中把 configSUPPORT_STATIC_ALLOCATION 定義為 1 來使能。這是個用于使能靜態內存分配的宏,需要的內存在程序編譯的時候分配好,由用戶自己定義,其實創建過程與 xQueueCreate()都是差不多的:
函數原型 | QueueHandle_t xQueueCreateStatic(UBaseType_tuxQueueLength, ????????????????????????????????????????????????????????????UBaseType_t uxltemSize, ????????????????????????????????????????????????????????????uint8_t *pucQueueStorageBuffer, ????????????????????????????????????????????????????????????StaticQueue_t *pxQueueBuffer); | |
功能 | 用于創建一個新的隊列。 | |
參數 | uxQueueLength | 隊列能夠存儲的最大單元數目,即隊列深度。 |
uxItemSize | 隊列中數據單元的長度,以字節為單位。 | |
pucQueueStorageBuffer | 指針,指向一個uint8_t類型的數組,數組的大小至少有uxQueueLength* uxltemSize個字節。當uxItemSize為0時,pucQueueStorageBuffer可以為NULL. | |
pxQueueBuffer | 指針,指向 StaticQueue_t 類型的變量,該變量用于存儲隊列的數據結構。 | |
返回值 | 如果創建成功則返回一個隊列句柄,用于訪問創建的隊列。如果創建不成功則返回NULL,可能原因是創建隊列需要的RAM無法分配成功。 |
靜態任務創建模版:
基于STM32F103ZET6的FreeRTOS靜態任務創建模版資源-CSDN文庫
? ? ? ? 用靜態方法創建和動態方法差不多,只是靜態方法需要自己手動分配內存,比較麻煩一些,首先對隊列長度以及隊列數據大小進行一個宏定義:
/* 創建一個可以最多可以存儲 10 個 64 位變量的隊列 */
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint64_t )
? ? ? ? 創建一個變量用于用于存儲隊列的數據結構:
static StaticQueue_t xStaticQueue;
? ? ? ? 通過上面創建函數我們了解到為了分配足夠消息存儲空間,空間的大小為隊列長度*單個消息大小,靜態變量需要自己分配內存,因此我們將隊列的存儲區域分配為:
uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];
? ? ? ? 靜態創建函數代碼:
static void AppTaskCreate(void)
{taskENTER_CRITICAL(); //進入臨界區QueueHandle_t xQueue;/* 創建一個隊列 */ xQueue = xQueueCreateStatic( QUEUE_LENGTH, /* 隊列深度 */ ITEM_SIZE, /* 隊列數據單元的單位 */ ucQueueStorageArea,/* 隊列的存儲區域 */ &xStaticQueue ); /* 隊列的數據結構 */ if(xQueue != NULL) {printf("靜態隊列創建成功!\r\n");} else {printf("靜態隊列創建失敗!\r\n");}vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
完整工程:
FreeRTOS消息隊列-靜態創建消息隊列.zip資源-CSDN文庫
運行結果:
7.3??消息隊列刪除函數? vQueueDelete()
????????隊列刪除函數是根據消息隊列句柄直接刪除的,刪除之后這個消息隊列的所有信息都會被系統回收清空,而且不能再次使用這個消息隊列了,但是需要注意的是,如果某個消息隊列沒有被創建,那也是無法被刪除的,xQueue 是 vQueueDelete()函數的形參,是消息隊列句柄,表示的是要刪除哪個想隊列:
void vQueueDelete( QueueHandle_t xQueue )
{Queue_t * const pxQueue = ( Queue_t * ) xQueue;/* 斷言 */configASSERT( pxQueue ); //(1)traceQUEUE_DELETE( pxQueue );#if ( configQUEUE_REGISTRY_SIZE > 0 ){/* 將消息隊列從注冊表中刪除,我們目前沒有添加到注冊表中,暫時不用理會 */vQueueUnregisterQueue( pxQueue ); //(2)}#endif#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) ) {/* 因為用的消息隊列是動態分配內存的,所以需要調用vPortFree 來釋放消息隊列的內存 */vPortFree( pxQueue ); //(3)}
}
(1):對傳入的消息隊列句柄進行檢查,如果消息隊列是有效的才允許進行刪除操作。
(2):將消息隊列從注冊表中刪除,我們目前沒有添加到注冊表中,暫時不用理會。
(3):因為用的消息隊列是動態分配內存的,所以需要調用 vPortFree()函數來釋放消息隊列的內存。
????????消息隊列刪除函數 vQueueDelete()的使用也是很簡單的,只需傳入要刪除的消息隊列的句柄即可,調用函數時,系統將刪除這個消息隊列。需要注意的是調用刪除消息隊列函數前,系統應存在 xQueueCreate()或 xQueueCreateStatic()函數創建的消息隊列。此外vQueueDelete()也可用于刪除信號量。如果刪除消息隊列時,有任務正在等待消息,則不應該進行刪除操作(官方說的是不允許進行刪除操作,但是源碼并沒有禁止刪除的操作,使用的時候注意一下就行了),舉個例子:
? ? ? ? 這里我們在上面7.1的代碼的基礎上進行修改,消息隊列刪除函數 vQueueDelete()的使用非常的簡單,只需傳入要刪除的消息隊列的句柄即可:
vQueueDelete( Test_Queue );//刪除已創建的消息隊列
//任務創建函數
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue){printf("創建Test_Queue消息隊列成功!\r\n");vQueueDelete( Test_Queue );//刪除已創建的消息隊列}else{printf("創建Test_Queue消息隊列失敗!\r\n"); }vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
? ? ? ? 不過這樣我們發現,我們無法看到消息隊列是否刪除成功,我們可以加一個if語句進行判斷,開始我是想判斷if(NULL == Test_Queue),即:
//任務創建函數
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue){printf("創建Test_Queue消息隊列成功!\r\n");vQueueDelete( Test_Queue );//刪除已創建的消息隊列if(NULL == Test_Queue){printf("刪除Test_Queue消息隊列成功!\r\n");}else{printf("刪除Test_Queue消息隊列失敗!\r\n"); }}else{printf("創建Test_Queue消息隊列失敗!\r\n"); }vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
? ? ? ? 但是了解發現在 FreeRTOS 中,消息隊列被刪除后,其句柄會變為無效,但 FreeRTOS?不會自動將句柄設為 NULL,需要在刪除隊列后,手動將句柄置為 NULL,后續通過檢查 NULL 判斷隊列是否存在:
//任務創建函數
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue){printf("創建Test_Queue消息隊列成功!\r\n");vQueueDelete( Test_Queue );//刪除已創建的消息隊列Test_Queue = NULL;//手動置空if(NULL == Test_Queue){printf("刪除Test_Queue消息隊列成功!\r\n");}else{printf("刪除Test_Queue消息隊列失敗!\r\n"); }}else{printf("創建Test_Queue消息隊列失敗!\r\n"); }vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
? ? ? ? 這時候運行可以發現:?
解釋一下原因:
在 FreeRTOS 中,當調用 vQueueDelete() 刪除一個隊列時,系統會釋放該隊列占用的內存,但不會自動將隊列句柄(指針)設為 NULL。這意味著句柄變成"懸空指針",刪除隊列后,句柄仍然指向原來的內存地址,但該地址已被 FreeRTOS 回收,如果后續誤用該句柄(如調用 xQueueSend()),可能導致內存訪問沖突(HardFault),調試時難以區分 "有效隊列" 和 "已刪除隊列"。
? ? ? ? 根據上面我們又需要思考,這一塊的條件成立,是因為手動置空的原因還是刪除的原因:
Test_Queue = NULL;//手動置空if(NULL == Test_Queue){printf("刪除Test_Queue消息隊列成功!\r\n");}else{printf("刪除Test_Queue消息隊列失敗!\r\n"); }
? ? ? ? 我們可以在刪除前面加一個判斷進行驗證:
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue){printf("創建Test_Queue消息隊列成功!\r\n");Test_Queue = NULL;//手動置空if(NULL == Test_Queue){printf("Test_Queue被手動刪除!\r\n");}else{printf("Test_Queue未被手動刪除!\r\n"); }vQueueDelete( Test_Queue );//刪除已創建的消息隊列Test_Queue = NULL;//手動置空if(NULL == Test_Queue){printf("刪除Test_Queue消息隊列成功!\r\n");}else{printf("刪除Test_Queue消息隊列失敗!\r\n"); }}else{printf("創建Test_Queue消息隊列失敗!\r\n"); }vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
? ? ? ? 運行后會發現,由于Test_Queue被手動置空,當刪除函數調用時,會發生編譯錯誤:
那是因為,隊列剛創建成功就被丟棄句柄,導致隊列內存無法被訪問或刪除,造成內存泄漏,導致內部崩潰。
完整工程:
FreeRTOS消息隊列-刪除消息隊列.zip資源-CSDN文庫
7.4??向消息隊列發送消息函數
????????任務或者中斷服務程序都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊,FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將自動由阻塞態轉為就緒態。當任務等待的時間超過了指定的阻塞時間,即使隊列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程序會收到一個錯誤碼 errQUEUE_FULL。
????????發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。
????????其實消息隊列發送函數有好幾個,都是使用宏定義進行展開的,有些只能在任務調用,有些只能在中斷中調用。
7.4.1??xQueueSend()與 xQueueSendToBack()
xQueueSend()函數原型:
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \( xTicksToWait ), queueSEND_TO_BACK )
xQueueSendToBack()函數原型:
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \( xTicksToWait ), queueSEND_TO_BACK )
????????xQueueSend()是一個宏,宏展開是調用函數 xQueueGenericSend(),該宏是為了向后兼容沒 有包含 xQueueSendToFront() 和 xQueueSendToBack() 這兩個宏的 FreeRTOS 版本 。xQueueSend() 等同于xQueueSendToBack()。
????????xQueueSend()用于向隊列尾部發送一個隊列消息。消息以拷貝的形式入隊,而不是以引用的形式。
該函數絕對不能在中斷服務程序里面被調用,中斷中必須使用帶有中斷保護功能的 xQueueSendFromISR()來代替。
函數原型 | BaseType_t xQueueSend(QueueHandle_t xQueue, ???????????????????????????????????????????const void * pvltemToQueue, ???????????????????????????????????????????TickType_txTicksToWait); | |
功能 | 用于向隊列尾部發送一個隊列消息。 | |
參數 | xQueue | 隊列句柄。 |
pvItemToQueue | 指針,指向要發送到隊列尾部的隊列消息。 | |
xTicksToWait | 隊列滿時,等待隊列空閑的最大超時時間。如果隊列滿并且xTicksToWait被設置成0,函數立刻返回。超時時間的單位為系統節拍周期,常量portTICK-PERIOD-MS用于輔助計算真實的時間,單位為ms。如果INCLUDE_vTaskSuspend設置成1,并且指定延時為 portMAX_DELAY 將導致任務掛起(沒有超時)。 | |
返回值 | 消息發送成功成功返回pdTRUE,否則返回errQUEUE-FULL。 |
實例演示:
? ? ? ? 我們以7.1的任務創建函數為模版,首先我們先創建一個發送任務的任務句柄:
static TaskHandle_t Send_Task_Handle = NULL;/* 發送任務句柄 */
? ? ? ? 然后創建一個發送任務主體,任務完成的功能,我們通過按鍵按下進行發送不同的信息,這里我們不進行等待:
//發送任務主體
static void Send_Task(void* parameter)
{ BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */uint32_t send_data1 = 1;uint32_t send_data2 = 2;while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ){/* K1 被按下 */printf("發送消息send_data1! \r\n");xReturn = xQueueSend( Test_Queue, /* 消息隊列的句柄 */&send_data1,/* 發送的消息內容 */0 ); /* 等待時間 0 */if(pdPASS == xReturn)printf("消息send_data1發送成功! \r\n");} if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ){/* K2 被按下 */printf("發送消息send_data2! \r\n");xReturn = xQueueSend( Test_Queue, /* 消息隊列的句柄 */&send_data2,/* 發送的消息內容 */0 ); /* 等待時間 0 */if(pdPASS == xReturn)printf("消息send_data2發送成功! \r\n");}vTaskDelay(20);/* 延時20個tick */}
}
? ? ? ? 然后在任務創建函數中對該任務進行棧空間,優先級等的分配:
//任務創建函數
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue)printf("創建Test_Queue消息隊列成功!\r\n");/* 創建Send_Task任務 */xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任務入口函數 */(const char* )"Send_Task",/* 任務名字 */(uint16_t )512, /* 任務棧大小 */(void* )NULL,/* 任務入口函數參數 */(UBaseType_t )3, /* 任務的優先級 */(TaskHandle_t* )&Send_Task_Handle);/* 任務控制塊指針 */ if(pdPASS == xReturn)printf("創建Send_Task任務成功!\r\n"); vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}
運行結果:
完整工程:
FreeRTOS消息隊列-發送消息-非中斷.zip資源-CSDN文庫
7.4.2??xQueueSendFromISR()與 xQueueSendToBackFromISR()
xQueueSendFromISR()函數原型:
#define xQueueSendToFrontFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ),\( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
xQueueSendToBackFromISR()函數原型:
#define xQueueSendToBackFromISR(xQueue,pvItemToQueue,pxHigherPriorityTaskWoken) \xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), \( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
????????xQueueSendFromISR()是一個宏,宏展開是調用函數 xQueueGenericSendFromISR()。該宏是 xQueueSend()的中斷保護版本,用于在中斷服務程序中向隊列尾部發送一個隊列消息,等價于 xQueueSendToBackFromISR()。xQueueSendFromISR()函數具體說明:
函數原型 | BaseType_t xQueueSendFromISR(QueueHandle_txQueue, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const void *pvItemToQueue, ?????????????????????????????????????????????????????????BaseType_t*pxHigherPriority TaskWoken); | |
功能 | 在中斷服務程序中用于向隊列尾部發送一個消息。 | |
參數 | xQueue | 隊列句柄。 |
pvltemToQueue | 指針,指向要發送到隊列尾部的消息。 | |
pxHigherPriority TaskWoken | 如果入隊導致一個任務解鎖,并且解鎖的任務優先級高于當前被中斷的任務,則將*pxHigherPriorityTaskWoken設置成 pdTRUE,然后在中斷退出前需要進行一次上下文切換,去執行被喚醒的優先級更高的任務。從FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken作為一個可選參數,可以設置為 NULL。 | |
返回值 | 消息發送成功返回pdTRUE,否則返回errQUEUE-FULL。 |
7.4.3? xQueueSendToFront()
xQueueSendToFront()函數原型:
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), \( xTicksToWait ), queueSEND_TO_FRONT )
????????xQueueSendToFron() 是一個宏,宏展開也是調用函數 xQueueGenericSend() 。xQueueSendToFront()用于向隊列隊首發送一個消息。消息以拷貝的形式入隊,而不是以引用的形式。該函數絕不能在中斷服務程序里面被調用,而是必須使用帶有中斷保護功能的xQueueSendToFrontFromISR ()來代替。
函數原型 | BaseType_t xQueueSendToFront(QueueHandle_t xQueue, ???????????????????????????????????????????????????????const void * pvItemToQueue, ???????????????????????????????????????????????????????TickType_t xTicksToWait); | |
功能 | 于向隊列隊首發送一個消息。 | |
參數 | xQueue | 隊列句柄。 |
pvltemToQueue | 指針,指向要發送到隊首的消息。 | |
xTicksToWait | 隊列滿時,等待隊列空閑的最大超時時間。如果隊列滿并且xTicksToWait被設置成0,函數立刻返回。超時時間的單位為系統節拍周期,常量 portTICK_PERIOD_MS 用于輔助計算真實的時間,單位為 ms。如果 INCLUDE_vTaskSuspend 設置成 1,并且指定延時為 portMAX_DELAY 將導致任務無限阻塞(沒有超時)。 | |
返回值 | 發送消息成功返回pdTRUE,否則返回errQUEUE-FULL。 |
7.4.4??xQueueSendToFrontFromISR()
xQueueSendToFrontFromISR()函數原型:
#define xQueueSendToFrontFromISR( xQueue,pvItemToQueue,pxHigherPriorityTaskWoken ) \xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ),\( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
????????xQueueSendToFrontFromISR() 是一個宏,宏展開是調用函數xQueueGenericSendFromISR()。該宏是 xQueueSendToFront()的中斷保護版本,用于在中斷服務程序中向消息隊列隊首發送一個消息。
函數原型 | BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, ?????????????????????????????????????????????????????????????????????const void *pvItemToQueue, ?????????????????????????????????????????????????????????????????????BaseType_t*pxHigherPriorityTaskWoken); | |
功能 | 在中斷服務程序中向消息隊列隊首發送一個消息。 | |
參數 | xQueue | 隊列句柄。 |
pvltemToQueue | 指針,指向要發送到隊首的消息。 | |
pxHigherPriority TaskWoken | 如果入隊導致一個任務解鎖,并且解鎖的任務優先級高于當前被中斷的任務,則將*pxHigherPriorityTaskWoken設置成 pdTRUE,然后在中斷退出前需要進行一次上下文切換,去執行被喚醒的優先級更高的任務。從FreeRTOS V7.3.0 起, pxHigherPriorityTaskWoken作為一個可選參數,可以設置為 NULL。 | |
返回值 | 隊列項投遞成功返回pdTRUE,否則返回errQUEUE_FULL。 |
7.4.5? 通用消息隊列發送函數 xQueueGenericSend()
????????上面看到的那些在任務中發送消息的函數都是 xQueueGenericSend()展開的宏定義,真正起作用的就是 xQueueGenericSend()函數,根據指定的參數不一樣,發送消息的結果就不一樣,下面一起看看任務級的通用消息隊列發送函數的實現過程:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition
) {BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;TimeOut_t xTimeOut;Queue_t * const pxQueue = ( Queue_t * ) xQueue;/* 參數斷言檢查(實際代碼中通常啟用) */configASSERT(pxQueue);configASSERT(!(pvItemToQueue == NULL && pxQueue->uxItemSize != 0));configASSERT(!(xCopyPosition == queueOVERWRITE && pxQueue->uxLength != 1));for (;;) {taskENTER_CRITICAL();{/* 檢查隊列是否有空間 */if ((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE)) {traceQUEUE_SEND(pxQueue);/* 將數據寫入隊列 */xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);/* 如果有任務在等待接收數據 */if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE) {/* 喚醒最高優先級的接收任務 */if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive)) != pdFALSE) {/* 如果喚醒的任務優先級更高,觸發任務切換 */queueYIELD_IF_USING_PREEMPTION();}} else if (xYieldRequired != pdFALSE) {/* 特殊情況下(如覆蓋寫入)也需要切換 */queueYIELD_IF_USING_PREEMPTION();}taskEXIT_CRITICAL();return pdPASS;}/* 隊列已滿時的處理 */else {if (xTicksToWait == (TickType_t)0) {/* 不阻塞,直接返回錯誤 */taskEXIT_CRITICAL();traceQUEUE_SEND_FAILED(pxQueue);return errQUEUE_FULL;} else if (xEntryTimeSet == pdFALSE) {/* 初始化阻塞超時計時器 */vTaskSetTimeOutState(&xTimeOut);xEntryTimeSet = pdTRUE;}}}taskEXIT_CRITICAL();/* 掛起調度器并鎖隊列 */vTaskSuspendAll();prvLockQueue(pxQueue);/* 檢查是否超時 */if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE) {if (prvIsQueueFull(pxQueue) != pdFALSE) {traceBLOCKING_ON_QUEUE_SEND(pxQueue);/* 將當前任務加入發送等待列表 */vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToSend), xTicksToWait);prvUnlockQueue(pxQueue);/* 恢復調度器(可能觸發切換) */if (xTaskResumeAll() == pdFALSE) {portYIELD_WITHIN_API();}} else {/* 隊列有空閑,重試 */prvUnlockQueue(pxQueue);(void)xTaskResumeAll();}} else {/* 超時處理 */prvUnlockQueue(pxQueue);(void)xTaskResumeAll();traceQUEUE_SEND_FAILED(pxQueue);return errQUEUE_FULL;}}
}
? ? ? ? 我們來解釋一下上面代碼,首先函數原型:
BaseType_t xQueueGenericSend(QueueHandle_t xQueue, // (1) 隊列句柄const void *pvItemToQueue, // (2) 待發送數據指針TickType_t xTicksToWait, // (3) 阻塞超時時間const BaseType_t xCopyPosition // (4) 數據寫入位置(隊首/隊尾/覆蓋)
);
????????隊列未滿時的快速路徑:
if (pxQueue->uxMessagesWaiting < pxQueue->uxLength || xCopyPosition == queueOVERWRITE) {xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition); // (7)if (有任務在等待接收) {xTaskRemoveFromEventList(&pxQueue->xTasksWaitingToReceive); // (9)if (需任務切換) queueYIELD_IF_USING_PREEMPTION(); // (10)}return pdPASS;
}
(9):如果有任務在等待獲取此消息,就要將任務從阻塞中恢復,調用xTaskRemoveFromEventList() 函數將等待的任務從隊列的等待接收列表 xTasksWaitingToReceive 中刪除,并且添加到就緒列表中。
(10):將任務從阻塞中恢復,如果恢復的任務優先級比當前運行任務的優先級高,那么需要進行一次任務切換。
????????隊列已滿時的阻塞處理:
if (xTicksToWait == 0) {return errQUEUE_FULL; // (14) 非阻塞模式直接返回
} else {vTaskSetTimeOutState(&xTimeOut); // (15) 初始化超時計時vTaskSuspendAll(); // 掛起調度器prvLockQueue(pxQueue); // 鎖隊列if (!xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)) { // (17)if (prvIsQueueFull(pxQueue)) { // (18)vTaskPlaceOnEventList(&pxQueue->xTasksWaitingToSend, xTicksToWait); // (19)prvUnlockQueue(pxQueue); // (20)xTaskResumeAll(); // 恢復調度器(可能觸發切換)}} else {return errQUEUE_FULL; // (22) 超時退出}
}
(14):如果用戶不指定阻塞超時時間,則直接退出,不會發送消息。
(15):而如果用戶指定了超時時間,系統就會初始化阻塞超時結構體變量,初始化進入阻塞的時間 xTickCount 和溢出次數 xNumOfOverflows,為后面的阻塞任務做準備。
????????從消息隊列的入隊操作我們可以看出:如果阻塞時間不為 0,則任務會因為等待入隊而進入阻塞,在將任務設置為阻塞的過程中,系統不希望有其它任務和中斷操作這個隊列的xTasksWaitingToReceive 列表和 xTasksWaitingToSend 列表,因為可能引起其它任務解除阻塞,這可能會發生優先級翻轉。比如任務 A 的優先級低于當前任務,但是在當前任務進入阻塞的過程中,任務 A 卻因為其它原因解除阻塞了,這顯然是要絕對禁止的。因此FreeRTOS 使用掛起調度器禁止其它任務操作隊列,因為掛起調度器意味著任務不能切換并且不準調用可能引起任務切換的 API 函數。但掛起調度器并不會禁止中斷,中斷服務函數仍然可以操作隊列事件列表,可能會解除任務阻塞、可能會進行上下文切換,這也是不允許的。于是,解決辦法是不但掛起調度器,還要給隊列上鎖,禁止任何中斷來操作隊列。
7.4.6? 消息隊列發送函數 xQueueGenericSendFromISR()
????????既然有任務中發送消息的函數,當然也需要有在中斷中發送消息函數,其實這個函數跟 xQueueGenericSend() 函數很像,只不過是執行的上下文環境是不一樣的,xQueueGenericSendFromISR()函數只能用于中斷中執行,是不帶阻塞機制的:
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,//消息隊列句柄const void * const pvItemToQueue,//指針,指向要發送的消息。BaseType_t * const pxHigherPriorityTaskWoken,//(1)const BaseType_t xCopyPosition )//(2)
{BaseType_t xReturn;UBaseType_t uxSavedInterruptStatus;Queue_t * const pxQueue = ( Queue_t * ) xQueue;/* 中斷安全檢查(實際使用時需根據移植層實現) */configASSERT(pxQueue);configASSERT(!( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 )));configASSERT(!(pvItemToQueue == NULL && pxQueue->uxItemSize != 0));/* 保存當前中斷狀態并禁用中斷 */uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{/* 檢查隊列是否有空間 */if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){const int8_t cTxLock = pxQueue->cTxLock;traceQUEUE_SEND_FROM_ISR( pxQueue );/* 將數據寫入隊列 */( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );/* 如果隊列未上鎖 */if( cTxLock == queueUNLOCKED ){/* 檢查是否有任務在等待接收數據 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){/* 喚醒接收任務 */if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* 標記需要上下文切換 */if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}}}}else{/* 隊列已上鎖,增加發送鎖計數 */pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else{traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );xReturn = errQUEUE_FULL;}}/* 恢復中斷狀態 */portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
(1):如果入隊導致一個任務解鎖,并且解鎖的任務優先級高于當前運行的任務,則該函數將 *pxHigherPriorityTaskWoken 設置成 pdTRUE 。 如果xQueueSendFromISR()設置這個值為 pdTRUE,則中斷退出前需要一次上下文切換。從 FreeRTOS V7.3.0 起,pxHigherPriorityTaskWoken 稱為一個可選參數,并可以設置為 NULL。?
(2):發送數據到消息隊列的位置,有以下 3 個選擇,在 queue.h 中有定義,queueSEND_TO_BACK:發送到隊尾;queueSEND_TO_FRONT:發送到隊頭;queueOVERWRITE:以覆蓋的方式發送。
????????xQueueGenericSendFromISR()函數沒有阻塞機制,只能用于中斷中發送消息,代碼簡單了很多,當成功入隊后,如果有因為等待出隊而阻塞的任務,系統會將該任務解除阻塞,要注意的是,解除了任務并不是會馬上運行的,只是任務會被掛到就緒列表中。在執行解除阻塞操作之前,會判斷隊列是否上鎖。如果沒有上鎖,則可以解除被阻塞的任務,然后根據任務優先級情況來決定是否需要進行任務切換;如果隊列已經上鎖,則不能解除被阻塞的任務,只能是記錄 xTxLock 的值,表示隊列上鎖期間消息入隊的個數,也用來記錄可以解除阻塞任務的個數,在隊列解鎖中會將任務解除阻塞。
7.5??從消息隊列讀取消息函數
????????當任務試圖讀隊列中的消息時,可以指定一個阻塞超時時間,當且僅當消息隊列中有消息的時候,任務才能讀取到消息。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務程序往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉為就緒態。當任務等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
7.5.1??xQueueReceive()與 xQueuePeek()
xQueueReceive()函數原型:
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) \xQueueGenericReceive( ( xQueue ), ( pvBuffer ), \( xTicksToWait ), pdFALSE )
????????xQueueReceive() 是 一 個 宏 , 宏 展 開 是 調 用 函 數 xQueueGenericReceive() 。xQueueReceive()用于從一個隊列中接收消息并把消息從隊列中刪除。接收的消息是以拷貝的形式進行的,所以我們必須提供一個足夠大空間的緩沖區。具體能夠拷貝多少數據到緩沖區,這個在隊列創建的時候已經設定。該函數絕不能在中斷服務程序里面被調用,而是必須使用帶有中斷保護功能的 xQueueReceiveFromISR ()來代替:
函數原型 | BaseType_t xQueueReceive(QueueHandle_t xQueue, ??????????????????????????????????????????????void *pvBuffer, ??????????????????????????????????????????????TickType_txTicksToWait); | |
功能 | 用于從一個隊列中接收消息,并把接收的消息從隊列中刪除。 | |
參數 | xQueue | 隊列句柄。 |
pvBuffer | 指針,指向接收到要保存的數據。 | |
xTicksToWait | 隊列空時,阻塞超時的最大時間。如果該參數設置為0,函數立刻返回。超時時間的單位為系統節拍周期,常量portTICK_PERIOD_MS用于輔助計算真實的時間,單位為ms。如果INCLUDE_vTaskSuspend設置成1,并且指定延時為portMAX_DELAY將導致任務無限阻塞(沒有超時)。 | |
返回值 | 隊列項接收成功返回pdTRUE,否則返回pdFALSE。 |
? ? ? ? 下面我們在7.4.1的基礎上增加接收隊列的東西,首先創建一個接收任務句柄:
static TaskHandle_t Receive_Task_Handle = NULL;/* 接收任務句柄 */
? ? ? ? 對于接收任務主體,我們通過按鍵發送的數值給到消息隊列,我們接收消息隊列的數據,如果未接收到就選擇移植等待直到接收到為止:
//接收任務主體
static void Receive_Task(void* parameter)
{ BaseType_t xReturn = pdTRUE;/* 定義一個創建信息返回值,默認為pdTRUE */uint32_t r_queue; /* 定義一個接收消息的變量 */while (1){xReturn = xQueueReceive( Test_Queue, /* 消息隊列的句柄 */&r_queue, /* 發送的消息內容 */portMAX_DELAY); /* 等待時間 一直等 */if(pdTRUE == xReturn)printf("本次接收到的數據是%d\n\n",r_queue);elseprintf("數據接收出錯,錯誤代碼0x%lx\n",xReturn);}
}
? ? ? ? 然后在AppTaskCreate()增加接收任務的創建:
/* 創建Receive_Task任務 */xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任務入口函數 */(const char* )"Receive_Task",/* 任務名字 */(uint16_t )512, /* 任務棧大小 */(void* )NULL, /* 任務入口函數參數 */(UBaseType_t )2, /* 任務的優先級 */(TaskHandle_t* )&Receive_Task_Handle);/* 任務控制塊指針 */if(pdPASS == xReturn)printf("創建Receive_Task任務成功!\r\n");
? ? ? ? 完整代碼:
#include "stm32f10x.h" // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h" /* FreeRTOS頭文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"/* * 任務句柄是一個指針,用于指向一個任務,當任務創建好之后,它就具有了一個任務句柄* 以后我們要想操作這個任務都需要通過這個任務句柄,如果是自身的任務操作自己,那么* 這個句柄可以為NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 創建任務句柄 */
static TaskHandle_t Receive_Task_Handle = NULL;/* 接收任務句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* 發送任務句柄 *//********************************** 內核對象句柄 *********************************/
/** 信號量,消息隊列,事件標志組,軟件定時器這些都屬于內核的對象,要想使用這些內核* 對象,必須先創建,創建成功之后會返回一個相應的句柄。實際上就是一個指針,后續我* 們就可以通過這個句柄操作這些內核對象。** 內核對象說白了就是一種全局的數據結構,通過這些數據結構我們可以實現任務間的通信,* 任務間的事件同步等各種功能。至于這些功能的實現我們是通過調用這些內核對象的函數* 來完成的* */
QueueHandle_t Test_Queue =NULL;/******************************* 宏定義 ************************************/
/** 當我們在寫應用程序的時候,可能需要用到一些宏定義。*/
#define QUEUE_LEN 4 /* 隊列的長度,最大可包含多少個消息 */
#define QUEUE_SIZE 4 /* 隊列中每個消息大小(字節) *///一些函數聲明
static void AppTaskCreate(void);/* 用于創建任務 */
static void Receive_Task(void* pvParameters);/* Receive_Task任務實現 */
static void Send_Task(void* pvParameters);/* Send_Task任務實現 */
static void All_Function_Init(void);/* 用于初始化板載相關資源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */All_Function_Init();//硬件初始化while (1){/* 創建AppTaskCreate任務 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任務入口函數 */(const char* )"AppTaskCreate",/* 任務名字 */(uint16_t )512, /* 任務棧大小 */(void* )NULL,/* 任務入口函數參數 */(UBaseType_t )1, /* 任務的優先級 */(TaskHandle_t* )&AppTaskCreate_Handle);/* 任務控制塊指針 */ /* 啟動任務調度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 啟動任務,開啟調度 */elsereturn -1; }
}//任務創建函數
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */taskENTER_CRITICAL(); //進入臨界區/* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL != Test_Queue)printf("創建Test_Queue消息隊列成功!\r\n");/* 創建Receive_Task任務 */xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任務入口函數 */(const char* )"Receive_Task",/* 任務名字 */(uint16_t )512, /* 任務棧大小 */(void* )NULL, /* 任務入口函數參數 */(UBaseType_t )2, /* 任務的優先級 */(TaskHandle_t* )&Receive_Task_Handle);/* 任務控制塊指針 */if(pdPASS == xReturn)printf("創建Receive_Task任務成功!\r\n");/* 創建Send_Task任務 */xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任務入口函數 */(const char* )"Send_Task",/* 任務名字 */(uint16_t )512, /* 任務棧大小 */(void* )NULL,/* 任務入口函數參數 */(UBaseType_t )3, /* 任務的優先級 */(TaskHandle_t* )&Send_Task_Handle);/* 任務控制塊指針 */ if(pdPASS == xReturn)printf("創建Send_Task任務成功!\r\n");vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區
}//接收任務主體
static void Receive_Task(void* parameter)
{ BaseType_t xReturn = pdTRUE;/* 定義一個創建信息返回值,默認為pdTRUE */uint32_t r_queue; /* 定義一個接收消息的變量 */while (1){xReturn = xQueueReceive( Test_Queue, /* 消息隊列的句柄 */&r_queue, /* 發送的消息內容 */portMAX_DELAY); /* 等待時間 一直等 */if(pdTRUE == xReturn)printf("本次接收到的數據是%d\r\n",r_queue);elseprintf("數據接收出錯,錯誤代碼0x%lx\n",xReturn);}
}//發送任務主體
static void Send_Task(void* parameter)
{ BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */uint32_t send_data1 = 1;uint32_t send_data2 = 2;while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ){/* K1 被按下 */printf("發送消息send_data1! \r\n");xReturn = xQueueSend( Test_Queue, /* 消息隊列的句柄 */&send_data1,/* 發送的消息內容 */0 ); /* 等待時間 0 */if(pdPASS == xReturn)printf("消息send_data1發送成功! \r\n");} if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ){/* K2 被按下 */printf("發送消息send_data2! \r\n");xReturn = xQueueSend( Test_Queue, /* 消息隊列的句柄 */&send_data2,/* 發送的消息內容 */0 ); /* 等待時間 0 */if(pdPASS == xReturn)printf("消息send_data2發送成功! \r\n");}vTaskDelay(20);/* 延時20個tick */}
}//初始化聲明
static void All_Function_Init(void)
{/** STM32中斷優先級分組為4,即4bit都用來表示搶占優先級,范圍為:0~15* 優先級分組只需要分組一次即可,以后如果有其他的任務需要用到中斷,* 都統一用這個優先級分組,千萬不要再分組,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */USART_Config();//按鍵初始化Key_GPIO_Config();}
運行結果:
完整工程:
FreeRTOS消息隊列-接收消息-非中斷.zip資源-CSDN文庫
7.5.2??xQueueReceiveFromISR()與 xQueuePeekFromISR()
????????xQueueReceiveFromISR()是 xQueueReceive ()的中斷版本,用于在中斷服務程序中接收一個隊列消息并把消息從隊列中刪除;xQueuePeekFromISR()是 xQueuePeek()的中斷版本,用于在中斷中從一個隊列中接收消息,但并不會把消息從隊列中移除。說白了這兩個函數只能用于中斷,是不帶有阻塞機制的,并且是在中斷中可以安全調用:
函數原型 | BaseType_t xQueueReceiveFromISR(QueueHandle_txQueue, ??????????????????????????????????????????????????????????????void *pvBuffer, ??????????????????????????????????????????????????????????????BaseType_t*pxHigherPriorityTaskWoken); | |
功能 | 在中斷中從一個隊列中接收消息,并從隊列中刪除該消息。 | |
參數 | xQueue | 隊列句柄。 |
pvBuffer | 指針,指向接收到要保存的數據。 | |
pxHigherPriority TaskWoken | 任務在往隊列投遞信息時,如果隊列滿,則任務將阻塞在該隊列上。如果 xQueueReceiveFromISR()到賬了一個任務解鎖了則將*pxHigherPriorityTaskWoken設置為pdTRUE, 否則*pxHigherPriorityTaskWoken的值將不變。從FreeRTOS V7.3.0起, pxHigherPriorityTaskWoken作為一個可選參數,可以設置為NULL。 | |
返回值 | 隊列項接收成功返回pdTRUE,否則返回pdFALSE。 |
函數原型 | BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?void *pvBuffer); | |
功能 | 在中斷中從一個隊列中接收消息,但并不會把消息從該隊列中移除。 | |
參數 | xQueue | 隊列句柄。 |
pvBuffer | 指針,指向接收到要保存的數據。 | |
返回值 | 隊列項接收(peek)成功返回pdTRUE,否則返回pdFALSE。 |
FreeRTOS菜鳥入門系列_時光の塵的博客-CSDN博客