ESP32使用freeRTOS的消息队列
零. 聲明
本專欄文章我們會以連載的方式持續更新,本專欄計劃更新內容如下:
第一篇:ESP-IDF基本介紹,主要會涉及模組,芯片,開發板的介紹,環境搭建,程序編譯下載,啟動流程等一些基本的操作,讓你對ESP-IDF開發有一個總體的認識,比我們后續學習打下基礎!
第二篇:ESP32-IDF外設驅動介紹,主要會根據esp-idf現有的driver,提供各個外設的驅動,比如LED,OLED,SPI LCD,TOUCH,紅外,Codec ic等等,在這一篇中,我們不僅僅來做外設驅動,還會對常用的外設總線做一個介紹,讓大家知其然又知其所以然!
第三篇:目前比較火熱的GUI LVGL介紹,主要會設計LVGL7.1,LVGL8的移植介紹,并且也會介紹各個組件,知道原理后,最后,我們會推出一款組態軟件來構建我們的GUI,來提升我們的效率!
第四篇:ESP32-藍牙,熟悉我的,應該都知道,我即使從事藍牙協議棧的開發的,所以這個是我們獨有的優勢,在這一篇章,我們會提供不僅僅是藍牙應用方法的知識,也會應用結合藍牙底層協議棧的理論,讓你徹底從上到下打通藍牙任督二脈!
第五篇:Wi-Fi介紹,熟悉我的,應該也知道,我們也做過一款sdio wifi的驅動教程板子,所以在wifi這方面我們也是有獨有的優勢,在這一篇章,我們同樣不僅僅提供Wi-Fi應用方面的知識,也會結合底層理論,讓你對Wi-Fi有一個清晰的認知!
另外,我們的教程包括但是不局限于以上篇章,為了給你一個更好的導航,以下信息尤其重要,請詳細查看!!
------------------------------------------------------------------------------------------------------------------------------------------
購買開發板(點擊我)
文檔目錄(點擊我)
Github代碼倉庫(點擊我)
藍牙交流扣扣群:539357317
微信公眾號↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
????
------------------------------------------------------------------------------------------------------------------------------------------
一.消息隊列的概念
隊列又稱消息隊列,是一種常用于任務間通信的數據結構,隊列可以在任務與任務間、中斷和任務間傳遞信息,實現了任務接收來自其他任務或中斷的不固定長度的消息,任務能夠從隊列里面讀取消息,當隊列中的消息是空時,讀取消息的任務將被阻塞,用戶還可以指定阻塞的任務時間 xTicksToWait,在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當隊列中有新消息時,被阻塞的任務會被喚醒并處理新消息;當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉為就緒態。消息隊列是一種異步的通信方式。通過消息隊列服務,任務或中斷服務例程可以將一條或多條消息放入消息隊列中。同樣,一個或多個任務可以從消息隊列中獲得消息。當有多個消息發送到消息隊列時,通常是將先進入消息隊列的消息先傳給任務,也就是說,任務先得到的是最先進入消息隊列的消息,即先進先出原則(FIFO),但是也支持后進先出原則(LIFO)。FreeRTOS 中使用隊列數據結構實現任務異步通信工作,具有如下特性:
? 消息支持先進先出方式排隊,支持異步讀寫工作方式。
? 讀寫隊列均支持超時機制。
? 消息支持后進先出方式排隊,往隊首發送消息(LIFO)。
? 可以允許不同長度(不超過隊列節點最大值)的任意類型消息。
? 一個任務能夠從任意一個消息隊列接收和發送消息。
? 多個任務能夠從同一個消息隊列接收和發送消息。
? 當隊列使用結束后,可以通過刪除隊列函數進行刪除。
二.消息隊列的工作機制
創建消息隊列時 FreeRTOS 會先給消息隊列分配一塊內存空間,這塊內存的大小等于消息隊列控制塊大小加上(單個消息空間大小與消息隊列長度的乘積),接著再初始化消息隊列,此時消息隊列為空。FreeRTOS 的消息隊列控制塊由多個元素組成,當消息隊列被創建時,系統會為控制塊分配對應的內存空間,用于保存消息隊列的一些信息如消息的存儲位置,頭指針 pcHead、尾指針 pcTail、消息大小 uxItemSize 以及隊列長度 uxLength 等。同時每個消息隊列都與消息空間在同一段連續的內存空間中,在創建成功的時候,這些內存就被占用了,只有刪除了消息隊列的時候,這段內存才會被釋放掉,創建成功的時候就已經分配好每個消息空間與消息隊列的容量,無法更改,每個消息空間可以存放不大于消息大小 uxItemSize 的任意類型的數據,所有消息隊列中的消息空間總數即是消息隊列的長度,這個長度可在消息隊列創建時指定。
任務或者中斷服務程序都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊,FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程序會收到一個錯誤碼 errQUEUE_FULL。
發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。
當某個任務試圖讀一個隊列時,其可以指定一個阻塞超時時間。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務程序往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
當消息隊列不再被使用時,應該刪除它以釋放系統資源,一旦操作完成,消息隊列將
被永久性的刪除。
消息隊列的運作過程具體見圖:
三.消息隊列的阻塞機制
很簡單,因為 FreeRTOS 已經為我們做好了,我們直接使用就好了,每個對消息隊列讀寫的函數,都有這種機制,我稱之為阻塞機制。假設有一個任務 A 對某個隊列進行讀操作的時候(也就是我們所說的出隊),發現它沒有消息,那么此時任務 A 有 3 個選擇:第一個選擇,任務 A 扭頭就走,既然隊列沒有消息,那我也不等了,干其它事情去,這樣子任務 A 不會進入阻塞態;第二個選擇,任務 A 還是在這里等等吧,可能過一會隊列就有消息,此時任務 A 會進入阻塞狀態,在等待著消息的道來,而任務 A 的等待時間就由我們自己定義,比如設置 1000 個系統時鐘節拍 tick 的等待,在這 1000 個 tick 到來之前任務 A 都是處于阻塞態,當阻塞的這段時間任務 A 等到了隊列的消息,那么任務 A 就會從阻塞態變成就緒態,如果此時任務 A 比當前運行的任務優先級還高,那么,任務 A 就會得到消息并且運行;假如 1000 個 tick 都過去了,隊列還沒消息,那任務 A 就不等了,從阻塞態中喚醒,返回一個沒等到消息的錯誤代碼,然后繼續執行任務 A 的其他代碼;第三個選擇,任務 A 死等,不等到消息就不走了,這樣子任務 A 就會進入阻塞態,直到完成讀取隊列的消息。
而在發送消息操作的時候,為了保護數據,當且僅當隊列允許入隊的時候,發送者才能成功發送消息;隊列中無可用消息空間時,說明消息隊列已滿,此時,系統會根據用戶指定的阻塞超時時間將任務阻塞,在指定的超時時間內如果還不能完成入隊操作,發送消息的任務或者中斷服務程序會收到一個錯誤碼 errQUEUE_FULL,然后解除阻塞狀態;當然,只有在任務中發送消息才允許進行阻塞狀態,而在中斷中發送消息不允許帶有阻塞機制的,需要調用在中斷中發送消息的 API 函數接口,因為發送消息的上下文環境是在中斷中,不允許有阻塞的情況。
假如有多個任務阻塞在一個消息隊列中,那么這些阻塞的任務將按照任務優先級進行排序,優先級高的任務將優先獲得隊列的訪問權。
四.消息隊列的常用函數
使用隊列模塊的典型流程如下:
? 創建消息隊列。
? 寫隊列操作。
? 讀隊列操作。
? 刪除隊列。
1.消息隊列創建函數 xQueueCreate()
xQueueCreate() 用于創建一個新的隊列并返回可用于訪問這個隊列的隊列句柄。隊列句柄其實就是一個指向隊列數據結構類型的指針。
隊列就是一個數據結構,用于任務間的數據的傳遞。每創建一個新的隊列都需要為其分配 RAM ,一部分用于存儲隊列的狀態,剩下 的作為隊列消息的存儲區域 。使用xQueueCreate() 創建隊列 時 , 使用的是動態內存分配,所以 要想使用該函數必須在FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 定義為 1 來使能,這是個用于使能動態內存分配的宏,通常情況下,在 FreeRTOS 中,凡是創建任務,隊列,信號量和互斥量等內核對象都需要使用動態內存分配,所以這個宏默認在 FreeRTOS.h 頭文件中已經使能(即定義為 1)。如果想使用靜態內存,則可以使用 xQueueCreateStatic() 函數來創建一個隊列。使用靜態創建消息隊列函數創建隊列時需要的形參更多,需要的內存由編譯的時候預先分配好,一般很少使用這種方法。使用說明具體見表格
2.消息隊列靜態創建函數 xQueueCreateStatic()
xQueueCreateStatic()用于創建一個新的隊列并返回可用于訪問這個隊列的隊列句柄。隊列句柄其實就是一個指向隊列數據結構類型的指針。
隊列就是一個數據結構,用于任務間的數據的傳遞。每創建一個新的隊列都需要為其分配 RAM , 一 部 分 用 于 存 儲 隊 列 的 狀 態 , 剩 下 的 作 為 隊 列 的 存 儲 區 。 使 用xQueueCreateStatic() 創建隊列時,使用的是靜態內存分配,所以要想使用該函數必須在FreeRTOSConfig.h 中把 configSUPPORT_STATIC_ALLOCATION 定義為 1 來使能。這是個用于使能靜態內存分配的宏,需要的內存在程序編譯的時候分配好,由用戶自己定義,其實創建過程與 xQueueCreate()都是差不多的,我們暫不深入講解
3.消息隊列刪除函數 vQueueDelete()
隊列刪除函數是根據消息隊列句柄直接刪除的,刪除之后這個消息隊列的所有信息都會被系統回收清空,而且不能再次使用這個消息隊列了,但是需要注意的是,如果某個消息隊列沒有被創建,那也是無法被刪除的,動腦子想想都知道,沒創建的東西就不存在,怎么可能被刪除。xQueue 是 vQueueDelete()函數的形參,是消息隊列句柄,表示的是要刪除哪個想隊列。
4.向消息隊列發送消息函數
任務或者中斷服務程序都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊,FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將自動由阻塞態轉為就緒態。當任務等待的時間超過了指定的阻塞時間,即使隊列中還不允許入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程序會收到
一個錯誤碼 errQUEUE_FULL。
發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。
其實消息隊列發送函數有好幾個,都是使用宏定義進行展開的,有些只能在任務調用,有些只能在中斷中調用,具體見下面講解。
4.1 xQueueSend()與 xQueueSendToBack()
xQueueSend()用于向隊列尾部發送一個隊列消息。消息以拷貝的形式入隊,而不是以引用的形式。該函數絕對不能在中斷服務程序里面被調用,中斷中必須使用帶有中斷保護功能的 xQueueSendFromISR()來代替。xQueueSend() 等 同 于xQueueSendToBack()
4.2 xQueueSendFromISR()與 xQueueSendToBackFromISR()
xQueueSendFromISR()是一個宏,宏展開是調用函數 xQueueGenericSendFromISR()。該宏是 xQueueSend()的中斷保護版本,用于在中斷服務程序中向隊列尾部發送一個隊列消息,等價于 xQueueSendToBackFromISR()
4.3 xQueueSendToFront()
xQueueSendToFron() 是 一個 宏, 宏 展 開 也 是 調 用 函數 xQueueGenericSend() 。xQueueSendToFront()用于向隊列隊首發送一個消息。消息以拷貝的形式入隊,而不是以引用的形式。該函數絕不能在中斷服務程序里面被調用,而是必須使用帶有中斷保護功能的
xQueueSendToFrontFromISR ()來代替
4.4 xQueueSendToFrontFromISR()
xQueueSendToFrontFromISR() 是 一 個 宏 , 宏 展 開 是 調 用 函數xQueueGenericSendFromISR()。該宏是 xQueueSend ToFront()的中斷保護版本,用于在中斷服務程序中向消息隊列隊首發送一個消息。
5.從消息隊列讀取消息函數
5.1 xQueueReceive()與 xQueuePeek()
xQueueReceive() 是 一個 宏, 宏 展 開 是 調 用 函數 xQueueGenericReceive() 。xQueueReceive()用于從一個隊列中接收消息并把消息從隊列中刪除。接收的消息是以拷貝的形式進行的,所以我們必須提供一個足夠大空間的緩沖區。具體能夠拷貝多少數據到緩沖區,這個在隊列創建的時候已經設定。該函數絕不能在中斷服務程序里面被調用,而是必須使用帶有中斷保護功能的 xQueueReceiveFromISR () 來代替。
看到這里,有人就問了如果我接收了消息不想刪除怎么辦呢?其實,你能想到的東西,FreeRTOS 看到也想到了,如果不想刪除消息的話,就調用 xQueuePeek ()函數。其實這個函數與 xQueueReceive()函數的實現方式一樣,連使用方法都一樣,只不過xQueuePeek()函數接收消息完畢不會刪除消息隊列中的消息而已
5.2 xQueueReceiveFromISR()與 xQueuePeekFromISR()
xQueueReceiveFromISR() 是 xQueueReceive ()的中斷版本,用于在中斷服務程序中接收一個隊列消息并把消息從隊列中刪除;xQueuePeekFromISR()是 xQueuePeek()的中斷版本,用于在中斷中從一個隊列中接收消息,但并不會把消息從隊列中移除。說白了這兩個函數只能用于中斷,是不帶有阻塞機制的,并且是在中斷中可以安全調用。
xQueuePeekFromISR跟xQueueReceiveFromISR作用一樣,從中斷中接收一個消息的,但是不會把消息從消息隊列中刪除。
五.消息隊列的例子
1.代碼
#include <stdio.h> #include <string.h> #include <stdint.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h"static QueueHandle_t message_queue;typedef struct {uint8_t *q_data;uint16_t q_data_len; } message_data_t;void msg_queue_send_task(void *pvParameters) {printf("msg_queue_send_task\n");while(1){uint8_t data_len = rand() % 256;message_data_t message_data;if(!data_len)data_len = 1;message_data.q_data_len = data_len;message_data.q_data = malloc(data_len);if (message_data.q_data == NULL) {printf("Malloc q_data_len failed!");return;}printf("message data len = %d\n",data_len);memset(message_data.q_data,data_len,data_len);if (xQueueSend(message_queue, (void *)&message_data, ( TickType_t ) 0) != pdTRUE) {printf("Failed to enqueue message_queue. Queue full.");/* If data sent successfully, then free the pointer in `xQueueReceive'* after processing it. Or else if enqueue in not successful, free it* here. */free(message_data.q_data);}vTaskDelay(1000 / portTICK_RATE_MS);}}void app_main(void) {message_data_t message_data;message_queue = xQueueCreate(15, sizeof(message_data_t));if (message_queue == NULL) {printf("Queue creation failed\n");return;}xTaskCreate(&msg_queue_send_task, "msg_queue_send_task", 2048, NULL, 6, NULL);while(1){if (xQueueReceive(message_queue, &message_data, portMAX_DELAY) != pdPASS) {printf("Queue receive error");} else {printf("message_data len:%d\n",message_data.q_data_len);printf("message_data[0]=%d message_data[%d]=%d\n",message_data.q_data[0],message_data.q_data_len-1,message_data.q_data[message_data.q_data_len-1]);free(message_data.q_data);}} }整份代碼看懂了API的使用是超級簡單的,就是創建一個task,然后在task中每隔1s往消息隊列中塞一個數據,塞得數據是這樣子,數據的長度是隨機1~255,然后buffer中所有的數據等于長度,然后主循環中一直接收數據,并打印出來
參考內容:野火FreeRTOS內核實現與應用實戰
總結
以上是生活随笔為你收集整理的ESP32使用freeRTOS的消息队列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [机器学习] 深入理解 目标函数,损失函
- 下一篇: mongodb数据备份