在電子設備中,待機(Standby)和睡眠(Sleep)是兩種不同的省電模式。 1. 待機模式(Standby Mode):在待機模式下,設備仍然保持一定程度的活動,但大部分功能處于暫停狀態。
電源
STM32的工作電壓(VDD)為2.0~3.6V。通過內置的電壓調節器提供所需的1.8V電源
當主電源VDD掉電后,通過VBAT腳為實時時鐘(RTC)和備份寄存器提供電源
這張圖展示了STM32微控制器(MCU)內部的電源分配方案。整個系統可分為三大主要供電區域:模擬部分供電(VDDA)、數字部分供電包括VDD供電區域和1.8v供電區域、后備供電(VBAT)。
-
首先來看模擬部分供電,即VDDA區域,負責為模擬功能如AD轉換器、溫度傳感器、復位模塊及PLL鎖相環提供電力。這些組件的正極連接至VDDA,而負極則連接至VSSA。特別地,AD轉換器的參考電壓輸入端VREF+和VREF-通常會在在引腳多的型號里會單獨引出來;而在像C8T6這樣的少引腳型號中,它們已在芯片內部直接連接到VDDA和VSSA上。
-
接下來是數字部分供電,該部分分為兩個子區域:VDD供電區和1.8V供電區。VDD供電區包含了I/O電路、待機電路、喚醒邏輯和獨立看門狗等功能。這部分電路的工作電壓通常是3.3V。為了提高能效,STM32的設計采用了低壓策略,因此大部分關鍵的內部電路,例如CPU、存儲器和數字外設,實際上是以1.8V的較低電壓運行。
關于1.8V供電區,它是由VDD通過內置的電壓調節器降壓得到的,提供給CPU核心、存儲器和內置數字外設。當這些外設需要與外界進行交流時,才會通過I/O電路轉換到3.3V。這種設計有助于顯著降低系統的功耗,因為較低的電壓意味著更低的功率消耗。
需要注意的是,STM32的工作電壓(VDD)范圍為2.0~3.6V,而1.8V電源是通過內置的電壓調節器提供的。
- 最后討論的是后備供電區域。此區域包括了LSE 32K晶體振蕩器、后備寄存器、RCC BDCR寄存器和實時時鐘(RTC)。RCC BDCR是RTC的控制寄存器之一,也屬于后備區域的一部分,因此同樣可以通過VBAT供電。此外,圖中還顯示了一個低電壓檢測器,它可以監測主電源VDD的狀態,并在VDD失效時自動切換到VBAT供電模式,確保RTC和其他關鍵的后備功能即使在主電源斷開的情況下也能繼續工作。
電源管理器
上電復位和掉電復位,還有可編程電壓監測器這兩個內容了解即可。
上電復位和掉電復位
上電復位和掉電復位的功能在于,當VDD或VDDA的電壓降至一定水平時,內部電路會自動觸發復位操作,防止STM32在電壓不穩定時進行錯誤操作。為此,系統設置了一個40毫伏的遲滯電壓,以避免電壓波動導致的不穩定。具體來說,當電壓超過上限(POR閾值,即1.92V)時,系統解除復位狀態;而當電壓低于下限(PDR閾值,即1.88V)時,系統進入復位狀態。這種設計采用了遲滯比較器,通過設定上下兩個閾值,有效防止了電壓在閾值附近波動時引起的輸出抖動。
需要注意的是,復位信號是低電平有效,意味著在電壓過低(前后兩種情況)時,系統會進入復位狀態,而在電壓正常(中間狀態)時,系統則不會復位。關于具體的電壓閾值和復位滯后時間,可以參考STM32的數據手冊,具體信息在5.3.3節“內嵌復位和電源控制模塊特性”中有詳細說明。根據數據手冊,PDR的典型下限閾值是1.88V,而POR的典型上限閾值是1.92V,這40毫伏的遲滯閾值確保了電壓穩定。簡而言之,電壓大于1.9V時系統上電,低于1.9V時系統掉電。此外,復位持續時間(TRSTTEMPO)的典型值為2.5毫秒。
這個復位持續時間很重要,實際產品這里經常出偶發問題又難以排查
可編程電壓監測器
PVD的工作原理與前述的上電復位和掉電復位類似,都是監測VDD和VDDA的供電電壓。然而,PVD的獨特之處在于其閾值電壓是可以編程設定的,提供了更大的靈活性。
根據數據手冊中的相關表格,可以通過配置PLS寄存器的3個位來選擇PVD的閾值。這些閾值的選擇范圍大約在2.2V到2.9V之間,且PVD的上限和下限閾值之間的遲滯電壓為100毫伏。值得注意的是,PVD的監測電壓范圍高于上電和掉電復位的閾值。
為了更直觀地理解,可以想象一個電壓從3.3V的正常供電逐漸降低的情景。當電壓降至2.9V至2.2V之間時,就進入了PVD的監測范圍。在這個范圍內,您可以設置一個警告線,以便在電壓進一步降低至1.9V以下,即復位電路的檢測范圍時,系統可以采取行動。
當PVD被觸發時,微控制器仍然可以正常工作,但這是對用戶的一個提醒,表明電源電壓已經過低。PVD的輸出是正邏輯,即電壓過低時輸出為1,電壓正常時輸出為0。這個信號可以用來申請中斷,在電壓上升或下降時觸發,從而提醒程序進行相應的處理。
關于PVD的中斷申請,它通過外部中斷實現。在EXTI(外部中斷)的基本結構圖中,可以看到PVD的輸出信號是如何接入的。因此,如果想要使用PVD,記得正確配置外部中斷。
另外,盡管RTC有自己的中斷系統,但為了在低功耗模式下喚醒停止模式,只有外部中斷能夠實現這一點。這也是為什么其他設備,如USB和ETH的喚醒信號,也要通過外部中斷來喚醒停止模式。理解這一點對于低功耗設計至關重要。
低功耗模式
STM32F10xxx有三種低功耗模式:
睡眠模式
要進入睡眠模式,只需直接調用WFI(等待中斷)或WFE(等待事件)指令,這兩個都是內核指令,對應的庫函數中也提供了相應的調用方法。WFI的作用是讓CPU進入睡眠狀態,直到有中斷發生時喚醒;而WFE則是讓CPU等待一個特定的事件來喚醒。喚醒條件分別是:WFI模式下,任何中斷都能喚醒CPU;WFE模式下,則需要一個特定的事件來喚醒。喚醒后,WFI通常需要處理中斷服務函數,而WFE則可能直接繼續執行。
睡眠模式對電路的影響有限,主要表現在關閉了CPU時鐘,而其他時鐘和ADC時鐘不受影響。電壓調節器保持開啟狀態,因此,睡眠模式主要是通過停止CPU時鐘來降低功耗。關閉時鐘意味著所有的運算和時序操作暫停,但寄存器和存儲器中的數據得以保持。睡眠模式的喚醒條件相對寬松,任何中斷都能喚醒CPU,因此,它相當于是在保持身體其他部分工作的情況下,大腦稍作休息,省電程度評為一般。
停機模式
要進入停機模式,首先需要將sleepdeep位設置為1,指示CPU進入深度睡眠。PDDS位用于區分停機模式或待機模式,PDDS為0時進入停機模式。之后LPDS位用于控制電壓調節器是保持開啟還是進入低功耗模式(RPDS等于電壓調節器開啟,RPDS等于1電壓調節器進入低功耗v)。
設置好這些位后,調用WFI或WFE即可進入停機模式。停機模式的喚醒條件比睡眠模式苛刻,只有外部中斷能夠喚醒。這意味著,如PVD、RTC鬧鐘、USB喚醒、ETH喚醒等通過外部中斷的信號可以喚醒系統。停機模式關閉了所有1.8伏區域的時鐘,包括HSI和HSE振蕩器,但LSI和LSE振蕩器保持運行。電壓調節器可以選擇開啟或低功耗模式,后者更省電但喚醒時間更長。停機模式相當于整個人的工作完全停止,只有外部中斷才能喚醒,省電程度評為非常省電。
注意:
系統從停止模式被中斷或喚醒事件喚醒時,HSI(內部高速時鐘)會被自動選為系統時鐘。這是因為,在我們的程序中,默認在SystemInit函數里配置的是使用HSE(外部高速時鐘),并通過PLL(鎖相環)倍頻來獲得72MHz的主頻。然而,一旦進入停止模式,PLL和HSE都會停止工作。
因此,當系統從停止模式喚醒時,它不會自動通過PLL倍頻來恢復到原來的72MHz主頻,而是直接使用HSI的8MHz作為主頻。如果忽略這一點,就可能會出現以下現象:程序剛上電時運行在72MHz的主頻,但進入停止模式并在喚醒之后,主頻會降為8MHz。不止慢9倍這么簡單,帶時序的外設基本上都出問題。
為了避免這種情況,我們通常需要在停止模式喚醒后的第一時間重新啟動HSE,并將主頻重新配置為72MHz。這一操作并不復雜,因為相關的配置函數已經為我們準備好了。我們只需要在喚醒后調用SystemInit函數,即可完成主頻的重新配置。這樣,系統就能恢復到停止模式之前的工作狀態,確保程序的正常運行。
待機模式
進入待機模式的步驟與停機模式相似,但PDDS位需設置為1。待機模式的喚醒條件最為嚴格,普通外設中斷和外部中斷都無法喚醒,只有特定的信號,如wake up引腳的上升沿、RTC鬧鐘事件、NRST引腳的外部復位和IWDG獨立看門狗復位,才能喚醒。待機模式幾乎關閉了所有電路,包括1.8伏區域的時鐘和電壓調節器,這意味著內部存儲器和寄存器的數據會丟失。但與停機模式一樣,LSI和LSE振蕩器保持運行以支持RTC和獨立看門狗。待機模式相當于徹底下班,除非有緊急事項,否則不會返回工作,省電程度評為極為省電。
在之前的討論中,我們提到了多個與低功耗模式相關的寄存器位,這些模式還有一些更細致的劃分。例如,睡眠模式中有SLEEP-NOW和SLEEP-ON-EXIT的區別,停機模式中則有電壓調節器開啟與低功耗模式的區別。了解如何配置這些模式對我們理解程序有很大幫助。
首先這里有一句,執行WFI等待中斷或者WFE等待事件指令后,STM32進入低功耗模式,就說這兩個指令是最終開啟低功耗模式的觸發條件,配置其他的寄存器都要在這兩個指令之前 。
以下是基于配置流程的詳細說明:
- 執行WFI或WFE指令:這兩個指令是啟動低功耗模式的觸發點。在執行這兩個指令之前,需要配置好相關的寄存器。
- 判斷sleep deep位:這一位決定了是進入淺睡眠還是深度睡眠模式。
- 如果sleep deep位為0,則進入睡眠模式。
- 如果sleep deep位為1,則進入深度睡眠模式,即停機模式或待機模式。
- 睡眠模式的細分:在睡眠模式下,SLEEPONEXIT位可以進一步細分模式。
- 當SLEEPONEXIT位為0時,執行WFI或WFE后立即進入睡眠模式。
- 當SLEEPONEXIT位為1時,執行WFI或WFE后會等待當前中斷處理完成后才進入睡眠模式。這種情況適用于中斷處理中還有一些緊急任務需要完成。
- 深度睡眠模式的判斷:對于深度睡眠模式,需要進一步判斷PDDS位。
- 如果PDDS位為0,則進入停機模式。
- 如果PDDS位為1,則進入待機模式。
- 停機模式的進一步配置:在停機模式下,LPDS位決定了電壓調節器的工作狀態。
- 如果LPDS位為0,則電壓調節器保持開啟狀態。
- 如果LPDS位為1,則電壓調節器進入低功耗模式,雖然更省電,但喚醒延遲更長。
【手冊/天書講解環節請看VCR】
【system_stm32f10x.c/.h文件講解】
代碼實戰:修改主頻&睡眠模式&停止模式&待機模式
- 修改主頻
最好不要去中途改主頻,因為那些外設初始化時都是根據SystemCoreClock來的,就運行一次,主頻改完后得重新配置外設初始化那些
- 睡眠模式+串口發送+接收
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
uint8_t RxData;
uint8_t Pin_9, Pin_10;
int main(void)
{OLED_Init();Serial_Init();OLED_ShowString(1, 1, "RxData:");while (1){if (Serial_GetRxFlag() == 1){RxData = Serial_GetRxData();Serial_SendByte(RxData);OLED_ShowHexNum(1, 8, RxData, 2);}// 沒有數據要發送但代碼一直執行所以可以采用睡眠模式OLED_ShowString(2, 1, "Running...");Delay_ms(500);OLED_ShowString(2, 1, " ");Delay_ms(500);__WFI(); // 進入睡眠,中斷喚醒//執行WFI這時CPU會立刻睡眠,程序就停在了WFI指令這里,但是各個外設比如USRT還是工作狀態//等到我們用串口助手發送數據時,USRT外設收到數據產生中斷,喚醒CPU之后程序在暫停的地方繼續運行}
}
- 停止模式+對射式紅外傳感器計次
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化CountSensor_Init(); //計數傳感器初始化/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //開啟PWR的時鐘//停止模式和待機模式一定要記得開啟/*顯示靜態字符串*/OLED_ShowString(1, 1, "Count:");while (1){OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不斷刷新顯示CountSensor_Get的返回值OLED_ShowString(2, 1, "Running"); //OLED閃爍Running,指示當前主循環正在運行Delay_ms(100);OLED_ShowString(2, 1, " ");Delay_ms(100);PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI); //STM32進入停止模式,并等待中斷喚醒SystemInit(); //喚醒后,要重新配置時鐘,重啟HSE配置72M主頻//退出停止模式時,HSI被選為系統時鐘,也就是在我們首次復位后,SystemInit函數里配置的是HSE*9倍頻的72M主頻//所以復位后第一次Running閃爍很快,而之后進入停止模式,再退出時默認時鐘就變成HSI了,HSI是8M,所以喚醒之后的程序運行就會明顯變慢}
}
- 待機模式+實時時鐘
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化MyRTC_Init(); //RTC初始化/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //開啟PWR的時鐘//停止模式和待機模式一定要記得開啟,雖然MyRTC_Init里開啟了,多次開啟無所謂,防止其他沒調用MyRTC_Init的場景 但時鐘沒開啟外設就不會工作/*顯示靜態字符串*/OLED_ShowString(1, 1, "CNT :");//秒計數器OLED_ShowString(2, 1, "ALR :");//鬧鐘值OLED_ShowString(3, 1, "ALRF:");//鬧鐘標志位/*使能WKUP引腳*/PWR_WakeUpPinCmd(ENABLE); //使能位于PA0的WKUP引腳,WKUP引腳上升沿喚醒待機模式//手冊里PWR_CSR的寄存器描述,這里寫了使能wake up引腳后,wake up引腳被強制為輸入下拉的配置,所以不用再GPIO初始化了/*設定鬧鐘*/uint32_t Alarm = RTC_GetCounter() + 10; //鬧鐘為喚醒后當前時間的后10sRTC_SetAlarm(Alarm); //寫入鬧鐘值到RTC的ALR寄存器 這個寄存器只寫不可讀,所以使用變量Alarm顯示到OLED上OLED_ShowNum(2, 6, Alarm, 10); //顯示鬧鐘值while (1){OLED_ShowNum(1, 6, RTC_GetCounter(), 10); //顯示32位的秒計數器OLED_ShowNum(3, 6, RTC_GetFlagStatus(RTC_FLAG_ALR), 1); //顯示鬧鐘標志位OLED_ShowString(4, 1, "Running"); //OLED閃爍Running,指示當前主循環正在運行Delay_ms(100);OLED_ShowString(4, 1, " ");Delay_ms(100);OLED_ShowString(4, 9, "STANDBY"); //OLED閃爍STANDBY,指示即將進入待機模式Delay_ms(1000);OLED_ShowString(4, 9, " ");Delay_ms(100);OLED_Clear(); //OLED清屏,模擬關閉外部所有的耗電設備,以達到極度省電PWR_EnterSTANDBYMode(); //STM32進入停止模式,并等待指定的喚醒事件(WKUP上升沿或RTC鬧鐘)/*待機模式喚醒后,程序會重頭開始運行*///待機模式之后的代碼執行不到,下次繼續從頭開始 在程序剛開始的時候自動調用SystemInit初始化時鐘,所以待機模式我們就不用像停止模式那樣,自己調用SystemInit了//并且這個while循環,實際上也只有執行一遍的機會,把這個while循環去掉也是可以的}
}