一棠众、低功耗模式簡介
系統(tǒng)提供了多個低功耗模式胃夏,可在 CPU 不需要運行時(例如等待外部事件時)節(jié)省功耗宇姚。由用戶根據(jù)應(yīng)用選擇具體的低功耗模式,以在低功耗秉撇、短啟動時間和可用喚醒源之間尋求最佳平衡甜攀。
睡眠模式、停止模式及待機模式中琐馆,若備份域電源正常供電规阀,備份域內(nèi)的 RTC 都可以正常運行,備份域內(nèi)的寄存器的數(shù)據(jù)會被保存瘦麸,不受功耗模式影響谁撼。
從表中可以看到,這三種低功耗模式層層遞進滋饲,運行的時鐘或芯片功能越來越少厉碟,因而功耗越來越低。
模式名稱 | 說明 | 進入方式 | 喚醒方式 | 對1.8V區(qū)域時鐘的影響 | 對VDD區(qū)域時鐘的影響 | 調(diào)壓器 |
---|---|---|---|---|---|---|
睡眠模式 | 內(nèi)核停止屠缭,所有外設(shè)包括M3核心的外設(shè)箍鼓,如NVIC、系統(tǒng)時鐘(SysTick)等仍在運行 | 調(diào)用WFI 命令 |
任意中斷 | 內(nèi)核時鐘關(guān)呵曹,對其他時鐘和ADC時鐘無影響 | 無 | 開 |
睡眠模式 | 內(nèi)核停止款咖,所有外設(shè)包括M3核心的外設(shè)何暮,如NVIC、系統(tǒng)時鐘(SysTick)等仍在運行 | 調(diào)用WFE 命令 |
喚醒事件 | 內(nèi)核時鐘關(guān)铐殃,對其他時鐘和ADC時鐘無影響 | 無 | 開 |
停止模式 | 所有的時鐘都已停止 | 配置PWR_CR寄存器的PDDS +LPDS 位+SLEEPDEEP 位+WFI 或WFE 命令 |
任意外部中斷EXTI (在外部中斷寄存器中設(shè)置) |
關(guān)閉所有1.8V區(qū)域的時鐘 | HSI和HSE的振蕩器關(guān)閉 | 開啟或處于低功耗模式(依據(jù)電源控制寄存器的設(shè)定) |
待機模式 | 1.8V電源關(guān)閉 | 配置PWR_CR寄存器的PDDS +SLEEPDEEP 位+WFI 或WFE 命令 |
WKUP上升沿海洼、引腳的RTC鬧鐘事件、NRST引腳上的外部復(fù)位背稼、IWDG復(fù)位 | 關(guān)閉所有1.8V區(qū)域的時鐘 | HSI和HSE的振蕩器關(guān)閉 | 關(guān) |
1.1 睡眠模式
在睡眠模式中贰军,僅關(guān)閉了內(nèi)核時鐘,內(nèi)核停止運行蟹肘,但其片上外設(shè)词疼,CM3 核心的外設(shè)全都還照常運行。有兩種方式進入睡眠模式帘腹,它的進入方式?jīng)Q定了從睡眠喚醒的方式贰盗,分別是 WFI(wait for interrupt) 和 WFE(wait for event),即由等待“中斷”喚醒和由“事件”喚醒阳欲。
特性和說明:
- 立即睡眠: 在執(zhí)行
WFI
或WFE
指令時立即進入睡眠模式舵盈。- 退出時睡眠: 在退出優(yōu)先級最低的中斷服務(wù)程序后才進入睡眠模式。
- 進入方式: 內(nèi)核寄存器的
SLEEPDEEP=0
球化,然后調(diào)用WFI
或WFE
指令即可進入睡眠模式秽晚;SLEEPONEXIT=1
時,進入“退出時睡眠”模式筒愚。- 喚醒方式: 如果是使用
WFI
指令睡眠的赴蝇,則可使用任意中斷喚醒;如果是使用WFE
指令睡眠的巢掺,則由事件喚醒句伶。- 睡眠時: 關(guān)閉內(nèi)核時鐘,內(nèi)核停止陆淀,而外設(shè)正常運行考余,在軟件上表現(xiàn)為不再執(zhí)行新的代碼。這個狀態(tài)會保留睡眠前的內(nèi)核寄存器轧苫、內(nèi)存的數(shù)據(jù)楚堤。
- 喚醒延遲: 無延遲。
- 喚醒后: 若由中斷喚醒含懊,先進入中斷钾军,退出中斷服務(wù)程序后,接著執(zhí)行
WFI
指令后的程序绢要;若由事件喚醒吏恭,直接接著執(zhí)行WFE
后的程序。
1.2 停止模式
在停止模式中重罪,進一步關(guān)閉了其它所有的時鐘樱哼,于是所有的外設(shè)都停止了工作哀九,但由于其 1.8V 區(qū)域的部分電源沒有關(guān)閉,還保留了內(nèi)核的寄存器搅幅、內(nèi)存的信息阅束,所以從停止模式喚醒,并重新開啟時鐘后茄唐,還可以從上次停止處繼續(xù)執(zhí)行代碼息裸。停止模式可以由任意一個外部中斷(EXTI)喚醒,在停止模式中可以選擇電壓調(diào)節(jié)器為開模式或低功耗模式沪编。
特性和說明:
- 調(diào)壓器低功耗模式: 在停止模式下調(diào)壓器可工作在正常模式或低功耗模式呼盆,可進一步降低功耗。
- 進入方式: 內(nèi)核寄存器的
SLEEPDEEP=1
蚁廓,PWR_CR 寄存器中的PDDS=0
访圃,然后調(diào)用WFI
或WFE
指令即可進入停止模式;PWR_CR 寄存器的LPDS=0
時相嵌,調(diào)壓器工作在正常模式腿时,LPDS=1
時工作在低功耗模式。- 喚醒方式: 如果是使用
WFI
指令睡眠的饭宾,可使用任意 EXTI 線的中斷喚醒批糟;如果是使用WFE
指令睡眠的,可使用任意配置為事件模式的 EXTI 線事件喚醒看铆。- 停止時: 內(nèi)核停止跃赚,片上外設(shè)也停止。這個狀態(tài)會保留停止前的內(nèi)核寄存器性湿、內(nèi)存的數(shù)據(jù)。
- 喚醒延遲: 基礎(chǔ)延遲為 HSI 振蕩器的啟動時間满败,若調(diào)壓器工作在低功耗模式肤频,還需要加上調(diào)壓器從低功耗切換至正常模式下的時間。
- 喚醒后: 若由中斷喚醒算墨,先進入中斷宵荒,退出中斷服務(wù)程序后,接著執(zhí)行
WFI
指令后的程序净嘀;若由事件喚醒报咳,直接接著執(zhí)行WFE
后的程序。喚醒后挖藏,STM32 會使用 HSI 作為系統(tǒng)時鐘暑刃。
1.3 待機模式
待機模式,它除了關(guān)閉所有的時鐘膜眠,還把 1.8V 區(qū)域的電源也完全關(guān)閉了岩臣,也就是說溜嗜,從待機模式喚醒后,由于沒有之前代碼的運行記錄架谎,只能對芯片復(fù)位炸宵,重新檢測 boot 條件,從頭開始執(zhí)行程序谷扣。它有四種喚醒方式土全,分別是 WKUP(PA0)引腳的上升沿,RTC 鬧鐘事件会涎,NRST 引腳的復(fù)位和 IWDG(獨立看門狗)復(fù)位裹匙。
特性和說明:
- 進入方式: 內(nèi)核寄存器的
SLEEPDEEP=1
,PWR_CR 寄存器中的PDDS=1
在塔,PWR_CR 寄存器中的喚醒狀態(tài)位WUF=0
幻件,然后調(diào)用WFI
或WFE
指令即可進入待機模式。- 喚醒方式: 通過 WKUP 引腳的上升沿蛔溃,RTC 鬧鐘绰沥、喚醒、入侵贺待、時間戳事件或 NRST 引腳外部復(fù)位及 IWDG 復(fù)位喚醒徽曲。
- 待機時: 內(nèi)核停止,片上外設(shè)也停止麸塞;內(nèi)核寄存器秃臣、內(nèi)存的數(shù)據(jù)會丟失;除復(fù)位引腳哪工、RTC_AF1 引腳及 WKUP 引腳奥此,其它 I/O 口均工作在高阻態(tài)。
- 喚醒延遲: 芯片復(fù)位的時間雁比。
- 喚醒后: 相當于芯片復(fù)位稚虎,在程序表現(xiàn)為從頭開始執(zhí)行代碼。
1.4 WFI與WFE命令
我們了解到進入各種低功耗模式時都需要調(diào)用 WFI
或 WFE
命令偎捎,它們實質(zhì)上都是內(nèi)核指令蠢终,在庫文件 core_cm3.h
中把這些指令封裝成了函數(shù)。
/** brief 等待中斷
等待中斷 是一個暫停執(zhí)行指令
暫停至任意中斷產(chǎn)生后被喚醒
*/
#define __WFI __wfi
/** brief 等待事件
等待事件 是一個暫停執(zhí)行指令
暫停至任意事件產(chǎn)生后被喚醒
*/
#define __WFE __wfe
對于這兩個指令茴她,我們應(yīng)用時一般只需要知道寻拂,調(diào)用它們都能進入低功耗模式,需要使用函數(shù)的格式“__WFI();”和“__WFE();”來調(diào)用(因為__wfi 及__wfe 是編譯器內(nèi)置的函數(shù)丈牢,函數(shù)內(nèi)部調(diào)用了相應(yīng)的匯編指令)祭钉。
其中
WFI
指令決定了它需要用中斷喚醒,而WFE
則決定了它可用事件來喚醒己沛。
二朴皆、新建工程
1. 打開 STM32CubeMX 軟件帕识,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設(shè)置,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
開啟 LSE(外部低速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration遂铡,配置系統(tǒng)時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后肮疗,輸入回車,軟件會自動修改所有配置
4. 配置調(diào)試模式
非常重要的一步扒接,否則會造成第一次燒錄程序后續(xù)無法識別調(diào)試器
SYS 設(shè)置伪货,選擇 Debug 為 Serial Wire
三、停止模式
3.1 WFI按鍵外部中斷喚醒
3.1.1 流程圖
3.1.2 HAL庫與標準庫代碼比較
STM32CubeMX 使用 HAL 庫的代碼:
int main(void)
{
···
while(1)
{
···
// 暫停滴答時鐘钾怔,防止通過滴答時鐘中斷喚醒
HAL_SuspendTick();
/* 進入停止模式碱呼,設(shè)置電壓調(diào)節(jié)器為低功耗模式,等待中斷喚醒 */
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_STOPENTRY_WFI);
SYSCLKConfig_STOP();
// 被喚醒后宗侦,恢復(fù)滴答時鐘
HAL_ResumeTick();
···
}
}
static void SYSCLKConfig_STOP(void)
{
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
uint32_t pFLatency = 0;
/* 啟用電源控制時鐘 */
__HAL_RCC_PWR_CLK_ENABLE();
/* 根據(jù)內(nèi)部 RCC 寄存器獲取振蕩器配置 */
HAL_RCC_GetOscConfig(&RCC_OscInitStruct);
/* 從停止模式喚醒后重新配置系統(tǒng)時鐘: 啟用 HSE 和 PLL */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
while (1)
{;
}
}
/* 根據(jù)內(nèi)部 RCC 寄存器獲取時鐘配置 */
HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency);
/* 選擇 PLL 作為系統(tǒng)時鐘源, 并配置 HCLK愚臀、PCLK1 和 PCLK2 時鐘分頻系數(shù) */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK)
{
while (1)
{;
}
}
}
使用 STM32 標準庫的代碼:
int main(void)
{
···
while(1)
{
···
/* 進入停止模式,設(shè)置電壓調(diào)節(jié)器為低功耗模式矾利,等待中斷喚醒 */
PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);
// 從停止模式下被喚醒后使用的是 HSI 時鐘姑裂,此處重啟 HSE 時鐘,使用 PLLCLK
SYSCLKConfig_STOP();
···
}
}
static void SYSCLKConfig_STOP(void)
{
/* 停機喚醒后配置系統(tǒng)時鐘 */
/* 使能 HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* 等待 HSE 準備就緒 */
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
/* 使能 PLL */
RCC_PLLCmd(ENABLE);
/* 等待 PLL 準備就緒 */
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
/* 選擇 PLL 作為系統(tǒng)時鐘源 */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* 等待 PLL 被選擇為系統(tǒng)時鐘源 */
while (RCC_GetSYSCLKSource() != 0x08);
}
3.1.3 添加按鍵
初始化按鍵 PA0
中斷模式,以便當系統(tǒng)進入睡眠模式的時候可以通過按鍵來喚醒男旗。
查看 STM32CubeMX學習筆記(3)——EXTI(外部中斷)接口使用
3.1.4 添加LED燈
添加綠燈 PB0
表示運行狀態(tài)舶斧,紅燈 PB5
表示睡眠狀態(tài),藍燈 PB1
表示剛從睡眠狀態(tài)中被喚醒察皇。
查看 STM32CubeMX學習筆記(2)——GPIO接口使用
3.1.5 添加串口打印
添加 USART1
用于打印信息茴厉。
查看 STM32CubeMX學習筆記(6)——USART串口使用
3.1.6 生成代碼
輸入項目名和項目路徑
選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5
每個外設(shè)生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應(yīng)的外設(shè)文件。 如 GPIO 初始化代碼生成在 gpio.c 中什荣。
點擊 GENERATE CODE 生成代碼
3.1.7 修改中斷回調(diào)函數(shù)
當系統(tǒng)進入停止狀態(tài)后矾缓,我們按下實驗板上的 KEY1 按鍵,即可使系統(tǒng)回到正常運行的狀態(tài)稻爬,當執(zhí)行完中斷服務(wù)函數(shù)后嗜闻,會繼續(xù)執(zhí)行
WFI
指令后的代碼。
打開 stm32f1xx_it.c
中斷服務(wù)函數(shù)文件因篇,找到 EXTI0 中斷的服務(wù)函數(shù) EXTI0_IRQHandler()
中斷服務(wù)函數(shù)里面就調(diào)用了 GPIO 外部中斷處理函數(shù) HAL_GPIO_EXTI_IRQHandler()
打開
stm32f1xx_hal_gpio.c
文件,找到外部中斷處理函數(shù)原型 HAL_GPIO_EXTI_IRQHandler()
笔横,其主要作用就是判斷是幾號線中斷竞滓,清除中斷標識位,然后調(diào)用中斷回調(diào)函數(shù) HAL_GPIO_EXTI_Callback()
吹缔。/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
這個函數(shù)不應(yīng)該被改變商佑,如果需要使用回調(diào)函數(shù),請重新在用戶文件中實現(xiàn)該函數(shù)厢塘。
HAL_GPIO_EXTI_Callback()
按照官方提示我們應(yīng)該再次定義該函數(shù)茶没,__weak
是一個弱化標識肌幽,帶有這個的函數(shù)就是一個弱化函數(shù),就是你可以在其他地方寫一個名稱和參數(shù)都一模一樣的函數(shù)抓半,編譯器就會忽略這一個函數(shù)喂急,而去執(zhí)行你寫的那個函數(shù);而 UNUSED(GPIO_Pin)
笛求,這就是一個防報錯的定義廊移,當傳進來的GPIO端口號沒有做任何處理的時候,編譯器也不會報出警告探入。其實我們在開發(fā)的時候已經(jīng)不需要去理會中斷服務(wù)函數(shù)了狡孔,只需要找到這個中斷回調(diào)函數(shù)并將其重寫即可而這個回調(diào)函數(shù)還有一點非常便利的地方這里沒有體現(xiàn)出來,就是當同時有多個中斷使能的時候蜂嗽,STM32CubeMX會自動地將幾個中斷的服務(wù)函數(shù)規(guī)整到一起并調(diào)用一個回調(diào)函數(shù)苗膝,也就是無論幾個中斷,我們只需要重寫一個回調(diào)函并判斷傳進來的端口號即可植旧。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_GPIO_EXTI_Callback()
/* USER CODE BEGIN 1 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 亮藍燈
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 1 */
3.1.8 修改main函數(shù)
初始化完成后使用 LED 及串口表示運行狀態(tài)辱揭,LED 燈為綠色時表示正常運行,紅燈時表示停止狀態(tài)隆嗅,藍燈時表示剛從停止狀態(tài)中被喚醒界阁。在停止模式下,I/O 口會保持停止前的狀態(tài)胖喳,所以 LED 彩燈在停止模式時也會保持亮紅燈泡躯。
程序執(zhí)行一段時間后,調(diào)用庫函數(shù)
HAL_PWR_EnterSTOPMode
把調(diào)壓器設(shè)置在低功耗模式丽焊,進入停止狀態(tài)较剃。由于WFI
停止模式可以使用任意 EXTI 的中斷喚醒,所以我們可以使用按鍵中斷喚醒技健。當系統(tǒng)進入停止狀態(tài)后写穴,我們按下實驗板上的 KEY1 按鍵,即可喚醒系統(tǒng)雌贱,當執(zhí)行完中斷服務(wù)函數(shù)后啊送,會繼續(xù)執(zhí)行
HAL_PWR_EnterSTOPMode
函數(shù)后的代碼。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t SYSCLK_Frequency = 0;
uint32_t HCLK_Frequency = 0;
uint32_t PCLK1_Frequency = 0;
uint32_t PCLK2_Frequency = 0;
uint32_t SYSCLK_Source = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("stop mode test\r\n");
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 使用綠燈指示欣孤,運行狀態(tài)
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_SET);
// 任務(wù)執(zhí)行完畢馋没,進入睡眠降低功耗
// 使用紅燈指示,進入停止狀態(tài)
HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_RESET);
// 暫停滴答時鐘降传,防止通過滴答時鐘中斷喚醒
HAL_SuspendTick();
// 使能PWR時鐘
__HAL_RCC_PWR_CLK_ENABLE();
// 清除喚醒標記
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// 進入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_SET);
// 剛從STOP模式喚醒時鐘默認使用內(nèi)部高速8M時鐘篷朵,所以需要重新配置時鐘
SystemClock_Config();
// 被喚醒后,恢復(fù)滴答時鐘
HAL_ResumeTick();
// 獲取重新配置后的時鐘狀態(tài)
SYSCLK_Frequency = HAL_RCC_GetSysClockFreq();
HCLK_Frequency = HAL_RCC_GetHCLKFreq();
PCLK1_Frequency = HAL_RCC_GetPCLK1Freq();
PCLK2_Frequency = HAL_RCC_GetPCLK2Freq();
SYSCLK_Source = __HAL_RCC_GET_SYSCLK_SOURCE();
// 重新配置時鐘源后始終狀態(tài)
printf("\r\n 重新配置后的時鐘狀態(tài):\r\n");
printf(" SYSCLK 頻率:%d,\r\n HCLK 頻率:%d,\r\n PCLK1 頻率:%d,\r\n PCLK2 頻率:%d,\r\n 時鐘源:%d (0 表示 HSI,8 表示 PLLCLK)\n",SYSCLK_Frequency,HCLK_Frequency,PCLK1_Frequency,PCLK2_Frequency,SYSCLK_Source);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_SET);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.2 RTC時鐘喚醒
3.2.1 添加RTC時鐘
查看 STM32CubeMX學習筆記(14)——RTC實時時鐘使用
3.1.2 添加LED燈
添加綠燈 PB0
表示運行狀態(tài)声旺,紅燈 PB5
表示睡眠狀態(tài)笔链,藍燈 PB1
表示剛從睡眠狀態(tài)中被喚醒。
查看 STM32CubeMX學習筆記(2)——GPIO接口使用
3.1.3 添加串口打印
添加 USART1
用于打印信息腮猖。
查看 STM32CubeMX學習筆記(6)——USART串口使用
3.2.4 使能RTC鬧鐘中斷
3.2.5 生成代碼
輸入項目名和項目路徑
選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5
每個外設(shè)生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應(yīng)的外設(shè)文件鉴扫。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
3.2.6 修改中斷回調(diào)函數(shù)
打開 stm32f1xx_it.c
中斷服務(wù)函數(shù)文件缚够,找到 RTC 鬧鐘中斷的服務(wù)函數(shù) RTC_Alarm_IRQHandler()
中斷服務(wù)函數(shù)里面就調(diào)用了 RTC 鬧鐘中斷處理函數(shù) HAL_RTC_AlarmIRQHandler()
打開 stm32f1xx_hal_rtc.c
文件幔妨,找到RTC鬧鐘中斷處理函數(shù)原型 HAL_RTC_AlarmIRQHandler()
,其主要作用就是判斷是否RTC中斷谍椅,清除中斷標識位误堡,然后調(diào)用中斷回調(diào)函數(shù) HAL_RTC_AlarmAEventCallback()
。
/* NOTE: This function Should not be modified, when the callback is needed,
theHAL_RTC_AlarmAEventCallback could be implemented in the user file
*/
這個函數(shù)不應(yīng)該被改變雏吭,如果需要使用回調(diào)函數(shù)锁施,請重新在用戶文件中實現(xiàn)該函數(shù)。
HAL_RTC_AlarmAEventCallback()
按照官方提示我們應(yīng)該再次定義該函數(shù)杖们,__weak
是一個弱化標識悉抵,帶有這個的函數(shù)就是一個弱化函數(shù),就是你可以在其他地方寫一個名稱和參數(shù)都一模一樣的函數(shù)摘完,編譯器就會忽略這一個函數(shù)姥饰,而去執(zhí)行你寫的那個函數(shù)揩魂;而 UNUSED(hrtc)
济竹,這就是一個防報錯的定義签舞,當傳進來的RTC號沒有做任何處理的時候玷室,編譯器也不會報出警告。其實我們在開發(fā)的時候已經(jīng)不需要去理會中斷服務(wù)函數(shù)了艰额,只需要找到這個中斷回調(diào)函數(shù)并將其重寫即可而這個回調(diào)函數(shù)還有一點非常便利的地方這里沒有體現(xiàn)出來陶因,就是當同時有多個中斷使能的時候堰汉,STM32CubeMX會自動地將幾個中斷的服務(wù)函數(shù)規(guī)整到一起并調(diào)用一個回調(diào)函數(shù)杭措,也就是無論幾個中斷费什,我們只需要重寫一個回調(diào)函并判斷傳進來的端口號即可。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_RTC_AlarmAEventCallback()
/* USER CODE BEGIN 1 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
SystemClock_Config(); // STOP模式喚醒后默認時鐘主頻為內(nèi)部8M時鐘手素,所以要先初始化時鐘配置
// 亮藍燈
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
printf("3s時間到鸳址,喚醒!\r\n");
}
/* USER CODE END 1 */
3.2.7 添加RTC鬧鐘中斷啟動函數(shù)
void RTC_AlarmStart(void)
{
RTC_AlarmTypeDef sAlarm = {0};
RTC_TimeTypeDef tim = {0};
// 獲取當前時間
HAL_RTC_GetTime(&hrtc, &tim, RTC_FORMAT_BIN);
sAlarm.AlarmTime.Hours = tim.Hours;
sAlarm.AlarmTime.Minutes = tim.Minutes;
sAlarm.AlarmTime.Seconds = tim.Seconds + 3; /* 設(shè)置下次鬧鐘提醒時間是當前時間的3s之后 */
sAlarm.Alarm = RTC_ALARM_A;
// 啟動鬧鐘中斷事件
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
3.2.8 修改main函數(shù)
初始化完成后使用 LED 及串口表示運行狀態(tài)泉懦,LED 燈為綠色時表示正常運行稿黍,紅燈時表示停止狀態(tài),藍燈時表示剛從停止狀態(tài)中被喚醒祠斧。在停止模式下闻察,I/O 口會保持停止前的狀態(tài)拱礁,所以 LED 彩燈在停止模式時也會保持亮紅燈琢锋。
程序執(zhí)行一段時間后辕漂,調(diào)用庫函數(shù) HAL_PWR_EnterSTOPMode 把調(diào)壓器設(shè)置在低功耗模式,進入停止狀態(tài)吴超。由于 WFI 停止模式可以使用任意 EXTI 的中斷喚醒钉嘹,所以我們可以使用RTC鬧鐘中斷喚醒。
當系統(tǒng)進入停止狀態(tài)后鲸阻,隔 3 秒后鬧鐘喚醒系統(tǒng)跋涣,當執(zhí)行完中斷服務(wù)函數(shù)后,會繼續(xù)執(zhí)行 HAL_PWR_EnterSTOPMode 函數(shù)后的代碼鸟悴。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint32_t SYSCLK_Frequency = 0;
uint32_t HCLK_Frequency = 0;
uint32_t PCLK1_Frequency = 0;
uint32_t PCLK2_Frequency = 0;
uint32_t SYSCLK_Source = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("stop mode test\r\n");
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 使用綠燈指示陈辱,運行狀態(tài)
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_SET);
// 任務(wù)執(zhí)行完畢,進入睡眠降低功耗
// 使用紅燈指示细诸,進入停止狀態(tài)
HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_RESET);
// 暫停滴答時鐘沛贪,防止通過滴答時鐘中斷喚醒
HAL_SuspendTick();
// 配置下次喚醒的鬧鐘時間
RTC_AlarmStart();
// 使能PWR時鐘
__HAL_RCC_PWR_CLK_ENABLE();
// 清除喚醒標記
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// 進入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
HAL_GPIO_WritePin(GPIOB, LED_R_Pin, GPIO_PIN_SET);
// 被喚醒后,恢復(fù)滴答時鐘
HAL_ResumeTick();
// 獲取重新配置后的時鐘狀態(tài)
SYSCLK_Frequency = HAL_RCC_GetSysClockFreq();
HCLK_Frequency = HAL_RCC_GetHCLKFreq();
PCLK1_Frequency = HAL_RCC_GetPCLK1Freq();
PCLK2_Frequency = HAL_RCC_GetPCLK2Freq();
SYSCLK_Source = __HAL_RCC_GET_SYSCLK_SOURCE();
// 重新配置時鐘源后始終狀態(tài)
printf("\r\n 重新配置后的時鐘狀態(tài):\r\n");
printf(" SYSCLK 頻率:%d,\r\n HCLK 頻率:%d,\r\n PCLK1 頻率:%d,\r\n PCLK2 頻率:%d,\r\n 時鐘源:%d (0 表示 HSI震贵,8 表示 PLLCLK)\n",SYSCLK_Frequency,HCLK_Frequency,PCLK1_Frequency,PCLK2_Frequency,SYSCLK_Source);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_SET);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
四利赋、注意事項
用戶代碼要加在
USER CODE BEGIN N
和USER CODE END N
之間,否則下次使用 STM32CubeMX 重新生成代碼后猩系,會被刪除媚送。
進入低功耗之前可以將引腳全部配置為浮空輸入或者Anglog模式,這樣最省電寇甸,如果你是用STM32CUBEMX塘偎,在這里可以看到這么一項配置就是將沒有用到的引腳配置為了Anglog模式:
當系統(tǒng)處于睡眠模式低功耗狀態(tài)時(包括后面講解的停止模式及待機模式)幽纷,使用 DAP 下載器是無法給芯片下載程序的式塌,所以下載程序時要先把系統(tǒng)喚醒∮呀或者使用如下方法:按著板子的復(fù)位按鍵峰尝,使系統(tǒng)處于復(fù)位狀態(tài),然后點擊電腦端的下載按鈕下載程序收恢,這時再釋放復(fù)位按鍵武学,就能正常給板子下載程序了。
? 由 Leung 寫于 2021 年 3 月 8 日
? 參考:STM32CubeMX系列教程14:電源控制器(PWR)
STM32MX電源管理低功耗模式
STM32F1系列使用HAL庫低功耗STOP和STANDBY模式喚醒(RTC時鐘喚醒+外部中斷喚醒示例)