学习《FreeRTOS源码详解与应用开发》笔记
1、注意:任務函數內部定義局部變量的內存大小不能大于此任務堆棧內存的大小。
2、FreeRTOS定義任務優先級時,0優先級(空閑中斷占用)和最高優先級31級(定時器占用)不能用。
3、用start_task創建任務task1和task2時,start_task只創建一次就行,不用多次創建,所以創建完start_task后,就可以把start_task任務刪除,用vTaskDelete()函數刪除,要刪除那個任務,只需把那個函數的任務句柄放在vTaskDelete后面的括號里面即可,或者直接在括號里面用一個NULL也可以。
4、vTaskList()函數可查詢任務的名稱、任務運行狀態(就緒態、運行態、阻塞態和刪除態等)、優先級、任務運行后剩余的堆棧余量等,是一個重量級的函數。注意,當定義一個數組變量去接收vTaskList()函數返回的指針值時,這個數組變量緩存要大些,因為任務多,占用堆棧內存大。且這個數組變量最好定義成全局變量,如果定義成函數局部變量,那內存是從定義這個數組變量的函數開始調用這個數組變量,就需要把這個任務函數的堆棧改大,否則程序會出錯。
5、vTaskGetRunTimeStatus()函數,這個函數可查詢當前運行的任務中哪個任務比較耗時,可把這個任務拆分成小的任務。減小任務耗時。
6、時序要求嚴格的時鐘通信協議中,如在I2C、SPI正在讀取數據,高優先級的任務到來時,高優先級就搶占CPU,導致I2C或SPI無法讀取正確的數據,可用臨界任務保護函數,在I2C\SPI讀取數據時,禁止高優先級搶占CPU,使I2C和SPI讀到正確的數據。可調用taskENTER_CRITICAL()函數進入任務保護,調用taskEXIT_CRITICAL()函數退出任務保護??蛇M行多次嵌套臨界段代碼保護。注意,在使用臨界區保護函數時,要快進快出,避免一些緊急的事件任務需要處理時,臨界區保護的任務還未完成,那緊急任務事件得不到實時處理,會出現問題的。如在電力系統和醫療急救中,緊急事件任務得不到處理,會出現大問題。
7、函數askENTER_CRITCAL_FROM_ISR()和函數taskEXIT_CRITCAL_FROM_ISR()是中斷級臨界區代碼段保護函數,用在中斷服務函數中,而且這個中斷的優先級一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY(高于這個宏定義定義的中斷優先級,不可調用FreeRTOS系統的API)。
8、在使用taskENTER_CRITCAL_FROM_ISR()時,先定義一個變量讀取這個函數返回的值,等退出臨界保護時,把這個變量作為實參傳遞給taskEXIT_CRITCAL_FROM_ISR()來退出臨界保護。
用法如下:
uint32_t ?taskISR_Value;
taskENTER_CRITCAL_FROM_ISR();
被保護代碼段
taskEXIT_CRITCAL_FROM_ISR(taskISR_Value);
9、延時函數:vTaskDelay()相對延時函數和vTaskDelayUntil()絕對延時函數。
10、隊列是用于任務與任務、任務與中斷之間的通信。可在任務與任務、任務與中斷之間傳遞信息,因此隊列也叫做消息隊列。隊列可存儲有限的、大小固定的數據項目。任務與任務、任務與中斷之間要交流的數據保存在隊列中,叫隊列項目。隊列能保存的最大數據項目數量叫隊列的長度。創建隊列的時候需指定數據項目的大小和隊列的長度。注:信號量也是依據隊列來實現的。
11、平時不用操作系統時,用串口中斷接收一幀數據,但不知道這幀數據什么時候到來,通常我們會定義一個接收數據標志的全局變量來確定一幀數據是否接收完成,在main函數里面不停輪詢判斷這個接收標志位是否等于1,如果等于1,則確定接收到了新的幀數據并在main函數或其它子函數進行數據處理,這種方式效率低且耗費CPU。此時可用操作系統做信號量的任務同步,當串口中斷服務函數/定時器確定接收到一幀數據,可發出一個信號量。在程序應用中創建一個任務去請求獲取接收串口中斷服務函數/定時器發出的信號量信息的信號量,當這個信號量無效時,任務會進入阻塞態,把CPU讓給其他任務去執行;當信號量有效時,這個任務接著執行處理串口接收到的數據。這個信號量可實現任務與任務之間的同步,也可實現中斷(串口中斷/定時器中斷/外部中斷等)和任務之間的同步。
12、二值信號量適合用于同步,如任務與任務之間的同步,任務和中斷之間的同步?;コ庑盘柫窟m合用于簡單的互斥訪問。
13、在使用二值信號量時,會產生任務優先級翻轉。解決任務優先級翻轉可以使用互斥信號量。
14、互斥信號量使用的API操作函數和二值信號量使用的API操作函數是相同的。所以互斥信號量也可設置阻塞時間,與二值信號量不同的是,互斥信號量具有優先級繼承的特性。當一個互斥信號量正在被一個低優先級的任務使用,而此時有個高優先級的任務也嘗試獲取這個互斥信號量的話就會被阻塞。不過這個高優先級的任務會將低優先級任務的優先級提升到與自己相同的優先級,這個過程就是優先級繼承。優先級繼承盡可能地降低了高優先級任務處于阻塞時間,并且將已經出現的“優先級翻轉”的影響降到最低。如,現有三個依次為低、中、高的三個任務,中高優先級任務處于就緒掛起態,低優先級任務在運行,此時低優先級任務想要訪問共享資源,則需要獲取該資源的信號量。低優先級任務獲取信號量后并開始訪問共享資源,此時,高優先級任務開始運行,也想訪問低優先級任務正在使用的共享資源,但是該共享資源的信號量還在被低優先級任務占用著,高優先級任務只能進入掛起態,默默的等待低優先級任務釋放該共享資源的信號量。就在高優先級任務默默等待看著低優先級任務什么時候釋放共享資源的信號量時,中優先級任務等待的事件發生后,中級優先級任務也開始運行了,由于中優先級任務的優先級高于低優先級任務的優先級,所以就搶占了CPU的使用權。一直等到中優先級任務把自己的事件處理完成后,才把CPU使用權給低優先級任務,低優先級任務繼續使用CPU去訪問共享資源,待低優先級任務訪問共享資源完成了,低優先級任務釋放共享資源的信號量,最后高優先級任務才獲取低優先級任務釋放的信號量去訪問共享資源。這種現象看起來就是中優先級任務的優先級要比高優先級任務的優先級高。此時高優先級任務可以使用互斥信號量,把低優先級任務的優先級提高到與自己優先級一樣,在低優先級在使用信號量時,高優先級任務進入掛起態,中優先級任務事件發生后想要獲取CPU的使用權,但由于高優先級任務把低優先級任務的優先級提高了,中優先級任務只好等待這兩個高優先級任務運行完后,才可獲得CPU使用權。而原先的高優先級任務等到原低優先級任務釋放信號量以后,就可以獲取信號量去訪問原低優先級任務訪問的共享資源。
15、互斥信號量不能用于中斷服務器中。原因有兩個:一、互斥信號量有優先級繼承機制,只能應用在任務中,不能用于中斷服務函數。二、中斷服務函數中不能因為要等待互斥信號量二設置阻塞時間進入阻塞態。
16、遞歸信號量與互斥信號量一樣,不能用在中服務函數中。
17、創建的軟件定時器是休眠狀態的,要調用開啟軟件定時器的函數開啟定時器。
18、任務通知可完成消息隊列、信號量、事件標志組的功能,不過任務通知是只能實現一對一的,也就是一個任務對一個任務。
19、接收任務可以因為接收任務通知而進入阻塞態,但是發送任務不會因為任務通知發送失敗而阻塞。但是隊列就有入隊阻塞和出對阻塞,信號量是根據隊列來實現的,信號量也會出現跟隊列一樣的情況。
20、雖然任務通知可以模擬二值信號量,但任務通知不能用在資源管理上,只有二值信號量能用。
21、降低系統主頻可以降低CPU的功耗。
22、內存管理,heap1特點如下:
(1)適用于那些一旦創建好任務、信號量和隊列就再也不會刪除的應用,實際上大多數的FreeRTOS 應用都是這樣的。
(2)具有可確定性(執行所花費的時間大多數都是一樣的),而且不會導致內存碎片。
(3)代碼實現和內存分配過程都非常簡單,內存是從一個靜態數組中分配到的,也就是適合于那些不需要動態內存分配的應用。
22、內存管理,heap2能申請內存特點如下:
(1)可以使用在那些可能會重復的刪除任務、隊列、信號量等的應用中,要注意有內存碎片產生!
(2)如果分配和釋放的內存 n 大小是隨機的,那么就要慎重使用了,比如下面的示例:
=》如果一個應用動態的創建和刪除任務,而且任務需要分配的堆棧大小都是一樣的,那么 heap_2 就非常合適。如果任務所需的堆棧大小每次都是不同,那么 heap_2 就不適合了,因為這樣會導致內存碎片產生,最終導致任務分配不到合適的堆棧!不過 heap_4 就很適合這種場景了。
=》如果一個應用中所使用的隊列存儲區域每次都不同,那么 heap_2 就不適合了,和上面一樣,此時可以使用 heap_4。
=》應用需要調用 pvPortMalloc()和 pvPortFree()來申請和釋放內存,而不是通過其他FreeRTOS 的其他 API 函數來間接的調用,這種情況下 heap_2 不適合。
(3)如果應用中的任務、隊列、信號量和互斥信號量具有不可預料性(如所需的內存大小不能確定,每次所需的內存都不相同,? 或者說大多數情況下所需的內存都是不同的)的話可能會導致內存碎片。雖然這是小概率事件,但是還是要引起我們的注意!
(4)具有不可確定性,但是也遠比標準 C 中的 mallo()和 free()效率高!heap_2 基本上可以適用于大多數的需要動態分配內的工程中,而 heap_4 更是具有將內存碎片合并成一個大的空閑內存塊(就是內存碎片回收)的功能。
23、內存管理,heap3內存分配方法是對標準C中的函數malloc()和free()的簡單封裝,FreeRTOS對這兩函數做了線程保護。特點如下:
(1)需要編譯器提供一個內存堆,編譯器庫要提供 malloc()和 free()函數。若使用STM32的話可以通過修改啟動文件中的Heap_Size 來修改內存堆的大小。
(2)具有不確定性。
(3)可能會增加代碼量。
24、內存管理,heap4提供了一個最優的匹配算法,不像heap2,heap4會將內存碎片合成一個大的可用內存塊。它提供了內存合并算法。
特點如下:
(1)可以用在那些需要重復創建和刪除任務、隊列、信號量和互斥信號量等的應用中。
(2)不會像 heap_2 那樣產生嚴重的內存碎片,即使分配的內存大小是隨機的。
(3)具有不確定性,但是遠比 C 標準庫中的 malloc()和 free()效率高。heap_4 非常適合于那些需要直接調用函數pvPortMalloc()和 vPortFree()來申請和釋放內存的應用,注意,我們移植 FreeRTOS 的時候就選擇的 heap_4!heap_4也使用鏈表結構來管理空閑內存塊,鏈表結構體與 heap_2 一樣。 heap_4 也定義了兩個局部靜態變量 pxStart 和 pxEnd來表示鏈表頭和尾,其中 pxEnd是指向 BlockLink_t 的指針。
25、內存管理,heap_5 基本上和 heap_4 一樣,只是 heap_5 支持內存堆使用不連續的內存塊。?heap_5 允許內存堆跨越多個不連續的內存段。比如 STM32 的內部 RAM 可以作為內存堆,但是 STM32 內部 RAM 比較小,遇到那些需要大容量 RAM 的應用就不行了,如音視頻處理。不過 STM32 可以外接 SRAM 甚至大容量的 SDRAM,如果使用 heap_4 的話你就只能在內部 RAM 和外部SRAM 或 SDRAM 之間二選一了,使用 heap_5 的話就不存在這個問題,兩個都可以一起作為內存堆來用。
26、用pvPortMalloc()申請內存、pvPortFree()釋放內存,也可查看內存使用剩余量??捎靡粋€指針變量如uint8_t buffer去接收申請到的內存。釋放內存以后需要將 buffer 設置為 NULL,函數 pvPortFree()釋放內存以后并不會將 buffer清零,此時 buffer 還是上次申請到的內存地址,如果此時再調用指針 buffer 的話你會發現還是可以使用的!感覺好像沒有釋放內存,所以需要將 buffer 清零。內存申請、內存釋放、查看內存使用剩余量如下:
uint8_t *buffer,times = 0;
uint32_t freemem;
申請內存
buffer = pvPortMalloc(20); //申請內存,20個字節?
printf("申請到的內存地址為:%#x\r\n",(int)buffer);
釋放內存
if(buffer!=NULL){
? ?vPortFree(buffer); //釋放內存
}
buffer = NULL;
使用內存
if(buffer!=NULL) //buffer 可用,使用 buffer (5)
{
? ?times++;
? ?sprintf((char*)buffer,"User %d Times",times);//向buffer中填寫一些數據
? ?printf("%s\r\n",buffer); ? ? ? ? ? ? ? ? ? ? //把剛剛寫進去的數據打印出來
}
查看內存剩余大小
freemem = xPortGetFreeHeapSize(); //獲取剩余內存大小
注意:我們申請20字節的內存大小,但實際申請到的內存大小要比20字節大,為什么會這樣?因為在申請內存的時候,會自動進行內存堆的開始地址對齊,申請的內存地址也需要進行字節對齊。所以我們申請的是20字節,但是經過字節對齊后計算,實際會分配大于20字節的內存給我們。
?
?
?
?
總結
以上是生活随笔為你收集整理的学习《FreeRTOS源码详解与应用开发》笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习正点原子讲解FreeRTOS中断管理
- 下一篇: STM32 HAL库 串口DMA(收发)