一、低功耗模式簡介
系統(tǒng)提供了多個(gè)低功耗模式,可在 CPU 不需要運(yùn)行時(shí)(例如等待外部事件時(shí))節(jié)省功耗。由用戶根據(jù)應(yīng)用選擇具體的低功耗模式,以在低功耗、短啟動(dòng)時(shí)間和可用喚醒源之間尋求最佳平衡。
睡眠模式、停止模式及待機(jī)模式中,若備份域電源正常供電,備份域內(nèi)的 RTC 都可以正常運(yùn)行,備份域內(nèi)的寄存器的數(shù)據(jù)會(huì)被保存,不受功耗模式影響。
從表中可以看到,這三種低功耗模式層層遞進(jìn),運(yùn)行的時(shí)鐘或芯片功能越來越少,因而功耗越來越低。
| 模式名稱 | 說明 | 進(jìn)入方式 | 喚醒方式 | 對(duì)1.8V區(qū)域時(shí)鐘的影響 | 對(duì)VDD區(qū)域時(shí)鐘的影響 | 調(diào)壓器 |
|---|---|---|---|---|---|---|
| 睡眠模式 | 內(nèi)核停止,所有外設(shè)包括M3核心的外設(shè),如NVIC、系統(tǒng)時(shí)鐘(SysTick)等仍在運(yùn)行 | 調(diào)用WFI命令 |
任意中斷 | 內(nèi)核時(shí)鐘關(guān),對(duì)其他時(shí)鐘和ADC時(shí)鐘無影響 | 無 | 開 |
| 睡眠模式 | 內(nèi)核停止,所有外設(shè)包括M3核心的外設(shè),如NVIC、系統(tǒng)時(shí)鐘(SysTick)等仍在運(yùn)行 | 調(diào)用WFE命令 |
喚醒事件 | 內(nèi)核時(shí)鐘關(guān),對(duì)其他時(shí)鐘和ADC時(shí)鐘無影響 | 無 | 開 |
| 停止模式 | 所有的時(shí)鐘都已停止 | 配置PWR_CR寄存器的PDDS+LPDS位+SLEEPDEEP位+WFI或WFE命令 |
任意外部中斷EXTI(在外部中斷寄存器中設(shè)置) |
關(guān)閉所有1.8V區(qū)域的時(shí)鐘 | HSI和HSE的振蕩器關(guān)閉 | 開啟或處于低功耗模式(依據(jù)電源控制寄存器的設(shè)定) |
| 待機(jī)模式 | 1.8V電源關(guān)閉 | 配置PWR_CR寄存器的PDDS+SLEEPDEEP位+WFI或WFE命令 |
WKUP上升沿、引腳的RTC鬧鐘事件、NRST引腳上的外部復(fù)位、IWDG復(fù)位 | 關(guān)閉所有1.8V區(qū)域的時(shí)鐘 | HSI和HSE的振蕩器關(guān)閉 | 關(guān) |
1.1 睡眠模式
在睡眠模式中,僅關(guān)閉了內(nèi)核時(shí)鐘,內(nèi)核停止運(yùn)行,但其片上外設(shè),CM3 核心的外設(shè)全都還照常運(yùn)行。有兩種方式進(jìn)入睡眠模式,它的進(jìn)入方式?jīng)Q定了從睡眠喚醒的方式,分別是 WFI(wait for interrupt) 和 WFE(wait for event),即由等待“中斷”喚醒和由“事件”喚醒。
特性和說明:
- 立即睡眠: 在執(zhí)行
WFI或WFE指令時(shí)立即進(jìn)入睡眠模式。- 退出時(shí)睡眠: 在退出優(yōu)先級(jí)最低的中斷服務(wù)程序后才進(jìn)入睡眠模式。
- 進(jìn)入方式: 內(nèi)核寄存器的
SLEEPDEEP=0,然后調(diào)用WFI或WFE指令即可進(jìn)入睡眠模式;SLEEPONEXIT=1時(shí),進(jìn)入“退出時(shí)睡眠”模式。- 喚醒方式: 如果是使用
WFI指令睡眠的,則可使用任意中斷喚醒;如果是使用WFE指令睡眠的,則由事件喚醒。- 睡眠時(shí): 關(guān)閉內(nèi)核時(shí)鐘,內(nèi)核停止,而外設(shè)正常運(yùn)行,在軟件上表現(xiàn)為不再執(zhí)行新的代碼。這個(gè)狀態(tài)會(huì)保留睡眠前的內(nèi)核寄存器、內(nèi)存的數(shù)據(jù)。
- 喚醒延遲: 無延遲。
- 喚醒后: 若由中斷喚醒,先進(jìn)入中斷,退出中斷服務(wù)程序后,接著執(zhí)行
WFI指令后的程序;若由事件喚醒,直接接著執(zhí)行WFE后的程序。
1.2 停止模式
在停止模式中,進(jìn)一步關(guān)閉了其它所有的時(shí)鐘,于是所有的外設(shè)都停止了工作,但由于其 1.8V 區(qū)域的部分電源沒有關(guān)閉,還保留了內(nèi)核的寄存器、內(nèi)存的信息,所以從停止模式喚醒,并重新開啟時(shí)鐘后,還可以從上次停止處繼續(xù)執(zhí)行代碼。停止模式可以由任意一個(gè)外部中斷(EXTI)喚醒,在停止模式中可以選擇電壓調(diào)節(jié)器為開模式或低功耗模式。
特性和說明:
- 調(diào)壓器低功耗模式: 在停止模式下調(diào)壓器可工作在正常模式或低功耗模式,可進(jìn)一步降低功耗。
- 進(jìn)入方式: 內(nèi)核寄存器的
SLEEPDEEP=1,PWR_CR 寄存器中的PDDS=0,然后調(diào)用WFI或WFE指令即可進(jìn)入停止模式;PWR_CR 寄存器的LPDS=0時(shí),調(diào)壓器工作在正常模式,LPDS=1時(shí)工作在低功耗模式。- 喚醒方式: 如果是使用
WFI指令睡眠的,可使用任意 EXTI 線的中斷喚醒;如果是使用WFE指令睡眠的,可使用任意配置為事件模式的 EXTI 線事件喚醒。- 停止時(shí): 內(nèi)核停止,片上外設(shè)也停止。這個(gè)狀態(tài)會(huì)保留停止前的內(nèi)核寄存器、內(nèi)存的數(shù)據(jù)。
- 喚醒延遲: 基礎(chǔ)延遲為 HSI 振蕩器的啟動(dòng)時(shí)間,若調(diào)壓器工作在低功耗模式,還需要加上調(diào)壓器從低功耗切換至正常模式下的時(shí)間。
- 喚醒后: 若由中斷喚醒,先進(jìn)入中斷,退出中斷服務(wù)程序后,接著執(zhí)行
WFI指令后的程序;若由事件喚醒,直接接著執(zhí)行WFE后的程序。喚醒后,STM32 會(huì)使用 HSI 作為系統(tǒng)時(shí)鐘。
1.3 待機(jī)模式
待機(jī)模式,它除了關(guān)閉所有的時(shí)鐘,還把 1.8V 區(qū)域的電源也完全關(guān)閉了,也就是說,從待機(jī)模式喚醒后,由于沒有之前代碼的運(yùn)行記錄,只能對(duì)芯片復(fù)位,重新檢測(cè) boot 條件,從頭開始執(zhí)行程序。它有四種喚醒方式,分別是 WKUP(PA0)引腳的上升沿,RTC 鬧鐘事件,NRST 引腳的復(fù)位和 IWDG(獨(dú)立看門狗)復(fù)位。
特性和說明:
- 進(jìn)入方式: 內(nèi)核寄存器的
SLEEPDEEP=1,PWR_CR 寄存器中的PDDS=1,PWR_CR 寄存器中的喚醒狀態(tài)位WUF=0,然后調(diào)用WFI或WFE指令即可進(jìn)入待機(jī)模式。- 喚醒方式: 通過 WKUP 引腳的上升沿,RTC 鬧鐘、喚醒、入侵、時(shí)間戳事件或 NRST 引腳外部復(fù)位及 IWDG 復(fù)位喚醒。
- 待機(jī)時(shí): 內(nèi)核停止,片上外設(shè)也停止;內(nèi)核寄存器、內(nèi)存的數(shù)據(jù)會(huì)丟失;除復(fù)位引腳、RTC_AF1 引腳及 WKUP 引腳,其它 I/O 口均工作在高阻態(tài)。
- 喚醒延遲: 芯片復(fù)位的時(shí)間。
- 喚醒后: 相當(dāng)于芯片復(fù)位,在程序表現(xiàn)為從頭開始執(zhí)行代碼。
1.4 WFI與WFE命令
我們了解到進(jìn)入各種低功耗模式時(shí)都需要調(diào)用 WFI 或 WFE 命令,它們實(shí)質(zhì)上都是內(nèi)核指令,在庫文件 core_cm3.h 中把這些指令封裝成了函數(shù)。
/** brief 等待中斷
等待中斷 是一個(gè)暫停執(zhí)行指令
暫停至任意中斷產(chǎn)生后被喚醒
*/
#define __WFI __wfi
/** brief 等待事件
等待事件 是一個(gè)暫停執(zhí)行指令
暫停至任意事件產(chǎn)生后被喚醒
*/
#define __WFE __wfe
對(duì)于這兩個(gè)指令,我們應(yīng)用時(shí)一般只需要知道,調(diào)用它們都能進(jìn)入低功耗模式,需要使用函數(shù)的格式“__WFI();”和“__WFE();”來調(diào)用(因?yàn)開_wfi 及__wfe 是編譯器內(nèi)置的函數(shù),函數(shù)內(nèi)部調(diào)用了相應(yīng)的匯編指令)。
其中
WFI指令決定了它需要用中斷喚醒,而WFE則決定了它可用事件來喚醒。
二、新建工程
1. 打開 STM32CubeMX 軟件,點(diǎn)擊“新建工程”

2. 選擇 MCU 和封裝

3. 配置時(shí)鐘
RCC 設(shè)置,選擇 HSE(外部高速時(shí)鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
開啟 LSE(外部低速時(shí)鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)

選擇 Clock Configuration,配置系統(tǒng)時(shí)鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車,軟件會(huì)自動(dòng)修改所有配置

4. 配置調(diào)試模式
非常重要的一步,否則會(huì)造成第一次燒錄程序后續(xù)無法識(shí)別調(diào)試器
SYS 設(shè)置,選擇 Debug 為 Serial Wire

三、待機(jī)模式
3.1 WKUP按鍵喚醒
3.1.1 流程圖

3.1.2 HAL庫與標(biāo)準(zhǔn)庫代碼比較
STM32CubeMX 使用 HAL 庫的代碼:
int main(void)
{
// 檢測(cè)復(fù)位來源
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\n 待機(jī)喚醒復(fù)位 \r\n");
}
else
{
printf("\r\n 非待機(jī)喚醒復(fù)位 \r\n");
}
···
while(1)
{
···
HAL_Delay(5000);
/*清除 WU 狀態(tài)位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能 WKUP 引腳的喚醒功能 ,使能 PA0*/
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 進(jìn)入待機(jī)模式 */
HAL_PWR_EnterSTANDBYMode();
···
}
}
使用 STM32 標(biāo)準(zhǔn)庫的代碼:
int main(void)
{
/* 使能電源管理單元的時(shí)鐘,必須要使能時(shí)鐘才能進(jìn)入待機(jī)模式 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 檢測(cè)復(fù)位來源
if(PWR_GetFlagStatus(PWR_FLAG_WU) == SET)
{
printf("\r\n 待機(jī)喚醒復(fù)位 \r\n");
}
else
{
printf("\r\n 非待機(jī)喚醒復(fù)位 \r\n");
}
···
while(1)
{
···
Delay(0xFFFF);
/*清除 WU 狀態(tài)位*/
PWR_ClearFlag(PWR_FLAG_WU);
/* 使能 WKUP 引腳的喚醒功能 ,使能 PA0*/
PWR_WakeUpPinCmd(ENABLE);
/* 進(jìn)入待機(jī)模式 */
PWR_EnterSTANDBYMode();
···
}
}
3.1.3 添加WKUP按鍵
添加系統(tǒng)喚醒按鍵 PA0 ,以便當(dāng)系統(tǒng)進(jìn)入待機(jī)模式的時(shí)候可以通過按鍵來喚醒。

或者勾選
System Wake-Up
3.1.4 添加LED燈
添加綠燈 PB0 表示本次復(fù)位是上電或引腳復(fù)位,藍(lán)燈 PB1 表示本次是待機(jī)喚醒的復(fù)位。
查看 STM32CubeMX學(xué)習(xí)筆記(2)——GPIO接口使用
3.1.5 添加串口打印
添加 USART1 用于打印信息。
查看 STM32CubeMX學(xué)習(xí)筆記(6)——USART串口使用
3.1.6 生成代碼
輸入項(xiàng)目名和項(xiàng)目路徑

選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5

每個(gè)外設(shè)生成獨(dú)立的
’.c/.h’ 文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對(duì)應(yīng)的外設(shè)文件。 如 GPIO 初始化代碼生成在 gpio.c 中。

點(diǎn)擊 GENERATE CODE 生成代碼

3.1.7 修改main函數(shù)
程序中首先初始化了系統(tǒng)時(shí)鐘、LED 燈及串口以便用于指示芯片的運(yùn)行狀態(tài),由于待機(jī)模式喚醒使用 WKUP 引腳并不需要特別的引腳初始化。
使用庫函數(shù) __HAL_PWR_GET_FLAG 檢測(cè) PWR_FLAG_SB 標(biāo)志位,當(dāng)這個(gè)標(biāo)志位為 SET 狀態(tài)的時(shí)候,表示本次系統(tǒng)是從待機(jī)模式喚醒的復(fù)位,否則可能是上電復(fù)位。
我們利用這個(gè)區(qū)分兩種復(fù)位形式,分別使用藍(lán)色 LED 燈或綠色 LED 燈來指示。在使用庫函數(shù) HAL_PWR_EnableWakeUpPin 發(fā)送待機(jī)命令前,要先使用庫函數(shù) __HAL_PWR_CLEAR_FLAG 清除 PWR_FLAG_WU 標(biāo)志位,并且使用庫函數(shù) HAL_PWR_EnableWakeUpPin 使能 WKUP 喚醒功能,這樣進(jìn)入待機(jī)模式后才能使用 WKUP 喚醒。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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("standby mode test\r\n");
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\n standby reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
}
else
{
printf("\r\n normal reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(5000);
/*清除 WU 狀態(tài)位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能 WKUP 引腳的喚醒功能 ,使能 PA0*/
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 進(jìn)入待機(jī)模式 */
HAL_PWR_EnterSTANDBYMode();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.2 RTC時(shí)鐘喚醒
3.2.1 添加RTC時(shí)鐘
查看 STM32CubeMX學(xué)習(xí)筆記(14)——RTC實(shí)時(shí)時(shí)鐘使用
3.1.2 添加LED燈
添加綠燈 PB0 表示本次復(fù)位是上電或引腳復(fù)位,藍(lán)燈 PB1 表示本次是待機(jī)喚醒的復(fù)位。
查看 STM32CubeMX學(xué)習(xí)筆記(2)——GPIO接口使用
3.1.3 添加串口打印
添加 USART1 用于打印信息。
查看 STM32CubeMX學(xué)習(xí)筆記(6)——USART串口使用
3.2.4 使能RTC鬧鐘中斷

3.2.5 生成代碼
輸入項(xiàng)目名和項(xiàng)目路徑

選擇應(yīng)用的 IDE 開發(fā)環(huán)境 MDK-ARM V5

每個(gè)外設(shè)生成獨(dú)立的
’.c/.h’ 文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對(duì)應(yīng)的外設(shè)文件。 如 GPIO 初始化代碼生成在 gpio.c 中。

點(diǎn)擊 GENERATE CODE 生成代碼

3.2.6 添加RTC鬧鐘中斷啟動(dòng)函數(shù)
void RTC_AlarmStart(void)
{
RTC_AlarmTypeDef sAlarm = {0};
RTC_TimeTypeDef tim = {0};
// 獲取當(dāng)前時(shí)間
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è)置下次鬧鐘提醒時(shí)間是當(dāng)前時(shí)間的3s之后 */
sAlarm.Alarm = RTC_ALARM_A;
// 啟動(dòng)鬧鐘中斷事件
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
3.2.7 修改main函數(shù)
程序中首先初始化了系統(tǒng)時(shí)鐘、LED 燈及串口以便用于指示芯片的運(yùn)行狀態(tài),由于待機(jī)模式喚醒使用 WKUP 引腳并不需要特別的引腳初始化。
使用庫函數(shù) __HAL_PWR_GET_FLAG 檢測(cè) PWR_FLAG_SB 標(biāo)志位,當(dāng)這個(gè)標(biāo)志位為 SET 狀態(tài)的時(shí)候,表示本次系統(tǒng)是從待機(jī)模式喚醒的復(fù)位,否則可能是上電復(fù)位。
我們利用這個(gè)區(qū)分兩種復(fù)位形式,分別使用藍(lán)色 LED 燈或綠色 LED 燈來指示。在使用庫函數(shù) HAL_PWR_EnableWakeUpPin 發(fā)送待機(jī)命令前,要先使用庫函數(shù) __HAL_PWR_CLEAR_FLAG 清除 PWR_FLAG_WU 標(biāo)志位,并且使用庫函數(shù) HAL_PWR_EnableWakeUpPin 使能 WKUP 喚醒功能,這樣進(jìn)入待機(jī)模式后才能使用 WKUP 喚醒。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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("standby mode test\r\n");
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\n standby reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_B_Pin, GPIO_PIN_RESET);
}
else
{
printf("\r\n normal reset \r\n");
HAL_GPIO_WritePin(GPIOB, LED_G_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(5000);
RTC_AlarmStart();
/*清除 WU 狀態(tài)位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能 WKUP 引腳的喚醒功能 ,使能 PA0*/
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 進(jìn)入待機(jī)模式 */
HAL_PWR_EnterSTANDBYMode();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
四、注意事項(xiàng)
用戶代碼要加在
USER CODE BEGIN N和USER CODE END N之間,否則下次使用 STM32CubeMX 重新生成代碼后,會(huì)被刪除。

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

當(dāng)系統(tǒng)處于睡眠模式低功耗狀態(tài)時(shí)(包括后面講解的停止模式及待機(jī)模式),使用 DAP 下載器是無法給芯片下載程序的,所以下載程序時(shí)要先把系統(tǒng)喚醒。或者使用如下方法:按著板子的復(fù)位按鍵,使系統(tǒng)處于復(fù)位狀態(tài),然后點(diǎn)擊電腦端的下載按鈕下載程序,這時(shí)再釋放復(fù)位按鍵,就能正常給板子下載程序了。
? 由 Leung 寫于 2021 年 3 月 11 日
? 參考:STM32CubeMX系列教程14:電源控制器(PWR)
STM32MX電源管理低功耗模式
STM32F1系列使用HAL庫低功耗STOP和STANDBY模式喚醒(RTC時(shí)鐘喚醒+外部中斷喚醒示例)
《嵌入式-STM32開發(fā)指南》第二部分 基礎(chǔ)篇 - 第10章 低功耗(HAL庫)