【阿福的隨筆】NXP i.MXRT 系列之淺談 嵌入的 OS 使用方式(中)

一、說明

NXP i.MXRT 通常都有嵌入的 OS,一般來說都是 RTOS 的 Free RTOS

FreeRTOS 是一種免費可使用  即時作業系統,但它比較精簡,沒有控制週邊,比較像是即時的分時多工的程序 ,但參數很多,到底要如何設定和使用呢? 在此做一些簡單的整理

 

以下是 FreeRTOS 的下載點

FreeRTOS V10.0.0

下載點 : https://sourceforge.net/projects/freertos/files/FreeRTOS/V10.0.0/FreeRTOSv10.0.0.zip/download

Amazon FreeRTOS

下載點 : https://github.com/aws/amazon-freertos

如何設定參數

參數都在 FreeRTOSConfig.h

https://www.freertos.org/a00110.html

 

二、FreeRTOS 的 'config' 參數如下 =>

 
1. configUSE_PREEMPTION

設置為1以使用搶占式RTOS調度程序,或設置為0以配合的方式使用RTOS調度程序。

搶占式表示操作系統完全決定進程調度方案,操作系統可以剝奪耗時長的進程的時間片,提供給其它進程; 配合的方式 ( 協作式 ) 表示下一個進程被調度的前提是當前進程主動放棄時間片;

圖出自https://www.rapitasystems.com/blog/cooperative-and-preemptive-scheduling-algorithms


2. configUSE_PORT_OPTIMISED_TASK_SELECTION

通用方式,沒有優化---配置宏定義configUSE_PORT_OPTIMISED_TASK_SELECTION為0:

所有平台的移植文件都可以配置為0,因為這是通用方式。

純C編寫,比專用方式效率低。可用的優先級數量不限制。

 

專用方式,進行優化---配置宏定義configUSE_PORT_OPTIMISED_TASK_SELECTION為1:

部分平台支持。

這些平台架構有專用的彙編指令,比如CLZ(Count Leading Zeros)指令,通過這些指令可以加速算法執行速度。比通用方式高效。

 
3. configUSE_TICKLESS_IDLE

宏定義 configUSE_TICKLESS_IDLE 為 1 即可。 如果配置此參數為 2,那麼用户可以自定義 tickless 低功耗模式的實現。

當用户將宏定義 configUSE_TICKLESS_IDLE 配置為 1 且系統運行滿足以下兩個條件( 3.1 & 3.2 ) 時,系統內核會自動的調用低功耗宏定義函數 portSUPPRESS_TICKS_AND_SLEEP()

3.1 當前空閒任務正在運行,所有其它的任務處在掛起狀態或者阻塞狀態。
3.2 根據用户配置 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 的大小,只有當系統可運行於低功耗模式的時鐘節拍數大於等於這個參數時,系統才可以進入到低功耗模式。 此參數默認已經在 FreeRTOS.h 文檔進行定義了,下面是具體的定義內容,當然,用户也可以在 FreeRTOSConfig.h 文檔中重新定義:

#ifndef configEXPECTED_IDLE_TIME_BEFORE_SLEEP

    #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2

#endif

 

#if configEXPECTED_IDLE_TIME_BEFORE_SLEEP < 2

    #error configEXPECTED_IDLE_TIME_BEFORE_SLEEP must not be less than 2

#endif

 

默認定義的大小是 2 個系統時鐘節拍,且用户自定義的話,不可以小於 2 個系統時鐘節拍。

 

函數 portSUPPRESS_TICKS_AND_SLEEP 是 FreeRTOS 實現 tickles 模式的關鍵,此函數被空閒任務

調用,其定義是在 portmacro.h 文檔中:

/* Tickless idle/low power functionality. */

#ifndef portSUPPRESS_TICKS_AND_SLEEP

    extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime );

    #define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) vPortSuppressTicksAndSleep( xExpectedIdleTime )

#endif

其中函數 vPortSuppressTicksAndSleep 是實際的低功耗執行代碼,在 port.c 文件中定義,參數 xExpectedIdleTime 就是系統可以處于低功耗模式的系統時鐘節拍數。

 

3.2.1 FreeRTOS 的低功耗模式配置 ( 跟此參數有關,在這裡說明一下 )
關於 FreeRTOS 低功耗方面的配置主要涉及到以下幾個問題。

滴答定時器頻率與系統主頻的關係
對於 Cortex-M3 和 M4 內核的單片機來説,實時操作系統一般都是採用滴答定時器做系統時鐘,FreeRTOS 也不例外。 SysTick 滴答定時器是一個 24 bits 的遞減計數器,有兩種時鐘源可選擇,一個是系統主頻,另一個是系統主頻的八分頻,默認的 port.c 移植文檔中是用的系統主頻。 這裏我們就根據這兩種時鐘源來説一説配置上的不同。


(1)SysTick 滴答定時器時鐘源選擇系統主頻
如果滴答定時器選擇系統主頻的話,那麼需要配置 configSYSTICK_CLOCK_HZ 等於
configCPU_CLOCK_HZ,這種關係已經在 Source/portable/RVDS/ARM_CM4F/fsl_tickless_generic.h文檔中進行默認配置了:

#ifndef configSYSTICK_CLOCK_HZ

#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ

/* Ensure the SysTick is clocked at the same frequency as the core. */

#define portNVIC_SYSTICK_CLK_BIT (1UL << 2UL)

#else

/* The way the SysTick is clocked is not modified in case it is not the same

as the core. */

#define portNVIC_SYSTICK_CLK_BIT (0)

#endif

其中系統主頻 configCPU_CLOCK_HZ 是在 FreeRTOSConfig.h 文檔中進行定義的。

有些 DEMO code 也會直接將 configSYSTICK_CLOCK_HZ 在 FreeRTOSConfig.h 文檔中進行定義。

這種情況的話,需要用户在 FreeRTOSConfig.h 文檔中專門配置 configSYSTICK_CLOCK_HZ 為實際的頻率。

 

(2) 系統時鐘節拍不使用滴答定時器
這種情況我們這裏不做討論,用户看 FreeRTOS 官網此處的説明即可:
http://www.freertos.org/low-power-ARM-cortex-rtos.html

(3) 如何使用單片機其它低功耗模式
前面我們説了,對 Cortex-M3 和 M4 內核來説,FreeRTOS 自帶的低功耗模式是通過指令 WFI 讓系統進入睡眠模式,如果想讓系統進入停機模式,又該怎麼修改呢?FreeRTOS 為我們提供了兩個函數:
configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
configPOST_SLEEP_PROCESSING( xExpectedIdleTime )
這兩個函數的定義是在 FreeRTOS.h 文檔中定義的,什麼都沒有執行:

#ifndef configPRE_SLEEP_PROCESSING

    #define configPRE_SLEEP_PROCESSING( x )

#endif

 

#ifndef configPOST_SLEEP_PROCESSING

    #define configPOST_SLEEP_PROCESSING( x )

#endif

如果需要實際執行代碼需要用户在 FreeRTOSConfig.h 文檔中重新進行宏定義,將其映射到一個實際的函數中。

 

4. configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
執行低功耗模式前,用户可以在這個函數裏面關閉外設時鐘來進一步降低系統功耗。 設置其它低功耗方式也是在這個函數裏面,用户只需設置參數 xExpectedIdleTime=0 即可屏蔽掉默認的 WFI ( Wait for interrupt ) 指令執行方式,因為退出這個函數後會通過 if 語句檢測此參數是否大於 0,即上面的代碼所示。 因此,如果用 户 想 實 現 其 它 低 功 耗 模 式 還 是 比 較 方 便 的 , 配 置 好 其 它 低 功 耗 模 式 後 , 設 置 參 數 xExpectedIdleTime = 0 即可,但切不可將此參數隨意設置為 0 以外的其它數值。


5. configPOST_SLEEP_PROCESSING ( xExpectedIdleTime )

退出低功耗模式後,此函數會得到調用,之前在 configPRE_SLEEP_PROCESSING 裏面關閉的外設時鍾,可以在此函數裏面重新打開,讓系統恢復到正常運行狀態。

FreeRTOS 實現 tickless 模式的框架對 Cortex-M3 和 M4 內核的單片機來説,FreeRTOS 已經提供了 tickless 低功耗模式的代碼,對於沒有支持的單片機,用户可以在 FreeRTOSConfig.h 文檔中配置 portSUPPRESS_TICKS_AND_SLEEP 宏定義,來映射實際執行函數。如果用户不想使用 FreeRTOS 提供的的 tickless 也可以自定義,方法也是在 FreeRTOSConfig.h 文檔中配置 portSUPPRESS_TICKS_AND_SLEEP 宏定義,來映射實際執行函數。下面是 FreeRTOS 實現低功耗 tickless 模式的代碼框架,方便用户對 tickles 模式有一個認識,同時也方便 FreeRTOS 沒有支持的單片機,用户可以參考實現。 當然,不侷限於這種方法,用户有更好的方法,也可以的。 其中函數 vTaskStepTick 和 eTaskConfirmSleepModeStatus 是 FreeRTOS 提供的,其餘的函數是需要用户實現的。


6. configUSE_IDLE_HOOK

HOOK函數:HOOK翻譯出來的意思是鉤子、鉤住,所以也叫“鉤子函數”,可以理解為鉤住你想要的東西。 HOOK函數實際上是操作系統消息處理機制的程序段,通過系統調用,把它掛入系統。 HOOK函數可以說是操作系統必有的一類函數,像Windows、Linux以及嵌入式 RTOS都有這類鉤子函數。HOOK函數是提供給開發者的函數,實現自己想要實現的東西。比如:RTOS中IDLE空閒任務就提供了一個HOOK函數,想要對其計數,計數到10000,就打印出來(就是實現了自己想要實現的東西)。

7. configUSE_MALLOC_FAILED_HOOK

定義了configUSE_MALLOC_FAILED_HOOK == 1 後, 當申請失敗的時候會調用鉤子函數, 也可以自己添加其他處理代碼。

在 heap_1.c, heap_2.c, heap_3.c, heap_4.c and heap_5.c

void *pvPortMalloc( size_t xWantedSize )

{





#if( configUSE_MALLOC_FAILED_HOOK == 1 )

{

if( pvReturn == NULL )

{

extern void vApplicationMallocFailedHook( void );

vApplicationMallocFailedHook();

}

}

#endif



return pvReturn;

}

每當一個任務,隊列,信號量被創建時,內核若使用 pvPortMalloc分配內存的話,FreeRTOS 提供了5個簡單內存分配策略 => Heap 1~5  ( 只能使用其中1種 )
(1) Heap_1 比較簡單,按順序分配,所以只需要判斷剩下的內存夠大,直接切出來,更新已分配大小的值,返回地址就可以了

(2) Heap_2 內存分配使用最佳匹配算法(best fit algorithm),比如我們申請35k的內存,而可申請內存中有四塊對應大小25K, 50K ,75K和100 K,按照最小匹配,這時候會把50k進行分割並返回申請內存的地址,剩餘部分插回link Table留待下次申請。
Heap_2 支持內存回收,但是不會把碎片合併,對於每次申請內存大小都比較固定的,這個方式是沒有問題的。
開始和 Heap_1 差不多, 在內存中開闢了一個靜態數組作為堆疊( Heap )的空間,定義大小,字節對齊處理等。

(3) Heap_3 實現是直接對標準庫的 malloc 進行封裝,保證流程安全。
這種模式下,堆疊大小不再由 FreeRTOSConfig.h 中定義的常量 configTOTAL_HEAP_SIZE 決定,而是由連接器或者啟動代碼決定。

(4) Heap_4 與 Heap_2 相似,相比 Heap_2, Heap_4 能夠把內存碎片合併成大塊內存,為了實現這個合併算法,空閒內存的 link table是按內存地址大小進行存儲的( Heap_2 是按照內存塊大小進行存儲 )。相比 Heap_2 差別不大,主要是在分配過程多了一個位標記防止出錯。

(5) Heap_5 是允許使用多個不連續的區域組成堆疊,與前面方式1、2和4 方式都是靜態申請一個數組作為堆疊不同,申請函數前,必須通過函數vPortDefineHeapRegions() 進行設置,對於一些內存分佈不連續的嵌入式設備還是很有價值的,之後其他操作,和Heap_4 基本一致。


8. configUSE_DAEMON_TASK_STARTUP_HOOK

如果configUSE_TIMERS和configUSE_DAEMON_TASK_STARTUP_HOOK都設置為1,那麼應用程序必須定義一個鉤子函數,其名稱和原型如下所示:
void void vApplicationDaemonTaskStartupHook( void ); 
當RTOS守護程序任務(也稱為定時器服務任務)第一次執行時,上述的鉤子函數將被精確調用一次。 

需要RTOS運行的任何應用程序初始化代碼都可以放在hook函數中。


9. configUSE_TICK_HOOK

設置為1使用時間片鉤子(Tick Hook),0忽略時間片鉤子。
時間片中斷可以周期性的調用一個被稱為鉤子函數(回調函數)的應用程序。  時間片鉤子函數可以很方便的實現一個定時器功能。
只有在FreeRTOSConfig.h中的configUSE_TICK_HOOK設置成1時才可以使用時間片鉤子。 

一旦此值設置成1,就要定義鉤子函數,函數名和參數如下所示:
  void vApplicationTickHook( void );
  vApplicationTickHook()函數在中斷服務程序中執行,因此這個函數必須非常短小,不能大量使用堆棧,不能調用以”FromISR” 或 “FROM_ISR”結尾的API函數。  



10. configCPU_CLOCK_HZ

寫入實際的CPU內核時鐘頻率,也就是CPU指令執行頻率。配置此值是為了正確的配置系統節拍中斷週期。常定義成系統內核時間 =>

#define configCPU_CLOCK_HZ ( SystemCoreClock )



11. configTICK_RATE_HZ

RTOS 系統自己的節拍中斷的頻率。即一秒中斷的次數,每次中斷RTOS都會進行任務調度。
系統節拍中斷用來測量時間,因此,越高的測量頻率意味著可測到越高的分辨率時間。  但是,高的系統節拍中斷頻率也意味著RTOS內核佔用更多的CPU時間,因此會降低效率。 RTOS演示例程都是使用系統節拍中斷頻率為1000HZ,這是為了測試RTOS內核,比實際使用的要高。 (實際使用時不用這麼高的系統節拍中斷頻率)
多個任務可以共享一個優先級,RTOS調度器為相同優先級的任務分享CPU時間,在每一個RTOS 系統節拍中斷到來時進行任務切換。  高的系統節拍中斷頻率會降低分配給每一個任務的“時間片”持續時間。



12. configMAX_PRIORITIES

配置應用程序有效的優先級數目。任何數量的任務都可以共享一個優先級,使用協程可以單獨的給與它們優先權。見configMAX_CO_ROUTINE_PRIORITIES。
在RTOS內核中,每個有效優先級都會消耗一定量的RAM,因此這個值不要超過你的應用實際需要的優先級數目。  

每一個任務都會被分配一個優先級,優先級值從0~(configMAX_PRIORITIES - 1)之間。  低優先級數表示低優先級任務。空閒任務的優先級為0(tskIDLE_PRIORITY),因此它是最低優先級任務。
FreeRTOS調度器將確保處於就緒狀態(Ready)或運行狀態(Running)的高優先級任務比同樣處於就緒狀態的低優先級任務優先獲取處理器時間。換句話說,處於運行狀態的任務永遠是高優先級任務。處於就緒狀態的相同優先級任務使用時間片調度機制共享處理器時間。  



13. configMINIMAL_STACK_SIZE

任務堆棧的最小大小,FreeRTOS根據這個參數來給idle task 分配堆棧空間。這個值如果設置的比實際需要的空間小,會導致程序掛掉。因此,最好不要減小Demo 程序中給出的大小。



14. configMAX_TASK_NAME_LEN

任務名稱最大的長度,這個長度是以字節為單位的,並且包括最後的 NULL 結束字節。

 

15. configUSE_TRACE_FACILITY

如果程序中需要用到TRACE功能,則需將這個宏設為1。否則設為0。開啟TRACE功能後,會激活一些附加的結構體成員和函數,RAM佔用量會增大許多,因此在設為1之前請三思。



16. configUSE_STATS_FORMATTING_FUNCTIONS

設置configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS為1,則vTaskList()和vTaskGetRunTimeStats()編譯。任意一個設置為0,都會使得跳過編譯這兩個函數。



17. configUSE_16_BIT_TICKS

設置系統節拍計數器變量數據類型,系統節拍計數器變量類型為16 bits or 32 bits,設1為16 bits


18. configIDLE_SHOULD_YIELD

 控制任務處在空閒模式(IDLE)時,控制同等優先級的其他用戶任務的行為。


0:空閒任務不為其他同優先級的任務讓出CPU使用權。
1:空閒任務會為其他同優先級的任務讓出CPU使用權,花費在空閒任務上的時間會變少,也有了副作用如圖。

圖出自https://www.freertos.org/a00110.html

優先權 B>C>A =I , A 和 I的優先權相同且同時使用了一個時間片段( T2~T3,T5~T6 ),造成了A的時間變短了,讓給 I  使用。


19. configUSE_TASK_NOTIFICATIONS

設置為1,啟動任務通知功能,相關API會被編譯。開啟此功能,

每個任務會多增加8 bytes 的 RAM 使用空間。



20. configUSE_MUTEXES 

設置為1,使用互斥信號量,為0 使用二進制信號量

 

二進制信號量和互斥量非常相似,但也有細微的區別:互斥量具有優先級繼承機制,二進制信號量沒有這個機制這使得二進制信號量更適合用於同步(任務之間或者任務和中斷之間),互斥量更適合互鎖。互斥量和二進制信號量都是SemaphoreHandle_t類型。


(1) 二進制信號量
一旦獲得二進制信號量後不需要恢復,一個任務或中斷不斷的產生信號,而另一個任務不斷的取走這個信號,通過這樣的方式來實現同步。


(2) 互斥量
互斥量是一個包含優先級繼承機制的二進制信號量。

若用於實現同步(任務之間或者任務與中斷之間)的,優先考慮使用二進制信號量,互斥量通常用於簡單的互鎖。中斷不能因為等待互斥量而阻塞。
低優先級任務擁有互斥量的時候,如果另一個高優先級任務也企圖獲取這個互斥量,則低優先級任務的優先級會被臨時提高,提高到和高優先級任務相同的優先級。這意味著互斥量必須要釋放,否則高優先級任務將不能獲取這個互斥量,並且那個擁有互斥量的低優先級任務也永遠不會被剝奪,這就是操作系統中的優先級翻轉。



21. configUSE_RECURSIVE_MUTEXES

設置為1,啟用遞迴互斥信號量



22. configUSE_COUNTING_SEMAPHORES

是否用計數式的SEMAPHORES,SEMAPHORES也是任務間通訊的一種方式

semaphore用來讓一個task喚醒(wake)另一個task做事

1:使用計數訊號量,0:不使用



23. configUSE_ALTERNATIVE_API

設置成1表示使用“替代”隊列函數('alternative'queue functions),設置成0不使用。替代API在queue.h頭文件中有詳細描述。注:“替代”隊列函數已經被棄用,在新的設計中不要使用它!



24. configCHECK_FOR_STACK_OVERFLOW

每個任務維護自己的棧空間,任務創建時會自動分配任務需要的佔內存,分配內存大小由創建任務函數(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 bytes,確保標記數據沒有被覆蓋。如果這16 bytes有任何一個被改變,則調用堆棧溢出鉤子函數。這個方法比第一種方法要慢,但也相當快了。它能有效捕捉堆棧溢出事件(即使堆棧溢出沒有發生在上下文切換時),但是理論上它也不能百分百的捕捉到所有堆棧溢出(比如堆棧溢出的值和標記值相同,當然,這種情況發生的概率極小)。

使用這個方法需要設置configCHECK_FOR_STACK_OVERFLOW為2.


25. configQUEUE_REGISTRY_SIZE

此參數是記錄佇列和訊號量的最大數目,隊列記錄有兩個目的,都涉及到RTOS內核的調試:


(1) 它允許在調試GUI中使用一個隊列的文本名稱來簡單識別隊列;
(2) 包含調試器需要的每一個記錄隊列和信號量定位信息;

除了進行內核調試外,隊列記錄沒有其它任何目的。
configQUEUE_REGISTRY_SIZE定義可以記錄的隊列和信號量的最大數目。如果你想使用RTOS內核調試器查看隊列和信號量信息,則必須先將這些隊列和信號量進行註冊,只有註冊後的隊列和信號量才可以使用RTOS內核調試器查看。查看API參考手冊中的vQueueAddToRegistry() 和vQueueUnregisterQueue()函數獲取更多信息。



26. configUSE_QUEUE_SETS

設置成1使能 queue 功能(可以阻塞、掛起到多個隊列和信號量),設置成0取消queue功能。需配合 configQUEUE_REGISTRY_SIZE 使用。

 


27. configUSE_TIME_SLICING 

FreeRTOS(V7.5.0新增)時間片段切割
默認情況下(宏configUSE_TIME_SLICING未定義或者宏configUSE_TIME_SLICING設置為1),FreeRTOS使用基於時間片的優先級搶占式調度器。這意味著RTOS調度器總是運行處於最高優先級的就緒任務,在每個RTOS 系統節拍中斷時在相同優先級的多個任務間進行任務切換。如果宏configUSE_TIME_SLICING設置為0,RTOS調度器仍然總是運行處於最高優先級的就緒任務,但是當RTOS 系統節拍中斷發生時,相同優先級的多個任務之間不再進行任務切換。



28. configENABLE_BACKWARD_COMPATIBILITY

FreeRTOS.h包含一系列#define宏定義,用來映射版本V8.0.0和V8.0.0之前版本的數據類型名字。這些宏可以確保RTOS內核升級到V8.0.0或以上版本時,之前的應用代碼不用做任何修改。在FreeRTOSConfig.h文件中設置宏configENABLE_BACKWARD_COMPATIBILITY為0會去掉這些宏定義,並且需要用戶確認升級之前的應用沒有用到這些名字。



29. configNUM_THREAD_LOCAL_STORAGE_POINTERS

設置每個任務 ( Task ) 的線程 ( Thread ) 本地存儲 Index數組大小。

FreeRTOS提供了一個靈活的機制,使得應用程序可以使用線程本地存儲Index pointer來讀寫線程本地存儲。線程本地存儲允許應用程序在任務的控制塊中存儲一些值,每個任務都有自己獨立的儲存空間,configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每個任務線程本地存儲Index pointer數組的大小。

API函數vTaskSetThreadLocalStoragePointer()用於向 Index pointer數組中寫入值,API函數pvTaskGetThreadLocalStoragePointer()用於從Index pointer數組中讀取值。

比如,許多庫函數都包含一個叫做errno的全局變量。某些庫函數使用errno返回庫函數錯誤信息,應用程序檢查這個全局變量來確定發生了那些錯誤。在單線程程序中,將errno定義成全局變量是可以的,但是在多線程應用中,每個線程(任務)必須具有自己獨有的errno值,否則,一個任務可能會讀取到另一個任務的errno值。


30. configSUPPORT_STATIC_ALLOCATION

使用靜態方式創建任務時需要使將 configSUPPORT_STATIC_ALLOCATION設置為 1,即使用靜態內存。

還需要將函數vApplicationGetIdleTaskMemory()和 ApplicationGetTimerTaskMemory()進行實現。通過這兩個函數來給空閑任務 和定時器服的任務堆 棧和任務控制塊分配內存。在maiinc.c中進行定義。定義靜態的任務堆棧和任務控制塊內存,然後將這些內存傳遞給函數參數。最後創建空閑任務和定時器任務的API函數會調用這兩個函數。



31. configSUPPORT_DYNAMIC_ALLOCATION

configSUPPORT_DYNAMIC_ALLOCATION設置為1時,FreeRTOS 支持 5 種動態內存管理方案,分別通過文件 heap_1,heap_2,heap_3,heap_4 和 heap_5實現,這 5 個文件在 FreeRTOS 軟件包中的路徑是:FreeRTOS\Source\portable\MemMang。用戶創建的 FreeRTOS 工程項目僅需要 5 種方式中的一種。這 5 種動態內存管理方式在 configUSE_MALLOC_FAILED_HOOK,已有進行講解。



32. configTOTAL_HEAP_SIZE

RTOS內核總計可用的有效的RAM大小。僅在你使用官方下載包中附帶的內存分配策略時,才有可能用到此值。每當創建任務、隊列、互斥量、軟件定時器或信號量時,RTOS內核會為此分配RAM,這裡的RAM都屬於configTOTAL_HEAP_SIZE指定的內存區。在動態內存管理的內存配置會詳細講到官方給出的內存分配策略。



33. configAPPLICATION_ALLOCATED_HEAP

configAPPLICATION_ALLOCATED_HEAP 該宏定義為1時用戶可以自己設置堆內存,堆內存的詳細代碼在heap_1.c到heap_5.c中。具體取決於用戶選擇哪種內存管理方式。網上都推薦使用4。



34. configGENERATE_RUN_TIME_STATS

configGENERATE_RUN_TIME_STATS 置為 1開啟時間統計功能,開啟時間統計功能,相應的 API函數會被編譯,為 0時關閉間統計功能。時關閉間統計功能。

設置宏configGENERATE_RUN_TIME_STATS為1使能運行時間統計功能。一旦設置為1,則下面兩個marco必須被定義:
(1)portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():用戶程序需要提供一個基準時鐘函數,函數完成初始化基準時鐘功能,這個函數要被define到portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()上。這是因為運行時間統計需要一個比系統節拍中斷頻率還要高分辨率的基准定時器,否則,統計可能不精確。基准定時器中斷頻率要比統節拍中斷快10~100倍。基准定時器中斷頻率越快,統計越精準,但能統計的運行時間也越短(比如,基准定時器10ms中斷一次,8位無符號整形變量可以計到2.55秒,但如果是1秒中斷一次,8位無符號整形變量可以統計到255秒)。


(2)portGET_RUN_TIME_COUNTER_VALUE():用戶程序需要提供一個返回基準時鐘當前“時間”的函數,這個函數要被define到portGET_RUN_TIME_COUNTER_VALUE()上。

舉一個例子,假如我們配置了一個定時器,每500us中斷一次。在定時器中斷服務例程中簡單的使長整形變量ulHighFrequencyTimerTicks自增。那麼上面提到兩個marco定義如下(可以在FreeRTOSConfig.h中添加):

extern volatile unsigned longulHighFrequencyTimerTicks;
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL )
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTick



35. configUSE_CO_ROUTINES

設成1表示使用協程,0表示不使用協程。如果使用協程,必須在工程中包含croutine.c文件。

協程(Co-routines)主要用於資源發非常受限的嵌入式系統(RAM非常少),通常不會用於32位微處理器。在當前嵌入式硬件環境下,不建議使用協程,FreeRTOS的開發者早已經停止開發協程。



36. configMAX_CO_ROUTINE_PRIORITIES

應用程序協程(Co-routines)的有效優先級數目,任何數目的協程都可以共享一個優先級。使用協程可以單獨的分配給任務優先級。見configMAX_PRIORITIES。



37. configUSE_TIMERS

configUSE_TIMERS設置成1使用軟件定時器,為0不使用軟件定時器功能。



38. configTIMER_TASK_PRIORITY

設置軟件定時器服務/守護進程的優先級



39. configTIMER_QUEUE_LENGTH

設置軟件定時器命令隊列的長度



40. configTIMER_TASK_STACK_DEPTH

設置軟件定時器服務/守護進程任務的堆棧深度



41. configKERNEL_INTERRUPT_PRIORITY

configMAX_SYSCALL_INTERRUPT_PRIORITY and
configMAX_API_CALL_INTERRUPT_PRIORITY

 

這是移植和應用FreeRTOS出錯最多的地方,需注意。

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,這兩個marco是相同的意義,後者是前者的新名字,用於更新的移植層代碼。

注意下面的描述中,在中斷服務例程中僅可以調用以“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



為什麼Interrupt Handler內使用FreeRTOS時,要使用FromISR()結尾的API?

FreeRTOS提供的API中,有一類是FromISP結尾的,像是

  • xSemaphoreGiveFromISR()
  • xSemaphoreTakeFromISR()
  • xQueueSendFromISR()
  • xQueueReceiveFromISR()
  • ……etc

事實上,所有From_ISR開頭的API和一般API的差別是多了一個參數,叫做
signed BaseType_t *pxHigherPriorityTaskWoken

這個參數的用意是,通常當你使用.......FromISR()這類的API時,是為了做communication。告訴系統某個資源已經釋放、某個任務要執行了。作法是讓這個參數紀錄,是否有優先權更高的Task被喚醒了。

xHigherPriorityTaskWoken = pdFALSE;xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );

因此在ISR的結尾,要記得呼叫

portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

意思是,如果xHigherPriorityTaskWoken被改成True,那就發生Context-Switch,就能去執行優先權更高的那個任務。



42. configASSERT

/* Define configASSERT() to disable interrupts and sit in a loop. */

#define configASSERT( ( x ) )     if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }

 

Assert 可作為程式檢查的一種機制,若是檢查失敗,一般會停止執行或是卡住在迴圈中。在執行期間若是 Assert 不成立,就可專注於在此程式段間邏輯不一致的部份,並設法修正。

 

三、參考資料

https://zh.wikipedia.org/wiki/FreeRTOS

http://wiki.csie.ncku.edu.tw/embedded/freertos

https://www.rapitasystems.com/blog/cooperative-and-preemptive-scheduling-algorithms

★博文內容均由個人提供,與平台無關,如有違法或侵權,請與網站管理員聯繫。

★文明上網,請理性發言。內容一周內被舉報5次,發文人進小黑屋喔~

評論