FreeRTOS開發實戰_FreeRTOS核心配置專案解析
2.3 移植FreeRTOS
2.3.1 核心配置檔案
FreeRTOS核心是高度可定製的,使用配置檔案FreeRTOSConfig.h進行定製。每個FreeRTOS應用都必須包含這個標頭檔案,使用者根據實際應用來裁剪定製FreeRTOS核心。這個配置檔案是針對使用者程式的,而非核心,因此配置檔案一般放在應用程式目錄下,不要放在RTOS核心原始碼目錄下。
可以參考Demo中的FreeRTOSConfig.h,有些例程的配置檔案是比較舊的版本,可能不會包含所有有效選項。如果沒有在配置檔案中指定某個選項,那麼RTOS核心會使用預設值。
在初始移植的時候,可以考慮使用default的配置,以便先完成porting後再細挑。而也可以暫時跳過這個章節。
2.3.1.1 configUSE_PREEMPTION
l 1:RTOS使用搶佔式排程器
l 0:RTOS使用協作式排程器(時間片)。
2.3.1.2 configUSE_PORT_OPTIMISED_TASK_SELECTION
用於選擇下一個task的排程方式,通用方法和特定於硬體的方法。
l 通用方法
- configUSE_PORT_OPTIMISED_TASK_SELECTION設定為0或者硬體不支援這種特殊方法。
- 可以用於所有FreeRTOS支援的硬體。
- 完全用C實現,效率略低於特殊方法。
- 不強制要求限制最大可用優先順序數目
l 特定方法
- 並非所有硬體都支援。
- 必須將configUSE_PORT_OPTIMISED_TASK_SELECTION設定為1。
- 依賴一個或多個特定架構的彙編指令(一般是類似計算前導零[CLZ]指令)。
- 比通用方法更高效。
- 一般強制限定最大可用優先順序數目為32。
2.3.2.3 configUSE_TICKLESS_IDLE
- 2 使用者可以自定義tickless低功耗模式的實現
- 1使能低功耗tickless模式
- 0保持系統節拍(tick)中斷一直執行。
通常情況下,FreeRTOS回撥空閒任務鉤子函式(需要設計者自己實現),在空閒任務鉤子函式中設定微處理器進入低功耗模式來達到省電的目的。因為系統要響應系統節拍中斷事件,因此使用這種方法會週期性的退出、再進入低功耗狀態。如果系統節拍中斷頻率過快,則大部分電能和CPU時間會消耗在進入和退出低功耗狀態上。
FreeRTOS的tickless空閒模式會在空閒週期時停止週期性系統節拍中斷。停止週期性系統節拍中斷可以使微控制器長時間處於低功耗模式。移植層需要配置外部喚醒中斷,當喚醒事件到來時,將微控制器從低功耗模式喚醒。微控制器喚醒後,會重新使能系統節拍中斷。由於微控制器在進入低功耗後,系統節拍計數器是停止的,但我們又需要知道這段時間能折算成多少次系統節拍中斷週期,這就需要有一個不受低功耗影響的外部時鐘源,即微處理器處於低功耗模式時它也在計時的,這樣在重啟系統節拍中斷時就可以根據這個外部計時器計算出一個調整值並寫入RTOS 系統節拍計數器變數中。
2.3.1.4 configUSE_IDLE_HOOK
- 1使用空閒鉤子(IdleHook類似於回撥函式);
- 0忽略空閒鉤子。
當RTOS排程器開始工作後,為了保證至少有一個任務在執行,空閒任務被自動建立,佔用最低優先順序(0優先順序)。對於已經刪除的RTOS任務,空閒任務可以釋放分配給它們的堆疊記憶體。因此,在應用中應該注意,使用vTaskDelete()函式時要確保空閒任務獲得一定的處理器時間。除此之外,空閒任務沒有其它特殊功能,因此可以任意的剝奪空閒任務的處理器時間。
應用程式也可能和空閒任務共享同個優先順序。
那麼,如何實現hook函式呢?
空閒任務鉤子是一個函式,這個函式由使用者來實現,RTOS規定了函式的名字和引數,這個函式在每個空閒任務週期都會被呼叫。
要建立一個空閒鉤子:
- 設定FreeRTOSConfig.h 檔案中的configUSE_IDLE_HOOK 為1;
- 定義一個函式,函式名和引數如下所示:
voidvApplicationIdleHook(void );
這個鉤子函式不可以呼叫會引起空閒任務阻塞的API函式(例如:vTaskDelay()、帶有阻塞時間的佇列和訊號量函式),在鉤子函式內部使用協程是被允許的。
使用空閒鉤子函式設定CPU進入省電模式是很常見的。
2.3.1.5 configUSE_MALLOC_FAILED_HOOK
- 1,那麼必須定義一個malloc()失敗鉤子函式
- 0,malloc()失敗鉤子函式不會被呼叫,即便已經定義了這個函式
當pvPortMalloc()申請記憶體失敗的回撥函式。只是針對heap_1.c,heap_2.c,heap_3.c,heap_4.c,heap_5.c有效果。 如果定義並正確配置malloc()失敗鉤子函式,則這個函式會在pvPortMalloc()函式返回NULL時被呼叫。只有FreeRTOS在響應記憶體分配請求時發現堆記憶體不足才會返回NULL。
malloc()失敗鉤子函式的函式名和原型必須如下所示:
void vApplicationMallocFailedHook(void);
2.3.1.6 configUSE_TICK_HOOK
- 1使用時間片鉤子(TickHook),
- 0忽略時間片鉤子。
時間片中斷可以週期性的呼叫一個被稱為鉤子函式(回撥函式)的應用程式。時間片鉤子函式可以很方便的實現一個定時器功能。
只有在FreeRTOSConfig.h中的configUSE_TICK_HOOK設定成1時才可以使用時間片鉤子。一旦此值設定成1,就要定義鉤子函式,函式名和引數如下所示:
void vApplicationTickHook(void );
vApplicationTickHook()函式在中斷服務程式中執行,因此這個函式必須非常短小,不能大量使用堆疊,不能呼叫以”FromISR" 或 "FROM_ISR”結尾的API函式。
在FreeRTOSVx.x.x\FreeRTOS\Demo\Common\Minimal資料夾下的crhook.c檔案中有使用時間片鉤子函式的例程。
2.3.1.7 configCPU_CLOCK_HZ
寫入實際的CPU核心時鐘頻率,也就是CPU指令執行頻率,通常稱為Fcclk。配置此值是為了正確的配置系統節拍中斷週期。
2.3.1.8 configTICK_RATE_HZ
RTOS 系統節拍中斷的頻率。即一秒中斷的次數,每次中斷RTOS都會進行任務排程。
系統節拍中斷用來測量時間,因此,越高的測量頻率意味著可測到越高的解析度時間。但是,高的系統節拍中斷頻率也意味著RTOS核心佔用更多的CPU時間,因此會降低效率。RTOS演示例程都是使用系統節拍中斷頻率為1000HZ,這是為了測試RTOS核心,比實際使用的要高。(實際使用時不用這麼高的系統節拍中斷頻率)
多個任務可以共享一個優先順序,RTOS排程器為相同優先順序的任務分享CPU時間,在每一個RTOS 系統節拍中斷到來時進行任務切換。高的系統節拍中斷頻率會降低分配給每一個任務的“時間片”持續時間。
2.3.1.9 configMAX_PRIORITIES
配置應用程式有效的優先順序數目。任何數量的任務都可以共享一個優先順序,使用協程可以單獨的給與它們優先權。見configMAX_CO_ROUTINE_PRIORITIES。
在RTOS核心中,每個有效優先順序都會消耗一定量的RAM,因此這個值不要超過你的應用實際需要的優先順序數目。
注:任務優先順序
每一個任務都會被分配一個優先順序,優先順序值從0~ (configMAX_PRIORITIES- 1)之間。低優先順序數表示低優先順序任務。空閒任務的優先順序為0(tskIDLE_PRIORITY),因此它是最低優先順序任務。
FreeRTOS排程器將確保處於就緒狀態(Ready)或執行狀態(Running)的高優先順序任務比同樣處於就緒狀態的低優先順序任務優先獲取處理器時間。換句話說,處於執行狀態的任務永遠是高優先順序任務。
處於就緒狀態的相同優先順序任務使用時間片排程機制共享處理器時間。
2.3.1.10 configMINIMAL_STACK_SIZE
定義空閒任務使用的堆疊大小。通常此值不應小於對應處理器演示例程檔案FreeRTOSConfig.h中定義的數值。
就像xTaskCreate()函式的堆疊大小引數一樣,堆疊大小不是以位元組為單位而是以字為單位的,比如在32位架構下,棧大小為100表示棧記憶體佔用400位元組的空間。
2.3.1.11 configTOTAL_HEAP_SIZE
RTOS核心總計可用的有效的RAM大小。僅在你使用官方下載包中附帶的記憶體分配策略時,才有可能用到此值。每當建立任務、佇列、互斥量、軟體定時器或訊號量時,RTOS核心會為此分配RAM,這裡的RAM都屬於configTOTAL_HEAP_SIZE指定的記憶體區。
2.3.1.12 configMAX_TASK_NAME_LEN
呼叫任務函式時,需要設定描述任務資訊的字串,這個巨集用來定義該字串的最大長度。這裡定義的長度包括字串結束符’\0’。
2.3.1.13 configUSE_TRACE_FACILITY
1表示啟動視覺化跟蹤除錯,會啟用一些附加的結構體成員和函式。
2.3.1.14 configUSE_STATS_FORMATTING_FUNCTIONS
設定巨集configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS為1會編譯vTaskList()和vTaskGetRunTimeStats()函式。如果將這兩個巨集任意一個設定為0,上述兩個函式不會被編譯。
2.3.1.15 configUSE_16_BIT_TICKS
- 1意味著portTickType代表16位無符號整形
- 0意味著portTickType代表32位無符號整形。
使用16位型別可以大大提高8位和16位架構微處理器的效能,但這也限制了最大時鐘計數為65535個’Tick’。因此,如果Tick頻率為250HZ(4MS中斷一次),對於任務最大延時或阻塞時間,16位計數器是262秒,而32位是17179869秒。
2.3.1.16 configIDLE_SHOULD_YIELD
這個引數控制任務在空閒優先順序中的行為。僅在滿足下列條件後,才會起作用。
- 使用搶佔式核心排程
- 使用者任務使用空閒優先順序。
ü 設定為0將阻止空閒任務為使用者任務讓出CPU,直到空閒任務的時間片結束。這確保所有處在空閒優先順序的任務分配到相同多的處理器時間,但是,這是以分配給空閒任務更高比例的處理器時間為代價的。
通過時間片共享同一個優先順序的多個任務,如果共享的優先順序大於空閒優先順序,並假設沒有更高優先順序任務,這些任務應該獲得相同的處理器時間。
但如果共享空閒優先順序時,情況會稍微有些不同。當configIDLE_SHOULD_YIELD為1時,其它共享空閒優先順序的使用者任務就緒時,空閒任務立刻讓出CPU,使用者任務執行,這樣確保了能最快響應使用者任務。處於這種模式下也會有不良效果(取決於你的程式需要),描述如下:
圖中描述了四個處於空閒優先順序的任務,任務A、B和C是使用者任務,任務I是空閒任務。上下文切換週期性的發生在T0、T1…T6時刻。當用戶任務執行時,空閒任務立刻讓出CPU,但是,空閒任務已經消耗了當前時間片中的一定時間。這樣的結果就是空閒任務I和使用者任務A共享一個時間片。使用者任務B和使用者任務C因此獲得了比使用者任務A更多的處理器時間。
可以通過下面方法避免:
- 如果合適的話,將處於空閒優先順序的各單獨的任務放置到空閒鉤子函式中;
- 建立的使用者任務優先順序大於空閒優先順序;
- 設定IDLE_SHOULD_YIELD為0;
2.3.1.17 configUSE_TASK_NOTIFICATIONS(V8.2.0新增)
- 1(或不定義巨集configUSE_TASK_NOTIFICATIONS)將會開啟任務通知功能,有關的API函式也會被編譯。
- 0則關閉任務通知功能,相關API函式也不會被編譯。
預設這個功能是開啟的。開啟後,每個任務多增加8位元組RAM。 每個RTOS任務具有一個32位的通知值,RTOS任務通知相當於直接向任務傳送一個事件,接收到通知的任務可以解除任務的阻塞狀態(因等待任務通知而進入阻塞狀態)。相對於以前必須分別建立佇列、二進位制訊號量、計數訊號量或事件組的情況,使用任務通知顯然更靈活。更好的是,相比於使用訊號量解除任務阻塞,使用任務通知可以快45%(使用GCC編譯器,-o2優化級別)
2.3.1.18 configUSE_MUTEXES
- 1表示使用互斥量,
- 0表示忽略互斥量。
讀者應該瞭解在FreeRTOS中互斥量和二進位制訊號量的區別。關於互斥量和二進位制訊號量簡單說:
- 互斥型訊號量必須是同一個任務申請,同一個任務釋放,其他任務釋放無效。
- 二進位制訊號量,一個任務申請成功後,可以由另一個任務釋放。
- 互斥型訊號量是二進位制訊號量的子集
2.3.1.19 configUSE_RECURSIVE_MUTEXES
- 1表示使用遞迴互斥量,
- 0表示不使用。
2.3.1.20 configUSE_COUNTING_SEMAPHORES
- 1表示使用計數訊號量,
- 0表示不使用。
2.3.1.21 configUSE_ALTERNATIVE_API
- 1表示使用“替代”佇列函式('alternative' queue functions),
- 0不使用。
替代API在queue.h標頭檔案中有詳細描述。
注:“替代”佇列函式已經被棄用,在新的設計中不要使用它!
2.3.1.22 configCHECK_FOR_STACK_OVERFLOW
用於除錯堆疊的溢位情況。堆疊溢位是裝置執行不穩定的最常見原因,因此FreeeRTOS提供了兩個可選機制用來輔助檢測和改正堆疊溢位。配置巨集configCHECK_FOR_STACK_OVERFLOW為不同的常量來使用不同堆疊溢位檢測機制。
- 0表示不使用這個機制
推薦僅在開發或測試階段使用棧溢位檢查,因為堆疊溢位檢測會增大上下文切換開銷。如果巨集configCHECK_FOR_STACK_OVERFLOW沒有設定成0,使用者必須提供一個棧溢位鉤子函式,這個鉤子函式的函式名和引數必須如下所示:
voidvApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName);
引數xTask和pcTaskName為堆疊溢位任務的控制代碼和名字。請注意,如果溢位非常嚴重,這兩個引數資訊也可能是錯誤的!在這種情況下,可以直接檢查pxCurrentTCb變數。
- 1方法速度很快,但是不能檢測到所有堆疊溢位情況(比如,堆疊溢位沒有發生在上下文切換時)。
任務切換出去後,該任務的上下文環境被儲存到自己的堆疊空間,這時很可能堆疊的使用量達到了最大(最深)值。在這個時候,RTOS核心會檢測堆疊指標是否還指向有效的堆疊空間。如果堆疊指標指向了有效堆疊空間之外的地方,堆疊溢位鉤子函式會被呼叫。
- 2有效捕捉堆疊溢位事件(即使堆疊溢位沒有發生在上下文切換時),但是理論上它也不能百分百的捕捉到所有堆疊溢位(比如堆疊溢位的值和標記值相同,當然,這種情況發生的概率極小)。
當堆疊首次建立時,在它的堆疊區中填充一些已知值(標記)。當任務切換時,RTOS核心會檢測堆疊最後的16個位元組,確保標記資料沒有被覆蓋。如果這16個位元組有任何一個被改變,則呼叫堆疊溢位鉤子函式。
2.3.1.23 configQUEUE_REGISTRY_SIZE
佇列記錄有兩個目的,都涉及到RTOS核心的除錯:
- 它允許在除錯GUI中使用一個佇列的文字名稱來簡單識別佇列;
- 包含偵錯程式需要的每一個記錄佇列和訊號量定位資訊;
除了進行核心除錯外,佇列記錄沒有其它任何目的。
configQUEUE_REGISTRY_SIZE定義可以記錄的佇列和訊號量的最大數目。如果你想使用RTOS核心偵錯程式檢視佇列和訊號量資訊,則必須先將這些佇列和訊號量進行註冊,只有註冊後的佇列和訊號量才可以使用RTOS核心偵錯程式檢視。檢視API參考手冊中的vQueueAddToRegistry() 和vQueueUnregisterQueue()函式獲取更多資訊。
2.3.1.24 configUSE_QUEUE_SETS
- 1使能佇列集功能(可以阻塞、掛起到多個佇列和訊號量),
- 0取消佇列集功能。
2.3.1.25 configUSE_TIME_SLICING
預設情況下(巨集configUSE_TIME_SLICING未定義或者巨集configUSE_TIME_SLICING設定為1),FreeRTOS使用基於時間片的優先順序搶佔式排程器。這意味著RTOS排程器總是執行處於最高優先順序的就緒任務,在每個RTOS 系統節拍中斷時在相同優先順序的多個任務間進行任務切換。如果巨集configUSE_TIME_SLICING設定為0,RTOS排程器仍然總是執行處於最高優先順序的就緒任務,但是當RTOS 系統節拍中斷髮生時,相同優先順序的多個任務之間不再進行任務切換。
2.3.1.26 configUSE_NEWLIB_REENTRANT
如果巨集configUSE_NEWLIB_REENTRANT設定為1,每一個建立的任務會分配一個newlib(一個嵌入式C庫)reent結構。
2.3.1.27 configENABLE_BACKWARD_COMPATIBILITY
標頭檔案FreeRTOS.h包含一系列#define巨集定義,用來對映版本V8.0.0和V8.0.0之前版本的資料型別名字。這些巨集可以確保RTOS核心升級到V8.0.0或以上版本時,之前的應用程式碼不用做任何修改。在FreeRTOSConfig.h檔案中設定巨集configENABLE_BACKWARD_COMPATIBILITY為0會去掉這些巨集定義,並且需要使用者確認升級之前的應用沒有用到這些名字。
2.3.1.28 configNUM_THREAD_LOCAL_STORAGE_POINTERS
設定每個任務的執行緒本地儲存指標陣列大小。
執行緒本地儲存允許應用程式在任務的控制塊中儲存一些值,每個任務都有自己獨立的儲存空間,巨集configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每個任務執行緒本地儲存指標陣列的大小。API函式vTaskSetThreadLocalStoragePointer()用於向指標陣列中寫入值,API函式pvTaskGetThreadLocalStoragePointer()用於從指標陣列中讀取值。
比如,許多庫函式都包含一個叫做errno的全域性變數。某些庫函式使用errno返回庫函式錯誤資訊,應用程式檢查這個全域性變數來確定發生了那些錯誤。在單執行緒程式中,將errno定義成全域性變數是可以的,但是在多執行緒應用中,每個執行緒(任務)必須具有自己獨有的errno值,否則,一個任務可能會讀取到另一個任務的errno值。
FreeRTOS提供了一個靈活的機制,使得應用程式可以使用執行緒本地儲存指標來讀寫執行緒本地儲存。
2.3.1.29 configGENERATE_RUN_TIME_STATS
設定巨集configGENERATE_RUN_TIME_STATS為1使能執行時間統計功能。一旦設定為1,則下面兩個巨集必須被定義:
ü portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():
使用者程式需要提供一個基準時鐘函式,函式完成初始化基準時鐘功能,這個函式要被define到巨集portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()上。這是因為執行時間統計需要一個比系統節拍中斷頻率還要高解析度的基準定時器,否則,統計可能不精確。基準定時器中斷頻率要比統節拍中斷快10~100倍。基準定時器中斷頻率越快,統計越精準,但能統計的執行時間也越短(比如,基準定時器10ms中斷一次,8位無符號整形變數可以計到2.55秒,但如果是1秒中斷一次,8位無符號整形變數可以統計到255秒)。
ü portGET_RUN_TIME_COUNTER_VALUE():
使用者程式需要提供一個返回基準時鐘當前“時間”的函式,這個函式要被define到巨集portGET_RUN_TIME_COUNTER_VALUE()上。
舉一個例子,假如我們配置了一個定時器,每500us中斷一次。在定時器中斷服務例程中簡單的使長整形變數ulHighFrequencyTimerTicks自增。那麼上面提到兩個巨集定義如下(可以在FreeRTOSConfig.h中新增):
extern volatile unsignedlongulHighFrequencyTimerTicks;
#defineportCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL)
#defineportGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
2.3.1.30 configUSE_CO_ROUTINES
- 1表示使用協程,
- 0表示不使用協程。
如果使用協程,必須在工程中包含croutine.c檔案。
注:協程(Co-routines)主要用於資源發非常受限的嵌入式系統(RAM非常少),通常不會用於32位微處理器。
在當前嵌入式硬體環境下,不建議使用協程,FreeRTOS的開發者早已經停止開發協程。
2.3.1.31 configMAX_CO_ROUTINE_PRIORITIES
應用程式協程(Co-routines)的有效優先順序數目,任何數目的協程都可以共享一個優先順序。使用協程可以單獨的分配給任務優先順序。見configMAX_PRIORITIES。
2.3.1.32 configUSE_TIMERS
- 1使用軟體定時器,
- 0不使用軟體定時器功能。
2.3.1.33 configTIMER_TASK_PRIORITY
設定軟體定時器服務/守護程序的優先順序。
2.3.1.34 configTIMER_QUEUE_LENGTH
設定軟體定時器命令佇列的長度。
2.3.1.35 configTIMER_TASK_STACK_DEPTH
設定軟體定時器服務/守護程序任務的堆疊深度
2.3.1.36 configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY
這是移植和應用FreeRTOS出錯最多的地方,所以需要打起精神仔細讀懂。
關於Cortex-M3的優先順序請參考文章《Cortex-M3異常處理機制研究》,連結地址為:http://www.junsion.icoc.bz/nd.jsp?id=6#_np=2_405
Cortex-M3、PIC24、dsPIC、PIC32、SuperH和RX600硬體裝置需要設定巨集configKERNEL_INTERRUPT_PRIORITY;
PIC32、RX600和Cortex-M硬體裝置需要設定巨集configMAX_SYSCALL_INTERRUPT_PRIORITY。
configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY,這兩個巨集是等價的,後者是前者的新名字,用於更新的移植層程式碼。
注意下面的描述中,在中斷服務例程中僅可以呼叫以“FromISR”結尾的API函式。
僅需要設定configKERNEL_INTERRUPT_PRIORITY的硬體裝置(也就是巨集configMAX_SYSCALL_INTERRUPT_PRIORITY不會用到):configKERNEL_INTERRUPT_PRIORITY用來設定RTOS核心自己的中斷優先順序。呼叫API函式的中斷必須執行在這個優先順序;不呼叫API函式的中斷,可以執行在更高的優先順序,所以這些中斷不會被因RTOS核心活動而延時。
configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY都需要設定的硬體裝置:configKERNEL_INTERRUPT_PRIORITY用來設定RTOS核心自己的中斷優先順序。因為RTOS核心中斷不允許搶佔使用者使用的中斷,因此這個巨集一般定義為硬體最低優先順序。configMAX_SYSCALL_INTERRUPT_PRIORITY用來設定可以在中斷服務程式中安全呼叫FreeRTOS API函式的最高中斷優先順序。優先順序小於等於這個巨集所代表的優先順序時,程式可以在中斷服務程式中安全的呼叫FreeRTOS API函式;如果優先順序大於這個巨集所代表的優先順序,表示FreeRTOS無法禁止這個中斷,在這個中斷服務程式中絕不可以呼叫任何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時,下圖描述了每一個優先級別可以和不可做的事件:
configMAX_SYSCALL_INTERRUPT_PRIORITY=4
configKERNEL_INTERRUPT_PRIORITY=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定義的優先順序。
回到STM32來看,這個微控制器基於Cortex-M3的核心,晶片廠商為STM32提供0~15個可搶佔的優先順序,這個是通過如下的程式碼實現的:
/* 優先順序分組設定為4,可配置0~15級搶佔式優先順序,0級子優先順序*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); |
那麼就強烈建議,在FreeRTOSConfig.h中將configMAX_SYSCALL_INTERRUPT_PRIORITY配置為16。這樣FreeRTOS的優先順序配置就不會影響到STM32自身的可搶佔式的優先順序配置。
以“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函式。
巨集configKERNEL_INTERRUPT_PRIORITY指定RTOS核心使用的中斷優先順序,因為RTOS核心不可以搶佔使用者任務,因此這個巨集一般設定為硬體支援的最小優先順序。對於Cortex-M硬體,RTOS使用到硬體的PendSV和SysTick硬體中斷,在函式xPortStartScheduler()中(該函式在port.c中,由啟動排程器函式vTaskStartScheduler()呼叫),將PendSV和SysTick硬體中斷優先順序暫存器設定為巨集configKERNEL_INTERRUPT_PRIORITY指定的值。
1. /*PendSV優先順序設定暫存器地址為0xe000ed22SysTick優先順序設定暫存器地址為0xe000ed23*/ #define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) ) #define portNVIC_PENDSV_PRI ( portMIN_INTERRUPT_PRIORITY << 16UL ) #define portNVIC_SYSTICK_PRI ( portMIN_INTERRUPT_PRIORITY << 24UL ) /* Make PendSV and SysTick the lowest priority interrupts. */ portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; |
2.3.1.37 configASSERT
斷言,除錯時可以檢查傳入的引數是否合法。FreeRTOS核心程式碼的關鍵點都會呼叫configASSERT( x )函式,如果引數x為0,則會丟擲一個錯誤。這個錯誤很可能是傳遞給FreeRTOS API函式的無效引數引起的。定義configASSERT()有助於除錯時發現錯誤,但是,定義configASSERT()也會增大應用程式程式碼量,增大執行時間。推薦在開發階段使用這個斷言巨集。
舉一個例子,我們想把非法引數所在的檔名和程式碼行數打印出來,可以先定義一個函式vAssertCalled,該函式有兩個引數,分別接收觸發configASSERT巨集的檔名和該巨集所在行,然後通過顯示屏或者串列埠輸出。程式碼如下:
#define configASSERT( ( x )) if( ( x ) == 0 )vAssertCalled(__FILE__, __LINE__ )
這裡__FILE__和__LINE__是大多數編譯器預定義的巨集,分別表示程式碼所在的檔名(字串格式)和行數(整形)。
這個例子雖然看起來很簡單,但由於要把整形__LINE__轉換成字串再顯示,在效率和實現上,都不能讓人滿意。我們可以使用C標準庫assert的實現方法,這樣函式vAssertCalled只需要接收一個字串形式的引數(推薦仔細研讀下面的程式碼並理解其中的技巧):
#defineSTR(x) VAL(x)
#defineVAL(x) #x
#defineconfigASSERT(x) ((x)?(void) 0 :xAssertCalld(__FILE__ ":"STR(__LINE__) " " #x"\n"))
這裡稍微講解一下,由於內建巨集__LINE__是整數型的而不是字串型,把它轉化成字串需要一個額外的處理層。巨集STR和和巨集VAL正是用來輔助完成這個轉化。巨集STR用來把整形行號替換掉__LINE__,巨集VAL用來把這個整形行號字串化。忽略巨集STR和VAL中的任何一個,只能得到字串”__LINE__”,這不是我們想要的。
這裡使用三目運算子’?:’來代替引數判斷if語句,這樣可以接受任何引數或表示式,程式碼也更緊湊,更重要的是程式碼優化度更高,因為如果引數恆為真,在編譯階段就可以去掉不必要的輸出語句。
2.3.1.38 INCLUDE Parameters
以“INCLUDE”起始的巨集允許使用者不編譯那些應用程式不需要的實時核心元件(函式),這可以確保在你的嵌入式系統中RTOS佔用最少的ROM和RAM。
每個巨集以這樣的形式出現:
INCLUDE_FunctionName
在這裡FunctionName表示一個你可以控制是否編譯的API函式。如果你想使用該函式,就將這個巨集設定成1,如果不想使用,就將這個巨集設定成0。比如,對於API函式vTaskDelete():
#defineINCLUDE_vTaskDelete 1
表示希望使用vTaskDelete(),允許編譯器編譯該函式
#defineINCLUDE_vTaskDelete 0
表示禁止編譯器編譯該函式。
2.3.1.39 INCLUDE_vTaskSuspend
注意,xSemaphoreTake函式的第二個引數設定為portMAX_DELAY,且在FreeRTOSConig.h 中設定INCLUDE_vTaskSuspend 為1,那麼阻塞等待將沒有超時限制。