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