一、低功耗模式簡介
系統提供了多個低功耗模式,可在 CPU 不需要運行時(例如等待外部事件時)節省功耗。由用戶根據應用選擇具體的低功耗模式,以在低功耗、短啟動時間和可用喚醒源之間尋求最佳平衡。
睡眠模式、停止模式及待機模式中,若備份域電源正常供電,備份域內的 RTC 都可以正常運行,備份域內的寄存器的數據會被保存,不受功耗模式影響。
從表中可以看到,這三種低功耗模式層層遞進,運行的時鐘或芯片功能越來越少,因而功耗越來越低。
模式名稱 | 說明 | 進入方式 | 喚醒方式 | 對1.8V區域時鐘的影響 | 對VDD區域時鐘的影響 | 調壓器 |
---|---|---|---|---|---|---|
睡眠模式 | 內核停止,所有外設包括M3核心的外設,如NVIC、系統時鐘(SysTick)等仍在運行 | 調用WFI 命令 |
任意中斷 | 內核時鐘關,對其他時鐘和ADC時鐘無影響 | 無 | 開 |
睡眠模式 | 內核停止,所有外設包括M3核心的外設,如NVIC、系統時鐘(SysTick)等仍在運行 | 調用WFE 命令 |
喚醒事件 | 內核時鐘關,對其他時鐘和ADC時鐘無影響 | 無 | 開 |
停止模式 | 所有的時鐘都已停止 | 配置PWR_CR寄存器的PDDS +LPDS 位+SLEEPDEEP 位+WFI 或WFE 命令 |
任意外部中斷EXTI (在外部中斷寄存器中設置) |
關閉所有1.8V區域的時鐘 | HSI和HSE的振蕩器關閉 | 開啟或處于低功耗模式(依據電源控制寄存器的設定) |
待機模式 | 1.8V電源關閉 | 配置PWR_CR寄存器的PDDS +SLEEPDEEP 位+WFI 或WFE 命令 |
WKUP上升沿、引腳的RTC鬧鐘事件、NRST引腳上的外部復位、IWDG復位 | 關閉所有1.8V區域的時鐘 | HSI和HSE的振蕩器關閉 | 關 |
1.1 睡眠模式
在睡眠模式中,僅關閉了內核時鐘,內核停止運行,但其片上外設,CM3 核心的外設全都還照常運行。有兩種方式進入睡眠模式,它的進入方式決定了從睡眠喚醒的方式,分別是 WFI(wait for interrupt) 和 WFE(wait for event),即由等待“中斷”喚醒和由“事件”喚醒。
特性和說明:
- 立即睡眠: 在執行
WFI
或WFE
指令時立即進入睡眠模式。- 退出時睡眠: 在退出優先級最低的中斷服務程序后才進入睡眠模式。
- 進入方式: 內核寄存器的
SLEEPDEEP=0
,然后調用WFI
或WFE
指令即可進入睡眠模式;SLEEPONEXIT=1
時,進入“退出時睡眠”模式。- 喚醒方式: 如果是使用
WFI
指令睡眠的,則可使用任意中斷喚醒;如果是使用WFE
指令睡眠的,則由事件喚醒。- 睡眠時: 關閉內核時鐘,內核停止,而外設正常運行,在軟件上表現為不再執行新的代碼。這個狀態會保留睡眠前的內核寄存器、內存的數據。
- 喚醒延遲: 無延遲。
- 喚醒后: 若由中斷喚醒,先進入中斷,退出中斷服務程序后,接著執行
WFI
指令后的程序;若由事件喚醒,直接接著執行WFE
后的程序。
1.2 停止模式
在停止模式中,進一步關閉了其它所有的時鐘,于是所有的外設都停止了工作,但由于其 1.8V 區域的部分電源沒有關閉,還保留了內核的寄存器、內存的信息,所以從停止模式喚醒,并重新開啟時鐘后,還可以從上次停止處繼續執行代碼。停止模式可以由任意一個外部中斷(EXTI)喚醒,在停止模式中可以選擇電壓調節器為開模式或低功耗模式。
特性和說明:
- 調壓器低功耗模式: 在停止模式下調壓器可工作在正常模式或低功耗模式,可進一步降低功耗。
- 進入方式: 內核寄存器的
SLEEPDEEP=1
,PWR_CR 寄存器中的PDDS=0
,然后調用WFI
或WFE
指令即可進入停止模式;PWR_CR 寄存器的LPDS=0
時,調壓器工作在正常模式,LPDS=1
時工作在低功耗模式。- 喚醒方式: 如果是使用
WFI
指令睡眠的,可使用任意 EXTI 線的中斷喚醒;如果是使用WFE
指令睡眠的,可使用任意配置為事件模式的 EXTI 線事件喚醒。- 停止時: 內核停止,片上外設也停止。這個狀態會保留停止前的內核寄存器、內存的數據。
- 喚醒延遲: 基礎延遲為 HSI 振蕩器的啟動時間,若調壓器工作在低功耗模式,還需要加上調壓器從低功耗切換至正常模式下的時間。
- 喚醒后: 若由中斷喚醒,先進入中斷,退出中斷服務程序后,接著執行
WFI
指令后的程序;若由事件喚醒,直接接著執行WFE
后的程序。喚醒后,STM32 會使用 HSI 作為系統時鐘。
1.3 待機模式
待機模式,它除了關閉所有的時鐘,還把 1.8V 區域的電源也完全關閉了,也就是說,從待機模式喚醒后,由于沒有之前代碼的運行記錄,只能對芯片復位,重新檢測 boot 條件,從頭開始執行程序。它有四種喚醒方式,分別是 WKUP(PA0)引腳的上升沿,RTC 鬧鐘事件,NRST 引腳的復位和 IWDG(獨立看門狗)復位。
特性和說明:
- 進入方式: 內核寄存器的
SLEEPDEEP=1
,PWR_CR 寄存器中的PDDS=1
,PWR_CR 寄存器中的喚醒狀態位WUF=0
,然后調用WFI
或WFE
指令即可進入待機模式。- 喚醒方式: 通過 WKUP 引腳的上升沿,RTC 鬧鐘、喚醒、入侵、時間戳事件或 NRST 引腳外部復位及 IWDG 復位喚醒。
- 待機時: 內核停止,片上外設也停止;內核寄存器、內存的數據會丟失;除復位引腳、RTC_AF1 引腳及 WKUP 引腳,其它 I/O 口均工作在高阻態。
- 喚醒延遲: 芯片復位的時間。
- 喚醒后: 相當于芯片復位,在程序表現為從頭開始執行代碼。
1.4 WFI與WFE命令
我們了解到進入各種低功耗模式時都需要調用 WFI
或 WFE
命令,它們實質上都是內核指令,在庫文件 core_cm3.h
中把這些指令封裝成了函數。
/** brief 等待中斷
等待中斷 是一個暫停執行指令
暫停至任意中斷產生后被喚醒
*/
#define __WFI __wfi
/** brief 等待事件
等待事件 是一個暫停執行指令
暫停至任意事件產生后被喚醒
*/
#define __WFE __wfe
對于這兩個指令,我們應用時一般只需要知道,調用它們都能進入低功耗模式,需要使用函數的格式“__WFI();”和“__WFE();”來調用(因為__wfi 及__wfe 是編譯器內置的函數,函數內部調用了相應的匯編指令)。
其中
WFI
指令決定了它需要用中斷喚醒,而WFE
則決定了它可用事件來喚醒。
二、新建工程
1. 打開 STM32CubeMX 軟件,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設置,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
開啟 LSE(外部低速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration,配置系統時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車,軟件會自動修改所有配置
4. 配置調試模式
非常重要的一步,否則會造成第一次燒錄程序后續無法識別調試器
SYS 設置,選擇 Debug 為 Serial Wire
三、停止模式
3.1 WFI按鍵外部中斷喚醒
3.1.1 流程圖
3.1.2 HAL庫與標準庫代碼比較
STM32CubeMX 使用 HAL 庫的代碼:
int main(void)
{
···
while(1)
{
···
// 暫停滴答時鐘,防止通過滴答時鐘中斷喚醒
HAL_SuspendTick();
/* 進入停止模式,設置電壓調節器為低功耗模式,等待中斷喚醒 */
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_STOPENTRY_WFI);
SYSCLKConfig_STOP();
// 被喚醒后,恢復滴答時鐘
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();
/* 根據內部 RCC 寄存器獲取振蕩器配置 */
HAL_RCC_GetOscConfig(&RCC_OscInitStruct);
/* 從停止模式喚醒后重新配置系統時鐘: 啟用 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)
{;
}
}
/* 根據內部 RCC 寄存器獲取時鐘配置 */
HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency);
/* 選擇 PLL 作為系統時鐘源, 并配置 HCLK、PCLK1 和 PCLK2 時鐘分頻系數 */
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)
{
···
/* 進入停止模式,設置電壓調節器為低功耗模式,等待中斷喚醒 */
PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);
// 從停止模式下被喚醒后使用的是 HSI 時鐘,此處重啟 HSE 時鐘,使用 PLLCLK
SYSCLKConfig_STOP();
···
}
}
static void SYSCLKConfig_STOP(void)
{
/* 停機喚醒后配置系統時鐘 */
/* 使能 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 作為系統時鐘源 */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* 等待 PLL 被選擇為系統時鐘源 */
while (RCC_GetSYSCLKSource() != 0x08);
}
3.1.3 添加按鍵
初始化按鍵 PA0
中斷模式,以便當系統進入睡眠模式的時候可以通過按鍵來喚醒。
查看 STM32CubeMX學習筆記(3)——EXTI(外部中斷)接口使用
3.1.4 添加LED燈
添加綠燈 PB0
表示運行狀態,紅燈 PB5
表示睡眠狀態,藍燈 PB1
表示剛從睡眠狀態中被喚醒。
查看 STM32CubeMX學習筆記(2)——GPIO接口使用
3.1.5 添加串口打印
添加 USART1
用于打印信息。
查看 STM32CubeMX學習筆記(6)——USART串口使用
3.1.6 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發環境 MDK-ARM V5
每個外設生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設文件。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
3.1.7 修改中斷回調函數
當系統進入停止狀態后,我們按下實驗板上的 KEY1 按鍵,即可使系統回到正常運行的狀態,當執行完中斷服務函數后,會繼續執行
WFI
指令后的代碼。
打開 stm32f1xx_it.c
中斷服務函數文件,找到 EXTI0 中斷的服務函數 EXTI0_IRQHandler()
中斷服務函數里面就調用了 GPIO 外部中斷處理函數 HAL_GPIO_EXTI_IRQHandler()
打開
stm32f1xx_hal_gpio.c
文件,找到外部中斷處理函數原型 HAL_GPIO_EXTI_IRQHandler()
,其主要作用就是判斷是幾號線中斷,清除中斷標識位,然后調用中斷回調函數 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
*/
這個函數不應該被改變,如果需要使用回調函數,請重新在用戶文件中實現該函數。
HAL_GPIO_EXTI_Callback()
按照官方提示我們應該再次定義該函數,__weak
是一個弱化標識,帶有這個的函數就是一個弱化函數,就是你可以在其他地方寫一個名稱和參數都一模一樣的函數,編譯器就會忽略這一個函數,而去執行你寫的那個函數;而 UNUSED(GPIO_Pin)
,這就是一個防報錯的定義,當傳進來的GPIO端口號沒有做任何處理的時候,編譯器也不會報出警告。其實我們在開發的時候已經不需要去理會中斷服務函數了,只需要找到這個中斷回調函數并將其重寫即可而這個回調函數還有一點非常便利的地方這里沒有體現出來,就是當同時有多個中斷使能的時候,STM32CubeMX會自動地將幾個中斷的服務函數規整到一起并調用一個回調函數,也就是無論幾個中斷,我們只需要重寫一個回調函并判斷傳進來的端口號即可。
接下來我們就在 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函數
初始化完成后使用 LED 及串口表示運行狀態,LED 燈為綠色時表示正常運行,紅燈時表示停止狀態,藍燈時表示剛從停止狀態中被喚醒。在停止模式下,I/O 口會保持停止前的狀態,所以 LED 彩燈在停止模式時也會保持亮紅燈。
程序執行一段時間后,調用庫函數
HAL_PWR_EnterSTOPMode
把調壓器設置在低功耗模式,進入停止狀態。由于WFI
停止模式可以使用任意 EXTI 的中斷喚醒,所以我們可以使用按鍵中斷喚醒。當系統進入停止狀態后,我們按下實驗板上的 KEY1 按鍵,即可喚醒系統,當執行完中斷服務函數后,會繼續執行
HAL_PWR_EnterSTOPMode
函數后的代碼。
/**
* @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)
{
// 使用綠燈指示,運行狀態
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_SET);
// 任務執行完畢,進入睡眠降低功耗
// 使用紅燈指示,進入停止狀態
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模式喚醒時鐘默認使用內部高速8M時鐘,所以需要重新配置時鐘
SystemClock_Config();
// 被喚醒后,恢復滴答時鐘
HAL_ResumeTick();
// 獲取重新配置后的時鐘狀態
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();
// 重新配置時鐘源后始終狀態
printf("\r\n 重新配置后的時鐘狀態:\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
表示運行狀態,紅燈 PB5
表示睡眠狀態,藍燈 PB1
表示剛從睡眠狀態中被喚醒。
查看 STM32CubeMX學習筆記(2)——GPIO接口使用
3.1.3 添加串口打印
添加 USART1
用于打印信息。
查看 STM32CubeMX學習筆記(6)——USART串口使用
3.2.4 使能RTC鬧鐘中斷
3.2.5 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發環境 MDK-ARM V5
每個外設生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設文件。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
3.2.6 修改中斷回調函數
打開 stm32f1xx_it.c
中斷服務函數文件,找到 RTC 鬧鐘中斷的服務函數 RTC_Alarm_IRQHandler()
中斷服務函數里面就調用了 RTC 鬧鐘中斷處理函數 HAL_RTC_AlarmIRQHandler()
打開 stm32f1xx_hal_rtc.c
文件,找到RTC鬧鐘中斷處理函數原型 HAL_RTC_AlarmIRQHandler()
,其主要作用就是判斷是否RTC中斷,清除中斷標識位,然后調用中斷回調函數 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
*/
這個函數不應該被改變,如果需要使用回調函數,請重新在用戶文件中實現該函數。
HAL_RTC_AlarmAEventCallback()
按照官方提示我們應該再次定義該函數,__weak
是一個弱化標識,帶有這個的函數就是一個弱化函數,就是你可以在其他地方寫一個名稱和參數都一模一樣的函數,編譯器就會忽略這一個函數,而去執行你寫的那個函數;而 UNUSED(hrtc)
,這就是一個防報錯的定義,當傳進來的RTC號沒有做任何處理的時候,編譯器也不會報出警告。其實我們在開發的時候已經不需要去理會中斷服務函數了,只需要找到這個中斷回調函數并將其重寫即可而這個回調函數還有一點非常便利的地方這里沒有體現出來,就是當同時有多個中斷使能的時候,STM32CubeMX會自動地將幾個中斷的服務函數規整到一起并調用一個回調函數,也就是無論幾個中斷,我們只需要重寫一個回調函并判斷傳進來的端口號即可。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_RTC_AlarmAEventCallback()
/* USER CODE BEGIN 1 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
SystemClock_Config(); // STOP模式喚醒后默認時鐘主頻為內部8M時鐘,所以要先初始化時鐘配置
// 亮藍燈
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
printf("3s時間到,喚醒!\r\n");
}
/* USER CODE END 1 */
3.2.7 添加RTC鬧鐘中斷啟動函數
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; /* 設置下次鬧鐘提醒時間是當前時間的3s之后 */
sAlarm.Alarm = RTC_ALARM_A;
// 啟動鬧鐘中斷事件
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
3.2.8 修改main函數
初始化完成后使用 LED 及串口表示運行狀態,LED 燈為綠色時表示正常運行,紅燈時表示停止狀態,藍燈時表示剛從停止狀態中被喚醒。在停止模式下,I/O 口會保持停止前的狀態,所以 LED 彩燈在停止模式時也會保持亮紅燈。
程序執行一段時間后,調用庫函數 HAL_PWR_EnterSTOPMode 把調壓器設置在低功耗模式,進入停止狀態。由于 WFI 停止模式可以使用任意 EXTI 的中斷喚醒,所以我們可以使用RTC鬧鐘中斷喚醒。
當系統進入停止狀態后,隔 3 秒后鬧鐘喚醒系統,當執行完中斷服務函數后,會繼續執行 HAL_PWR_EnterSTOPMode 函數后的代碼。
/**
* @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)
{
// 使用綠燈指示,運行狀態
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
HAL_Delay(2000);
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_SET);
// 任務執行完畢,進入睡眠降低功耗
// 使用紅燈指示,進入停止狀態
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);
// 被喚醒后,恢復滴答時鐘
HAL_ResumeTick();
// 獲取重新配置后的時鐘狀態
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();
// 重新配置時鐘源后始終狀態
printf("\r\n 重新配置后的時鐘狀態:\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模式:
當系統處于睡眠模式低功耗狀態時(包括后面講解的停止模式及待機模式),使用 DAP 下載器是無法給芯片下載程序的,所以下載程序時要先把系統喚醒。或者使用如下方法:按著板子的復位按鍵,使系統處于復位狀態,然后點擊電腦端的下載按鈕下載程序,這時再釋放復位按鍵,就能正常給板子下載程序了。
? 由 Leung 寫于 2021 年 3 月 8 日
? 參考:STM32CubeMX系列教程14:電源控制器(PWR)
STM32MX電源管理低功耗模式
STM32F1系列使用HAL庫低功耗STOP和STANDBY模式喚醒(RTC時鐘喚醒+外部中斷喚醒示例)