FreeRTOS之全配置項詳解、裁剪(FreeRTOSConfig.h)
簡介
首先,我們需要明確一個問題,FreeRTOSConfig.h是一個使用者級別的檔案,不屬於核心檔案。每個使用者可以有不同的FreeRTOSConfig.h。
FreeRTOS作為一個可高度配置的實時核心,其絕大多數配置選項都體現在FreeRTOS.h(注意是FreeRTOS.h不是FreeRTOSConfig.h)中。為什麼這麼說?開啟FreeRTOS.h看看就知道了,這個檔案唯一要乾的活就是負責根據巨集值來對FreeRTOS進行配置的。那麼,如果使用者想要根據自己的需要來配置FreeRTOS,那該怎麼辦呢?難道需要直接改核心標頭檔案FreeRTOS.h?當然不是。FreeRTOS的設計者採用了一個很靈活的方式:FreeRTOS.h通過檢查一個名為FreeRTOSConfig.h的使用者級別
那麼使用者具體如何配置呢?簡單,使用者只需要根據需要,在FreeRTOSConfig.h中以巨集值的形式給出定義即可。那麼,巨集值如何來的呢?為什麼使用者定義了巨集值就可以呢?首先,巨集值全部來自於FreeRTOS.h這個系統級標頭檔案,使用者需要什麼功能,需要在該檔案中查詢對應的巨集值;其次,FreeRTOS.h中會檢查特定功能的巨集值有沒有定義(預設認為使用者使用FreeRTOSConfig.h這個檔案),如果使用者定義了指定巨集值,則FreeRTOS就根據使用者的定義來實現。這樣就實現了使用者配置。
裁剪、配置詳解
注意2:下面的說明的引用部分為譯者所新增,非FreeRTOS原有文件內容。
注意3:部分專業詞彙參考網路,不足之處隨時指正。
首先,看看官網給出的FreeRTOSConfig.h
的模板:
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* 再此包含自己的標頭檔案,例如使用STM32系列晶片時,可以再次包含STM32庫的標頭檔案 */
#include "something.h"
/* 以下為一些常用的配置專案 */
#define configUSE_PREEMPTION 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ 60000000 /* 這個 必須 更改為自己晶片的實際頻率 */
#define configTICK_RATE_HZ 250 /* FreeRTOS的中斷頻率,根據需要修改 */
#define configMAX_PRIORITIES 5 /* 任務的最大優先順序,根據需要修改*/
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_MUTEXES 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_ALTERNATIVE_API 0 /* Deprecated! */
#define configQUEUE_REGISTRY_SIZE 10
#define configUSE_QUEUE_SETS 0
#define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
/* Memory allocation related definitions. */
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE 10240 /* 為FreeRTOS分配的記憶體,具體和選擇的heap_x.c相關 */
#define configAPPLICATION_ALLOCATED_HEAP 1
/* Hook function related definitions. */
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_TRACE_FACILITY 0
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
/* Co-routine related definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES 1
/* Software timer related definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY 3
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE
/* Interrupt nesting behaviour configuration. */
#define configKERNEL_INTERRUPT_PRIORITY [dependent of processor]
#define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and application]
#define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on processor and application]
/* Define to trap errors during development. */
#define configASSERT( ( x ) ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
/* FreeRTOS MPU specific definitions. */
#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
/* Optional functions - most linkers will remove unused functions anyway. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_xResumeFromISR 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 0
#define INCLUDE_xTaskGetIdleTaskHandle 0
#define INCLUDE_eTaskGetState 0
#define INCLUDE_xEventGroupSetBitFromISR 1
#define INCLUDE_xTimerPendFunctionCall 0
#define INCLUDE_xTaskAbortDelay 0
#define INCLUDE_xTaskGetHandle 0
#define INCLUDE_xTaskResumeFromISR 1
/* A header file that defines trace macro can be included here. */
#endif /* FREERTOS_CONFIG_H */
模板中的配置項還是挺多的,下面一一詳細進行說明:
1. configUSE_PREEMPTION
為 1 時RTOS使用搶佔式排程器,即當程序位於核心空間時,有一個更高優先順序的任務出現時,如果當前核心允許搶佔,則可以將當前任務掛起,執行優先順序更高的程序;為 0 時RTOS使用協作式排程器(時間片)高優先順序的程序不能中止正在核心中執行的低優先順序的程序而搶佔CPU執行。。
2. configUSE_PORT_OPTIMISED_TASK_SELECTION
某些執行FreeRTOS的硬體有兩種方法選擇下一個要執行的任務:通用方法和特定於硬體的方法。
通用方法:
- configUSE_PORT_OPTIMISED_TASK_SELECTION設定為 0 或者 沒有實現埠特定方法時,將顯式或者隱式使用通用方法。
- 可以用於所有FreeRTOS支援的硬體。
- 完全用C實現,效率略低於特殊方法。
- 不強制要求限制最大可用優先順序數量。
特定硬體方法:
- 並非所有硬體都支援。
- 必須將configUSE_PORT_OPTIMISED_TASK_SELECTION設定為 1 。
- 依賴於一個或多個架構特定彙編指令(通常為計數前導零(CLZ)或等效指令),因此只能與其專門編寫的體系結構一起使用。
- 比通用方法更有效率。
- 一般強制限定最大可用優先順序數目為32。
Tips:官方使用了 FreeRTOS port 這個詞,原始碼的port.c即與特定硬體相關的實現,因此上面翻譯為特定硬體
3. configUSE_TICKLESS_IDLE
設定configUSE_TICKLESS_IDLE
為 1,使能低功耗tickless模式;為 0 保持系統節拍(SysTick)中斷一直執行。
通常情況下,FreeRTOS回撥空閒任務鉤子函式(需要設計者自己實現),在空閒任務鉤子函式中設定微處理器進入低功耗模式來達到省電的目的。因為系統要響應系統節拍中斷事件,因此使用這種方法會週期性的退出、再進入低功耗狀態。如果系統節拍中斷頻率過快,則大部分電能和CPU時間會消耗在進入和退出低功耗狀態上。
FreeRTOS的tickless空閒模式會在空閒週期時停止週期性系統節拍中斷。停止週期性系統節拍中斷可以使微控制器長時間處於低功耗模式。移植層需要配置外部喚醒中斷,當喚醒事件到來時,將微控制器從低功耗模式喚醒。微控制器喚醒後,會重新使能系統節拍中斷。由於微控制器在進入低功耗後,系統節拍計數器是停止的,但我們又需要知道這段時間能折算成多少次系統節拍中斷週期,這就需要有一個不受低功耗影響的外部時鐘源,即微處理器處於低功耗模式時它也在計時的,這樣在重啟系統節拍中斷時就可以根據這個外部計時器計算出一個調整值並寫入RTOS 系統節拍計數器變數中。
關於低功耗模式,可參見此文https://blog.csdn.net/zcshoucsdn/article/details/77879746
4. configUSE_IDLE_HOOK
設定為1使用空閒鉤子(類似於回撥函式),0忽略空閒鉤子。
當RTOS排程器開始工作後,為了保證至少有一個任務在執行,空閒任務被自動建立,佔用最低優先順序(0優先順序)。對於已經刪除的RTOS任務,空閒任務可以釋放分配給它們的堆疊記憶體。因此,在應用中應該注意,使用vTaskDelete()函式時要確保空閒任務獲得一定的處理器時間。除此之外,空閒任務沒有其它特殊功能,因此可以任意的剝奪空閒任務的處理器時間。
空閒任務鉤子是一個函式,這個函式由使用者來實現,FreeRTOS規定了函式的名字和引數,這個函式在每個空閒任務週期都會被呼叫。要建立一個空閒鉤子:
1. 設定FreeRTOSConfig.h 檔案中的configUSE_IDLE_HOOK
為 1;
2. 定義一個函式,函式名和引數如下所示:
void vApplicationIdleHook(void );
該鉤子函式不可以呼叫會引起空閒任務阻塞的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
中。
僅當使用以上這五個簡單策略之一時,這個巨集才有意義。
如果定義並正確配置malloc()失敗鉤子函式,則這個函式會在pvPortMalloc()函式返回NULL時被呼叫。只有FreeRTOS在響應記憶體分配請求時發現堆記憶體不足才會返回NULL。
如果巨集configUSE_MALLOC_FAILED_HOOK
設定為1,那麼必須定義一個malloc()失敗鉤子函式,如果巨集configUSE_MALLOC_FAILED_HOOK
設定為0,malloc()失敗鉤子函式不會被呼叫,即便已經定義了這個函式。malloc()失敗鉤子函式的函式名和原型必須如下所示:
void vApplicationMallocFailedHook( void);
6. configUSE_DAEMON_TASK_STARTUP_HOOK
如果configUSE_TIMERS
和configUSE_DAEMON_TASK_STARTUP_HOOK
都設定為1,那麼應用程式必須定義一個鉤子函式,其名稱和原型如下所示:
void void vApplicationDaemonTaskStartupHook( void );
當RTOS守護程式任務(也稱為定時器服務任務)第一次執行時,鉤子函式將被精確呼叫一次。 需要RTOS執行的任何應用程式初始化程式碼都可以放在hook函式中。
7. configUSE_TICK_HOOK
設定為1使用時間片鉤子(Tick Hook),0忽略時間片鉤子。
時間片中斷可以週期性的呼叫一個被稱為鉤子函式(回撥函式)的應用程式。時間片鉤子函式可以很方便的實現一個定時器功能。
只有在FreeRTOSConfig.h中的configUSE_TICK_HOOK設定成1時才可以使用時間片鉤子。一旦此值設定成1,就要定義鉤子函式,函式名和引數如下所示:
void vApplicationTickHook( void );
vApplicationTickHook()
函式在中斷服務程式中執行,因此這個函式必須非常短小,不能大量使用堆疊,不能呼叫以”FromISR” 或 “FROM_ISR”結尾的API函式。
8. configCPU_CLOCK_HZ
寫入實際的CPU核心時鐘頻率,也就是CPU指令執行頻率,通常稱為。配置此值是為了正確的配置系統節拍中斷週期。
例如,在STM32中時,常用#define configCPU_CLOCK_HZ ( SystemCoreClock )
9. configTICK_RATE_HZ
RTOS 系統自己的節拍中斷的頻率。即一秒中斷的次數,每次中斷RTOS都會進行任務排程。
系統節拍中斷用來測量時間,因此,越高的測量頻率意味著可測到越高的解析度時間。但是,高的系統節拍中斷頻率也意味著RTOS核心佔用更多的CPU時間,因此會降低效率。RTOS演示例程都是使用系統節拍中斷頻率為1000HZ,這是為了測試RTOS核心,比實際使用的要高。(實際使用時不用這麼高的系統節拍中斷頻率)
多個任務可以共享一個優先順序,RTOS排程器為相同優先順序的任務分享CPU時間,在每一個RTOS 系統節拍中斷到來時進行任務切換。高的系統節拍中斷頻率會降低分配給每一個任務的“時間片”持續時間。
10. configMAX_PRIORITIES
配置應用程式有效的優先順序數目。任何數量的任務都可以共享一個優先順序,使用協程可以單獨的給與它們優先權。見configMAX_CO_ROUTINE_PRIORITIES。
在RTOS核心中,每個有效優先順序都會消耗一定量的RAM,因此這個值不要超過你的應用實際需要的優先順序數目。
每一個任務都會被分配一個優先順序,優先順序值從0~ (configMAX_PRIORITIES - 1)之間。低優先順序數表示低優先順序任務。空閒任務的優先順序為0(tskIDLE_PRIORITY),因此它是最低優先順序任務。
FreeRTOS排程器將確保處於就緒狀態(Ready)或執行狀態(Running)的高優先順序任務比同樣處於就緒狀態的低優先順序任務優先獲取處理器時間。換句話說,處於執行狀態的任務永遠是高優先順序任務。
處於就緒狀態的相同優先順序任務使用時間片排程機制共享處理器時間。
11. configMINIMAL_STACK_SIZE
定義空閒任務使用的堆疊大小。通常此值不應小於對應處理器演示例程檔案FreeRTOSConfig.h中定義的數值。
就像xTaskCreate()
和xTaskCreateStatic()
函式的堆疊大小引數一樣,堆疊大小不是以位元組為單位而是以字為單位的,比如在32位架構下,棧大小為100表示棧記憶體佔用400位元組的空間。
12.configMAX_TASK_NAME_LEN
呼叫任務函式時,需要設定描述任務資訊的字串,這個巨集用來定義該字串的最大長度。這裡定義的長度包括字串結束符'\0'
。
13.configUSE_TRACE_FACILITY
設定成1表示啟動視覺化跟蹤除錯,會啟用一些附加的結構體成員和函式。
該配置通常在除錯時才會使用,在真正釋出程式時必須將其關閉,因為其對於FreeRTOS的效能是有影響的。如下圖是使用Percepio Tracealyzer分析的FreeRTOS的執行情況圖:
14.configUSE_STATS_FORMATTING_FUNCTIONS
設定巨集configUSE_TRACE_FACILITY
和configUSE_STATS_FORMATTING_FUNCTIONS
為1會編譯vTaskList()和vTaskGetRunTimeStats()函式。如果將這兩個巨集任意一個設定為0,上述兩個函式將被忽略。通常也是在除錯時才使用,用來觀察各任務。
15.configUSE_16_BIT_TICKS
時間是以“ticks”來衡量的 - 這是自RTOS核心啟動以來tick事件中斷已執行的次數。 tick計數儲存在TickType_t型別
的變數中。
將configUSE_16_BIT_TICKS
定義為 1 會使TickType_t為無符號的16位型別定義(typedef’ed)。 將configUSE_16_BIT_TICKS
定義為 0 會使TickType_t為無符號32位型別定義(typedef’ed)。
使用16位型別將大大提高8位和16位架構的效能,但將最大可指定時間限制為65535個“ticks”。 因此,假設節拍頻率為250Hz,當使用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更多的處理器時間。
可以通過下面方法避免:
- 如果合適的話,將處於空閒優先順序的各單獨的任務放置到空閒鉤子函式中;
- 建立的使用者任務優先順序大於空閒優先順序;
- 設定IDLE_SHOULD_YIELD為0;
設定configIDLE_SHOULD_YIELD
為0將阻止空閒任務為使用者任務讓出CPU,直到空閒任務的時間片結束。這確保所有處在空閒優先順序的任務分配到相同多的處理器時間,但是,這是以分配給空閒任務更高比例的處理器時間為代價的。
17.configUSE_TASK_NOTIFICATIONS
設定巨集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
以下介紹均為官網的stack overflow detection章節內容
每個任務維護自己的棧空間,任務建立時會自動分配任務需要的佔記憶體,分配記憶體大小由建立任務函式(xTaskCreate())的一個引數指定。堆疊溢位是裝置執行不穩定的最常見原因,因此FreeeRTOS提供了兩個可選機制用來輔助檢測和改正堆疊溢位。配置巨集configCHECK_FOR_STACK_OVERFLOW
為不同的常量來使用不同堆疊溢位檢測機制。
注意,這個選項僅適用於記憶體對映未分段的微處理器架構。並且,在RTOS檢測到堆疊溢位發生之前,一些處理器可能先產生故障(fault)或異常(exception)來反映堆疊使用的惡化。如果巨集configCHECK_FOR_STACK_OVERFLOW
沒有設定成0,使用者必須提供一個棧溢位鉤子函式,這個鉤子函式的函式名和引數必須如下所示:
void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName );
引數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
預設情況下,該巨集會被定義為1。1 表示:FreeRTOS使用基於時間片的優先順序搶佔式排程器。這意味著FreeRTOS排程器總是執行處於最高優先順序的就緒任務,在每個FreeRTOS 系統節拍中斷時在相同優先順序的多個任務間進行任務切換。 如果configUSE_TIME_SLICING
設定為0,那麼RTOS排程程式仍將執行處於就緒狀態的最高優先順序任務,但是不會因為發生滴答中斷而在相同優先順序的任務之間切換。
26.configUSE_NEWLIB_REENTRANT
設定為1,每一個建立的任務會分配一個newlib(一個嵌入式C庫)reent結構。
注意Newlib支援已被普遍需求包括,但FreeRTOS維護者本身不使用。 FreeRTOS不對產生的newlib操作負責。 使用者必須熟悉newlib,並且必須提供必要存根的系統範圍實現。 請注意(在編寫本文時),當前的newlib設計實現了必須提供鎖的系統範圍的malloc()。
27.configENABLE_BACKWARD_COMPATIBILITY
標頭檔案FreeRTOS.h
包含一系列#define巨集定義
,用來對映版本V8.0.0和V8.0.0之前版本的資料型別名字。這些巨集可以確保RTOS核心升級到V8.0.0或以上版本時,之前的應用程式碼不用做任何修改。在FreeRTOSConfig.h
檔案中設定巨集configENABLE_BACKWARD_COMPATIBILITY
為0會去掉這些巨集定義,並且需要使用者確認升級之前的應用沒有用到這些名字。
就是為了相容之前的版本用的巨集。例如:在之前的版本中,FreeRTOS的各種型別均為xAAA(如xSemaphoreHandle),在最新版中,均使用AAA_t(如SemaphoreHandle_t)。因此,在新專案中,最好使用FreeRTOS的最新的各種型別定義。具體如下:
#if configENABLE_BACKWARD_COMPATIBILITY == 1
#define eTaskStateGet eTaskGetState
#define portTickType TickType_t
#define xTaskHandle TaskHandle_t
#define xQueueHandle QueueHandle_t
#define xSemaphoreHandle SemaphoreHandle_t
#define xQueueSetHandle QueueSetHandle_t
#define xQueueSetMemberHandle QueueSetMemberHandle_t
#define xTimeOutType TimeOut_t
#define xMemoryRegion MemoryRegion_t
#define xTaskParameters TaskParameters_t
#define xTaskStatusType TaskStatus_t
#define xTimerHandle TimerHandle_t
#define xCoRoutineHandle CoRoutineHandle_t
#define pdTASK_HOOK_CODE TaskHookFunction_t
#define portTICK_RATE_MS portTICK_PERIOD_MS
#define pcTaskGetTaskName pcTaskGetName
#define pcTimerGetTimerName pcTimerGetName
#define pcQueueGetQueueName pcQueueGetName
#define vTaskGetTaskInfo vTaskGetInfo
/* Backward compatibility within the scheduler code only - these definitions
are not really required but are included for completeness. */
#define tmrTIMER_CALLBACK TimerCallbackFunction_t
#define pdTASK_CODE TaskFunction_t
#define xListItem ListItem_t
#define xList List_t
#endif /* configENABLE_BACKWARD_COMPATIBILITY */
28. configNUM_THREAD_LOCAL_STORAGE_POINTERS
設定每個任務的執行緒本地儲存指標陣列大小。關於這部分的說明,詳細參考Thread Local Storage Pointers
29. configSUPPORT_STATIC_ALLOCATION
設定為1,那麼可以使用應用程式編寫器提供的RAM建立RTOS物件。設定為0,則只能使用從FreeRTOS堆分配的RAM建立RTOS物件。預設(未定義時)為0
如果設定為1,則應用程式編寫器還必須提供兩個回撥函式:vApplicationGetIdleTaskMemory()以提供記憶體供RTOS空閒任務使用;(如果configUSE_TIMERS設定為1)vApplicationGetTimerTaskMemory() 以提供記憶體供RTOS守護程序/定時器服務任務使用。
下面是官方給出的一個例子:
/* configSUPPORT_STATIC_ALLOCATION is set to 1, so the application must provide an
implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
used by the Idle task. */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
/* If the buffers to be provided to the Idle task are declared inside this
function then they must be declared static - otherwise they will be allocated on
the stack and so not exists after this function exits. */
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];
/* Pass out a pointer to the StaticTask_t structure in which the Idle task's
state will be stored. */
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
/* Pass out the array that will be used as the Idle task's stack. */
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
/* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/*-----------------------------------------------------------*/
/* configSUPPORT_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so the
application must provide an implementation of vApplicationGetTimerTaskMemory()
to provide the memory that is used by the Timer service task. */
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize )
{
/* If the buffers to be provided to the Timer task are declared inside this
function then they must be declared static - otherwise they will be allocated on
the stack and so not exists after this function exits. */
static StaticTask_t xTimerTaskTCB;
static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];
/* Pass out a pointer to the StaticTask_t structure in which the Timer
task's state will be stored. */
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
/* Pass out the array that will be used as the Timer task's stack. */
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configTIMER_TASK_STACK_DEPTH is specified in words, not bytes. */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
30. configSUPPORT_DYNAMIC_ALLOCATION
設定為1,則可以使用從FreeRTOS堆中自動分配的RAM建立RTOS物件。設定為0,則只能使用應用程式編寫器提供的RAM建立RTOS物件。預設(未定義)為1
31.configTOTAL_HEAP_SIZE
FreeRTOS堆中可用的RAM總量。該值僅在configSUPPORT_DYNAMIC_ALLOCATION
設定為 1 且應用程式使用FreeRTOS原始碼下載中提供的示例記憶體分配方案之一時該定義才有效。 有關詳細資訊,請參閱記憶體配置部分。
32. configAPPLICATION_ALLOCATED_HEAP
預設情況下,FreeRTOS堆由FreeRTOS宣告,並由連結器放置在記憶體中。 將configAPPLICATION_ALLOCATED_HEAP
設定為1允許應用程式編寫器宣告堆,這允許應用程式將堆放置在記憶體中的任何位置。
如果使用heap_1.c,heap_2.c或heap_4.c
,並且configAPPLICATION_ALLOCATED_HEAP
設定為1,那麼應用程式編寫器必須提供一個具有完全名稱和維度的uint8_t陣列,如下所示:
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
該陣列將用作FreeRTOS堆。 如何將陣列放置在特定的記憶體位置取決於使用的編譯器 - 請參閱您的編譯器文件。
33.configGENERATE_RUN_TIME_STATS
以下說明源自官方文件Run Time Statistics
設定為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 unsigned longulHighFrequencyTimerTicks;
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL )
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
34. configUSE_CO_ROUTINES
設定成1表示使用協程,0表示不使用協程。如果使用協程,必須在工程中包含croutine.c檔案。
注:協程(Co-routines)主要用於資源發非常受限的嵌入式系統(RAM非常少),通常不會用於32位微處理器。
在當前嵌入式硬體環境下,不建議使用協程,FreeRTOS的開發者早已經停止開發協程。
35. configMAX_CO_ROUTINE_PRIORITIES
應用程式協程(Co-routines)的有效優先順序數目,任何數目的協程都可以共享一個優先順序。使用協程可以單獨的分配給任務優先順序。見configMAX_PRIORITIES
。
36. configUSE_TIMERS
設定成1使用軟體定時器,為0不使用軟體定時器功能。詳細描述見Software Timers。
37. configTIMER_TASK_PRIORITY
設定軟體定時器服務/守護程序的優先順序。詳細描述見Software Timers。
38. configTIMER_QUEUE_LENGTH
設定軟體定時器命令佇列的長度。詳詳細描述見Software Timers。
39.configTIMER_TASK_STACK_DEPTH
設定軟體定時器服務/守護程序任務的堆疊深度。詳詳細描述見Software Timers。
40. configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY
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
,這兩個巨集是等價的,後者是前者的新名字,用於更新的移植層程式碼。
應該將configKERNEL_INTERRUPT_PRIORITY
設定為最低優先順序。
注意下面的描述中,在中斷服務例程中僅可以呼叫以“FromISR”結尾的API函式。
僅需要設定configKERNEL_INTERRUPT_PRIORITY
的硬體裝置
configKERNEL_INTERRUPT_PRIORITY
用來設定FreeRTOS核心自己的中斷優先順序。呼叫FreeRTOS API的中斷必須執行在這個優先順序;不呼叫FreeRTOS API的中斷,可以執行在更高的優先順序,所以這些中斷不會被因FreeRTOS核心活動而延時。
configKERNEL_INTERRUPT_PRIORITY
和configMAX_SYSCALL_INTERRUPT_PRIORITY
都需要設定的硬體裝置
configKERNEL_INTERRUPT_PRIORITY
用來設定FreeRTOS核心自己的中斷優先順序(因為FreeRTOS核心中斷不允許搶佔使用者使用的中斷,因此這個巨集一般定義為硬體最低優先順序)。configMAX_SYSCALL_INTERRUPT_PRIORITY
用來設定可以在中斷服務程式中安全呼叫FreeRTOS API函式的最高中斷優先順序。優先順序小於等於這個巨集所代表的優先順序時,程式可以在中斷服務程式中安全的呼叫FreeRTOS API函式;如果優先順序大於這個巨集所代表的優先順序,表示FreeRTOS無法禁止這個中斷,在這個中斷服務程式中絕不可以呼叫任何FreeRTOS API函式(即使是FromISR結尾的函式)。
通過設定configMAX_SYSCALL_INTERRUPT_PRIORITY的優先順序級別高於configKERNEL_INTERRUPT_PRIORITY可以實現完整的中斷巢狀模式。這意味著FreeRTOS核心不能完全禁止中斷,即使在臨界區。此外,這對於分段核心架構的微處理器是有利的。需要注意的是,當一個新中斷髮生後,某些微處理器架構會(在硬體上)禁止中斷,這意味著從硬體響應中斷到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)和中斷處理任務是三碼事:當中斷來臨時會進入中斷服務例程,中斷服務例程做必要的資料收集(更新),之後喚醒高優先順序任務。這個高優先順序任務在中斷服務例程結束後立即執行,它可能是其它任務也可能是中斷處理任務,如果是中斷處理任務,那麼就可以根據中斷服務例程中收集的資料做相應處理。
- 根據系統中的任何其他任務,中斷處理“任務”可以被寫入並優先化。 這些是由中斷喚醒的任務。 中斷服務程式(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
定義的優先順序。
ARM Cortex-M3和ARM Cortex-M4使用者的特別注意事項:請閱讀專用於中斷ARM Cortex-M器件優先順序設定的頁面。至少,請記住,ARM Cortex-M3核心使用數字優先順序較低的數字來表示高優先順序的中斷,這看起來是違反直覺的,很容易忘記!如果你想分配一個低優先順序的中斷,不要給它賦予0(或者其他低數字值)的優先順序,因為這會導致系統中實際上具有最高優先順序的中斷,因此如果這樣會導致系統崩潰優先順序高
configMAX_SYSCALL_INTERRUPT_PRIORITY
。實際上,ARM Cortex-M3核心的最低優先順序為255 - 不過,不同的ARM Cortex-M3供應商實現了不同數量的優先順序位,並提供了期望以不同方式指定優先順序的庫函式。例如,在STM32上,您可以在ST驅動程式庫呼叫中指定的最低優先順序實際上是15 - 您可以指定的最高優先順序為0。
41. configASSERT
configASSERT()
巨集的語義與標準C assert()巨集的語義相同。 如果傳遞給configASSERT()
的引數為零,則觸發斷言。
在整個FreeRTOS原始檔中呼叫configASSERT()
,以檢查應用程式如何使用FreeRTOS。 強烈建議使用configASSERT()
定義來開發FreeRTOS應用程式。
舉一個例子,我們想把非法引數所在的檔名和程式碼行數打印出來,可以先定義一個函式vAssertCalled,該函式有兩個引數,分別接收觸發configASSERT巨集的檔名和該巨集所在行,然後通過顯示屏或者串列埠輸出。程式碼如下:
#define configASSERT( ( x ) ) if( ( x ) == 0 )vAssertCalled( __FILE__, __LINE__ )
這裡__FILE__和__LINE__
是大多數編譯器預定義的巨集,分別表示程式碼所在的檔名(字串格式)和行數(整形)。
如果在偵錯程式的控制下執行FreeRTOS,那麼可以將configASSERT()
定義為僅禁用中斷並置於迴圈中,如下所示。 這將有效的停止在assert測試失敗的程式碼 - 暫停偵錯程式然後會立即帶你到違規行,所以你可以看到為什麼它失敗。
/* Define configASSERT() to disable interrupts and sit in a loop. */
#define configASSERT( ( x ) ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
這個例子雖然看起來很簡單,但由於要把整形__LINE__
轉換成字串再顯示,在效率和實現上,都不能讓人滿意。我們可以使用C標準庫assert的實現方法,這樣函式vAssertCalled只需要接收一個字串形式的引數(推薦仔細研讀下面的程式碼並理解其中的技巧):
#define STR(x) VAL(x)
#define VAL(x) #x
#define configASSERT(x) ((x)?(void) 0 :xAssertCalld(__FILE__ ":" STR(__LINE__) " " #x"\n"))
這裡稍微講解一下,由於內建巨集LINE是整數型的而不是字串型,把它轉化成字串需要一個額外的處理層。巨集STR和和巨集VAL正是用來輔助完成這個轉化。巨集STR用來把整形行號替換掉LINE,巨集VAL用來把這個整形行號字串化。忽略巨集STR和VAL中的任何一個,只能得到字串”LINE”,這不是我們想要的。
這裡使用三目運算子’?:’來代替引數判斷if語句,這樣可以接受任何引數或表示式,程式碼也更緊湊,更重要的是程式碼優化度更高,因為如果引數恆為真,在編譯階段就可以去掉不必要的輸出語句。
42. configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS
該值僅被FreeRTOS MPU使用。如果設定為1,那麼應用程式編寫器必須提供一個名為“application_defined_privileged_functions.h”的標頭檔案,在該標頭檔案中可以實現應用程式writer需要在特權模式下執行的功能。* 注意,儘管有.h副檔名,標頭檔案應該包含C函式的實現,而不僅僅是函式的原型(類似於ARM提供的Cortex-m的核心原始檔)。*
43. INCLUDE Parameters
以“INCLUDE”起始的巨集允許使用者不編譯那些應用程式不需要的實時核心元件(函式),這可以確保在你的嵌入式系統中RTOS佔用最少的ROM和RAM。
每個巨集以這樣的形式出現:
INCLUDE_FunctionName
這裡的FunctionName表示一個你可以控制是否編譯的API函式。如果你想使用該函式,就將這個巨集設定成1,如果不想使用,就將這個巨集設定成0。比如,對於API函式vTaskDelete():
#define INCLUDE_vTaskDelete 1
表示希望使用vTaskDelete(),允許編譯器編譯該函式
#define INCLUDE_vTaskDelete 0
表示禁止編譯器編譯該函式。