1)實驗平台:alientek 阿波羅 STM32F767 開發闆
2)摘自《STM32F7 開發指南(HAL 庫版)》關注官方微信号公衆号,擷取更多資料:正點原子
第二十二章 待機喚醒實驗
本章我們将向大家介紹 STM32F4 的待機喚醒功能。在本章中,我們将使用 KEY_UP 按鍵
來實作喚醒和進入待機模式的功能,然後使用 DS0 訓示狀态。本章将分為如下幾個部分:
22.1 STM32F4 待機模式簡介
22.2 硬體設計
22.3 軟體設計
22.4 下載下傳驗證
22.1 STM32F4 待機模式簡介
很多單片機都有低功耗模式,STM32F4 也不例外。在系統或電源複位以後,微控制器處于
運作狀态。運作狀态下的 HCLK 為 CPU 提供時鐘,核心執行程式代碼。當 CPU 不需繼續運作
時,可以利用多個低功耗模式來節省功耗,例如等待某個外部事件時。使用者需要根據最低電源
消耗,最快速啟動時間和可用的喚醒源等條件,標明一個最佳的低功耗模式。STM32F4 的 3 種
低功耗模式我們在 5.2.4 節有粗略介紹,這裡我們再回顧一下。
STM32F4 提供了 3 種低功耗模式,以達到不同層次的降低功耗的目的,這三種模式如下:
1)睡眠模式(CM4 核心停止工作,外設仍在運作);
2)停止模式(所有的時鐘都停止);
3)待機模式;
在運作模式下,我們也可以通過降低系統時鐘關閉 APB 和 AHB 總線上未被使用的外設的
時鐘來降低功耗。三種低功耗模式一覽表見表 22.1.1 所示:
表 22.1.1 STM32F4 低功耗一覽表
在這三種低功耗模式中,最低功耗的是待機模式,在此模式下,最低隻需要 2.2uA 左右的
電流。停機模式是次低功耗的,其典型的電流消耗在 350uA 左右。最後就是睡眠模式了。使用者
可以根據自己的需求來決定使用哪種低功耗模式。
本章,我們僅對 STM32F4 的最低功耗模式-待機模式,來做介紹。待機模式可實作 STM32F4
的最低功耗。該模式是在CM4 深睡眠模式時關閉電壓調節器。整個1.2V 供電區域被斷電。PLL、
HSI 和 HSE 振蕩器也被斷電。SRAM 和寄存器内容丢失。除備份域(RTC 寄存器、RTC 備份
寄存器和備份 SRAM)和待機電路中的寄存器外,SRAM 和寄存器内容都将丢失。
那麼我們如何進入待機模式呢?其實很簡單,隻要按圖 22.1.1 所示的步驟執行就可以了:
圖 22.1.1 STM32F4 進入及退出待機模式的條件
圖 22.1.1 還列出了退出待機模式的操作,從圖 22.1.1 可知,我們有多種方式可以退出待機
模式,包括:WKUP 引腳的上升沿、RTC 鬧鐘、RTC 喚醒事件、RTC 入侵事件、RTC 時間戳
事件、外部複位(NRST 引腳)、IWDG 複位等,微控制器從待機模式退出。
從待機模式喚醒後的代碼執行等同于複位後的執行(采樣啟動模式引腳,讀取複位向量等)。
電源控制/狀态寄存器(PWR_CSR)将會訓示核心由待機狀态退出。
在進入待機模式後,除了複位引腳、RTC_AF1 引腳(PC13)(如果針對入侵、時間戳、RTC
鬧鐘輸出或 RTC 時鐘校準輸出進行了配置)和 WK_UP(PA0)(如果使能了)等引腳外,其
他所有 IO 引腳都将處于高阻态。
圖 22.1.1 已經清楚的說明了進入待機模式的通用步驟,其中涉及到 2 個寄存器,即電源控
制寄存器(PWR_CR)和電源控制/狀态寄存器(PWR_CSR)。下面我們介紹一下這兩個寄存器:
電源控制寄存器(PWR_CR),該寄存器的各位描述如圖 22.1.2 所示:
圖 22.1.2 PWR_CR 寄存器各位描述
該寄存器我們隻關心 bit1 和 bit2 這兩個位,這裡我們通過設定 PWR_CR 的 PDDS 位,使
CPU 進入深度睡眠時進入待機模式,同時我們通過 CWUF 位,清除之前的喚醒位。
電源控制/狀态寄存器(PWR_CSR)的各位描述如圖 22.1.3 所示:
圖 22.1.3 PWR_ CSR 寄存器各位描述
這裡,我們通過設定 PWR_CSR 的 EWUP 位,來使能 WKUP 引腳用于待機模式喚醒。我
們還可以從 WUF 來檢查是否發生了喚醒事件,不過本章我們并沒有用到。關于 PWR_CR 和
PWR_CSR 這兩個寄存器的較長的描述,請看《STM32F4xx 中文參考手冊》第 5.4.1 節和 5.4.3 節。
對于使能了 RTC 鬧鐘中斷或 RTC 周期性喚醒等中斷的時候,進入待機模式前,必須按如
下操作處理:
1, 禁止 RTC 中斷(ALRAIE、ALRBIE、WUTIE、TAMPIE 和 TSIE 等)。
2, 清零對應中斷标志位。
3, 清除 PWR 喚醒(WUF)标志(通過設定 PWR_CR 的 CWUF 位實作)。
4, 重新使能 RTC 對應中斷。
5, 進入低功耗模式。
在有用到 RTC 相關中斷的時候,必須按以上步驟執行之後,才可以進入待機模式,這個大
家一定要注意,否則可能無法喚醒。詳情請參考《STM32F4xx 中文參考手冊》第 5.3.6 節。
通過以上介紹,我們了解了進入待機模式的方法,以及設定 KEY_UP 引腳用于把 STM32F4
從待機模式喚醒的方法。低功耗相關操作函數和定義在 HAL 庫檔案 stm32f4xx_hal_pwr.c 和頭
檔案 stm32f4xx_hal_pwr.h 中。具體步驟如下:
1)使能 PWR 時鐘。
因為要配置 PWR 寄存器,是以必須先使能 PWR 時鐘。
在 HAL 庫中,使能 PWR 時鐘的方法是:
__HAL_RCC_PWR_CLK_ENABLE(); //使能 PWR 時鐘
2) 設定 WK_UP 引腳作為喚醒源。
使能時鐘之後後再設定 PWR_CSR 的 EWUP 位,使能 WK_UP 用于将 CPU 從待機模式喚
醒。在 HAL 庫中,設定使能 WK_UP 用于喚醒 CPU 待機模式的函數是:
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); //設定 WKUP 用于喚醒
3)設定 SLEEPDEEP 位,設定 PDDS 位,執行 WFI 指令,進入待機模式。
進入待機模式,首先要設定 SLEEPDEEP 位(詳見《STM32F3 與 F4 系列 Cortex M4 核心
程式設計手冊》,第 214 頁 4.4.6 節),接着我們通過 PWR_CR 設定 PDDS 位,使得 CPU 進入深度睡眠時進入待機模式,最後執行 WFI 指令開始進入待機模式,并等待 WK_UP 中斷的到來。在
庫函數中,進行上面三個功能進入待機模式是在函數 HAL_PWR_EnterSTANDBYMode 中實作
的:
void HAL_PWR_EnterSTANDBYMode(void);
4)最後編寫 WK_UP 中斷服務函數。
因為我們通過 WK_UP 中斷(PA0 中斷)來喚醒 CPU,是以我們有必要設定一下該中斷函
數,同時我們也通過該函數裡面進入待機模式。關于外部中斷服務函數以及中斷服務回調函數
的使用方法請參考外部中斷實驗,這裡我們就不做過多講解。
通過以上幾個步驟的設定,我們就可以使用 STM32F4 的待機模式了,并且可以通過 KEY_UP
來喚醒 CPU,我們最終要實作這樣一個功能:通過長按(3 秒)KEY_UP 按鍵開機,并且通過
DS0 的閃爍訓示程式已經開始運作,再次長按該鍵,則進入待機模式,DS0 關閉,程式停止運
行。類似于手機的開關機。
22.2 硬體設計
本實驗用到的硬體資源有:
1) 訓示燈 DS0
2) KEY_UP 按鍵
3) TFTLCD 子產品
本章,我們使用了 KEY_UP 按鍵用于喚醒和進入待機模式。然後通過 DS0 和 TFTLCD 模
塊來訓示程式是否在運作。這幾個硬體的連接配接前面均有介紹。
22.3 軟體設計
打開待機喚醒實驗工程,我們可以發現工程中多了一個 wkup.c 和 wkup.h 檔案,相關的用
戶代碼寫在這兩個檔案中。同時,對于待機喚醒功能,我們需要引入 stm32f4xx_hal_pwr.c 和
stm32f4xx_hal_pwr.h 檔案。
打開 wkup.c,可以看到如下關鍵代碼:
//系統進入待機模式void Sys_Enter_Standby(void){ __HAL_RCC_AHB1_FORCE_RESET()//複位所有 IO 口while(WKUP_KD);//等待 WK_UP 按鍵松開(在有 RTC 中斷時,//必須等 WK_UP 松開再進入待機)__HAL_RCC_PWR_CLK_ENABLE();//使能 PWR 時鐘 __HAL_RCC_BACKUPRESET_FORCE(); //複位備份區域 HAL_PWR_EnableBkUpAccess();//後備區域通路使能//STM32F4,當開啟了 RTC 相關中斷後,必須先關閉 RTC 中斷,再清中斷标志位,//然後重新設定 RTC 中斷,再進入待機模式才可以正常喚醒,否則會有問題. __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB); __HAL_RTC_WRITEPROTECTION_DISABLE(&RTC_Handler);//關閉 RTC 寫保護 //關閉 RTC 相關中斷,可能在 RTC 實驗打開了 __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&RTC_Handler,RTC_IT_WUT); __HAL_RTC_TIMESTAMP_DISABLE_IT(&RTC_Handler,RTC_IT_TS); __HAL_RTC_ALARM_DISABLE_IT(&RTC_Handler,RTC_IT_ALRA|RTC_IT_ALRB);//清除 RTC 相關中斷标志位 __HAL_RTC_ALARM_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_ALRAF|RTC_FLAG_ALRBF); __HAL_RTC_TIMESTAMP_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_TSF); __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_WUTF); __HAL_RCC_BACKUPRESET_RELEASE(); //備份區域複位結束 __HAL_RTC_WRITEPROTECTION_ENABLE(&RTC_Handler); //使能 RTC 寫保護 __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); //清除 Wake_UP 标志 HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); //設定 WKUP 用于喚醒 HAL_PWR_EnterSTANDBYMode(); //進入待機模式}//檢測 WKUP 腳的信号//傳回值 1:連續按下 3s 以上// 0:錯誤的觸發u8 Check_WKUP(void){u8 t=0;u8 tx=0;//記錄松開的次數LED0=0; //亮燈 DS0while(1){if(WKUP_KD)//已經按下了{t++;tx=0;}else{tx++;if(tx>3)//超過 90ms 内沒有 WKUP 信号{LED0=1;return 0;//錯誤的按鍵,按下次數不夠}}delay_ms(30);if(t>=100)//按下超過 3 秒鐘{LED0=0; //點亮 DS0return 1; //按下 3s 以上了}}}//外部中斷線 0 中斷服務函數void EXTI0_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);}//中斷線 0 中斷處理過程//此函數會被 HAL_GPIO_EXTI_IRQHandler()調用//GPIO_Pin:引腳void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if(GPIO_Pin==GPIO_PIN_0)//PA0 { if(Check_WKUP())//關機 { Sys_Enter_Standby();//進入待機模式 } }}//PA0 WKUP 喚醒初始化void WKUP_Init(void){ GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOA_CLK_ENABLE(); //開啟 GPIOA 時鐘 GPIO_Initure.Pin=GPIO_PIN_0; //PA0 GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //中斷,上升沿 GPIO_Initure.Pull=GPIO_PULLDOWN; //下拉 GPIO_Initure.Speed=GPIO_SPEED_FAST; //快速 HAL_GPIO_Init(GPIOA,&GPIO_Initure); //檢查是否是正常開機 if(Check_WKUP()==0) { Sys_Enter_Standby();//不是開機,進入待機模式 } HAL_NVIC_SetPriority(EXTI0_IRQn,0x02,0x02);//搶占優先級 2,子優先級 2 HAL_NVIC_EnableIRQ(EXTI0_IRQn);}
該部分代碼比較簡單,我們在這裡說明兩點:
1,在 void Sys_Enter_Standby(void)函數裡面,我們要在進入待機模式前把所有開啟的外設
全部關閉,我們這裡僅僅複位了所有的 IO 口,使得 IO 口全部為浮空輸入。其他外設(比如
ADC 等),大家根據自己所開啟的情況進行一一關閉就可,這樣才能達到最低功耗!然後我們
調 用 函 數 __HAL_RCC_PWR_CLK_ENABLE() 來 使 能 PWR 時 鐘 , 調 用 函 數
HAL_PWR_EnableWakeUpPin 用 來 設 置 WK_UP 引 腳 作 為 喚 醒 源 。 最後調用
HAL_PWR_EnterSTANDBYMode 函數進入待機模式。
2,在 void WKUP_Init(void)函數裡面,我們首先要使能 GPIOA 時鐘,同時因為我們要使用到外部中斷,是以必須先使能 SYSCFG 時鐘。然後對 GPIOA 初始化位下拉輸入。同時調用
函數 SYSCFG_EXTILineConfig 配置 GPIOA.0 連接配接到中斷線 0。最後初始化 EXTI 中斷線以及
NVIC 中斷優先級。這上面的步驟實際上跟我們之前的外部中斷實驗知識是一樣的,是以不理
解的地方大家可以翻到外部中斷實驗章節看看。在上面初始化的過程中,我們還先先判斷
WK_UP 是否按下了 3 秒鐘,來決定要不要開機,如果沒有按下 3 秒鐘,程式直接就進入了待
機模式。是以在下載下傳完代碼的時候,是看不到任何反應的。我們必須先按 WK_UP 按鍵 3 秒開
機,才能看到 DS0 閃爍。
3,在中斷服務函數EXTI0_IRQHandler 内,我們通過調用函數 Check_WKUP來判斷 WK_UP
按下的時間長短,來決定是否進入待機模式,如果按下時間超過 3 秒,則進入待機,否則退出
中斷。
wkup.h 部分代碼比較簡單,我們就不多說了。最後我們看看 main 函數内容如下:
int main(void){HAL_Init(); //初始化 HAL 庫 Stm32_Clock_Init(336,8,2,7); //設定時鐘,168Mhzdelay_init(168); //初始化延時函數uart_init(115200); //初始化 USARTusmart_dev.init(84);//初始化 USMARTLED_Init();//初始化 LEDLCD_Init();//初始化 LCDWKUP_Init(); //待機喚醒初始化POINT_COLOR=RED;LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");LCD_ShowString(30,70,200,16,16,"WKUP TEST");LCD_ShowString(30,90,200,16,16,"[email protected]");LCD_ShowString(30,110,200,16,16,"2017/4/11");LCD_ShowString(30,130,200,16,16,"WK_UP:Stanby/WK_UP");while(1){ LED0=!LED0;delay_ms(250); //延時 250ms}}
這裡我們先初始化 LED 和 WK_UP 按鍵(通過 WKUP_Init()函數初始化),如果檢測到
有長按 WK_UP 按鍵 3 秒以上,則開機,并執行 LCD 初始化,在 LCD 上面顯示一些内容,如
果沒有長按,則在 WKUP_Init 裡面,調用 Sys_Enter_Standby 函數,直接進入待機模式了。
開機後,在死循環裡面等待 WK_UP 中斷的到來,在得到中斷後,在中斷函數裡面判斷
WK_UP 按下的時間長短,來決定是否進入待機模式,如果按下時間超過 3 秒,則進入待機,
否則退出中斷,繼續執行 main 函數的死循環等待,同時不停的取反 LED0,讓紅燈閃爍。
代碼部分就介紹到這裡,大家記住下載下傳代碼後,一定要長按 WK_UP 按鍵,來開機,否則
将直接進入待機模式,無任何現象。
22.4 下載下傳與測試
在代碼編譯成功之後,下載下傳代碼到探索者 STM32F4 開發闆上,此時,看到開發闆 DS0 亮
了一下(Check_WKUP 函數執行了 LED0=0 的操作),就沒有反應了。其實這是正常的,在程
序下載下傳完之後,開發闆檢測不到 WK_UP 的持續按下(3 秒以上),是以直接進入待機模式,看
起來和沒有下載下傳代碼一樣。此時,我們長按 WK_UP 按鍵 3 秒鐘左右,可以看到 DS0 開始閃爍,
液晶也會顯示一些内容。然後再長按 WK_UP,DS0 會滅掉,液晶滅掉,程式再次進入待機模
式。