本文主要學習 FreeRTOS 低功耗的相關知識,包括HAL 庫基礎時鐘、FreeRTOS 基礎時鐘、低功耗處理和 Tickless 模式等知識 ...
1、準備材料
STM32CubeMX軟體(Version 6.10.0)
Keil µVision5 IDE(MDK-Arm)
2、學習目標
本文主要學習 FreeRTOS 低功耗的相關知識,包括HAL 庫基礎時鐘、FreeRTOS 基礎時鐘、低功耗處理和 Tickless 模式等知識
3、前提知識
3.1、HAL 庫基礎時鐘
當我們使用 STM32CubeMX 軟體配置一個基本的工程時,往往需要首先在 Pinout & Configuration 頁面 RCC 中配置 HSE 和 LSE ,然後在 SYS 中配置 Debug 和 Timebase Source,這些都是必不可少的配置步驟,其中 Timebase Source 可以選擇預設的 SysTick ,也可以選擇任何一個定時器外設
3.1.1、使用 SysTick 定時器
學習 STM32 HAL 庫開發,在 SYS 中配置 Timebase Source 時,一般將時基源保持預設的 SysTick 即可,那麼這個預設的 SysTick 是如何被初始化以及使用呢?
3.1.1.1、工作原理
打開 “STM32CubeMX教程1 工程建立” 文章配置的 STM32 空工程,找到 main.c 文件中的 main() 主函數,SysTick 在主函數第一個被執行的函數HAL_Init() 中得到初始化,具體如下圖所示
其中滴答定時器頻率 uwTickFreq 參數預設為 HAL_TICK_FREQ_DEFAULT(1KHZ) ,當然也可以根據需要修改為 10HZ 和 100HZ,如下述枚舉類型定義
typedef enum
{
HAL_TICK_FREQ_10HZ = 100U,
HAL_TICK_FREQ_100HZ = 10U,
HAL_TICK_FREQ_1KHZ = 1U,
HAL_TICK_FREQ_DEFAULT = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;
當初始化完畢之後,滴答定時器就會以固定頻率發生中斷,然後進入中斷回調函數 SysTick_Handler() 中,滴答定時器中斷預設就會開啟
3.1.1.2、中斷處理
在 STM32CubeMX 軟體的 NVIC 管理頁面,可以發現預設開啟的滴答定時器中斷 Time base: System tick timer ,在軟體上該中斷不可關閉,但是可以設置中斷優先順序,具體如下圖所示
在 stm32f4xx_it.c 文件中可以找到滴答定時器的回調函數 SysTick_Handler() ,其只調用了 HAL_IncTick() 函數,該函數只做了一件事情,就是每次發生滴答定時器中斷的時候,將一個名為 uwTick 的全局變數加 1 ( uwTickFreq 參數預設為1),具體如下所示
根據這個全局變數的值我們就可以做一些延時的工作,比如常用到的 HAL_Delay() 延時函數就是通過滴答定時器中斷來實現的,具體如下所述
/**
* @brief HAL 庫延時函數
* @param Delay:延時時間,單位為ms
* @retval None
*/
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* 最少等待一個頻率時間 */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
/* 空迴圈延時等待 */
while((HAL_GetTick() - tickstart) < wait)
{
}
}
另外還有 HAL_SuspendTick() 和 HAL_ResumeTick() 兩個控制滴答定時器中斷停止和啟動的函數,具體如下所述
/**
* @brief 掛起滴答定時器中斷
* @retval None
*/
__weak void HAL_SuspendTick(void)
{
/* 禁用 SysTick 中斷 */
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
}
/**
* @brief 恢復掛起的滴答定時器中斷
* @retval None
*/
__weak void HAL_ResumeTick(void)
{
/* 使能 SysTick 中斷 */
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
}
3.1.2、使用其他定時器
當 SysTick 被其他軟體使用時(比如本系列教程的 FreeRTOS),STM32 還可以選擇任何一個定時器外設作為其 HAL 庫的時基源,比如選擇基礎定時器 TIM6
3.1.2.1、工作原理
當在 STM32CubeMX 軟體中配置 SYS 中的 Timebase Source 為 TIM6 然後生成工程之後,與 “3.1.1、使用 SysTick 定時器” 小節不同的是,其首先會在 Core 文件夾下多出一個名為 stm32f4xx_hal_timebase_tim.c 的文件,該文件中涉及了所有關於 TIM6 作為 HAL 庫系統嘀嗒定時器的配置程式,使用 TIM6 作為 HAL 庫系統嘀嗒定時器的初始化步驟如下圖所示
上圖內容其實就是將基礎定時器 TIM6 初始化為一個周期為 1ms 的定時器,並且啟動其周期中斷回調,如果對上述代碼不瞭解可以閱讀 “STM32CubeMX教程5 TIM 定時器概述及基本定時器” 實驗
3.1.2.2、中斷處理
當選擇基礎定時器 TIM6 作為 SysTick 時,在STM32CubeMX軟體的 NVIC 管理中 TIM6 的中斷就會被強制打開並且軟體內不可關閉,但是同樣可以修改優先順序,如下圖所示
同樣可以在 stm32f4xx_it.c 文件中可以找到 TIM6 的中斷回調函數 TIM6_DAC_IRQHandler() ,該函數調用了定時器的統一中斷處理函數 HAL_TIM_IRQHandler() ,該函數根據使用的不同定時器功能最終調用不同的中斷回調函數,這裡讀者只需要知道其調用了定時器周期回調函數 HAL_TIM_PeriodElapsedCallback() 即可,該函數由 STM32CubeMX 軟體在 main.c 文件中自動生成,具體如下所示
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM6) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
從函數體內內容可以看出其原理與 “3.1.1.2、中斷處理” 小節所敘述的一致,故此處不再贅述
3.2、FreeRTOS 基礎時鐘
STM32CubeMX 軟體配置使用 FreeRTOS 時,預設將 SysTick 滴答定時器分配給 FreeRTOS 使用,因此如果 HAL 庫的時基源也為 SysTick 時,在生成工程代碼時軟體就會警告用戶:“當使用 RTOS 時,強烈建議使用除 Systick 之外的 HAL 時基源,可以從 SYS 下的 Pinout 選項卡更改 HAL 時基源”,具體如下圖所示
因此在 STM32 需要使用 FreeRTOS 時,一般將 SysTick 分配給 FreeRTOS 使用,而 HAL 庫的時基源一般選擇除 SysTick 之外的定時器外設,同時如果用戶明確自己不需要使用 HAL 庫的 HAL_Delay() 延時函數,則可以關閉 HAL 庫的時基源
3.2.1、工作原理
FreeRTOS 的 SysTick 系統時基源是在 vPortSetupTimerInterrupt() 函數中被初始化的,在該函數中有一個名為 configUSE_TICKLESS_IDLE 參數用於設置是否使用 Tickless 模式,這是 FreeRTOS 中提供的一種低功耗模式,將在後面小節介紹,其對 SysTick 的初始化是直接對 SysTick 的寄存器進行操作的,其調用流程如下圖所示
SysTick 的寄存器可以閱讀 Arm® Cortex®-M4 Processor Technical Reference Manual手冊 “4.1 System control registers” 小節,如下圖所示
3.2.2、中斷處理
當將 SysTick 分配給 FreeRTOS 初始化並開啟對應中斷之後,SysTick 的中斷會被定義在 cmsis_os2.c 文件中(從 FreeRTOS_v10.3.1 之後),該函數清除了中斷標誌然後調用了 FreeRTOS 定義的硬體介面文件中的 xPortSysTickHandler() 函數,在 xPortSysTickHandler() 函數中增加了 RTOS 滴答定時器計數量,然後掛起 PednSV 中斷,請求上下文切換,具體如下所述
根據上面的分析我們知道了一件關於 FreeRTOS 很重要的事情,也就是:FreeRTOS 的任務調度發起是在系統滴答定時器中斷中發起的,然後真正進行上下文切換處理是在 PendSV 中斷中執行的
3.3、低功耗處理
3.3.1、睡眠、停止和待機模式
在 “STM32CubeMX教程25 PWR 電源管理 - 睡眠、停止和待機模式” 文章中曾經介紹了關於 STM32 電源管理的睡眠、停止和待機三種低功耗模式,在一個由 FreeRTOS 管理的系統中,一般只使用其中的睡眠模式即可,因為停止和待機模式的喚醒條件相對較為苛刻,感興趣的讀者請自行閱讀上述文章
3.3.2、低功耗思路
在一個 FreeRTOS 管理的多任務系統中,當所有任務處理完畢進入阻塞狀態等待下次處理時機時,空閑任務會一直執行,如果同時使能了 configUSE_IDLE_HOOK 參數,則每當處理器將要進入空閑任務時,就會先進入空閑任務鉤子函數中
因此我們可以在空閑任務鉤子函數中設置處理器進入睡眠模式,但是同時也會存在一個問題,就是每次滴答定時器中斷都會將處理器喚醒,這樣其運行時序圖應該如下圖所示
3.3.3、Tickless 模式
上述低功耗思路中存在的一個問題:“每次滴答定時器中斷都會將處理器喚醒” ,FreeRTOS 提供了一個 Tickless 模式,當處理器空閑時會一直處於睡眠狀態,然後在任務即將退出阻塞狀態之前處理器提前被喚醒,理想的低功耗模式應該如下圖所示
要使用 Tickless 模式只需要啟用 configUSE_TICKLESS_IDLE 參數即可,該參數可以通過 STM32CubeMX 軟體設置,有三個可以配置的選項,選擇 Built in functionality enabled 對應的參數值為 1 ,表示使用 FreeRTOS 內建的函數實現 Tickless 低功耗功能,選擇 User defined functionality enabled 則對應的參數值為 2 ,表示使用用戶自定義的函數實現 Tickless 低功耗功能,一般選擇使用 FreeRTOS 現成的函數來實現 Tickless 低功耗功能,如下圖所示
3.3.3.1、工作原理
當啟用 Tickless 之後,系統滿足以下兩點時就會自動進入睡眠模式
- 空閑任務正在運行
- 可運行低功耗的時間大於參數 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 設定值時(預設為2)
用戶需要註意的是進入睡眠的時間有最大值 xMaximumPossibleSuppressedTicks 限制,該變數在設置滴答定時器中斷 vPortSetupTimerInterrupt() 函數中被計算,當 MCU 頻率為168MHz,FreeRTOS 頻率為 1000Hz 時,該值為 99 ,也即單次最長進入睡眠時間的最大值為 99 個節拍,具體如下所示
3.3.3.2、vPortSuppressTicksAndSleep() 函數詳解
vPortSuppressTicksAndSleep() 是 Tickless 模式實現的具體函數,該函數會在啟用 Tickless 模式後在空閑任務中被調用,具體可以參考 “freeRTOS 低功耗模式 和 空閑任務” 文章
4、實驗一:Tickless 模式的使用
4.1、實驗目標
- 創建任務 Task_Main,在任務中實現 GREEN_LED 和 RED_LED 的閃爍程式
- 啟用/關閉 Tickless 模式,對比兩種不同情況下開發板的工作電流
4.2、CubeMX相關配置
首先讀者應按照 "FreeRTOS教程1 基礎知識" 章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求
本實驗需要初始化開發板上 GREEN_LED 和 RED_LED 兩個 LED 燈作為顯示,具體配置步驟請閱讀“STM32CubeMX教程2 GPIO輸出 - 點亮LED燈”,註意雖開發板不同但配置原理一致,如下圖所示
單擊 Middleware and Software Packs/FREERTOS ,在 Configuration 中單擊 Tasks and Queues 選項卡,雙擊預設任務修改其參數,如下所示
然後在 Configuration 中單擊 Config parameters 選項卡,在 Kernel settings 中找到 USE_TICKLESS_IDLE 參數,將其設置為 Disabled 或者 Built in functionality enabled,進行對比實驗
最後配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可
4.3、添加其他必要代碼
首先實現任務 Task_Main 使其每隔 500ms 改變一次 GREEN_LED 和 RED_LED 的狀態,具體如下所述
void AppTask_Main(void *argument)
{
/* USER CODE BEGIN AppTask_Main */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
HAL_GPIO_TogglePin(RED_LED_GPIO_Port, RED_LED_Pin);
vTaskDelay(pdMS_TO_TICKS(500));
}
/* USER CODE END AppTask_Main */
}
然後再進入睡眠模式之前關閉系統滴答定時器,在退出睡眠模式之後開啟系統滴答定時器,具體如下所述
__weak void PreSleepProcessing(uint32_t ulExpectedIdleTime)
{
/* place for user code */
HAL_SuspendTick();
}
__weak void PostSleepProcessing(uint32_t ulExpectedIdleTime)
{
/* place for user code */
HAL_ResumeTick();
}
4.4、燒錄驗證
在開啟 Tickless 和關閉 Tickless 兩種模式下讀者可以自行測試開發板工作電流,對比開啟和關閉兩種模式下工作電流的變化
參考資料
Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf