STM32CubeMX學習筆記(16)——電源管理(PWR)低功耗停止模式

一、低功耗模式簡介

系統提供了多個低功耗模式,可在 CPU 不需要運行時(例如等待外部事件時)節省功耗。由用戶根據應用選擇具體的低功耗模式,以在低功耗、短啟動時間和可用喚醒源之間尋求最佳平衡。

睡眠模式、停止模式及待機模式中,若備份域電源正常供電,備份域內的 RTC 都可以正常運行,備份域內的寄存器的數據會被保存,不受功耗模式影響。

從表中可以看到,這三種低功耗模式層層遞進,運行的時鐘或芯片功能越來越少,因而功耗越來越低。

模式名稱 說明 進入方式 喚醒方式 對1.8V區域時鐘的影響 對VDD區域時鐘的影響 調壓器
睡眠模式 內核停止,所有外設包括M3核心的外設,如NVIC、系統時鐘(SysTick)等仍在運行 調用WFI命令 任意中斷 內核時鐘關,對其他時鐘和ADC時鐘無影響
睡眠模式 內核停止,所有外設包括M3核心的外設,如NVIC、系統時鐘(SysTick)等仍在運行 調用WFE命令 喚醒事件 內核時鐘關,對其他時鐘和ADC時鐘無影響
停止模式 所有的時鐘都已停止 配置PWR_CR寄存器的PDDS+LPDS位+SLEEPDEEP位+WFIWFE命令 任意外部中斷EXTI(在外部中斷寄存器中設置) 關閉所有1.8V區域的時鐘 HSI和HSE的振蕩器關閉 開啟或處于低功耗模式(依據電源控制寄存器的設定)
待機模式 1.8V電源關閉 配置PWR_CR寄存器的PDDS+SLEEPDEEP位+WFIWFE命令 WKUP上升沿、引腳的RTC鬧鐘事件、NRST引腳上的外部復位、IWDG復位 關閉所有1.8V區域的時鐘 HSI和HSE的振蕩器關閉

1.1 睡眠模式

在睡眠模式中,僅關閉了內核時鐘,內核停止運行,但其片上外設,CM3 核心的外設全都還照常運行。有兩種方式進入睡眠模式,它的進入方式決定了從睡眠喚醒的方式,分別是 WFI(wait for interrupt)WFE(wait for event),即由等待“中斷”喚醒和由“事件”喚醒。

特性和說明:

  • 立即睡眠: 在執行 WFIWFE 指令時立即進入睡眠模式。
  • 退出時睡眠: 在退出優先級最低的中斷服務程序后才進入睡眠模式。
  • 進入方式: 內核寄存器的 SLEEPDEEP=0 ,然后調用 WFIWFE 指令即可進入睡眠模式;SLEEPONEXIT=1 時,進入“退出時睡眠”模式。
  • 喚醒方式: 如果是使用 WFI 指令睡眠的,則可使用任意中斷喚醒;如果是使用 WFE 指令睡眠的,則由事件喚醒。
  • 睡眠時: 關閉內核時鐘,內核停止,而外設正常運行,在軟件上表現為不再執行新的代碼。這個狀態會保留睡眠前的內核寄存器、內存的數據。
  • 喚醒延遲: 無延遲。
  • 喚醒后: 若由中斷喚醒,先進入中斷,退出中斷服務程序后,接著執行 WFI 指令后的程序;若由事件喚醒,直接接著執行 WFE 后的程序。

1.2 停止模式

在停止模式中,進一步關閉了其它所有的時鐘,于是所有的外設都停止了工作,但由于其 1.8V 區域的部分電源沒有關閉,還保留了內核的寄存器、內存的信息,所以從停止模式喚醒,并重新開啟時鐘后,還可以從上次停止處繼續執行代碼。停止模式可以由任意一個外部中斷(EXTI)喚醒,在停止模式中可以選擇電壓調節器為開模式或低功耗模式。

特性和說明:

  • 調壓器低功耗模式: 在停止模式下調壓器可工作在正常模式或低功耗模式,可進一步降低功耗。
  • 進入方式: 內核寄存器的 SLEEPDEEP=1,PWR_CR 寄存器中的 PDDS=0,然后調用 WFIWFE 指令即可進入停止模式;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,然后調用 WFIWFE 指令即可進入待機模式。
  • 喚醒方式: 通過 WKUP 引腳的上升沿,RTC 鬧鐘、喚醒、入侵、時間戳事件或 NRST 引腳外部復位及 IWDG 復位喚醒。
  • 待機時: 內核停止,片上外設也停止;內核寄存器、內存的數據會丟失;除復位引腳、RTC_AF1 引腳及 WKUP 引腳,其它 I/O 口均工作在高阻態。
  • 喚醒延遲: 芯片復位的時間。
  • 喚醒后: 相當于芯片復位,在程序表現為從頭開始執行代碼。

1.4 WFI與WFE命令

我們了解到進入各種低功耗模式時都需要調用 WFIWFE 命令,它們實質上都是內核指令,在庫文件 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 NUSER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除。

進入低功耗之前可以將引腳全部配置為浮空輸入或者Anglog模式,這樣最省電,如果你是用STM32CUBEMX,在這里可以看到這么一項配置就是將沒有用到的引腳配置為了Anglog模式:

當系統處于睡眠模式低功耗狀態時(包括后面講解的停止模式及待機模式),使用 DAP 下載器是無法給芯片下載程序的,所以下載程序時要先把系統喚醒。或者使用如下方法:按著板子的復位按鍵,使系統處于復位狀態,然后點擊電腦端的下載按鈕下載程序,這時再釋放復位按鍵,就能正常給板子下載程序了。


? 由 Leung 寫于 2021 年 3 月 8 日

? 參考:STM32CubeMX系列教程14:電源控制器(PWR)
    STM32MX電源管理低功耗模式
    STM32F1系列使用HAL庫低功耗STOP和STANDBY模式喚醒(RTC時鐘喚醒+外部中斷喚醒示例)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容