以下轉載自安富萊電子: http://forum.armfly.com/forum.php
本章節為大家講解 FreeRTOS 本身支持的低功耗模式 tickless 實現方法,tickless 低功耗機制是當前
小型 RTOS 所采用的通用低功耗方法,比如 embOS,RTX 和 uCOS-III(類似方法)都有這種機制。
tickless 低功耗模式介紹
tickless 低功耗機制是當前小型 RTOS 所采用的通用低功耗方法,比如 embOS,RTX 和 uCOS-III (類
似方法)都有這種機制。
FreeRTOS 的低功耗也是采用的這種方式,那么 tickless 又是怎樣一種模式呢?僅從字母上看 tick 是
滴答時鍾的意思,less 是 tick 的后綴,表示較少的,這里的含義可以表示為無滴答時鍾。 整體看這個字母
就是表示滴答時鍾節拍停止運行的情況。
反映在 FreeRTOS 上,tickless 又是怎樣一種情況呢?我們都知道,當用戶任務都被掛起或者阻塞時,
最低優先級的空閑任務會得到執行。 那么 STM32 支持的睡眠模式,停機模式就可以放在空閑任務里面實
現。 為了實現低功耗最優設計,我們還不能直接把睡眠或者停機模式直接放在空閑任務就可以了。 進入空
閑任務后,首先要計算可以執行低功耗的最大時間,也就是求出下一個要執行的高優先級任務還剩多少時
間。 然后就是把低功耗的喚醒時間設置為這個求出的時間,到時間后系統會從低功耗模式被喚醒,繼續執
行多任務。這個就是所謂的 tickless 模式。 從上面的講解中可以看出,實現 tickless 模式最麻煩是低功耗
可以執行的時間如何獲取。 關於這個問題,FreeRTOS 已經為我們做好了。
FreeRTOS 的低功耗模式介紹
對於 Cortex-M3 和 M4 內核來說,FreeRTOS 已經提供了 tickless 低功耗代碼的實現,通過調用指
令 WFI 實現睡眠模式,具體代碼的實現就在 port.c 文件中,用戶只需在 FreeRTOSConfig.h 文件中配置
宏定義 configUSE_TICKLESS_IDLE 為 1 即可。 如果配置此參數為 2,那么用戶可以自定義 tickless 低功
耗模式的實現。 當用戶將宏定義 configUSE_TICKLESS_IDLE 配置為 1 且系統運行滿足以下兩個條件時,
系統內核會自動的調用低功耗宏定義函數 portSUPPRESS_TICKS_AND_SLEEP():
-------------------------------
當前空閑任務正在運行,所有其它的任務處在掛起狀態或者阻塞狀態。
根據用戶配置 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 的大小,只有當系統可運行於低功耗模
式的時鍾節拍數大於等於這個參數時,系統才可以進入到低功耗模式。 此參數默認已經在 FreeRTOS.h
文件進行定義了,下面是具體的定義內容,當然,用戶也可以在 FreeRTOSConfig.h 文件中重新定義:
默認定義的大小是 2 個系統時鍾節拍,且用戶自定義的話,不可以小於 2 個系統時鍾節拍。
---------------------------------
函數 portSUPPRESS_TICKS_AND_SLEEP 是 FreeRTOS 實現 tickles 模式的關鍵,此函數被空閑任務
調用,其定義是在 portmacro.h 文件中:
FreeRTOS 在線電子手冊低功耗的說明 http://www.freertos.org/low-power-tickless-rtos.html。
FreeRTOS 的低功耗模式配置
關於 FreeRTOS 低功耗方面的配置主要涉及到以下幾個問題。
- 滴答定時器頻率與系統主頻的關系
對於 Cortex-M3 和 M4 內核的微控制器來說,實時操作系統一般都是采用滴答定時器做系統時鍾,
FreeRTOS 也不例外。 SysTick 滴答定時器是一個 24bit 的遞減計數器,有兩種時鍾源可選擇,一個是系
統主頻,另一個是系統主頻的八分頻,默認的 port.c 移植文件中是用的系統主頻。 這里我們就根據這兩種
時鍾源來說一說配置上的不同。
SysTick 滴答定時器時鍾源選擇系統主頻
如果滴答定時器選擇系統主頻的話,那么需要配置 configSYSTICK_CLOCK_HZ 等於
configCPU_CLOCK_HZ,這種關系已經在 port.c 文件中進行默認配置了:其中系統主頻 configCPU_CLOCK_HZ 是在 FreeRTOSConfig.h 文件中進行定義的。
SysTick 滴答定時器時鍾源選擇系統主頻的八分頻
這種情況的話,需要用戶在 FreeRTOSConfig.h 文件中專門配置 configSYSTICK_CLOCK_HZ 為實際
的頻率,即系統主頻的八分頻大小。 - 系統時鍾節拍不使用滴答定時器
這種情況我們這里不做討論,用戶看 FreeRTOS 官網此處的說明即可:
http://www.freertos.org/low-power-ARM-cortex-rtos.html - 如何使用微控制器其它低功耗模式
前面我們說了,對 Cortex-M3 和 M4 內核來說,FreeRTOS 自帶的低功耗模式是通過指令 WFI 讓系
統進入睡眠模式,如果想讓系統進入停機模式,又該怎么修改呢?FreeRTOS 為我們提供了兩個函數:
configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
configPOST_SLEEP_PROCESSING( xExpectedIdleTime )
這兩個函數的定義是在 FreeRTOS.h 文件中定義的,什么都沒有執行:如果需要實際執行代碼需要用戶在 FreeRTOSConfig.h 文件中重新進行宏定義,將其映射到一個實際的函
數中。 另外,這兩個函數是在 port.C 文件中被函數 vPortSuppressTicksAndSleep 調用,具體位置如下:這兩個函數位於指令 wfi 的前面和后面,用戶想實現其它低功耗方式的關鍵就在這兩個函數里面:
configPRE_SLEEP_PROCESSING( xExpectedIdleTime )
執行低功耗模式前,用戶可以在這個函數里面關閉外設時鍾來進一步降低系統功耗。 設置其它低功耗
方式也是在這個函數里面,用戶只需設置參數 xExpectedIdleTime=0 即可屏蔽掉默認的 wfi 指令執
行方式,因為退出這個函數后會通過 if 語句檢測此參數是否大於 0,即上面的代碼所示。 因此,如果
用 戶 想 實 現 其 它 低 功 耗 模 式 還 是 比 較 方 便 的 , 配 置 好 其 它 低 功 耗 模 式 后 , 設 置 參 數
xExpectedIdleTime = 0 即可,但切不可將此參數隨意設置為 0 以外的其它數值。
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 提供的,其
余的函數是需要用戶實現的。
顯然用戶自己配置要麻煩得多,好在FreeRTOS為M3,M4內核的做好了低功耗之睡眠模式,使得我們使用STM32 F1,F4系列的時候可以很簡單,只需要在配置文件config.h中加上一個宏定義:#define configUSE_TICKLESS_IDLE 1
那我們要實現停機模式呢?那就只有自己動手了。
在介紹實現停機模式之前,有必要提醒一下:在低功耗的時候,要根據情況配置
回到正題,如何添加用戶函數實現停機模式:
* 說 明 : 本實驗主要學習FreeRTOS的低功耗(tickless之停機模式) * 實驗目的: * 1. 學習FreeRTOS的低功耗(tickless之停機模式) * 2. FreeRTOS自帶的tickless 模式使用比較簡單,只需用戶使能宏配置: * #define configUSE_TICKLESS_IDLE 1 * 3. 為了打印系統信息,前面的所有試驗中始化一個定時器中斷,精度高於滴答定時器中斷, * 每50us進一次,本例子關閉了任務執行情況打印功能,因為高頻率的定時器中斷影響低 * 功耗tickless模式效率。 * 4. 本實驗在FreeRTOS自帶的tickless睡眠模式的基礎上實現停機模式,實現方法是重新 * 在文件FreeRTOSConfig.h定義如下函數兩個函數 * #define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x) * #define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x) * 在文件port.c里面函數vPortSuppressTicksAndSleep調用了上面這兩個函數: * --------------------------------------------------------------------- * configPRE_SLEEP_PROCESSING( xModifiableIdleTime ); * if( xModifiableIdleTime > 0 ) * { * __dsb( portSY_FULL_READ_WRITE ); * __wfi(); * __isb( portSY_FULL_READ_WRITE ); * } * configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); * ----------------------------------------------------------------------- * 函數OS_PreSleepProcessing和OS_PostSleepProcessing是需要用戶提供的,用戶可以在 * 這兩個函數里面實現進入停機模式和從停機模式恢復到正常的運行狀態。另外,通過在函數 * OS_PreSleepProcessing里設置其形參變量vParameters為0就可以屏蔽原有的休眠模式。
/* ********************************************************************************************************* * 函 數 名: OS_PreSleepProcessing * 功能說明: 下面的函數在文件FreeRTOSConfig.h文件里面進行了宏定義: * #define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x) * #define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x) * 在文件port.c里面函數vPortSuppressTicksAndSleep調用了上面這兩個函數: * --------------------------------------------------------------------- * configPRE_SLEEP_PROCESSING( xModifiableIdleTime ); * if( xModifiableIdleTime > 0 ) * { * __dsb( portSY_FULL_READ_WRITE ); * __wfi(); * __isb( portSY_FULL_READ_WRITE ); * } * configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); * ----------------------------------------------------------------------- * 通過這兩個函數可以實現在調用__WFI或者__WFE指令前后執行進一步的低功耗操作,主要有以下三種: * 1. 降低系統主頻。 * 2. 關閉外設時鍾。 * 3. IO引腳要做處理,防止拉電流和灌電流增加功耗。 * 如果此IO口帶上拉,請設置為高電平輸出或者高阻態輸入; * 如果此IO口帶下拉,請設置為低電平輸出或者高阻態輸入; * 下面的函數未做關閉外設時鍾的處理。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void OS_PreSleepProcessing(uint32_t vParameters) { (void)vParameters; /* 用戶可以考慮在此處加入關閉外設時鍾來進一步降低功耗 */ vParameters = 0; PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE); } void OS_PostSleepProcessing(uint32_t vParameters) { /* 如果前面關閉了外設時鍾,需要在這里恢復 */ /* 1、當一個中斷或喚醒事件導致退出停止模式時,HSI RC振盪器被選為系統時鍾。 2、退出低功耗的停機模式后,需要重新配置使用HSE。 */ RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET){} RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){} RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08){} }