做爲基於ARM七、Cortex-M3硬件開發的嵌入式工程師,我一直反對使用RTOS。不只由於不恰當的使用RTOS會給項目帶來額外的穩定性風險,更重要的是我認爲絕大多數基於ARM七、Cortex-M3硬件的項目,還沒複雜到使用RTOS的地步,使用狀態機就足夠了。html
對於現代的微處理器,特別是資源相對豐富ARM七、Cortex-M3硬件來講,RTOS佔用的硬件資源已經愈來愈能夠忽略。因此在當今環境下,咱們無需擔憂RTOS會拖累性能。相反,RTOS提供的事件驅動型設計方式,使得RTOS只是在處理實際任務時纔會運行,這可以更合理的利用CPU。在實際項目中,若是程序等待一個超時事件,傳統的無RTOS狀況下,要麼在原地一直等待而不能執行其它任務,要麼使用複雜(相對RTOS提供的任務機制而言)的狀態機機制。若是使用RTOS,則能夠很方便的將當前任務阻塞在該事件下,而後自動去執行別的任務,這顯然更方便,而且能夠高效的利用CPU。處理這類事件,是我使用RTOS的最大動力,但考慮到系統的穩定性,我不得再也不三權衡RTOS可能帶來的一些弊端:git
以上緣由是我拒絕在實際項目中使用RTOS的理由,可是否使用RTOS跟是否學習RTOS徹底是兩碼事。我認爲任何嵌入式軟件設計人員都應該至少學習一種RTOS,不只是須要掌握RTOS背後的操做系統原理、學習RTOS的編程方式,更是爲未來作準備。程序員
即使我認爲如今的物聯網有點言過其實,但我依然看好物聯網的發展前景。隨着物聯網的發展,將來的嵌入式產品必然更爲複雜、鏈接性更強以及須要更豐富的用戶界面。當處理這些任務時,一個好的RTOS就變得不可缺乏了。算法
書到用時方恨少,我但願本身永遠不會有這種感受。因此從如今起,我要開始深刻一個RTOS,探索它背後的原理,掌握其編程方法,避免其缺陷和陷阱,並將它安全的用在未來的項目中。編程
對比了許多RTOS,最終選擇FreeRTOS,緣由是多方面的:小程序
學習的資料來源主要是FreeRTOS的官方網站(www.freertos.org)和源代碼。FreeRTOS的創始人RichardBarry編寫了大量的移植代碼和配套文檔,我只不過是沿着Richard Barry鋪好的路前進,因此,這沒什麼困難的。數組
最後,感謝RichardBarry的付出,感謝Richard Barry的無私開源精神!xcode
附錄1:2010~2017年EEtimes雜誌嵌入式市場調查報告有關RTOS使用榜截圖緩存
2010和2011年RTOS使用榜安全
2012和2013年RTOS使用榜
2013年和2014年RTOS使用榜
2014年和2015年RTOS使用榜
2017年RTOS使用榜
FreeRTOS能夠被移植到不少不一樣架構的處理器和編譯器。每個RTOS移植都附帶一個已經配置好的演示例程,能夠方便快速啓動開發。更好的是,每一個演示例程都附帶一個說明網頁,提供如何定位RTOS演示工程源代碼、如何編譯演示例程、如何配置硬件平臺的所有信息。
演示例程說明網頁還提供基本的RTOS移植細節信息,包括如何編寫FreeRTOS兼容的中斷服務例程,不一樣架構的中斷處理會稍有不一樣。
經過下面的簡單說明,能夠在幾分鐘內運行RTOS。
FreeRTOS具備詳細的開發說明文檔,能夠在其官方網站上查看。首先打開官方網站,目前的網站地址是:http://www.freertos.org。在首頁左側的導航欄中,展開"Supported Devices & Demos"菜單項,單擊"OfficiallySupported Demos"連接,去查看FreeRTOS支持的微控制器製造商列表。單擊微控制器製造商名稱,進入具體的製造商文檔頁面列表。
到FreeRTOS官方網站下載源碼,下載包包含RTOS內核源碼和官方移植演示工程。解壓縮後放到合適的目錄下。(若是你不想訪問慢吞吞的國外網站,我在CSDN作了一個鏡像,能夠 點擊此處 ,這篇文章中有最新的大部分FreeRTOS源碼包下載連接)
每個RTOS移植包都附帶有預先配置好的演示例程 ,已經建立好了全部必須的RTOS源文件幷包含了必須的RTOS頭文件。推薦在提供的演示例程的基礎上進行本身的FreeRTOS應用編程。
FreeRTOS下載包中包含每一個處理器移植和演示例程的源碼。將全部移植包放入一個下載文件中大大簡化了分類處理,可是下載包中的文件數量也多的驚人!不管如何,目錄結構仍是很是簡單的,而且FreeRTOS實時內核僅僅只有3個文件(若是須要,還有一些附加文件,好比軟件定時器、事件組以及協程)。
下載包目錄包含兩個子目錄:FreeRTOS和FreeRTOS-Plus。以下所示:
FreeRTOS-Plus目錄樹包含多個自述文件(Readme)。接下來本文只描述FreeRTOS內核的核心源文件和演示例程,它們又被分紅兩個主要的子目錄,以下所示:
RTOS代碼的核心包含在三個文件中:tasks.c、queue.c、list.c。這三個文件位於FreeRTOS/Source目錄。在該目錄下還包含三個可選的文件:timers.c、event_groups.c、croutine.c,分別實現軟件定時、事件組和協程功能。
FreeRTOS/Source目錄結構以下所示:
每一個支持的處理器架構須要一小段與處理器架構相關的RTOS代碼。這個是RTOS移植層,它位於FreeRTOS/Source/Portable/[相應編譯器]/[相應CPU架構]子目錄。
對於FreeRTOS,堆棧設計也屬於移植層。FreeRTOS/Source/portable/MemMang目錄下heap_x.c文件給出了多種堆棧方案,後續文章將會詳細介紹堆棧操做。
移植層目錄舉例:
FreeRTOS下載包中還包含各類處理器架構和編譯器的演示例程。大多數的演示例程代碼對全部移植都是通用的,位於FreeRTOS/Demo/Common/Minimal目錄。FreeRTOS/Demo/Common/Full目錄下的是歷史遺留代碼,僅用於PC。
FreeRTOS/Demo目錄結構以下所示:
FreeRTOS/Demo目錄下剩餘的子目錄包含預先配置好的工程,能夠用於構建我的演示例程。子目錄的命名與移植平臺和編譯器相關。每個RTOS移植包都有本身的說明文檔。
演示例程目錄舉例:
根據上一節FreeRTOS源碼目錄結構說明的RTOS演示工程的所在的位置,打開並編譯演示工程。
演示例程附帶的說明網頁會介紹如何配置硬件、下載程序和執行演示例程。說明網頁還會提供演示例程的功能信息,這樣你就能夠判斷演示例程執行是否正確。
FreeRTOS下載包中已經包含不少演示例程- 每個例程都是針對於:
能夠在官方網站首頁左側的樹形菜單 'Supported Devices' 中找到這些例程介紹。
惋惜的是不可能爲全部微控制器、編譯器和評估板提供演示例程。所以,官方提供的演示例程可能不徹底符合你正在使用的開發平臺。本章描述如何經過修改或合併官方提供的演示例程,來知足本身的開發平臺需求(包括微處理器和編譯器)。
修改一個現有的評估板例程,使之運行到另外一個同類評估板上,一般是比較簡單的,稍微複雜些的是跨編譯器移植。本文介紹這兩狀況下的修改,只是對類似的平臺有效。然而,將FreeRTOS移植到一個全新的平臺、未支持的處理器架構,並非件簡單的事情。本文不討論如何將FreeRTOS移植到一個全新平臺。
本節描述如何經過修改一個官方提供的演示例程,使之運行到另外一個評估板,這裏兩個評估板使用同系列微處理器,使用相同編譯器。在這個例子中,將運行於SAM7S-EK硬件開發板上的IAR SAM7S演示例程,修改使之運行到Olimex SAM7-P64開發板。(注:兩塊開發板都是使用ATMEL公司的ARM7微處理器,前者使用AT91SAM7S256,後者使用AT91SAM7S64)
做爲修改練習的起點,被修改的演示例程是要能使用的。所以,在未作任何修改以前,首先檢查下載的演示例程可否被正確的編譯。絕大多數狀況下,演示例程編譯後是沒有任何錯誤和警告的。
關於演示例程所在目錄,參考《FreeRTOS系列第2篇---FreeRTOS入門指南》一文的第三節。
LED燈是用來指示演示例程運行的最簡單方法,所以點亮新硬件平臺上的LED燈一般是最容易的。
兩個不一樣評估板上的LED鏈接到相同的IO端口一般是不太可能的,所以,一些小幅度修改是必須的。
在partest.c文件中的vParTestInitialise() 函數包含IO端口的模式和方向配置。在main.c文件中的prvSetupHardware()函數包含更多的硬件初始化(好比,使能IO外設的時鐘模塊),可能須要根據不一樣的使用進行一些修改。
根據目標評估板的硬件,在上面兩個函數中作必要的修改,而後寫一段簡單程序,來檢查硬件LED是否無缺。這個簡單程序不使用FreeRTOS,只是爲了確保硬件LED可以正常工做。所以,註釋掉以前的main()函數,使用下面的例子代替:
一旦肯定硬件LED能夠正常工做,就能夠恢復原來的main()函數。
做爲入門級的多任務應用程序應該儘可能的簡單,LED閃爍測試程序經常擔任這樣的角色,能夠堪比經典的「Hello Wold」。這個任務幾乎在全部演示例程中都能看到,在main()函數中調用vStartLEDFlashTasks() (使用協程版本時調用vStartFlashCoRoutines())來實現。若是你使用的演示例程main()函數中並無調用vStartLEDFlashTasks()(或vStartFlashCoRoutines()),那麼須要你將FreeRTOS/Demo/Common/Minimal/Flash.c文件添加到你的工程,並在main()函數手動的增長vStartLEDFlashTasks()函數。
除了調用vStartLEDFlashTasks()外,註釋掉全部用於啓動一個或多個演示任務的函數。最後的main()函數僅調用三個函數:prvSetupHardware()、vStartLEDFlashTasks()和vTaskStartScheduler()。例如(基於典型的main()函數):
這是一個很是簡單的應用程序,正確執行後,LED0~2(包括2)或分別按照不一樣的頻率閃爍。
一旦簡單的LED閃爍例程正確執行後,你能夠恢復以前註釋掉的全部的演示任務。
如下要點需牢記:
本節主要描述如何修改一個現存的工程或者按照需求合併兩個現存的工程。好比,你但願使用GCC編譯器建立一個STR9演示工程(demo project),而且你下載的FreeRTOS軟件包中並無GCC版本的STR9演示例程,可是FreeRTOS下載包中有IAR版本的STR9演示例程和GCC版本的STR75x演示例程。則能夠經過這兩個現存的工程來創 建GCC版本的STR9演示工程。能夠有兩種方式完成:
使用GCC版本的STR75x演示工程,修改使之符合指定的微處理器(STR9評估板上的微處理器)。
使用GCC建立一個新的工程。從IAR版本的STR9演示工程中獲取文件和配置信息,使之符合GCC編譯器需求。
對於一個特定平臺,大多數(不是所有)硬件接口代碼包含在一個叫作FreeRTOS/source/portable/[編譯器]/[微控制器/port.c的文件中,和它對應的頭文件是FreeRTOS/source/portable/[編譯器]/[微控制器]/portmacro.h。
對於一些編譯器來講,port.c和portmacro.h就是所須要的所有硬件接口代碼。另外一些還須要一些彙編文件,這些文件叫作portasm.s或者portasm.asm。
最後,僅對於ARM7 GCC移植,一樣存在一個相似的硬件接口文件:portISR.c,portISR.c是從port.c中分離出來的,這些代碼必須在ARM模式下編譯,port.c中剩餘的代碼既能夠在ARM模式下編譯,也可在THUMB模式下編譯。
編譯器能夠爲嵌入式系統提供某些特定的C語言擴展。好比某個特定關鍵字能夠標識出一個函數是中斷處理服務函數。
擴展的C語言部分,是不屬於標準C語言規範的。所以,編譯器與編譯器之間是有差異的。FreeRTOS的文件中就包含相似的非標準C語言語法,在文件夾FreeRTOS/source/portable中(上文中提到的特定微控制器硬件接口代碼也在這個文件中)。此外,一些演示例程會使用到中斷服務程序,這些中斷服務程序並不屬於FreeRTOS的一部分,而且如何定義和使用這些中斷服務程序也是編譯器所特定的。
C啓動文件和連接腳本都屬於處理器和編譯器特定的。不推薦嘗試從無到有的建立這些文件,應該到FreeRTOS演示工程中尋找一個合適的來修改。
要特別當心ARM7啓動文件。它必須將IRQ中斷服務程序入口地址配置到快速中斷處理向量表或者普通中斷向量表中。這兩種狀況,演示工程都提供了例子。
連接腳本必須正確的描述當前使用處理器的內存映射。
每個工程一般都會定義一些宏,這些預處理宏定義了一些要被編譯的特定的硬件接口代碼。要包含portmacro.h文件才能識別這些宏。好比,當使用GCC編譯MegaAVR硬件接口代碼時,宏GCC_MEGA_AVR必須被定義;當使用IAR編譯MegaAVR硬件接口代碼時,宏IAR_MEGA_AVR必須被定義等等。參考演示例程工程以及FreeRTOS/source/include/portable.h文件能夠查找當前工程定義了那些宏。若是預處理宏未定義,那麼portmacro.h文件所在目錄的路徑必須被包含到預處理器的搜索路徑中。
其它的編譯器設置,好比優化選項,也是很關鍵的。能夠參考提供的演示工程。
具備IDE的編譯器一般具備目標微控制器選項並將它做爲工程設置的一部分,因此新的工程也必須適應新的目標微控制器,一樣的,若是使用到makefile文件,則makefile文件也必須更新以符合新的目標微控制器。
調用函數prvSetupTimerInterrupt()來配置系統節拍中斷,這個函數能夠在如下路徑的文件中找到:FreeRTOS/source/portable/[compiler]/[microcontroller]/port.c
FreeRTOS內存管理一章中描述了FreeRTOS如何使用RAM,而且描述了RAM是如何分配給RTOS內核的。
若是你要將演示例程移植到一個RAM稍小的微處理器上,那麼你可能須要減小configTOTAL_HEAP_SIZE的值(位於FreeRTOSConfig.h),而且減小演示例程的任務個數。能夠經過簡單的註釋掉不須要的任務來實現。
若是你要將演示例程移植到一個ROM較小的微處理器中,那麼你可能須要減小應用例程的文件數目,他們位於FreeRTOS/Demo/common文件夾目錄下。同時你還要刪除main函數中對他們的調用函數。
注:可能你是經過搜索引擎找到這篇文章,滿懷但願的點進來,覺得能解決本身移植的全部問題,可是看完後卻發現本文站的角度過高,並非特別適合對移植一無所知的你。先別急着覺得本文是標題黨而點踩,可能有一篇文章適合你,這篇文章以Cortex-M3硬件平臺爲例,詳細的介紹移植過程,請點擊這裏。
FreeRTOS的核心源代碼聽從MISRA編碼標準指南。這個標準篇幅稍長,你能夠在MISRA官方網站花少許錢買到,這裏再也不復制任何標準。
FreeRTOS源代碼不符合MISRA標準的項目以下所示:
FreeRTOS能夠在不少不一樣編譯器中編譯,其中的一些編譯器比同類有更高級特性。由於這個緣由,FreeRTOS不使用任何非C語言標準的特性或語法。一個例外狀況是頭文件stdint.h。在文件夾FreeRTOS/Source/include下包含一個叫作stdint.readme的文件,若是你的編譯器不提供stdint類型定義,能夠將stdint.readme文件重命名爲stdint.h。
RTOS內核和演示例程源代碼使用如下規則:
1> 變量
2> 函數
3> 宏
只有stdint.h和RTOS本身定義的數據類型能夠使用,但也有例外狀況,以下所示:
有三種類型會在移植層定義,它們是:
3.4風格指南
在FreeRTOS官方網站能夠下載到最新版的FreeRTOS包,我這裏使用的是V8.2.3版本。
下載包內的總文件數量多的使人生畏,但文件結構卻很簡潔。《FreeRTOS入門指南》一文的第3節詳細描述了下載包文件結構,咱們這裏只是簡單提一下。
下載包根目錄下包含兩個子目錄:FreeRTOS和FreeRTOS-Plus。其中,FreeRTOS-Plus文件夾中包含一些FreeRTOS+組件和演示例程(組件大都收費),咱們不對這個文件夾下的內容多作了解,重點說一下FreeRTOS文件夾。
FreeRTOS文件夾下包含兩個子目錄:Demo和Source。其中,Demo包含演示例程的工程文件,Source包含實時操做系統源代碼文件。
FreeRTOS實時操做系統內核僅包含三個必要文件,此外還有三個可選文件。RTOS核心代碼位於三個源文件中,分別是tasks.c、queue.c和list.c。這三個文件位於FreeRTOS/Source目錄下,在同一目錄下還有3個可選的文件,叫作timers.c、event_groups.c和croutine.c,分別用於軟件定時器、事件組和協程。
對於支持的處理器架構,RTOS須要一些與處理器架構相關的代碼。能夠稱之爲RTOS硬件接口層,它們位於FreeRTOS/Source/Portable/[相應編譯器]/[相應處理器架構]文件夾下。咱們此次要移植到Cortex-M3微控制,使用Keil MDK編譯器,因此須要的RTOS硬件接口代碼位於:FreeRTOS\Source\portable\RVDS\ARM_CM3文件夾下。
堆棧分配也是屬於硬件接口層(移植層),在FreeRTOS/Source/portable/MemMang文件夾下具備各類類型的堆棧分配方案。這裏咱們使用heap_1.c提供的堆棧分配方案。關於FreeRTOS的內存管理,後續《FreeRTOS內存管理》一文中會詳細介紹FreeRTOS內存管理的特性和用法,《FreeRTOS內存管理分析》一文會從源碼級別分析FreeRTOS內存管理的具體實現,這裏不用多糾結,你也能夠快速的瀏覽一下這兩篇文章,裏面或許有許多不懂的,但不要着急,先放過它們。
FreeRTOS文件夾下的Demo文件夾中還包括各類演示例程,涉及多種架構的處理器以及多種編譯器。FreeRTOS/Demo/Common/Minimal文件夾下的演示例程代碼中,絕大部分對全部移植硬件接口都是適用的。FreeRTOS/Demo/Common/Full文件夾下的代碼屬於歷史遺留代碼,僅用於PC移植層。
將tasks.c、queue.c和list.c這三個內核代碼加入工程,將port.c和heap_1.c這兩個與處理器相關代碼加入工程。port.c位於FreeRTOS\Source\portable\RVDS\ARM_CM3文件夾下,heap_1.c位於FreeRTOS/Source/portable/MemMang文件夾下。
對於剛接觸FreeRTOS的用戶來講,最簡單方法是找一個相似的Demo工程,複製該工程下的FreeRTOSConfig.h文件,在這個基礎上進行修改。詳細的配置說明將在後續《FreeRTOS內核配置說明》一文中給出,這裏依然沒必要糾結。
若是你在FreeRTOSConfig.h中設置了configUSE_TICK_HOOK=1,則必須編寫voidvApplicationTickHook( void )函數。該函數利用時間片中斷,能夠很方便的實現一個定時器功能。詳見後續文章《FreeRTOS內核配置說明》有關宏configUSE_TICK_HOOK一節。
若是你在FreeRTOSConfig.h中設置了configCHECK_FOR_STACK_OVERFLOW=1或=2,則必須編寫voidvApplicationStackOverflowHook( xTaskHandle pxTask, signed char *pcTaskName )函數,該函數用於檢測堆棧溢出,詳見後續文章《FreeRTOS內核配置說明》有關宏configCHECK_FOR_STACK_OVERFLOW一節。
爲了驗證你的硬件板子是否可靠的工做,首先編寫一個小程序片,好比閃爍一個LED燈或者發送一個字符等等,咱們這裏使用UART發送一個字符。代碼以下所示(假設你已經配置好了啓動代碼,並正確配置了UART):
若是硬件能夠正常發送字符,說明硬件以及啓動代碼OK,能夠進行下一步。
在Cortex-M3硬件下,FreeRTOS使用SysTick做爲系統節拍時鐘,使用SVC和PendSVC進行上下文切換。異常中斷服務代碼位於port.c文件中,FreeRTOS的做者已經爲各類架構的CPU寫好了這些代碼,能夠直接拿來用,須要用戶作的,僅僅是將這些異常中斷入口地址掛接到啓動代碼中。
在startup.s中,使用IMPORT關鍵字聲明要掛接的異常中斷服務函數名,而後將:
在步驟3.5中,咱們爲了測試硬件是是否可以工做,編寫了一個發送字符的小函數,這裏咱們將把這個小函數做爲咱們第一個任務要執行的主要代碼:每隔1秒鐘,發送一個字符。代碼以下所示:
FreeRTOS的任務以及編寫格式將在後續文章《FreeRTOS任務概述》一文中詳述,這裏只是一個很簡單的任務,先有有大致印象。這裏面有一個API函數vTaskDelay(),這個函數用於延時,具體用法將在後續文章《FreeRTOS任務控制》一文中詳細介紹,延時函數代碼級分析將在《FreeRTOS高級篇10---系統節拍時鐘分析》。這裏沒必要在乎太多的未知狀況,由於後面會一點點將這些未知空間探索一遍的。
這裏咱們使用SysTick定時器做爲系統的節拍時鐘,設定每隔10ms產生一次節拍中斷。因爲FreeRTOS對移植作了很是多的工做,以致於咱們只須要在FreeRTOSConfig.h中配置好如下兩個宏定義便可:
第一個宏定義CPU系統時鐘,也就是CPU執行時的頻率。第二個宏定義FreeRTOS的時間片頻率,這裏定義爲100,代表RTOS一秒鐘能夠切換100次任務,也就是每一個時間片爲10ms。
在prot.c中,函數vPortSetupTimerInterrupt()設置節拍時鐘。該函數根據上面的兩個宏定義的參數,計算SysTick定時器的重裝載數值寄存器,而後設置SysTick定時器的控制及狀態寄存器,設置以下:使用內核時鐘源、使能中斷、使能SysTick定時器。另外,函數vPortSetupTimerInterrupt()由函數vTaskStartScheduler()調用,這個函數用於啓動調度器。
這裏特別重要,由於涉及到中斷優先級和中斷嵌套。這裏先給出基於Cortex-M3硬件(lpc177x_8x系列微控制器)的一個配置例子,在FreeRTOSConfig.h中:
後續文章《FreeRTOS內核配置說明》會詳細介紹這些宏的含義,對於Cortex-M內核,後續文章《Cortex-M內核使用FreeRTOS特別注意事項》一文,會講述這些宏與硬件的聯繫,那個時候你必定會清楚這些宏所定義的數字會對你的硬件產生什麼影響的。如今,咱們只須要知道他們很重要就足夠了,沒人能一口吃成胖子。
還須要在FreeRTOSConfig.h設置一些必要的宏,這些宏以下所示:
調用FreeRTOS提供的API函數來建立任務,代碼以下所示:
關於詳細的建立任務API函數,會在後續文章《FreeRTOS任務建立和刪除》一文中介紹。
調用FreeRTOS提供的API函數來啓動調度器,代碼以下所示:
關於詳細的開啓調度器API函數,會在後續文章《FreeRTOS內核控制》一文中介紹。
此時的main函數代碼以下所示:
到這裏,一個最基本的FreeRTOS應用程序就已經運行起來,將硬件板子接到PC的RS232串口,能夠觀察到每隔一秒鐘,板子都會向PC發送一個指定的字符。
回頭看一下移植過程,FreeRTOS移植到Cortex-M3硬件是多麼的簡單,這一方面歸功於FreeRTOS的設計師已經爲移植作了大量工做,同時,新一代的Cortex-M3硬件也爲操做系統增長了一些列便利特性,好比SysTick定時器和全新的中斷及異常。
可是移植成功也只是萬里長征的第一步,由於這只是最簡單的應用。咱們還不清楚FreeRTOS背後的機理、調度算法的面貌、甚至連信號量也都沒有涉及。就本文的移植過程來看,咱們也刻意忽略了不少細節,好比FreeRTOSConfig.h文件中的宏都有什麼意義?改動後對RTOS有何影響?好比FreeRTOS任務API的細節、調度API的細節,再好比FreeRTOS的內存如何分配?如何進行堆棧溢出檢查等等。
因此,先不要沾沾自喜,曲折的道路還遠沒到來呢。
接下來的不少篇文章會圍繞這個最簡單的移植例程作詳細的講解,要把本篇文章中刻意隱藏的細節一一拿出來。這要一直持續到咱們介紹隊列、信號量、互斥量等通信機制爲止。
FreeRTOS內核是高度可定製的,使用配置文件FreeRTOSConfig.h進行定製。每一個FreeRTOS應用都必須包含這個頭文件,用戶根據實際應用來裁剪定製FreeRTOS內核。這個配置文件是針對用戶程序的,而非內核,所以配置文件通常放在應用程序目錄下,不要放在RTOS內核源碼目錄下。
在下載的FreeRTOS文件包中,每一個演示例程都有一個FreeRTOSConfig.h文件。有些例程的配置文件是比較舊的版本,可能不會包含全部有效選項。若是沒有在配置文件中指定某個選項,那麼RTOS內核會使用默認值。典型的FreeRTOSConfig.h配置文件定義以下所示,隨後會說明裏面的每個參數。
1.configUSE_PREEMPTION
爲1時RTOS使用搶佔式調度器,爲0時RTOS使用協做式調度器(時間片)。
注:在多任務管理機制上,操做系統能夠分爲搶佔式和協做式兩種。協做式操做系統是任務主動釋放CPU後,切換到下一個任務。任務切換的時機徹底取決於正在運行的任務。
2.configUSE_PORT_OPTIMISED_TASK_SELECTION
某些運行FreeRTOS的硬件有兩種方法選擇下一個要執行的任務:通用方法和特定於硬件的方法(如下簡稱「特殊方法」)。
通用方法:
特殊方法:
3.configUSE_TICKLESS_IDLE
設置configUSE_TICKLESS_IDLE爲1使能低功耗tickless模式,爲0保持系統節拍(tick)中斷一直運行。
一般狀況下,FreeRTOS回調空閒任務鉤子函數(須要設計者本身實現),在空閒任務鉤子函數中設置微處理器進入低功耗模式來達到省電的目的。由於系統要響應系統節拍中斷事件,所以使用這種方法會週期性的退出、再進入低功耗狀態。若是系統節拍中斷頻率過快,則大部分電能和CPU時間會消耗在進入和退出低功耗狀態上。
FreeRTOS的tickless空閒模式會在空閒週期時中止週期性系統節拍中斷。中止週期性系統節拍中斷能夠使微控制器長時間處於低功耗模式。移植層須要配置外部喚醒中斷,當喚醒事件到來時,將微控制器從低功耗模式喚醒。微控制器喚醒後,會從新使能系統節拍中斷。因爲微控制器在進入低功耗後,系統節拍計數器是中止的,但咱們又須要知道這段時間能折算成多少次系統節拍中斷週期,這就須要有一個不受低功耗影響的外部時鐘源,即微處理器處於低功耗模式時它也在計時的,這樣在重啓系統節拍中斷時就能夠根據這個外部計時器計算出一個調整值並寫入RTOS 系統節拍計數器變量中。
4.configUSE_IDLE_HOOK
設置爲1使用空閒鉤子(Idle Hook相似於回調函數),0忽略空閒鉤子。
當RTOS調度器開始工做後,爲了保證至少有一個任務在運行,空閒任務被自動建立,佔用最低優先級(0優先級)。對於已經刪除的RTOS任務,空閒任務能夠釋放分配給它們的堆棧內存。所以,在應用中應該注意,使用vTaskDelete()函數時要確保空閒任務得到必定的處理器時間。除此以外,空閒任務沒有其它特殊功能,所以能夠任意的剝奪空閒任務的處理器時間。
應用程序也可能和空閒任務共享同個優先級。
空閒任務鉤子是一個函數,這個函數由用戶來實現,RTOS規定了函數的名字和參數,這個函數在每一個空閒任務週期都會被調用。
要建立一個空閒鉤子:
這個鉤子函數不能夠調用會引發空閒任務阻塞的API函數(例如:vTaskDelay()、帶有阻塞時間的隊列和信號量函數),在鉤子函數內部使用協程是被容許的。
使用空閒鉤子函數設置CPU進入省電模式是很常見的。
5.configUSE_MALLOC_FAILED_HOOK
每當一個任務、隊列、信號量被建立時,內核使用一個名爲pvPortMalloc()的函數來從堆中分配內存。官方的下載包中包含5個簡單內存分配策略,分別保存在源文件heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c中。 僅當使用這五個簡單策略之一時,宏configUSE_MALLOC_FAILED_HOOK纔有意義。
若是定義並正確配置malloc()失敗鉤子函數,則這個函數會在pvPortMalloc()函數返回NULL時被調用。只有FreeRTOS在響應內存分配請求時發現堆內存不足纔會返回NULL。
若是宏configUSE_MALLOC_FAILED_HOOK設置爲1,那麼必須定義一個malloc()失敗鉤子函數,若是宏configUSE_MALLOC_FAILED_HOOK設置爲0,malloc()失敗鉤子函數不會被調用,即使已經定義了這個函數。malloc()失敗鉤子函數的函數名和原型必須以下所示:
6.configUSE_TICK_HOOK
設置爲1使用時間片鉤子(Tick Hook),0忽略時間片鉤子。
注:時間片鉤子函數(Tick Hook Function)
時間片中斷能夠週期性的調用一個被稱爲鉤子函數(回調函數)的應用程序。時間片鉤子函數能夠很方便的實現一個定時器功能。
只有在FreeRTOSConfig.h中的configUSE_TICK_HOOK設置成1時才能夠使用時間片鉤子。一旦此值設置成1,就要定義鉤子函數,函數名和參數以下所示:
vApplicationTickHook()函數在中斷服務程序中執行,所以這個函數必須很是短小,不能大量使用堆棧,不能調用以」FromISR" 或 "FROM_ISR」結尾的API函數。
在FreeRTOSVx.x.x\FreeRTOS\Demo\Common\Minimal文件夾下的crhook.c文件中有使用時間片鉤子函數的例程。
7.configCPU_CLOCK_HZ
寫入實際的CPU內核時鐘頻率,也就是CPU指令執行頻率,一般稱爲Fcclk。配置此值是爲了正確的配置系統節拍中斷週期。
8.configTICK_RATE_HZ
RTOS 系統節拍中斷的頻率。即一秒中斷的次數,每次中斷RTOS都會進行任務調度。
系統節拍中斷用來測量時間,所以,越高的測量頻率意味着可測到越高的分辨率時間。可是,高的系統節拍中斷頻率也意味着RTOS內核佔用更多的CPU時間,所以會下降效率。RTOS演示例程都是使用系統節拍中斷頻率爲1000HZ,這是爲了測試RTOS內核,比實際使用的要高。(實際使用時不用這麼高的系統節拍中斷頻率)
多個任務能夠共享一個優先級,RTOS調度器爲相同優先級的任務分享CPU時間,在每個RTOS 系統節拍中斷到來時進行任務切換。高的系統節拍中斷頻率會下降分配給每個任務的「時間片」持續時間。
9.configMAX_PRIORITIES
配置應用程序有效的優先級數目。任何數量的任務均可以共享一個優先級,使用協程能夠單獨的給與它們優先權。見configMAX_CO_ROUTINE_PRIORITIES。
在RTOS內核中,每一個有效優先級都會消耗必定量的RAM,所以這個值不要超過你的應用實際須要的優先級數目。
注:任務優先級
每個任務都會被分配一個優先級,優先級值從0~ (configMAX_PRIORITIES - 1)之間。低優先級數表示低優先級任務。空閒任務的優先級爲0(tskIDLE_PRIORITY),所以它是最低優先級任務。
FreeRTOS調度器將確保處於就緒狀態(Ready)或運行狀態(Running)的高優先級任務比一樣處於就緒狀態的低優先級任務優先獲取處理器時間。換句話說,處於運行狀態的任務永遠是高優先級任務。
處於就緒狀態的相同優先級任務使用時間片調度機制共享處理器時間。
10.configMINIMAL_STACK_SIZE
定義空閒任務使用的堆棧大小。一般此值不該小於對應處理器演示例程文件FreeRTOSConfig.h中定義的數值。
就像xTaskCreate()函數的堆棧大小參數同樣,堆棧大小不是以字節爲單位而是以字爲單位的,好比在32位架構下,棧大小爲100表示棧內存佔用400字節的空間。
11.configTOTAL_HEAP_SIZE
RTOS內核總計可用的有效的RAM大小。僅在你使用官方下載包中附帶的內存分配策略時,纔有可能用到此值。每當建立任務、隊列、互斥量、軟件定時器或信號量時,RTOS內核會爲此分配RAM,這裏的RAM都屬於configTOTAL_HEAP_SIZE指定的內存區。後續的內存配置會詳細講到官方給出的內存分配策略。
12.configMAX_TASK_NAME_LEN
調用任務函數時,須要設置描述任務信息的字符串,這個宏用來定義該字符串的最大長度。這裏定義的長度包括字符串結束符’\0’。
13.configUSE_TRACE_FACILITY
設置成1表示啓動可視化跟蹤調試,會激活一些附加的結構體成員和函數。
14.configUSE_STATS_FORMATTING_FUNCTIONS (V7.5.0新增)
設置宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS爲1會編譯vTaskList()和vTaskGetRunTimeStats()函數。若是將這兩個宏任意一個設置爲0,上述兩個函數不會被編譯。
15.configUSE_16_BIT_TICKS
定義系統節拍計數器的變量類型,即定義portTickType是表示16位變量仍是32位變量。
定義configUSE_16_BIT_TICKS爲1意味着portTickType表明16位無符號整形,定義configUSE_16_BIT_TICKS爲0意味着portTickType表明32位無符號整形。
使用16位類型能夠大大提升8位和16位架構微處理器的性能,但這也限制了最大時鐘計數爲65535個’Tick’。所以,若是Tick頻率爲250HZ(4MS中斷一次),對於任務最大延時或阻塞時間,16位計數器是262秒,而32位是17179869秒。
16.configIDLE_SHOULD_YIELD
這個參數控制任務在空閒優先級中的行爲。僅在知足下列條件後,纔會起做用。
經過時間片共享同一個優先級的多個任務,若是共享的優先級大於空閒優先級,並假設沒有更高優先級任務,這些任務應該得到相同的處理器時間。
但若是共享空閒優先級時,狀況會稍微有些不一樣。當configIDLE_SHOULD_YIELD爲1時,其它共享空閒優先級的用戶任務就緒時,空閒任務馬上讓出CPU,用戶任務運行,這樣確保了能最快響應用戶任務。處於這種模式下也會有不良效果(取決於你的程序須要),描述以下:
圖中描述了四個處於空閒優先級的任務,任務A、B和C是用戶任務,任務I是空閒任務。上下文切換週期性的發生在T0、T1…T6時刻。當用戶任務運行時,空閒任務馬上讓出CPU,可是,空閒任務已經消耗了當前時間片中的必定時間。這樣的結果就是空閒任務I和用戶任務A共享一個時間片。用戶任務B和用戶任務C所以得到了比用戶任務A更多的處理器時間。
能夠經過下面方法避免:
設置configIDLE_SHOULD_YIELD爲0將阻止空閒任務爲用戶任務讓出CPU,直到空閒任務的時間片結束。這確保全部處在空閒優先級的任務分配到相同多的處理器時間,可是,這是以分配給空閒任務更高比例的處理器時間爲代價的。
17.configUSE_TASK_NOTIFICATIONS(V8.2.0新增)
設置宏configUSE_TASK_NOTIFICATIONS爲1(或不定義宏configUSE_TASK_NOTIFICATIONS)將會開啓任務通知功能,有關的API函數也會被編譯。設置宏configUSE_TASK_NOTIFICATIONS爲0則關閉任務通知功能,相關API函數也不會被編譯。默認這個功能是開啓的。開啓後,每一個任務多增長8字節RAM。
這是個頗有用的特性,一大亮點。
每一個RTOS任務具備一個32位的通知值,RTOS任務通知至關於直接向任務發送一個事件,接收到通知的任務能夠解除任務的阻塞狀態(因等待任務通知而進入阻塞狀態)。相對於之前必須分別建立隊列、二進制信號量、計數信號量或事件組的狀況,使用任務通知顯然更靈活。更好的是,相比於使用信號量解除任務阻塞,使用任務通知能夠快45%(使用GCC編譯器,-o2優化級別)。
18.configUSE_MUTEXES
設置爲1表示使用互斥量,設置成0表示忽略互斥量。讀者應該瞭解在FreeRTOS中互斥量和二進制信號量的區別。
關於互斥量和二進制信號量簡單說:
19.configUSE_RECURSIVE_MUTEXES
設置成1表示使用遞歸互斥量,設置成0表示不使用。
20.configUSE_COUNTING_SEMAPHORES
設置成1表示使用計數信號量,設置成0表示不使用。
21.configUSE_ALTERNATIVE_API
設置成1表示使用「替代」隊列函數('alternative' queue functions),設置成0不使用。替代API在queue.h頭文件中有詳細描述。
注:「替代」隊列函數已經被棄用,在新的設計中不要使用它!
22.configCHECK_FOR_STACK_OVERFLOW
每一個任務維護本身的棧空間,任務建立時會自動分配任務須要的佔內存,分配內存大小由建立任務函數(xTaskCreate())的一個參數指定。堆棧溢出是設備運行不穩定的最多見緣由,所以FreeeRTOS提供了兩個可選機制用來輔助檢測和改正堆棧溢出。配置宏configCHECK_FOR_STACK_OVERFLOW爲不一樣的常量來使用不一樣堆棧溢出檢測機制。
注意,這個選項僅適用於內存映射未分段的微處理器架構。而且,在RTOS檢測到堆棧溢出發生以前,一些處理器可能先產生故障(fault)或異常(exception)來反映堆棧使用的惡化。若是宏configCHECK_FOR_STACK_OVERFLOW沒有設置成0,用戶必須提供一個棧溢出鉤子函數,這個鉤子函數的函數名和參數必須以下所示:
參數xTask和pcTaskName爲堆棧溢出任務的句柄和名字。請注意,若是溢出很是嚴重,這兩個參數信息也多是錯誤的!在這種狀況下,能夠直接檢查pxCurrentTCb變量。
推薦僅在開發或測試階段使用棧溢出檢查,由於堆棧溢出檢測會增大上下文切換開銷。
任務切換出去後,該任務的上下文環境被保存到本身的堆棧空間,這時極可能堆棧的使用量達到了最大(最深)值。在這個時候,RTOS內核會檢測堆棧指針是否還指向有效的堆棧空間。若是堆棧指針指向了有效堆棧空間以外的地方,堆棧溢出鉤子函數會被調用。
這個方法速度很快,可是不能檢測到全部堆棧溢出狀況(好比,堆棧溢出沒有發生在上下文切換時)。設置configCHECK_FOR_STACK_OVERFLOW爲1會使用這種方法。
當堆棧首次建立時,在它的堆棧區中填充一些已知值(標記)。當任務切換時,RTOS內核會檢測堆棧最後的16個字節,確保標記數據沒有被覆蓋。若是這16個字節有任何一個被改變,則調用堆棧溢出鉤子函數。
這個方法比第一種方法要慢,但也至關快了。它能有效捕捉堆棧溢出事件(即便堆棧溢出沒有發生在上下文切換時),可是理論上它也不能百分百的捕捉到全部堆棧溢出(好比堆棧溢出的值和標記值相同,固然,這種狀況發生的機率極小)。
使用這個方法須要設置configCHECK_FOR_STACK_OVERFLOW爲2.
23.configQUEUE_REGISTRY_SIZE
隊列記錄有兩個目的,都涉及到RTOS內核的調試:
除了進行內核調試外,隊列記錄沒有其它任何目的。
configQUEUE_REGISTRY_SIZE定義能夠記錄的隊列和信號量的最大數目。若是你想使用RTOS內核調試器查看隊列和信號量信息,則必須先將這些隊列和信號量進行註冊,只有註冊後的隊列和信號量才能夠使用RTOS內核調試器查看。查看API參考手冊中的vQueueAddToRegistry() 和vQueueUnregisterQueue()函數獲取更多信息。
24.configUSE_QUEUE_SETS
設置成1使能隊列集功能(能夠阻塞、掛起到多個隊列和信號量),設置成0取消隊列集功能。
25.configUSE_TIME_SLICING(V7.5.0新增)
默認狀況下(宏configUSE_TIME_SLICING未定義或者宏configUSE_TIME_SLICING設置爲1),FreeRTOS使用基於時間片的優先級搶佔式調度器。這意味着RTOS調度器老是運行處於最高優先級的就緒任務,在每一個RTOS 系統節拍中斷時在相同優先級的多個任務間進行任務切換。若是宏configUSE_TIME_SLICING設置爲0,RTOS調度器仍然老是運行處於最高優先級的就緒任務,可是當RTOS 系統節拍中斷髮生時,相同優先級的多個任務之間再也不進行任務切換。
26.configUSE_NEWLIB_REENTRANT(V7.5.0新增)
若是宏configUSE_NEWLIB_REENTRANT設置爲1,每個建立的任務會分配一個newlib(一個嵌入式C庫)reent結構。
27.configENABLE_BACKWARD_COMPATIBILITY
頭文件FreeRTOS.h包含一系列#define宏定義,用來映射版本V8.0.0和V8.0.0以前版本的數據類型名字。這些宏能夠確保RTOS內核升級到V8.0.0或以上版本時,以前的應用代碼不用作任何修改。在FreeRTOSConfig.h文件中設置宏configENABLE_BACKWARD_COMPATIBILITY爲0會去掉這些宏定義,而且須要用戶確認升級以前的應用沒有用到這些名字。
28.configNUM_THREAD_LOCAL_STORAGE_POINTERS
設置每一個任務的線程本地存儲指針數組大小。
線程本地存儲容許應用程序在任務的控制塊中存儲一些值,每一個任務都有本身獨立的儲存空間,宏configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每一個任務線程本地存儲指針數組的大小。API函數vTaskSetThreadLocalStoragePointer()用於向指針數組中寫入值,API函數pvTaskGetThreadLocalStoragePointer()用於從指針數組中讀取值。
好比,許多庫函數都包含一個叫作errno的全局變量。某些庫函數使用errno返回庫函數錯誤信息,應用程序檢查這個全局變量來肯定發生了那些錯誤。在單線程程序中,將errno定義成全局變量是能夠的,可是在多線程應用中,每一個線程(任務)必須具備本身獨有的errno值,不然,一個任務可能會讀取到另外一個任務的errno值。
FreeRTOS提供了一個靈活的機制,使得應用程序能夠使用線程本地存儲指針來讀寫線程本地存儲。具體參見後續文章《FreeRTOS系列第12篇---FreeRTOS任務應用函數》。
29.configGENERATE_RUN_TIME_STATS
設置宏configGENERATE_RUN_TIME_STATS爲1使能運行時間統計功能。一旦設置爲1,則下面兩個宏必須被定義:
舉一個例子,假如咱們配置了一個定時器,每500us中斷一次。在定時器中斷服務例程中簡單的使長整形變量ulHighFrequencyTimerTicks自增。那麼上面提到兩個宏定義以下(能夠在FreeRTOSConfig.h中添加):
30.configUSE_CO_ROUTINES
設置成1表示使用協程,0表示不使用協程。若是使用協程,必須在工程中包含croutine.c文件。
注:協程(Co-routines)主要用於資源發很是受限的嵌入式系統(RAM很是少),一般不會用於32位微處理器。
在當前嵌入式硬件環境下,不建議使用協程,FreeRTOS的開發者早已經中止開發協程。
31.configMAX_CO_ROUTINE_PRIORITIES
應用程序協程(Co-routines)的有效優先級數目,任何數目的協程均可以共享一個優先級。使用協程能夠單獨的分配給任務優先級。見configMAX_PRIORITIES。
32.configUSE_TIMERS
設置成1使用軟件定時器,爲0不使用軟件定時器功能。詳細描述見FreeRTOS software timers 。
33.configTIMER_TASK_PRIORITY
設置軟件定時器服務/守護進程的優先級。詳細描述見FreeRTOS software timers 。
34.configTIMER_QUEUE_LENGTH
設置軟件定時器命令隊列的長度。詳細描述見FreeRTOS software timers。
35.configTIMER_TASK_STACK_DEPTH
設置軟件定時器服務/守護進程任務的堆棧深度,詳細描述見FreeRTOS software timers 。
36.configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY
這是移植和應用FreeRTOS出錯最多的地方,因此須要打起精神仔細讀懂。
Cortex-M三、PIC2四、dsPIC、PIC3二、SuperH和RX600硬件設備須要設置宏configKERNEL_INTERRUPT_PRIORITY;PIC3二、RX600和Cortex-M硬件設備須要設置宏configMAX_SYSCALL_INTERRUPT_PRIORITY。
configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY,這兩個宏是等價的,後者是前者的新名字,用於更新的移植層代碼。
注意下面的描述中,在中斷服務例程中僅能夠調用以「FromISR」結尾的API函數。
經過設置configMAX_SYSCALL_INTERRUPT_PRIORITY的優先級級別高於configKERNEL_INTERRUPT_PRIORITY能夠實現完整的中斷嵌套模式。這意味着FreeRTOS內核不能徹底禁止中斷,即便在臨界區。此外,這對於分段內核架構的微處理器是有利的。請注意,當一個新中斷髮生後,某些微處理器架構會(在硬件上)禁止中斷,這意味着從硬件響應中斷到FreeRTOS從新使能中斷之間的這段短期內,中斷是不可避免的被禁止的。
不調用API的中斷能夠運行在比configMAX_SYSCALL_INTERRUPT_PRIORITY高的優先級,這些級別的中斷不會被FreeRTOS禁止,所以不會由於執行RTOS內核而被延時。
例如:假如一個微控制器有8箇中斷優先級別:0表示最低優先級,7表示最高優先級(Cortex-M3和Cortex-M4內核優先數和優先級別正好與之相反,後續文章會專門介紹它們)。當兩個配置選項分別爲4和0時,下圖描述了每個優先級別能夠和不可作的事件:
這些配置參數容許很是靈活的中斷處理:
在系統中能夠像其它任務同樣爲中斷處理任務分配優先級。這些任務經過一個相應中斷喚醒。中斷服務例程(ISR)內容應儘量的精簡---僅用於更新數據而後喚醒高優先級任務。ISR退出後,直接運行被喚醒的任務,所以中斷處理(根據中斷獲取的數據來進行的相應處理)在時間上是連續的,就像ISR在完成這些工做。這樣作的好處是當中斷處理任務執行時,全部中斷均可以處在使能狀態。
中斷、中斷服務例程(ISR)和中斷處理任務是三碼事:當中斷來臨時會進入中斷服務例程,中斷服務例程作必要的數據收集(更新),以後喚醒高優先級任務。這個高優先級任務在中斷服務例程結束後當即執行,它多是其它任務也多是中斷處理任務,若是是中斷處理任務,那麼就能夠根據中斷服務例程中收集的數據作相應處理。
configMAX_SYSCALL_INTERRUPT_PRIORITY接口有着更深一層的意義:在優先級介於RTOS內核中斷優先級(等於configKERNEL_INTERRUPT_PRIORITY)和configMAX_SYSCALL_INTERRUPT_PRIORITY之間的中斷容許全嵌套中斷模式並容許調用API函數。大於configMAX_SYSCALL_INTERRUPT_PRIORITY的中斷優先級毫不會由於執行RTOS內核而延時。
運行在大於configMAX_SYSCALL_INTERRUPT_PRIORITY的優先級中斷是不會被RTOS內核所屏蔽的,所以也不受RTOS內核功能影響。這主要用於很是高的實時需求中。好比執行電機轉向。可是,這類中斷的中斷服務例程中毫不能夠調用FreeRTOS的API函數。
爲了使用這個方案,應用程序要必須符合如下規則:調用FreeRTOS API函數的任何中斷,都必須和RTOS內核處於同一優先級(由宏configKERNEL_INTERRUPT_PRIORITY設置),或者小於等於宏configMAX_SYSCALL_INTERRUPT_PRIORITY定義的優先級。
37.configASSERT
斷言,調試時能夠檢查傳入的參數是否合法。FreeRTOS內核代碼的關鍵點都會調用configASSERT( x )函數,若是參數x爲0,則會拋出一個錯誤。這個錯誤極可能是傳遞給FreeRTOS API函數的無效參數引發的。定義configASSERT()有助於調試時發現錯誤,可是,定義configASSERT()也會增大應用程序代碼量,增大運行時間。推薦在開發階段使用這個斷言宏。
舉一個例子,咱們想把非法參數所在的文件名和代碼行數打印出來,能夠先定義一個函數vAssertCalled,該函數有兩個參數,分別接收觸發configASSERT宏的文件名和該宏所在行,而後經過顯示屏或者串口輸出。代碼以下:
這裏__FILE__和__LINE__是大多數編譯器預約義的宏,分別表示代碼所在的文件名(字符串格式)和行數(整形)。
這個例子雖然看起來很簡單,但因爲要把整形__LINE__轉換成字符串再顯示,在效率和實現上,都不能讓人滿意。咱們能夠使用C標準庫assert的實現方法,這樣函數vAssertCalled只須要接收一個字符串形式的參數(推薦仔細研讀下面的代碼並理解其中的技巧):
這裏稍微講解一下,因爲內置宏__LINE__是整數型的而不是字符串型,把它轉化成字符串須要一個額外的處理層。宏STR和和宏VAL正是用來輔助完成這個轉化。宏STR用來把整形行號替換掉__LINE__,宏VAL用來把這個整形行號字符串化。忽略宏STR和VAL中的任何一個,只能獲得字符串」__LINE__」,這不是咱們想要的。
這裏使用三目運算符’?:’來代替參數判斷if語句,這樣能夠接受任何參數或表達式,代碼也更緊湊,更重要的是代碼優化度更高,由於若是參數恆爲真,在編譯階段就能夠去掉沒必要要的輸出語句。
38.INCLUDE Parameters
以「INCLUDE」起始的宏容許用戶不編譯那些應用程序不須要的實時內核組件(函數),這能夠確保在你的嵌入式系統中RTOS佔用最少的ROM和RAM。
每一個宏以這樣的形式出現:
INCLUDE_FunctionName
在這裏FunctionName表示一個你能夠控制是否編譯的API函數。若是你想使用該函數,就將這個宏設置成1,若是不想使用,就將這個宏設置成0。好比,對於API函數vTaskDelete():
表示但願使用vTaskDelete(),容許編譯器編譯該函數
表示禁止編譯器編譯該函數。
在閱讀本文以前,有兩個定義在FreeRTOSConfig.h中的宏,你必須先明白它們是什麼意思,《FreeRTOS內核配置說明》一文中,講解了這兩個宏:
FreeRTOS與Cortex-M內核可謂是絕配,以致於讓移植和使用FreeRTOS都變得更簡單起來。根據FreeRTOS官方反饋,在Cortex-M內核上使用FreeRTOS大多數的問題點是由不正確的優先級設置引發的。這個問題也是在乎料之中的,由於儘管Cortex-M內核的中斷模式是很是強大的,但對於那些使用傳統中斷優先級架構的工程師來講,Cortex-M內核中斷機制也有點笨拙(或者是說使用比較繁瑣),而且違反直覺(這個主要是由於Cortex-M中斷優先級數值越大表明的優先級反而越小)。本章打算描述Cortex-M的中斷優先級機制,並描述怎樣結合RTOS內核使用。
說明:雖然Cortex-M內核的優先級方案看上去比較複雜,但每個官方發佈的FreeRTOS 接口包(在FreeRTOSV7.2.0\FreeRTOS\Source\portable文件夾中,通常爲port.c)內都會有正確配置的演示例程,能夠以此爲參考。
首先須要清楚有效優先級的總數,這取決於微控制器製造商怎麼使用Cortex內核。因此,並非全部的Cortex-M內核微處理器都具備相同的中斷優先級級別。
Cortex-M構架自身最多容許256級可編程優先級(優先級配置寄存器最多8位,因此優先級範圍從0x00~0xFF),可是絕大多數微控制器製造商只是使用其中的一部分優先級。好比,TI Stellaris Cortex-M3和Cortex-M4微控制器使用優先級配置寄存器的3個位,能提供8級優先級。再好比,NXP LPC17xx Cortex-M3微控制器使用優先級配置寄存器的5個位,能提供32級優先級。
RTOS中斷嵌套方案將有效的中斷優先級分紅兩組:一組能夠經過RTOS臨界區屏蔽,另外一組不受RTOS影響,永遠都是使能的。宏configMAX_SYSCALL_INTERRUPT_PRIORITY在FreeRTOSConfig.h中配置,定義兩組中斷優先級的邊界。邏輯優先級高於此值的中斷不受RTOS影響。最優值取決於微控制器使用的優先級配置寄存器的位數。
有必要先解釋一下優先級值和邏輯優先級:在Cortex-M內核中,假若有8級優先級,咱們說優先級值是0~7,但數值最大的優先級7卻表明着最低的邏輯優先級。不少使用傳統傳統中斷優先級架構的工程師會以爲這樣比較繞,違反直覺。如下內容提到的優先級要仔細區分是優先級數值仍是邏輯優先級。
接下來須要清楚的是,在Cortex-M內核中,一箇中斷的優先級數值越低,邏輯優先級卻越高。好比,中斷優先級爲2的中斷能夠搶佔中斷優先級爲5的中斷,但反過來就不行。換句話說,中斷優先級2比中斷優先級5的優先級更高。
這是Cortex-M內核最容易讓人犯錯之處,由於大多數的非Cortex-M內核微控制器的中斷優先級表述是與之相反的。
以「FromISR」結尾的FreeRTOS函數是具備中斷調用保護的(執行這些函數會進入臨界區),可是就算是這些函數,也不能夠被邏輯優先級高於configMAX_SYSCALL_INTERRUPT_PRIORITY的中斷服務函數調用。(宏configMAX_SYSCALL_INTERRUPT_PRIORITY定義在頭文件FreeRTOSConfig.h中)。所以,任何使用RTOSAPI函數的中斷服務例程的中斷優先級數值大於等於configMAX_SYSCALL_INTERRUPT_PRIORITY宏的值。這樣就能保證中斷的邏輯優先級等於或低於configMAX_SYSCALL_INTERRUPT_PRIORITY。
Cortex中斷默認狀況下有一個數值爲0的優先級。大多數狀況下0表明最高級優先級。所以,絕對不能夠在優先級爲0的中斷服務例程中調用RTOSAPI函數。
Cortex-M內核的中斷優先級寄存器是以最高位(MSB)對齊的。好比,若是使用了3位來表達優先級,則這3個位位於中斷優先級寄存器的bit五、bit六、bit7位。剩餘的bit0~bit4能夠設置成任何值,但爲了兼容,最好將他們設置成1.
Cortex-M優先級寄存器最多有8位,若是一個微控制器只使用了其中的3位,那麼這3位是以最高位對齊的,見下圖:
某微控制器只使用了優先級寄存器中的3位,下圖展現了優先級數值5(二進制101B)是怎樣在優先級寄存器中存儲的。若是優先級寄存器中未使用的位置1,下圖也展現了爲何數值5(二進制0000 0101B)能夠當作數值191(二進制1011 1111)的。
某微控制器只使用了優先級寄存器中的4位,下圖展現了優先級數值5(二進制101B)是怎樣在優先級寄存器中存儲的。若是優先級寄存器中未使用的位置1,下圖也展現了爲何數值5(二進制0000 0101B)能夠當作數值95(二進制0101 1111)的。
上文中已經描述,那些在中斷服務例程中調用RTOS API函數的中斷邏輯優先級必須低於或等於configMAX_SYSCALL_INTERRUPT_PRIORITY(低邏輯優先級意味着高優先級數值)。
CMSIS以及不一樣的微控制器供應商提供了能夠設置某個中斷優先級的庫函數。一些庫函數的參數使用最低位對齊,另外一些庫函數的參數可能使用最高位對齊,因此,使用時應該查閱庫函數的應用手冊進行正確設置。
能夠在FreeRTOSConfig.h中設置宏configMAX_SYSCALL_INTERRUPT_PRIORITY和configKERNEL_INTERRUPT_PRIORITY的值。這兩個宏須要根據Cortex-M內核自身的狀況進行設置,要以最高有效位對齊。好比某微控制器使用中斷優先級寄存器中的3位,設置configKERNEL_INTERRUPT_PRIORITY的值爲5,則代碼爲:
宏configKERNEL_INTERRUPT_PRIORITY指定RTOS內核使用的中斷優先級,由於RTOS內核不能夠搶佔用戶任務,所以這個宏通常設置爲硬件支持的最小優先級。對於Cortex-M硬件,RTOS使用到硬件的PendSV和SysTick硬件中斷,在函數xPortStartScheduler()中(該函數在port.c中,由啓動調度器函數vTaskStartScheduler()調用),將PendSV和SysTick硬件中斷優先級寄存器設置爲宏configKERNEL_INTERRUPT_PRIORITY指定的值。
有關代碼以下(位於port.c):
RTOS內核使用Cortex-M內核的BASEPRI寄存器來實現臨界區(注:BASEPRI爲優先級屏蔽寄存器,優先級數值大於或等於該寄存器的中斷都會被屏蔽,優先級數值越大,邏輯優先級越低,可是爲零時不屏蔽任何中斷)。這容許RTOS內核能夠只屏蔽一部分中斷,所以能夠提供一個靈活的中斷嵌套模式。
那些須要在中斷調用時保護的API函數,FreeRTOS使用寄存器BASEPRI實現中斷保護臨界區。當進入臨界區時,將寄存器BASEPRI的值設置成configMAX_SYSCALL_INTERRUPT_PRIORITY,當退出臨界區時,將寄存器BASEPRI的值設置成0。不少Bug反饋都提到,當退出臨界區時不該該將寄存器設置成0,應該恢復它以前的狀態(以前的狀態不必定是0)。可是Cortex-M NVIC決不會容許一個低優先級中斷搶佔當前正在執行的高優先級中斷,無論BASEPRI寄存器中是什麼值。與進入臨界區前先保存BASEPRI的值,退出臨界區再恢復的方法相比,退出臨界區時將BASEPRI寄存器設置成0的方法能夠得到更快的執行速度。
RTOS內核經過寫configMAX_SYSCALL_INTERRUPT_PRIORITY的值到BASEPRI寄存器的方法建立臨界區。中斷優先級0(具備最高的邏輯優先級)不能被BASEPRI寄存器屏蔽,所以,configMAX_SYSCALL_INTERRUPT_PRIORITY毫不能夠設置成0。
本文介紹內存管理的基礎知識,詳細源碼分析見《 FreeRTOS高級篇7---FreeRTOS內存管理分析》
FreeRTOS提供了幾個內存堆管理方案,有複雜的也有簡單的。其中最簡單的管理策略也能知足不少應用的要求,好比對安全要求高的應用,這些應用根本不容許動態內存分配的。
FreeRTOS也容許你本身實現內存堆管理,甚至容許你同時使用兩種內存堆管理方案。同時實現兩種內存堆容許任務堆棧和其它RTOS對象放置到快速的內部RAM,應用數據放置到低速的外部RAM。
每當建立任務、隊列、互斥量、軟件定時器、信號量或事件組時,RTOS內核會爲它們分配RAM。標準函數庫中的malloc()和free()函數有些時候可以用於完成這個任務,可是:
所以,提供一個替代的內存分配方案一般是必要的。
嵌入式/實時系統具備千差萬別的RAM和時間要求,所以一個RAM內存分配算法可能僅屬於一個應用的子集。
爲了不這個問題,FreeRTOS在移植層保留內存分配API函數。移植層在RTOS核心代碼源文件以外(不屬於核心源代碼),這使得不一樣的應用程序能夠提供適合本身的應用實現。當RTOS內核須要RAM時,調用pvPortMallo()函數來代替malloc()函數。當RAM要被釋放時,調用vPortFree()函數來代替free()函數。
FreeRTOS下載包中提供5種簡單的內存分配實現,本文稍後會進行描述。用戶能夠適當的選擇其中的一個,也能夠本身設計內存分配策略。
FreeRTOS提供的內存分配方案分別位於不一樣的源文件(heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c)之中,源文件位於下載包\FreeRTOS\Source\portable\MemMang文件夾中。其它實現方法能夠根據須要增長。若是要使用FreeRTOS提供的內存堆分配方案,選中的源文件必須被正確的包含到工程文件中。
這是全部實現中最簡單的一個。一旦分配內存以後,它甚至不容許釋放分配的內存。儘管這樣,heap_1.c仍是適用於大部分嵌入式應用程序。這是由於大多數深度嵌入式(deeplyembedded)應用只是在系統啓動時建立全部任務、隊列、信號量等,而且直到程序結束都會一直使用它們,永遠不須要刪除。
當須要分配RAM時,這個內存分配方案只是簡單的將一個大數組細分出一個子集來。大數組的容量大小經過FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏來設置。
API函數xPortGetFreeHeapSize()返回未分配的堆棧空間總大小,能夠經過這個函數返回值對configTOTAL_HEAP_SIZE進行合理的設置。
heap_1功能簡介:
和方案1不一樣,這個方案使用一個最佳匹配算法,它容許釋放以前分配的內存塊。它不會把相鄰的空閒塊合成一個更大的塊(換句話說,這會形成內存碎片)。
有效的堆棧空間大小由位於FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏來定義。
API函數xPortGetFreeHeapSize()返回剩下的未分配堆棧空間的大小(可用於優化設置configTOTAL_HEAP_SIZE宏的值),可是不能提供未分配內存的碎片細節信息。
heap_2功能簡介:
heap_2.c適用於須要動態建立任務的大多數小型實時系統(smallreal time)。
heap_3.c簡單的包裝了標準庫中的malloc()和free()函數,包裝後的malloc()和free()函數具有線程保護。
heap_3.c功能簡介:
注:使用heap_3時,FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏定義沒有做用。
這個方案使用一個最佳匹配算法,但不像方案2那樣。它會將相鄰的空閒內存塊合併成一個更大的塊(包含一個合併算法)。
有效的堆棧空間大小由位於FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE來定義。
API函數xPortGetFreeHeapSize()返回剩下的未分配堆棧空間的大小(可用於優化設置configTOTAL_HEAP_SIZE宏的值),可是不能提供未分配內存的碎片細節信息。
heap_4.c功能簡介:
heap_4.c還特別適用於移植層代碼,能夠直接使用pvPortMalloc()和 vPortFree()函數來分配和釋放內存。
這個方案一樣實現了heap_4.c中的合併算法,而且容許堆棧跨越多個非連續的內存區。
Heap_5經過調用vPortDefineHeapRegions()函數實現初始化,在該函數執行完成前不容許使用內存分配和釋放。建立RTOS對象(任務、隊列、信號量等等)會隱含的調用pvPortMalloc(),所以必須注意:使用heap_5建立任何對象前,要先執行vPortDefineHeapRegions()函數。
vPortDefineHeapRegions()函數只須要單個參數。該參數是一個HeapRegion_t結構體類型數組。HeapRegion_t在portable.h中定義,以下所示:
這個數組必須使用一個NULL指針和0字節元素做爲結束,起始地址必須從小到大排列。下面的代碼段提供一個例子。MSVCWin32模擬器演示例程使用了heap_5,所以能夠當作一個參考例程。
應用程序能夠使用任務也能夠使用協程,或者二者混合使用,可是任務和協程使用不一樣的API函數,所以在任務和協程之間不能使用同一個隊列或信號量傳遞數據。
一般狀況下,協程僅用在資源很是少的微處理器中,特別是RAM很是稀缺的狀況下。目前協程不多被使用到,所以對於協程FreeRTOS做者既沒有把它刪除也沒有進一步開發。
因此本系列文章之後不會對協程過多描述,包括其API函數。
簡而言之:使用RTOS的實時應用程序可認爲是一系列獨立任務的集合。每一個任務在本身的環境中運行,不依賴於系統中的其它任務或者RTOS調度器。在任什麼時候刻,只有一個任務獲得運行,RTOS調度器決定運行哪一個任務。調度器會不斷的啓動、中止每個任務,宏觀看上去就像整個應用程序都在執行。做爲任務,不須要對調度器的活動有所瞭解,在任務切入切出時保存上下文環境(寄存器值、堆棧內容)是調度器主要的職責。爲了實現這點,每一個任務都須要有本身的堆棧。當任務切出時,它的執行環境會被保存在該任務的堆棧中,這樣當再次運行時,就能從堆棧中正確的恢復上次的運行環境。
一個任務可爲下面中的一個:
每一個任務都要被指定一個優先級,從0~configMAX_PRIORITIES,configMAX_PRIORITIES定義在FreeRTOSConfig.h中。
若是某架構硬件支持CLZ(或相似)指令(計算前導零的數目,Cortex-M3是支持該指令的,從ARMv6T2才支持這個指令),而且打算在移植層使用這個特性來優化任務調度機制,須要有一些步驟,首先將FreeRTOSConfig.h中configUSE_PORT_OPTIMISED_TASK_SELECTION設置爲1,而且最大優先級數目configMAX_PRIORITIES不能大於32。除此以外,configMAX_PRIORITIES能夠設置爲任意值,可是考慮到configMAX_PRIORITIES設置越大,RAM消耗也越大,通常設置爲知足使用的最小值。
低優先級數值表明低優先級。空閒任務(idle task)的優先級爲0(tskIDLE_PRIORITY)。
FreeRTOS調度器確保處於最高優先級的就緒或運行態任務獲取處理器,換句話說,處於運行狀態的任務,只有其中的最高優先級任務纔會運行。
任何數量的任務能夠共享同一個優先級。若是宏configUSE_TIME_SLICING未定義或着宏configUSE_TIME_SLICING定義爲1,處於就緒態的多個相同優先級任務將會以時間片切換的方式共享處理器。
一個任務具備如下結構:
任務函數返回爲void,參數只有一個void類型指針。全部的任務函數都應該是這樣。void類型指針能夠向任務傳遞任意類型信息。
任務函數決不該該返回,所以一般任務函數都是一個死循環。
任務由xTaskCreate()函數建立,由vTaskDelete()函數刪除。
空閒任務是啓動RTOS調度器時由內核自動建立的任務,這樣能夠確保至少有一個任務在運行。空閒任務具備最低任務優先級,這樣若是有其它更高優先級的任務進入就緒態就能夠馬上讓出CPU。
刪除任務後,空閒任務用來釋放RTOS分配給被刪除任務的內存。所以,在應用中使用vTaskDelete()函數後確保空閒任務能得到處理器時間就很重要了。除此以外,空閒任務沒有其它有效功能,因此能夠被合理的剝奪處理器時間,而且它的優先級也是最低的。
應用程序任務共享空閒任務優先級(tskIDLE_PRIORITY)也是可能的。這種狀況如何配置能夠參考configIDLE_SHOULE_YIELD配置參數類獲取更多信息。
空閒任務鉤子是一個函數,每個空閒任務週期被調用一次。若是你想將任務程序功能運行在空閒優先級上,能夠有兩種選擇:
建立一個空閒鉤子步驟以下:
一般,使用這個空閒鉤子函數設置CPU進入低功耗模式。
在FreeRTOS移植到Cortex-M3硬件平臺的文章中,咱們已經見過任務建立API,但那篇文章的重點在於如何移植FreeRTOS,本文將重點放在任務的建立和刪除API函數上面。
任務建立和刪除API函數位於文件task.c中,須要包含task.h頭文件。
建立新的任務並加入任務就緒列表。
若是使用FreeRTOS-MPU(在官方下載包中,爲Cortex-M3內核寫了兩個移植方案,一個是普通的FreeRTOS移植層,還有一個是FreeRTOS-MPU移植層。後者包含完整的內存保護),那麼推薦使用函數xTaskCreateRestricted()來代替xTaskCreate()。在使用FreeRTOS-MPU的狀況下,使用xTaskCreate()函數能夠建立運行在特權模式或用戶模式(見下面對函數參數uxPriority的描述)的任務。當運行在特權模式下,任務能夠訪問整個內存映射;當處於用戶模式下,任務僅能訪問本身的堆棧。不管在何種模式下,MPU都不會自動捕獲堆棧溢出,所以標準的FreeRTOS堆棧溢出檢測機制仍然會被用到。xTaskCreateRestricted()函數具備更大的靈活性。
若是任務成功建立並加入就緒列表函數返回pdPASS,不然函數返回錯誤碼,具體參見projdefs.h。
voidvTaskDelete( TaskHandle_t xTask );
從RTOS內核管理器中刪除一個任務。任務刪除後將會從就緒、阻塞、暫停和事件列表中移除。在文件FreeRTOSConfig.h中,必須定義宏INCLUDE_vTaskDelete 爲1,本函數纔有效。
注:被刪除的任務,其在任務建立時由內核分配的存儲空間,會由空閒任務釋放。若是有應用程序調用xTaskDelete(),必須保證空閒任務獲取必定的微控制器處理時間。任務代碼本身分配的內存是不會自動釋放的,所以刪除任務前,應該將這些內存釋放。
xTask:被刪除任務的句柄。爲NULL表示刪除當前任務。
void vTaskDelay( portTickTypexTicksToDelay )
調用vTaskDelay()函數後,任務會進入阻塞狀態,持續時間由vTaskDelay()函數的參數xTicksToDelay指定,單位是系統節拍時鐘週期。常量portTICK_RATE_MS 用來輔助計算真實時間,此值是系統節拍時鐘中斷的週期,單位是毫秒。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelay 必須設置成1,此函數纔能有效。
vTaskDelay()指定的延時時間是從調用vTaskDelay()後開始計算的相對時間。好比vTaskDelay(100),那麼從調用vTaskDelay()後,任務進入阻塞狀態,通過100個系統時鐘節拍週期,任務解除阻塞。所以,vTaskDelay()並不適用與週期性執行任務的場合。此外,其它任務和中斷活動,會影響到vTaskDelay()的調用(好比調用前高優先級任務搶佔了當前任務),所以會影響任務下一次執行的時間。API函數vTaskDelayUntil()可用於固定頻率的延時,它用來延時一個絕對時間。
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_txTimeIncrement );
任務延時一個指定的時間。週期性任務能夠使用此函數,以確保一個恆定的頻率執行。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelayUntil 必須設置成1,此函數纔有效。
這個函數不一樣於vTaskDelay()函數的一個重要之處在於:vTaskDelay()指定的延時時間是從調用vTaskDelay()以後(執行完該函數)開始算起的,可是vTaskDelayUntil()指定的延時時間是一個絕對時間。
調用vTaskDelay()函數後,任務會進入阻塞狀態,持續時間由vTaskDelay()函數的參數指定,單位是系統節拍時鐘週期。所以vTaskDelay()並不適用於週期性執行任務的場合。由於調用vTaskDelay()到任務解除阻塞的時間不老是固定的而且該任務下一次調用vTaskDelay()函數的時間也不老是固定的(兩次執行同一任務的時間間隔自己就不固定,中斷或高優先級任務搶佔也可能會改變每一次執行時間)。
vTaskDelay()指定一個從調用vTaskDelay()函數後開始計時,到任務解除阻塞爲止的相對時間,而vTaskDelayUntil()指定一個絕對時間,每當時間到達,則解除任務阻塞。
應當指出的是,若是指定的喚醒時間已經達到,vTaskDelayUntil()馬上返回(不會有阻塞)。所以,使用vTaskDelayUntil()週期性執行的任務,不管任何緣由(好比,任務臨時進入掛起狀態)中止了週期性執行,使得任務少運行了一個或多個執行週期,那麼須要從新計算所須要的喚醒時間。這能夠經過傳遞給函數的指針參數pxPreviousWake指向的值與當前系統時鐘計數值比較來檢測,在大多數狀況下,這並非必須的。
常量portTICK_RATE_MS 用來輔助計算真實時間,此值是系統節拍時鐘中斷的週期,單位是毫秒。
當調用vTaskSuspendAll()函數掛起RTOS調度器時,不能夠使用此函數。
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask );
獲取指定任務的優先級。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskPriorityGet必須設置成1,此函數纔有效。
返回指定任務的優先級。
void vTaskPrioritySet( TaskHandle_txTask,
UBaseType_tuxNewPriority );
設置指定任務的優先級。若是設置的優先級高於當前運行的任務,在函數返回前會進行一次上下文切換。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskPrioritySet 必須設置成1,此函數纔有效。
void vTaskSuspend( TaskHandle_txTaskToSuspend );
掛起指定任務。被掛起的任務毫不會獲得處理器時間,無論該任務具備什麼優先級。
調用vTaskSuspend函數是不會累計的:即便屢次調用vTaskSuspend ()函數將一個任務掛起,也只需調用一次vTaskResume ()函數就能使掛起的任務解除掛起狀態。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskSuspend必須設置成1,此函數纔有效。
void vTaskResume( TaskHandle_txTaskToResume );
恢復掛起的任務。
經過調用一次或屢次vTaskSuspend()掛起的任務,能夠調用一次vTaskResume ()函數來再次恢復運行。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskSuspend必須置1,此函數纔有效。
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume );
用於恢復一個掛起的任務,用在ISR中。
經過調用一次或屢次vTaskSuspend()函數而掛起的任務,只需調用一次xTaskResumeFromISR()函數便可恢復運行。
xTaskResumeFromISR()不可用於任務和中斷間的同步,若是中斷恰巧在任務被掛起以前到達,這就會致使一次中斷丟失(任務尚未掛起,調用xTaskResumeFromISR()函數是沒有意義的,只能等下一次中斷)。這種狀況下,能夠使用信號量做爲同步機制。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 必須設置成1,此函數纔有效。
若是恢復任務後須要上下文切換返回pdTRUE,不然返回pdFALSE。由ISR肯定是否須要上下文切換。
該函數向TaskStatus_t結構體填充相關信息,系統中每個任務的信息均可以填充到TaskStatus_t結構體數組中,數組大小由uxArraySize指定。結構體TaskStatus_t定義以下:
注意,這個函數僅用來調試用,調用此函數會掛起全部任務,直到函數最後才恢復掛起的任務,所以任務可能被掛起很長時間。在文件FreeRTOSConfig.h中,宏configUSE_TRACE_FACILITY必須設置爲1,此函數纔有效。
被填充的TaskStatus_t結構體數量。這個值應該等於經過調用API函數uxTaskGetNumberOfTasks()返回的值,但若是傳遞給uxArraySize參數的值過小,則返回0。
TaskHandle_t xTaskGetCurrentTaskHandle(void );
在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetCurrentTaskHandle必須設置爲1,此函數纔有效。
返回當前任務(調用該函數的任務)的句柄。
TaskHandle_t xTaskGetIdleTaskHandle(void );
在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetIdleTaskHandle必須設置爲1,此函數纔有效。
返回空閒任務句柄。空閒任務在RTOS調度器啓動時自動建立。
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
任務的堆棧空間會隨着任務執行以及中斷處理而增加或縮小。該函數能夠返回任務啓動後的最小剩餘堆棧空間。換句話說,能夠間接估算出一個任務最多須要多少堆棧空間。在文件FreeRTOSConfig.h中,宏INCLUDE_uxTaskGetStackHighWaterMark 必須設置成1,此函數纔有效。
返回最小剩餘堆棧空間,以字爲單位。好比一個32爲架構處理器,返回值爲1表示有4字節堆棧空間沒有使用過。若是返回值爲0,則任務極可能已經發生了堆棧溢出。
eTaskState eTaskGetState( TaskHandle_txTask );
返回一個枚舉類型的任務狀態值。在文件FreeRTOSConfig.h中,宏INCLUDE_eTaskGetState必須設置爲1,此函數纔有效。
下表列出返回值和對應的任務狀態。
char * pcTaskGetTaskName( TaskHandle_txTaskToQuery );
獲取任務的描述內容,在文件FreeRTOSConfig.h中,宏INCLUDE_pcTaskGetTaskName必須設置成1,此函數纔有效。
一個指針,指向任務描述字符串。
volatile TickType_t xTaskGetTickCount(void );
這個函數不能在ISR中調用。在ISR中用xTaskGetTickCountFromISR(),原型爲volatileTickType_t xTaskGetTickCountFromISR( void )。
返回從vTaskStartScheduler函數調用後的系統時鐘節拍次數。
BaseType_t xTaskGetSchedulerState( void);
獲取調度器當前狀態。在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetSchedulerState或configUSE_TIMERS必須定義爲1,此函數纔有效。
返回值是如下常量之一(定義在task.h):taskSCHEDULER_NOT_STARTED(未啓動)、taskSCHEDULER_RUNNING(正常運行)、taskSCHEDULER_SUSPENDED(掛起)。
UBaseType_t uxTaskGetNumberOfTasks(void );
獲取RTOS內核當前管理的任務總數。包含全部就緒、阻塞和掛起狀態的任務。對於一個刪除的任務,若是它的堆棧空間尚未被空閒任務釋放掉,則這個被刪除的任務也含在計數值中。
返回RTOS內核當前管理的任務總數。
void vTaskList( char *pcWriteBuffer );
將每一個任務的狀態、堆棧使用狀況等以字符的形式保存到參數pcWriteBuffer指向的區域。vTaskList()函數調用usTaskGetSystemState()函數,而後將獲得的信息格式化爲程序員易讀的字符形式。輸出的內容例子以下圖所示,圖中State一欄中,B表示阻塞、R表示就緒、D表示刪除(等待清除內存)、S表示掛起或阻塞。
注意,調用這個函數會掛起全部任務,這一過程可能持續較長時間,所以本函數僅在調試時使用。在文件FreeRTOSConfig.h中,宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS必須定義爲1,此函數纔有效。
void vTaskGetRunTimeStats( char*pcWriteBuffer );
這個函數用於統計每一個任務的運行時間。要使用這個函數必須知足一些條件,那就是必須有一個用於時間統計的定時器或計數器,這個定時器或計數器的精度要至少大於10倍的系統節拍週期。這個定時器或計數器的配置以及獲取定時時間是由兩個宏定義實現的,這兩個宏通常在文件FreeRTOSConfig.h中定義。配置定時器或計數器的宏爲portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),獲取定時時間的宏爲portGET_RUN_TIME_COUNTER_VALUE。實現了這兩個宏定義後,還必須在文件FreeRTOSConfig.h中將宏configGENERATE_RUN_TIME_STATS和configUSE_STATS_FORMATTING_FUNCTIONS設置爲1,此API函數纔有效。
這個API函數調用usTaskGetSystemState()函數獲取每一個任務的狀態信息,並把其中的運行時間格式化爲程序員易讀的字符形式,並將這些信息保存到參數pcWriteBuffer指向的區域。
注意,調用這個函數會掛起全部任務,這一過程可能持續較長時間,所以本函數僅在調試時使用。
以lpc17xx系列爲控制爲例,咱們使用定時器0來做爲統計基準時鐘。
在文件FreeRTOSConfig.h中,設置宏configGENERATE_RUN_TIME_STATS和configUSE_STATS_FORMATTING_FUNCTIONS爲1,
在文件FreeRTOSConfig.h中,定義下列代碼:
能夠給每一個任務分配一個標籤值。這個值通常用於應用程序,RTOS內核不會使用。在文件FreeRTOSConfig.h中,宏configUSE_APPLICATION_TASK_TAG必須設置爲1,此函數纔有效。
注:TaskHookFunction_t原型定義:typedef BaseType_t (*TaskHookFunction_t)(void * )
TaskHookFunction_txTaskGetApplicationTaskTag( TaskHandle_t xTask );
返回分配給任務的標籤值。程序員定義標籤值,RTOS內核一般不會訪問標籤值。
函數僅對高級用戶使用。在文件FreeRTOSConfig.h中,宏configUSE_APPLICATION_TASK_TAG必須設置爲1,此函數纔有效。
返回指定任務的標籤值。
能夠爲每一個任務分配一個標籤值,當這個值是一個TaskHookFunction_t類型函數指針時,至關於應用程序向任務註冊了一個回調函數,而API函數xTaskCallApplicationTaskHook用來調用這個回調函數。
通常這個函數配合RTOS跟蹤鉤子宏使用,見12.設置任務標籤值一節的用法舉例。
此函數僅用於高級用戶。
線程本地存儲容許應用程序在任務的控制塊中存儲一些值,每一個任務都有本身獨立的儲存空間。
好比,許多庫函數都包含一個叫作errno的全局變量。某些庫函數使用errno返回庫函數錯誤信息,應用程序檢查這個全局變量來肯定發生了那些錯誤。在單線程程序中,將errno定義成全局變量是能夠的,可是在多線程應用中,每一個線程(任務)必須具備本身獨有的errno值,不然,一個任務可能會讀取到另外一個任務的errno值。
FreeRTOS提供了一個靈活的機制,使得應用程序能夠使用線程本地存儲指針來讀寫線程本地存儲。在文件FreeRTOSConfig.h中,宏configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每一個任務線程本地存儲指針數組的大小。API函數vTaskSetThreadLocalStoragePointer()用於向指針數組中寫入值,API函數pvTaskGetThreadLocalStoragePointer()用於從指針數組中讀取值。
參見16.獲取線程本地存儲指針一節。
此函數僅用於高級用戶。從線程本地存儲指針數組中讀取值。更詳細描述見15.設置線程本地存儲指針一節。
返回一個指針,這個指針存儲在線程本地存儲指針數組中,數組索引由參數xIndex指定。
void vTaskSetTimeOutState( TimeOut_t *const pxTimeOut );
此函數僅用於高級用戶,一般與API函數xTaskCheckForTimeOut()共同使用。
任務由於等待某事件而進入阻塞狀態,一般狀況下任務會設置一個等待超時週期。若是在等待事件超時,任務會退出阻塞狀態。想象一個這樣的應用,某任務等待一個事件而進入阻塞狀態,可是事件遲遲不發生,超時後任務退出阻塞狀態繼續執行任務。假如任務等待的事件仍然沒有發生,則任務又會阻塞在該事件下。只要任務等待的事件一直不發生,這個任務進入阻塞而後超時退出阻塞,再進入阻塞的循環就會一直存在。是否是能夠設定一個總超時時間,只要總阻塞時間大於這個總超時時間,則能夠結束這個任務或進行相應記錄?freeRTOS提供了兩個API函數來完成這個功能,這就是vTaskSetTimeOutState()和xTaskCheckForTimeOut()。
vTaskSetTimeOutState()函數用於設置初始條件,以後調用xTaskCheckForTimeOut()函數檢查任務總阻塞時間是否超過總超時時間,若是沒有超過,則調整剩餘的超時時間計數器。
參見18.超時檢測。
此函數僅用於高級用戶,一般與API函數vTaskSetTimeOutState共同使用。
詳細描述見17.設置超時狀態。
taskYIELD:用於強制上下文切換的宏。在中斷服務程序中的等價版本爲portYIELD_FROM_ISR,這也是個宏,其實現取決於移植層。
用於上下文切換的實際代碼由移植層提供。對於Cortex-M3硬件,這個宏會引發PendSV中斷。
taskENTER_CRITICAL:用於進入臨界區的宏。在臨界區中不會發生上下文切換。
進入臨界區的實際代碼由移植層提供,對於Cortex-M3硬件,先禁止全部RTOS可屏蔽中斷,這能夠經過向basepri 寄存器寫入configMAX_SYSCALL_INTERRUPT_PRIORITY來實現。basepri寄存器被設置成某個值後,全部優先級號大於等於此值的中斷都被禁止,但若被設置爲0,則不關閉任何中斷,0爲默認值。而後臨界區嵌套計數器增1。
退出臨界區的實際代碼有移植層提供,對於Cortex-M3硬件,先將臨界區嵌套計數器減1,若是臨界區計數器爲零,則使能全部RTOS可屏蔽中斷,這能夠經過向basepri 寄存器寫入0來實現。
taskDISABLE_INTERRUPTS:禁止全部RTOS可屏蔽中斷。在調用宏taskENTER_CRITICAL進入臨界區時,也會間接調用該宏禁止全部RTOS可屏蔽中斷。
taskENABLE_INTERRUPTS:使能全部RTOS可屏蔽中斷。在調用宏taskEXIT_CRITICAL退出臨界區時,也會間接調用該宏使能全部RTOS可屏蔽中斷。
void vTaskStartScheduler( void );
啓動RTOS調度器,以後RTOS內核控制哪一個任務執行以及什麼時候執行。
當調用vTaskStartScheduler()後,空閒任務被自動建立。若是configUSE_TIMERS被設置爲1,定時器後臺任務也會被建立。
若是vTaskStartScheduler()成功執行,則該函數不會返回,直到有任務調用了vTaskEndScheduler()。若是由於RAM不足而沒法建立空閒任務,該函數也可能執行失敗,並會馬上返回調用處。
void vTaskEndScheduler( void );
僅用於x86硬件架構中。
中止RTOS內核系統節拍時鐘。全部建立的任務自動刪除並中止多任務調度。
void vTaskSuspendAll( void );
掛起調度器,但不由止中斷。當調度器掛起時,不會進行上下文切換。調度器掛起後,正在執行的任務會一直繼續執行,內核再也不調度(意味着當前任務不會被切換出去),直到該任務調用了xTaskResumeAll ()函數。
內核調度器掛起期間,那些能夠引發上下文切換的API函數(如vTaskDelayUntil()、xQueueSend()等)決不可以使用。
BaseType_t xTaskResumeAll( void );
恢復因調用vTaskSuspendAll()函數而掛起的實時內核調度器。xTaskResumeAll()僅恢復調度器,它不會恢復那些被vTaskSuspend()函數掛起的任務。
返回pdTRUE 表示恢復調度器引發了一次上下文切換,不然,返回pdFALSE。
void vTaskStepTick( TickType_txTicksToJump );
若是RTOS使能tickless空閒功能,每當只有空閒任務被執行時,系統節拍時鐘中斷將會中止,微控制器進入低功耗模式。當微控制器退出低功耗後,系統節拍計數器必須被調整,將進入低功耗的時間彌補上。
若是FreeRTOS移植文件中定義了宏portSUPPRESS_TICKS_AND_SLEEP()實體,則函數vTaskStepTick用於在這個宏portSUPPRESS_TICKS_AND_SLEEP()實體內部調整系統節拍計數器。函數vTaskStepTick是一個全局函數,因此也能夠在宏portSUPPRESS_TICKS_AND_SLEEP()實體中重寫該函數。
在文件FreeRTOSConfig.h中,宏configUSE_TICKLESS_IDLE必須設置爲1,此函數纔有效。
注:本文介紹任務通知的基礎知識,詳細源碼分析見《FreeRTOS高級篇8---FreeRTOS任務通知分析》
每一個RTOS任務都有一個32位的通知值,任務建立時,這個值被初始化爲0。RTOS任務通知至關於直接向任務發送一個事件,接收到通知的任務能夠解除阻塞狀態,前提是這個阻塞事件是因等待通知而引發的。發送通知的同時,也能夠可選的改變接收任務的通知值。
能夠經過下列方法向接收任務更新通知:
相對於用前必須分別建立隊列、二進制信號量、計數信號量或事件組的狀況,使用任務通知顯然更靈活。更好的是,相比於使用信號量解除任務阻塞,使用任務通知能夠快45%、使用更少的RAM(使用GCC編譯器,-o2優化級別)。
使用API函數xTaskNotify()和xTaskNotifyGive()(中斷保護等價函數爲xTaskNotifyFromISR()和vTaskNotifyGiveFromISR())發送通知,在接收RTOS任務調用API函數xTaskNotifyWait()或ulTaskNotifyTake()以前,這個通知都被保持着。若是接收RTOS任務已經由於等待通知而進入阻塞狀態,則接收到通知後任務解除阻塞並清除通知。
RTOS任務通知功能默認是使能的,能夠經過在文件FreeRTOSConfig.h中設置宏configUSE_TASK_NOTIFICATIONS爲0來禁止這個功能,禁止後每一個任務節省8字節內存。
雖然RTOS任務通知速度更快而且佔用內存更少,但它也有一些限制:
向指定任務發送指定的通知值。若是打算使用RTOS任務通知實現輕量級的二進制或計數信號量,推薦使用API函數xTaskNotifyGive()來代替本函數。
此函數不能夠在中斷服務例程中調用,中斷保護等價函數爲xTaskNotifyFromISR()。
枚舉變量成員以及做用以下表所示。
參數eAction爲eSetValueWithoutOverwrite時,若是被通知任務還沒取走上一個通知,又接收到了一個通知,則此次通知值未能更新並返回pdFALSE,不然返回pdPASS。
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify );
其實這是一個宏,本質上至關於xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )。能夠使用該API函數代替二進制或計數信號量,但速度更快。在這種狀況下,應該使用API函數ulTaskNotifyTake()來等待通知,而不該該使用API函數xTaskNotifyWait()。
此函數不能夠在中斷服務例程中調用,中斷保護等價函數爲vTaskNotifyGiveFromISR()。
ulTaskNotifyTake()是專門爲使用更輕量級更快的方法來代替二進制或計數信號量而量身打造的。FreeRTOS獲取信號量的API函數爲xSemaphoreTake(),能夠使用ulTaskNotifyTake()函數等價代替。
當一個任務使用通知值來實現二進制或計數信號量時,其它任務或者中斷要使用API函數xTaskNotifyGive()或者使用參數eAction爲eIncrement的API函數xTaskNotify()。推薦使用xTaskNotifyGive()函數(實際上是個宏,咱們這裏把它看做一個API函數)。另外須要注意的是,若是在中斷中使用,要使用它們的中斷保護等價函數:vTaskNotifyGiveFromISR()和xTaskNotifyFromISR()。
API函數xTaskNotifyTake()有兩種方法處理任務的通知值,一種方法是在函數退出時將通知值清零,這種方法適用於實現二進制信號量;另一種方法是在函數退出時將通知值減1,這種方法適用於實現計數信號量。
若是RTOS任務的通知值爲0,使用xTaskNotifyTake()能夠可選的使任務進入阻塞狀態,直到該任務的通知值不爲0。進入阻塞的任務不消耗CPU時間。
返回任務的當前通知值,爲0或者爲調用API函數xTaskNotifyTake()以前的通知值減1。
若是打算使用RTOS任務通知實現輕量級的二進制或計數信號量,推薦使用API函數ulTaskNotifyTake()來代替本函數。
若是接收到通知,返回pdTRUE,若是API函數xTaskNotifyWait()等待超時,返回pdFALSE。
此函數與任務通知API函數xTaskNotify()很是像,只不過此函數具備一個附加參數,用來回傳任務當前的通知值,而後根據參數ulValue和eAction更新任務的通知值。
此函數不能在中斷服務例程中使用,在中斷服務例程中使用xTaskNotifyAndQueryFromISR()函數。
參數eAction爲eSetValueWithoutOverwrite時,若是被通知任務還沒取走上一個通知,又接收到了一個通知,則此次通知值未能更新並返回pdFALSE,不然返回pdPASS。
雖然這是介紹FreeRTOS系列的文章,但這篇文章偏重於命令行解釋器的實現。這一方面是由於任務通知使用起來很是簡單,另外一方面也由於對於嵌入式程序來講,使用命令行解釋器來輔助程序調試是很是有用的。程序調試是一門技術,基本上咱們須要兩種調試手段,一種是能夠單步仿真的硬件調試器,另一種是能夠長期監視程序狀態的狀態輸出,能夠經過串口、顯示屏等等手段輸出異常信息或者某些關鍵點。這裏的命令行解釋器就屬於後者。
本文實現的命令行解釋器具備如下特性:
一個帶參數的命令格式以下所示:
參數名 <參數1> <參數2> … <參數3>[回車換行符]
FreeRTOS的編碼標準及風格見《FreeRTOS系列第4篇---FreeRTOS編碼標準及風格指南》,但我本身的編碼風格跟FreeRTOS並不相同,而且我也不打算改變我當前堅持使用的編碼風格。因此在這篇或者之後的文章中可能會在一個程序中看到兩種不一樣的編碼風格,對於涉及FreeRTOS的代碼,我儘量使用FreeRTOS建議的編碼風格,與FreeRTOS無關的代碼,我仍然使用本身的編碼風格。我能夠保證,兩種編碼風格決不會影響程序的可讀性,編寫良好可讀性的代碼,是我一直注重並堅持的。
命令行解釋器使用一個硬件串口,須要外部提供兩個串口底層函數:一個是串口初始化函數init_cmd_uart(),用於初始化串口波特率、中斷等事件;另外一個是發送單個字符函數my_putc()。此外,命令行爲串口接收中斷服務程序提供函數fill_rec_buf(),用於保存接收到的字符,當收到回車換行符後,該函數向命令行分析任務發送通知。
類printf函數用來格式化輸出,我通常用來輔助調試,爲了方便的將調試代碼從程序中去除,須要將類printf函數進行封裝。個人文章《編寫優質嵌入式C程序》第5.2節給出了一個完整的類printf函數實現和封裝代碼,最終咱們使用到的類printf函數是以下形式的宏:
咱們將會建立一個任務,用來分析接收到的命令,若是命令有效則調用命令實現函數。這個任務名字爲vTaskCmdAnalyze()。串口接收中斷用於接收命令,若是接收到回車換行符,則向任務vTaskCmdAnalyze()發送任務通知,代表已經接收到一條完整命令,任務能夠去處理了。
示意框圖如圖3-1所示。
命令行解釋器程序須要涉及兩個數據結構:一個與命令有關,包括命令的名字、命令的最大參數數目、命令的回調函數類型、命令幫助信息等;另外一個與分析命令有關,包括接收命令字符緩衝區、存放參數緩衝區等。
定義以下:
須要說明一下命令回調函數的參數,argc保存接收到的參數數目,cmd_arg指向參數緩衝區,目前只支持32位的整形參數,這在絕大多數嵌入式場合是足夠的。
定義以下:
緩衝區的大小使用宏來定義,經過更改相應的宏定義,能夠設置整條命令的最大長度、命令參數最大數目等。
本文使用的串口軟件是SecureCRT,在這個軟件下敲擊的任何鍵盤字符,都會馬上經過串口硬件發送出去,這與Telnet相似。因此咱們無需使用串口的FIFO,每接收到一個字符就產生一次中斷。串口中斷與硬件關係密切,因此命令行解釋器提供了一個與硬件無關的函數fill_rec_buf(),每當串口中斷接收到一個字符,就以收到的字符爲參數調用這個函數。 fill_rec_buf()函數主要操做變量cmd_analyze,變量的聲明原型爲:
函數fill_rec_buf()的實現代碼爲:
命令行分析任務大部分時間都會由於等待任務通知而處於阻塞狀態。當接收到一個通知後,任務首先去除命令行中的無效字符和控制字符,而後找出命令名並分析參數數目、將參數轉換成十六進制數並保存到參數緩衝區中,最後檢查命令名和參數是否合法,若是合法則調用命令回調函數處理本條命令。
串口軟件SecureCRT支持控制字符。好比在輸入一串命令的時候,發現某個字符輸入錯誤,就要使用退格鍵或者左右移動鍵定位到錯誤的位置進行修改。這裏的退格鍵和左右移動鍵都屬於控制字符,好比退格鍵的鍵值爲0x0八、左移鍵的鍵值爲0x1B0x5B 0x44。咱們以前也說過,在軟件SecureCRT中輸入字符時,每敲擊一個字符,該字符馬上經過串口發送給咱們的嵌入式設備,也就是全部鍵值都會按照敲擊鍵盤的順序存入到接收緩衝區中,但這裏面可能有咱們不須要的字符,咱們首先須要利用控制字符將不須要的字符刪除掉。這個工做由函數get_true_char_stream()實現,代碼以下所示:
接收到的命令中可能帶有參數,咱們須要知道參數的數目,還須要把字符型的參數轉換成整形數並保存到參數緩衝區(這是由於命令回調函數須要這兩個參數)。這個工做由函數cmd_arg_analyze()實現,代碼以下所示:
在這個函數cmd_arg_analyze()中,調用了字符轉整形函數string_to_dec()。咱們只支持整形參數,這裏給出一個字符轉整形函數的簡單實現,能夠識別負號和十六進制的前綴’0x’。在這個函數中調用了三個C庫函數,分別是isdigit()、isxdigit()和tolower(),所以須要包含頭文件#include <ctype.h>。函數string_to_dec()實現代碼以下:
咱們舉兩個例子:第一個是不帶參數的例子,輸入命令後,函數返回一個「Helloworld!」字符串;第二個是帶參數的例子,咱們輸入命令和參數後,函數返回每個參數值。咱們在講數據結構的時候特別提到過命令回調函數的原型,這裏要根據這個函數原型來聲明命令回調函數。
在講數據結構的時候,咱們定義了與命令有關的數據結構。每條命令須要包括命名名、最大參數、命令回調函數、幫助等信息,這裏要將每條命令組織成列表的形式。
若是要定義本身的命令,只須要按照6.3節的格式編寫命令回調函數,而後將命令名、參數數目、回調函數和幫助信息按照本節格式加入到命令表中便可。
有了上面的基礎,命令行分析任務實現起來就很是輕鬆了,源碼以下:
推薦使用SecureCRT軟件,這是我以爲最適合命令行交互的串口工具。此外,這個軟件很是強大,除了支持串口,還支持SSH、Telnet等。對於串口,SecureCRT工具還支持文件發送協議:Xmodem、Ymodem和Zmodem。這在使用串口遠程升級時頗有用,能夠用來發送新的程序二進制文件。我曾經使用Ymodem作過遠程升級,之後有時間再詳細介紹SecureCRT的Ymodem功能細節。
要用於本文介紹的命令行解釋器,要對SecureCRT軟件作一些設置。
選擇Serial功能、設置端口、波特率、校驗等,特別要注意的是不要勾選任何流控制選項,如圖2-1所示。
圖2-1:設置串口參數
依次點擊菜單欄的「選項」---「會話選項」,在彈出的「會話選項」界面中,點擊左邊樹形菜單的「終端」---「仿真」---「模式」,在右邊的仿真模式區域選中「換行」和「新行模式」,如圖2-2所示。
圖2-2:設置新行模式
依次點擊菜單欄的「選項」---「會話選項」,在彈出的「會話選項」界面中,點擊左邊樹形菜單的「終端」---「仿真」---「高級」,在右邊的「高級仿真」區域,選中「本地回顯」,如圖2-3所示。
圖2-3:設置本地回顯
咱們經過6.3節和6.4接定義了兩個命令,第一條命令的名字爲」hello」,這是一個無參數命令,直接輸出字符串」Hello world!」。第二條命令的名字爲」arg」,是一個帶參數命令,輸出每一個參數的值。下面對這兩個命令進行測試。
設置好SecureCRT軟件,輸入字符」hello」後,按下回車鍵,設備會返回字符串」Hello world!」。如圖8-1所示。
圖8-1:無參數命令測試
設置好SecureCRT軟件,輸入字符」arg 1 2 -3 0x0a」後,按下回車鍵,設備會返回每一個參數值。如圖8-2所示。
圖8-2:帶參數命令測試
使用RTOS編程,爲每一個任務分配多大的堆棧空間就成了一項技術活:分配多了浪費系統資源,分配少了又恐怕會發生堆棧溢出。因爲中斷和搶佔式調度器的存在,咱們要估算出一個任務須要多少堆棧是很是困難的,今天咱們就介紹一種方法,來獲取每一個任務的剩餘堆棧空間。本文以NXP LPC177x_8x系列微控制器爲例。
咱們將這個功能作成一個命令,添加到《FreeRTOS系列第15篇---使用任務通知實現命令行解釋器》一文介紹的命令解釋列表中。當程序運行一段時間後,咱們在SecureCRT軟件中輸入命令「task」後回車,能看到如圖1-1所示的任務信息。這裏只有兩個任務,其中堆棧一列中的數字,表明對應任務剩餘的堆棧空間,單位是StackType_t類型,這個類型在移植層定義,通常定義爲4字節。
圖1-1:任務信息
如圖1-1所示,要實現堆棧使用量信息以及CPU使用率信息,必須將FreeRTOSConfig.h文件中的兩個宏設置爲1:
第一個宏用來使能可視化追蹤功能,第二個宏用來使能運行時間統計功能。若是第二個宏設置爲1,則下面兩個宏必須被定義:
咱們使用定時器1來產生基準時鐘,定時器1初始化函數爲:
定時器1被配置成每隔500微秒,TC寄存器值增一。咱們將定時器1的 TC寄存器值做爲基準時鐘當前時間。當TC寄存器值溢出時,大概要通過24.8天,這對於咱們這個應用是足夠的。
在FreeRTOSConfig.h中,定義初始化基準定時器宏和獲取當前時間宏:
獲取每一個任務的狀態信息使用的是API函數uxTaskGetSystemState(),該函數定義爲:
函數uxTaskGetSystemState()向TaskStatus_t結構體填充相關信息,系統中每個任務的信息均可以填充到TaskStatus_t結構體數組中,數組大小由uxArraySize指定。結構體TaskStatus_t定義以下:
注意,這個函數僅用來調試用,調用此函數會掛起全部任務,直到函數結束後才恢復掛起的任務,所以任務可能被掛起很長時間。在文件FreeRTOSConfig.h中,宏configUSE_TRACE_FACILITY必須設置爲1,此函數纔有效。
因爲咱們不使用動態內存分配策略,因此實現定義了最大任務個數並預先分配好了存儲任務狀態信息的數組:
正確調用函數uxTaskGetSystemState()後,任務的信息會被放在TaskStatus_t結構體中,咱們須要將這些信息格式化爲容易閱讀的形式,並共經過串口打印到屏幕。完成這些功能的函數叫作get_task_state(),代碼以下所示:
在《FreeRTOS系列第15篇---使用任務通知實現命令行解釋器》一文咱們講過了命令表,這裏只須要將get_task_state()函數添加到命令列表中,命令設置爲」task」,代碼以下所示:
隊列是主要的任務間通信方式。能夠在任務與任務間、中斷和任務間傳送信息。大多數狀況下,隊列用於具備線程保護的FIFO(先進先出)緩衝區:新數據放在隊列的後面。固然,數據也能夠放在隊列的前面,在下一篇講隊列API函數時,會涉及到數據的存放位置。
圖1-1:讀寫隊列
圖1-1所示的隊列中,最多能保存5個項目,而且假設隊列永遠不會滿。任務A使用API函數xQueueSendToBack()向隊列發送數據,每次發送一個數據,新入隊的數據置於上一次入隊數據的後面。任務B使用API函數xQueueReceive()將數據從隊列取出,先入隊的數據先出隊。
一般狀況下,魚和熊掌是不可兼得的,但FreeRTOS的隊列用戶模型管理卻兼顧簡單和靈活。發送到隊列的消息是經過拷貝實現的,這意味着隊列存儲的數據是原數據,而不是原數據的引用。FreeRTOS隊列具備如下特性:
API函數容許指定阻塞時間。
每當任務企圖從一個空的隊列讀取數據時,任務會進入阻塞狀態(這樣任務不會消耗任何CPU時間而且另外一個任務能夠運行)直到隊列中出現有效數據或者阻塞時間到期。
每當任務企圖向一個滿的隊列寫數據時,任務會進入阻塞狀態,直到隊列中出現有效空間或者阻塞時間到期。
若是多個任務阻塞在一個隊列上,那麼最高優先級別的任務會第一個解除阻塞。
注:中斷程序中毫不能夠使用不帶「FromISR」結尾的API函數!
總結一下隊列的基本用法:
FreeRTOS爲操做隊列提供了很是豐富的API函數,包括隊列的建立、刪除,靈活的入隊和出隊方式、帶中斷保護的入隊和出隊等等。下面就來詳細講述這些API函數。
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
返回隊列中存儲的信息數目。具備中斷保護的版本爲uxQueueMessagesWaitingFromISR(),原型爲:UBaseType_t uxQueueMessagesWaitingFromISR( const QueueHandle_t xQueue )。
UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );
返回隊列的空閒數目。
void vQueueDelete( QueueHandle_t xQueue );
刪除隊列並釋放全部分配給隊列的內存。
3.2參數描述
BaseType_t xQueueReset( QueueHandle_t xQueue );
將隊列復位到初始狀態。
FreeRTOSV7.2.0以及之後的版本老是返回pdPASS。
QueueHandle_t xQueueCreate (UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
建立新隊列。爲新隊列分配指定的存儲空間並返回隊列句柄。
成功建立隊列返回隊列句柄,否自返回0。
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
實際上是一個宏,真正被調用的函數是xQueueGenericSend()。定義這個宏是爲了向後兼容那些不包含函數xQueueSendToFront()和xQueueSendToBack()宏的FreeRTOS版本。它與xQueueSendToBack()等同。
這個宏向隊列尾部投遞一個隊列項。項目以拷貝的形式入隊,而不是引用形式入隊。毫不能夠在中斷服務例程中調用這個宏,使用帶有中斷保護的版本xQueueSendFromISR()來完成相同的功能。
6.2參數描述
隊列項入隊成功返回pdTRUE,不然返回errQUEUE_FULL。
實際上是一個宏,真正被調用的函數是xQueueGenericSendFromISR()。這個宏是xQueueSend()的中斷保護版本,用於中斷服務程序,等價於xQueueSendToBackFromISR()。
在中斷服務例程中向隊列尾部投遞一個隊列項。
列項入隊成功返回pdTRUE,不然返回errQUEUE_FULL。
實際上是一個宏,真正被調用的函數是xQueueGenericSend()。這個宏等價於xQueueSend()。
向隊列尾投遞一個隊列項。毫不能夠在中斷中調用這個宏,能夠使用帶有中斷保護的版本xQueueSendToBackFromISR ()來完成相同功能。
同xQueueSend()。
同xQueueSend()。
同xQueueSend()。
實際上是一個宏,真正被調用的函數是xQueueGenericSendFromISR()。這個宏是xQueueSendToBack()的中斷保護版本,用於中斷服務程序,等價於xQueueSendFromISR()。
在中斷服務例程中向隊列尾部投遞一個隊列項。
同QueueSendFromISR()。
同QueueSendFromISR()。
同QueueSendFromISR()。
實際上是一個宏,真正被調用的函數是xQueueGenericSend()。
這個宏向隊列首部投遞一個隊列項。毫不能夠在中斷服務例程中調用這個宏,能夠使用帶有中斷保護的版本xQueueSendToFrontFromISR ()來完成相同功能。
隊列項入隊成功返回pdTRUE,不然返回errQUEUE_FULL。
實際上是一個宏,真正被調用的函數是xQueueGenericSendFromISR()。這個宏是xQueueSendToFront ()的中斷保護版本,用於中斷服務程序。
列項入隊成功返回pdTRUE,不然返回errQUEUE_FULL。
實際上是一個宏,真正被調用的函數是xQueueGenericReceive()。
這個宏從隊列中讀取一個隊列項並把該隊列項從隊列中刪除。讀取隊列項是以拷貝的形式完成,而不是以引用的形式,所以必須提供足夠大的緩衝區以便容納隊列項。參數pvBuffer指向這個緩衝區。
毫不能夠在中斷服務例程中調用這個宏,能夠使用使用帶有中斷保護的版本xQueueReceiveFromISR來完成相同功能。
成功接收到列表項返回pdTRUE,不然返回pdFALSE。
從隊列中讀取一個隊列項並把該隊列項從隊列中刪除。功能與xQueueReceive()相同,用於中斷服務函數。
成功接收到列表項返回pdTRUE,不然返回pdFALSE。
實際上是一個宏,真正被調用的函數是xQueueGenericReceive()。
這個宏從隊列中讀取一個隊列項,但不會把該隊列項從隊列中移除。這個宏毫不能夠用在中斷服務例程中,能夠使用使用帶有中斷保護的版本xQueuePeekFromIS()來完成相同功能。
同xQueueReceive()。
同xQueueReceive()。
同xQueueReceive()。
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer,);
功能與xQueuePeek()相同,用於中斷服務程序。
成功接收到列表項返回pdTRUE,不然返回pdFALSE。
void vQueueAddToRegistry(QueueHandle_t xQueue, char *pcQueueName,);
爲隊列分配名字並進行註冊。
隊列註冊有兩個目的,這兩個目的都是爲了調試RTOS內核:
隊列註冊僅用於調試器。
宏configQUEUE_REGISTRY_SIZE定義了能夠註冊的隊列和信號量的最大數量。僅當你想使用可視化調試內核時,才進行隊列和信號量註冊。
void vQueueUnregisterQueue(QueueHandle_t xQueue);
從隊列註冊表中移除指定的隊列。
17.2參數描述
BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue );
查詢隊列是否爲空。這個函數僅用於ISR。
隊列非空返回pdFALSE,其它值表示隊列爲空。
BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t xQueue );
查詢隊列是否滿,僅用於ISR。
隊列沒有滿返回pdFALSE,其它值表示隊列滿。
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void * pvItemToQueue);
實際上是個宏,真正被調用的函數是xQueueGenericSend()。這個宏是xQueueSendToBack()的另外一個版本,向隊列尾投遞一個隊列項,若是隊列已滿,則覆蓋以前的隊列項。通常用於只有一個隊列項的隊列中,若是隊列的隊列項超過1個,使用這個宏會觸發一個斷言(已經正肯定義configASSERT()的狀況下)。這個宏毫不能夠在中斷服務程序中調用,能夠使用使用帶有中斷保護的版本xQueueOverwriteFromISR()來完成相同功能。
老是返回pdPASS。
實際上是個宏,真正被調用的函數是xQueueGenericSendFromISR()。這個宏的功能與xQueueOverwrite()相同,用在中斷服務程序中。
老是返回pdPASS。
FreeRTOS的信號量包括二進制信號量、計數信號量、互斥信號量(之後簡稱互斥量)和遞歸互斥信號量(之後簡稱遞歸互斥量)。
咱們能夠把互斥量和遞歸互斥量當作特殊的信號量。互斥量和信號量在用法上不一樣:
二進制信號量既能夠用於互斥功能也能夠用於同步功能。
二進制信號量和互斥量很是類似,可是有一些細微差異:互斥量包含一個優先級繼承機制,二進制信號量則沒有這個機制。這使得二進制信號量更好的用於實現同步(任務間或任務和中斷間),互斥量更好的用於實現簡單互斥。本節僅描述用於同步的二進制信號量。
信號量API函數容許指定一個阻塞時間。當任務企圖獲取一個無效信號量時,任務進入阻塞狀態,阻塞時間用來肯定任務進入阻塞的最大時間,阻塞時間單位爲系統節拍週期時間。若是有多個任務阻塞在同一個信號量上,那麼當信號量有效時,具備最高優先級別的任務最早解除阻塞。
能夠將二進制信號量看做只有一個項目(item)的隊列,所以這個隊列只能爲空或滿(所以稱爲二進制)。任務和中斷使用隊列無需關注誰控制隊列---只須要知道隊列是空仍是滿。利用這個機制能夠在任務和中斷之間同步。
考慮這樣一種狀況,一個任務用來維護外設。使用輪詢的方法會浪費CPU資源而且妨礙其它任務執行。更好的作法是任務的大部分時間處於阻塞狀態(容許其它任務執行),直到某些事件發生該任務才執行。能夠使用二進制信號量實現這種應用:當任務取信號量時,由於此時還沒有發生特定事件,信號量爲空,任務會進入阻塞狀態;當外設須要維護時,觸發一箇中斷服務例程,該中斷服務僅僅給出信號量(向隊列寫數據)。任務只是取信號,並不須要歸還,中斷服務只是給信號。
任務的優先級能夠用於確保外設及時得到維護。還能夠使用隊列來代替二進制信號量。中斷例程能夠捕獲與外設事件相關的數據並將它發往任務的隊列。任務發現隊列數據有效時解除阻塞,若是須要,則進行數據處理。第二種方案使得中斷執行儘量的短,其它處理過程能夠在任務中實現。
注:中斷程序中決不可以使用無「FromISR」結尾的API函數。
注:在大部分應用場合,任務通知均可以代替二進制信號量,而且速度更快、生成的代碼更少。
圖1-1:中斷和任務之間同步---使用信號量
如圖1-1所示,程序開始運行時,信號量無效,所以任務阻塞在這個信號量下。一段時間後,一箇中斷髮生,在中斷服務程序中使用API函數xSemaphoreGiveFromISR()給出了一個信號,信號量變得有效。當退出中斷服務程序後,執行上下文切換,任務解除阻塞,使用API函數xSemaphoreTake()取走信號量並執行任務。以後信號量變得無效,任務再次進入阻塞。
二進制信號量能夠被認爲是長度爲1的隊列,計數信號量則能夠被認爲長度大於1的隊列。此外,信號量使用者沒必要關心存儲在隊列中的數據,只需關心隊列是否爲空。
一般計數信號量用於下面兩種事件:
注:中斷程序中決不可以使用無「FromISR」結尾的API函數。
注:在大部分應用場合,任務通知均可以代替計數信號量,而且速度更快、生成的代碼更少。
互斥量是一個包含優先級繼承機制的二進制信號量。用於實現同步(任務之間或者任務與中斷之間)的話,二進制信號量是更好的選擇,互斥量用於簡單的互鎖。
用於互鎖的互斥量能夠充當保護資源的令牌。當一個任務但願訪問某個資源時,它必須先獲取令牌。當任務使用完資源後,必須還回令牌,以便其它任務能夠訪問同一資源。
互斥量和信號量使用相同的API函數,所以互斥量也容許指定一個阻塞時間。阻塞時間單位爲系統節拍週期時間,數目表示獲取互斥量無效時最多處於阻塞狀態的系統節拍週期個數。
互斥量與二進制信號量最大的不一樣是:互斥量具備優先級繼承機制。也就是說,若是一個互斥量(令牌)正在被一個低優先級任務使用,此時一個高優先級企圖獲取這個互斥量,高優先級任務會由於得不到互斥量而進入阻塞狀態,正在使用互斥量的低優先級任務會臨時將本身的優先級提高,提高後的優先級與與進入阻塞狀態的高優先級任務相同。這個優先級提高的過程叫作優先級繼承。這個機制用於確保高優先級任務進入阻塞狀態的時間儘量短,以及將已經出現的「優先級翻轉」影響下降到最小。
在不少場合中,某個硬件資源只有一個,當低優先級任務佔用該資源的時候,即使高優先級任務也只能乖乖的等待低優先級任務釋放資源。這裏高優先級任務沒法運行而低優先級任務能夠運行的現象稱爲「優先級翻轉」。
爲何優先級繼承可以下降優先級翻轉的影響呢?舉個例子,如今有任務A、任務B和任務C,三個任務的優先級順序爲任務C>任務B>任務A。任務A和任務C都要使用某一個硬件資源,而且當前任務A佔有該資源。
先看沒有優先級繼承的狀況:任務C也要使用該資源,可是此時任務A正在使用這個資源,所以任務C進入阻塞,此時三個任務的優先級順序沒有發生變化。在任務C進入阻塞以後,某硬件產生了一次中斷,喚醒了一個事件,該事件能夠解除任務B的阻塞狀態。在中斷結束後,由於任務B的優先級是大於任務A的,因此任務B搶佔任務A的CPU權限。那麼任務C的阻塞時間就至少爲:中斷處理時間+任務B的運行時間+任務A的運行時間。
再看有優先級繼承的狀況:任務C也要使用該資源,可是此時任務A正在使用這個資源,所以任務C進入阻塞,此時因爲優先級A會繼承任務C的優先級,三個任務的優先級順序發生了變化,新的優先級順序爲:任務C=任務A>任務B。在任務C進入阻塞以後,某硬件產生了一次中斷,喚醒了一個事件,該事件能夠解除任務B的阻塞狀態。在中斷結束後,由於任務A的優先級臨時被提升,大於任務B的優先級,因此任務A繼續得到CPU權限。任務A完成後,處於高優先級的任務C會接管CPU。因此任務C的阻塞時間爲:中斷處理時間+任務A的運行時間。看,任務C的阻塞時間變小了,這就是優先級繼承的優點。
優先級繼承不能解決優先級反轉,只能將這種狀況的影響下降到最小。硬實時系統在一開始設計時就要避免優先級反轉發生。
圖4-1 互斥量用於保護資源
如圖4-1所示,互斥量用來保護資源。爲了訪問資源,任務必須先獲取互斥量。任務A想獲取資源,首先它使用API函數xSemaphoreTake()獲取信號量,成功獲取到信號量後,任務A就持有了互斥量,能夠安全的訪問資源。期間任務B開始執行,它也想訪問資源,任務B也要先得到信號量,可是信號量此時是無效的,任務B進入阻塞狀態。當任務A執行完成後,使用API函數xSemaphoreGive()釋放信號量。以後任務B解除阻塞,任務B使用API函數xSemaphoreTake()獲取並獲得信號量,任務B能夠訪問資源。
已經獲取遞歸互斥量的任務能夠重複獲取該遞歸互斥量。使用xSemaphoreTakeRecursive() 函數成功獲取幾回遞歸互斥量,就要使用xSemaphoreGiveRecursive()函數返還幾回,在此以前遞歸互斥量都處於無效狀態。好比,某個任務成功獲取5次遞歸互斥量,那麼在它沒有返還5次該遞歸互斥量以前,這個互斥量對別的任務無效。
遞歸互斥量能夠當作帶有優先級繼承機制的信號量,獲取遞歸互斥量的任務在用完後必須返還。
互斥量不能夠用在中斷服務程序中,這是由於:
互斥量具備優先級繼承機制,只有在任務中獲取或給出互斥纔有意義。
中斷不能由於等待互斥量而阻塞。
FreeRTOS的信號量包括二進制信號量、計數信號量、互斥信號量(之後簡稱互斥量)和遞歸互斥信號量(之後簡稱遞歸互斥量)。咱們能夠把互斥量和遞歸互斥量當作特殊的信號量。
信號量API函數實際上都是宏,它使用現有的隊列機制。這些宏定義在semphr.h文件中。若是使用信號量或者互斥量,須要包含semphr.h頭文件。
二進制信號量、計數信號量和互斥量信號量的建立API函數是獨立的,可是獲取和釋放API函數都是相同的;遞歸互斥信號量的建立、獲取和釋放API函數都是獨立的。
SemaphoreHandle_t xSemaphoreCreateBinary( void );
這個函數用於建立一個二進制信號量。二進制信號量要麼有效要麼無效,這也是爲何叫作二進制的緣由。
新建立的信號量處於無效狀態,這意味着使用API函數xSemaphoreTake()獲取信號以前,須要先給出信號。
二進制信號量和互斥量很是類似,但也有細微的區別:互斥量具備優先級繼承機制,二進制信號量沒有這個機制。這使得二進制信號量更適合用於同步(任務之間或者任務和中斷之間),互斥量更適合互鎖。
一旦得到二進制信號量後不須要恢復,一個任務或中斷不斷的產生信號,而另外一個任務不斷的取走這個信號,經過這樣的方式來實現同步。
低優先級任務擁有互斥量的時候,若是另外一個高優先級任務也企圖獲取這個信號量,則低優先級任務的優先級會被臨時提升,提升到和高優先級任務相同的優先級。這意味着互斥量必需要釋放,不然高優先級任務將不能獲取這個互斥量,而且那個擁有互斥量的低優先級任務也永遠不會被剝奪,這就是操做系統中的優先級翻轉。
互斥量和二進制信號量都是SemaphoreHandle_t類型,而且能夠用於任何具備這類參數的API函數中。
建立計數信號量,計數信號量一般用於如下兩種狀況:
NULL表示信號量建立失敗,不然返回信號量句柄。
SemaphoreHandle_t xSemaphoreCreateMutex( void )
建立互斥量。能夠使用API函數xSemaphoreTake()和xSemaphoreGive()訪問互斥量,可是毫不能夠用xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()訪問。
二進制信號量和互斥量很是類似,但也有細微的區別:互斥量具備優先級繼承機制,二進制信號量沒有這個機制。這使得二進制信號量更適合用於同步(任務之間或者任務和中斷之間),互斥量更適合互鎖。
一旦得到二進制信號量後不須要恢復,一個任務或中斷不斷的產生信號,而另外一個任務不斷的取走這個信號,經過這樣的方式來實現同步。
低優先級任務擁有互斥量的時候,若是另外一個高優先級任務也企圖獲取這個信號量,則低優先級任務的優先級會被臨時提升,提升到和高優先級任務相同的優先級。這意味着互斥量必需要釋放,不然高優先級任務將不能獲取這個互斥量,而且那個擁有互斥量的低優先級任務也永遠不會被剝奪,這就是操做系統中的優先級翻轉。
互斥量和二進制信號量都是SemaphoreHandle_t類型,而且能夠用於任何具備這類參數的API函數中。
NULL表示信號量建立失敗,不然返回信號量句柄。
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
用於建立遞歸互斥量。被建立的互斥量能夠被API函數xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()使用,但不能夠被API函數xSemaphoreTake()和xSemaphoreGive()使用。
遞歸類型的互斥量能夠被擁有者重複獲取。擁有互斥量的任務必須調用API函數xSemaphoreGiveRecursive()將擁有的遞歸互斥量所有釋放後,該信號量才真正被釋放。好比,一個任務成功獲取同一個互斥量5次,那麼這個任務要將這個互斥量釋放5次以後,其它任務才能獲取到它。
遞歸互斥量具備優先級繼承機制,所以任務得到一次信號後必須在使用完後作一個釋放操做。
互斥量類型信號不能夠用在中斷服務例程中。
NULL表示互斥量建立失敗,不然返回互斥量句柄。
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
刪除信號量。若是有任務阻塞在這個信號量上,則這個信號量不要刪除。
xSemaphore:信號量句柄
xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)
獲取信號量。信號量必須是經過API函數xSemaphoreCreateBinary()、xSemaphoreCreateCounting()和xSemaphoreCreateMutex()預先建立過的。注意,遞歸互斥量類型信號量不能使用該函數、不用在中斷服務程序中使用該函數。
成功獲取到信號量返回pdTRUE,不然返回pdFALSE。
API函數xSemaphoreTake()的另外一版本,用於中斷服務程序。
信號量成功獲取返回pdTRUE,不然返回pdFALSE。
xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait );
獲取遞歸互斥信號量。互斥量必須是經過API函數xSemaphoreCreateRecursiveMutex()建立的類型。
文件FreeRTOSConfig.h中的宏configUSE_RECURSIVE_MUTEXES必須設置成1,此函數纔有效。
已經獲取遞歸互斥量的任務能夠重複獲取該遞歸互斥量。使用xSemaphoreTakeRecursive() 函數成功獲取幾回遞歸互斥量,就要使用xSemaphoreGiveRecursive()函數返還幾回,在此以前遞歸互斥量都處於無效狀態。好比,某個任務成功獲取5次遞歸互斥量,那麼在它沒有返還5次該遞歸互斥量以前,這個互斥量對別的任務無效。
成功獲取遞歸互斥量返回pdTURE,不然返回pdFALSE。
xSemaphoreGive(SemaphoreHandle_t xSemaphore )
用於釋放一個信號量。信號量必須是API函數xSemaphoreCreateBinary()、xSemaphoreCreateCounting()或xSemaphoreCreateMutex() 建立的。必須使用API函數xSemaphoreTake()獲取這個信號量。
這個函數毫不能夠在中斷服務例程中使用,能夠使用帶中斷保護版本的API函數xSemaphoreGiveFromISR()來實現相同功能。
這個函數不能用於使用API函數xSemaphoreCreateRecursiveMutex()所建立的遞歸互斥量。
信號量釋放成功返回pdTRUE,不然返回pdFALSE。
釋放信號量。是API函數xSemaphoreGive()的另個版本,用於中斷服務程序。信號量必須是經過API函數xSemaphoreCreateBinary()或xSemaphoreCreateCounting()建立的。這裏沒有互斥量,是由於互斥量不能夠用在中斷服務程序中。
xSemaphore:信號量句柄
pxHigherPriorityTaskWoken:若是*pxHigherPriorityTaskWoken爲pdTRUE,則須要在中斷退出前人爲的經行一次上下文切換。從FreeRTOS V7.3.0開始,該參數爲可選參數,並能夠設置爲NULL。
成功釋放信號量返回pdTURE,不然返回errQUEUE_FULL。
xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex )
釋放一個遞歸互斥量。互斥量必須是使用 API函數xSemaphoreCreateRecursiveMutex()建立的。文件FreeRTOSConfig.h中宏configUSE_RECURSIVE_MUTEXES必須設置成1本函數纔有效。
若是遞歸互斥量釋放成功,返回pdTRUE。
見「8 獲取遞歸互斥量」。
TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );
返回互斥量持有任務的句柄(若是有的話),互斥量由參數xMutex指定。
若是調用此函數的任務持有互斥量,那麼能夠可靠的返回任務句柄,可是若是是別的任務持有互斥量,則不總可靠。
文件FreeRTOSConfig.h中宏configUSE_MUTEXES必須設置成1本函數纔有效。
返回互斥量持有任務的句柄。若是參數xMutex不是互斥類型信號量或者雖然互斥量有效但這個互斥量不被任何任務持有則返回NULL。
這是FreeRTOS基礎篇的最後一篇博文,到這裏咱們已經能夠移植、熟練使用FreeRTOS了。但若是想知道FreeRTOS背後的運行機制,這些是遠遠不夠的,下面要走的路還會很長。要不要了解FreeRTOS背後運行機制,全憑各位的興趣,畢竟咱們即便不清楚汽車的構造細節,但只要掌握駕駛技巧也能夠很好的開車的。使用RTOS也與之類似,只要咱們掌握了基礎篇的那些知識,咱們已經能夠很好的使用FreeRTOS了。探索FreeRTOS背後運行的機制,是咱們對未知事件的好奇,也是咱們相信理解了FreeRTOS運行機制,可讓咱們更優雅、更少犯錯、更舉重若輕的的使用RTOS。
FreeRTOS高級篇已經開始寫了,能夠點擊這裏擦看最新的文章列表
FreeRTOSV9.0.0發行於2016.05.25,推薦官方網站下載。考慮到官方網站的服務器在美國,下載速度較慢,下面給出CSDN下載地址:點擊下載,下載須要1個資源分,請原諒博主須要資源分增長上傳權限。
在FreeRTOS V9.0.0rc2版本中,xTaskCreateStatic()原型爲:
在FreeRTOS V9.0.0正式版中,xTaskCreateStatic()原型爲:
注:博主看過FreeRTOS V9.0.0rc2版,也看過V9.0.0正式版,發現兩個版本函數xTaskCreateStatic()是相同的。
FreeRTOSV9.0.0rc2發行於2016.03.30,推薦官方網站下載。考慮到官方網站的服務器在美國,下載速度較慢,下面給出CSDN下載地址:點擊下載,下載須要1個資源分,請原諒博主須要資源分增長上傳權限。
FreeRTOSV9.0.0rc1發行於2016.02.19,推薦官方網站下載。考慮到官方網站的服務器在美國,下載速度較慢,下面給出CSDN下載地址:點擊下載,下載須要1個資源分,請原諒博主須要資源分增長上傳權限。
FreeRTOSV8.2.3發行於2015.10.16,推薦官方網站下載。考慮到官方網站的服務器在美國,下載速度較慢,下面給出CSDN下載地址:點擊下載 ,下載須要1個資源分,請原諒博主須要資源分增長上傳權限。
FreeRTOSV8.2.2發行於2015.08.12,推薦官方網站下載。考慮到官方網站的服務器在美國,下載速度較慢,下面給出CSDN下載地址:點擊下載 ,下載須要1個資源分,請原諒博主須要資源分增長上傳權限。