FreeRTOS命名及變量規則
初學FreeRTOS的用戶對其變量和函數的命名比較迷惑, FreeRTOS的核心源代碼遵從MISRA編碼標准指南,關於MISRA編碼標准,可以查看文章https://wenku.baidu.com/view/5e7b2f4ee518964bcf847c99.html。下面專門做一下介紹:
變量
uint32_t定義的變量都加上前綴ul。u代表unsigned 無符號,l代表long長整型。
uint16_t定義的變量都加上前綴us。u代表unsigned無符號,s代表short短整型。
uint8_t定義的變量都加上前綴uc。u代表unsigned無符號,c代表char字符型。
stdint.h文件中未定義的變量類型,在定義變量時需要加上前綴x,比如BaseType_t和TickType_t定義的變量。
stdint.h文件中未定義的無符號變量類型,在定義變量時要加上前綴u,比如UBaseType_t 定義的變量要加上前綴ux。
size_t 定義的變量也要加上前綴ux。
枚舉變量會加上前綴e。
指針變量會加上前綴p,比如uint16_t定義的指針變量會加上前綴pus。
根據MISRA代碼規則,char定義的變量只能用於ASCII字符,前綴使用c。
根據MISRA代碼規則,char *定義的指針變量只能用於ASCII字符串,前綴使用pc。
函數
加上了static聲明的函數,定義時要加上前綴prv,這個是單詞private的縮寫。
帶有返回值的函數,根據返回值的數據類型,加上相應的前綴,如果沒有返回值,即void類型 ,函數的前綴加上字母v。
根據文件名,文件中相應的函數定義時也將文件名加到函數命名中,比如tasks.c文件中函數vTaskDelete,函數中的task就是文件名中的task。
宏定義
根據宏定義所在的文件,文件中的宏定義聲明時也將文件名加到宏定義中,比如宏定義configUSE_PREEMPTION 是定義在文件 FreeRTOSConfig.h里面。宏定義中的config就是文件名中的config。另外注意,前綴要小寫。
除了前綴,其余部分全部大寫,同時用下划線分開。
數據類型
FreeRTOS使用的數據類型主要分為stdint.h文件中定義的和自己定義的兩種。其中char和char *定義的變量要特別注意。
- char:與MISRA編碼標准指南一致,char類型變量僅被允許保存ASCII字符
- char *:與MISRA編碼標准指南一致,char *類型變量僅允許指向ASCII字符串。當標准庫函數期望一個char *參數時,這樣做可以消除一些編譯器警告;特別是考慮到有些編譯器將char類型當做signed類型,還有些編譯器將char類型當做unsigned類型。
FreeRTOS主要自定義了以下四種數據類型:
TickType_t 如果用戶使能了宏定義 configUSE_16_BIT_TICKS,那么TickType_t定義的就是16位無符號數,如果沒有使能,那么TickType_t定義的就是32位無符號數。對於32位架構的處理器,一定要禁止此宏定義,即設置此宏定義數值為0即可。
BaseType_t 這個數據類型根據系統架構的位數而定,對於32位架構,BaseType_t定義的是32位有符號數,對於16位架構,BaseType_t定義的是16位有符號數。如果BaseType_t被定義成了char型,要特別注意將其設置為有符號數,因為部分函數的返回值是用負數來表示錯誤類型。
UBaseType_t 這個數據類型是BaseType_t類型的有符號版本。
StackType_棧變量數據類型定義,這個數量類型由系統架構決定,對於16位系統架構,StackType_t定義的是 16位變量,對於32位系統架構,StackType_t定義的是32位變量。
示例如下:
/* 首先在這里包含庫文件... */ #include <stdlib.h> /* ...然后是FreeRTOS的頭文件... */ #include "FreeRTOS.h" /* ...緊接着包含其它頭文件. */ #include "HardwareSpecifics.h" /* 隨后是#defines, 在合理的位置添加括號. */ #define A_DEFINITION ( 1 ) /* * 隨后是Static (文件內部的)函數原型, * 如果注釋有多行,參照本條注釋風格---每一行都以’*’起始. */ static void prvAFunction( uint32_t ulParameter ); /* 文件作用域變量(本文件內部使用)緊隨其后,要在函數體定義之前. */ static BaseType_t xMyVariable. /* 每一個函數的結束都有一行破折號,破折號與下面的第一個函數之間留一行空白。*/ /*-----------------------------------------------------------*/ void vAFunction( void ) { /* 函數體在此定義,注意要用大括號括住 */ } /*-----------------------------------------------------------*/ static UBaseType_t prvNextFunction( void ) { /* 函數體在此定義. */ } /*-----------------------------------------------------------*/ /* * 函數名字總是占一行,包括返回類型。 左括號之前沒有空格左括號之后有一個空格, * 每個參數后面有一個空格參數的命名應該具有一定的描述性. */ void vAnExampleFunction( long lParameter1, unsigned short usParameter2 ) { /* 變量聲明沒有縮進. */ uint8_t ucByte; /* 代碼要對齊. 大括號占獨自一行. */ for( ucByte = 0U; ucByte < fileBUFFER_LENGTH; ucByte++ ) { /* 這里再次縮進. */ } } /* * for、while、do、if結構具有相似的模式。這些關鍵字和左括號之間沒有空格。 * 左括號之后有一個空格,右括號前面也有一個空格,每個分號后面有一個空格。 * 每個運算符的前后各一個空格。使用圓括號明確運算符的優先級。不允許有0 * 以外的數字(魔鬼數)出現,必要時將這些數字換成能表示出數字含義的常量或 * 宏定義。 */ for( ucByte = 0U; ucByte < fileBUFFER_LENGTH; ucByte++ ) { } while( ucByte < fileBUFFER_LENGTH ) { } /* * 由於運算符優先級的復雜性,我們不能相信自己對運算符優先級時刻保持警惕 * 並能正確的使用,因此對於多個表達式運算時,使用括號明確優先級順序 */ if( ( ucByte < fileBUFFER_LENGTH ) && ( ucByte != 0U ) ) { ulResult = ( ( ulValue1 + ulValue2 ) - ulValue3 ) * ulValue4; } /* 條件表達式也要像其它代碼那樣對齊。 */ #if( configUSE_TRACE_FACILITY == 1 ) { /* 向TCB增加一個用於跟蹤的計數器. */ pxNewTCB->uxTCBNumber = uxTaskNumber; } #endif /*方括號前后各留一個空格*/ ucBuffer[ 0 ] = 0U; ucBuffer[ fileBUFFER_LENGTH - 1U ] = 0U;
裸機程序的運行缺點
采用中斷和查詢結合的方式可以解決大部分裸機應用,但隨着工程的復雜,裸機方式的缺點就暴露出來了:
必須在中斷 (ISR) 內處理時間關鍵運算 內處理時間關鍵運算 :
ISR ISR 函數變得非常復雜,並且需要很長執行時間 。
ISR ISR 嵌套可能產生不預測的執行時間和堆棧 需求。
超級循環和 ISR 之間的 數據交換是通過全局共享變量進行:
應用程序的員必須確保數據一致性 。
超級循環可以與系統計時器輕松同步,但:
如果系統需要多種不同的周期時間,則會很難實現 。
超過 超級循環周期的耗時函數需要做拆分。
增加 軟件開銷,應用程序難以理解 。
超級循環 使得 應用程序變得非常復雜,因此難以擴展 :
一個簡單的更改就可能產生不預測副作用 ,對這種副作用進行分析非常耗時。
超級循環概念的這些缺點可以通過使用實時操作系統 (RTOS) 來解決。
多任務的優點
針對這些情況,使用多任務系統就可以解決這些問題了。下面是一個多任務系統的流程圖:
多任務系統或者說RTOS的實現,重點就在這個調度器上,而調度器的作用就是使用相關的調度算法來決定當前需要執行的任務。如上圖所示的那樣,創建了任務並完成OS初始化后,就可以通過調度器來決定任務A,任務B和任務C的運行,從而實現多任務系統。另外需要初學者注意的是,這里所說的多任務系統同一時刻只能有一個任務可以運行,只是通過調度器的決策,看起來像所有任務同時運行一樣。為了更好的說明這個問題,再舉一個詳細的運行例子,運行條件如下:
使用搶占式調度器。
1個空閑任務,優先級最低。
2個應用任務,一個高優先級和一個低優先級,優先級都比空閑任務優先級高。
中斷服務程序,含USB中斷,串口中斷和系統滴答定時器中斷。
下圖是任務的運行過程,其中橫坐標是任務優先級由低到高排列,縱坐標是運行時間,時間刻度有小到大。
多任務系統運行過程
(1) 啟動RTOS,首先執行高優先級任務(vTaskStartScheduler)。
(2) 高優先級任務等待事件標志(xEventGroupWaitBits)被阻塞,低優先級任務得到執行。
(3) 低優先級任務執行的過程中產生USB中斷,進入USB中斷服務程序。
(4) 退出USB中斷復位程序,回到低優先級任務繼續執行。
(5) 低優先級任務執行過程中產生串口接收中斷,進入串口接收中斷服務程序。
(6) 退出串口接收中斷復位程序,並發送事件標志設置消息(xEventGroupSetBitsFromISR),被阻塞的高優先級任務就會重新進入就緒狀態,這個時候高優先級任務和低優先級任務都在就緒態,搶占式調度器就會讓高優先級的任務先執行,所以此時就會進入高優先級任務。
(7) 高優先級任務由於等待事件標志(xEventGroupWaitBits)會再次被阻塞,低優先級任務開始繼續執行。
(8) 低優先級任務調用函數vTaskDelay,低優先級任務被掛起,從而空閑任務得到執行。
(9) 空閑任務執行期間發生滴答定時器中斷,進入滴答定時器中斷服務程序。
(10) 退出滴答定時器中斷,由於低優先級任務延時時間到,低優先級任務繼續執行。
(11) 低優先級任務再次調用延遲函數vTaskDelay,低優先級任務被掛起,從而切換到空閑任務。空閑任務得到執行。
FreeRTOS就是一款支持多任務運行的實時操作系統,具有時間片,搶占式和合作式三種調度方法。通過FreeRTOS實時操作系統可以將程序函數分成獨立的任務,並為其提供合理的調度方式。
FreeRTOSreeRTOS reeRTOSreeRTOS 的運行支持以下 四種狀態 :
Run ninging —運行態
當任務處於實際運行狀態被稱之為運行態,即 CPU 的使用權被這個任務占用。
Ready —就緒態
處於就緒態的任務是指那些能夠運行(沒有被阻塞和掛起),但是當前沒有運行的任務, 因為同優先級或更高優先級的任務正在運行。
Blocked locked —阻塞態
由於等待信號量,消息等待信號量,消息 隊列 ,事件標志組等而處於的狀態被稱之為阻塞態,另外任務調用延遲函數也會處於阻塞態。
Suspended—掛起態
類似阻塞態,通過調用函數 vTaskSuspend( vTaskSuspend( )對指定任務進行掛起,后這個將不被執只對指定任務進行掛起,后這個將不被執只有調用函數 xTaskResume()才可以將這個將這個任務從掛起態恢復 。
下面是任務 在各個狀態之間切換的關系圖 ,通過這個圖 ,大家基本可以對任務的運行狀態有了一個整體的認識。
事件狀態組的轉換