10.3.2停止模式
进入停止模式之后,任何外部中断都可以唤醒低功耗,但是需要重新配置时钟,不然系统将以默认时钟(没有经过倍频)运行。笔者这里还是使用外部中断唤醒。我们先看看主函数:
/**
* @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 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
printf("n 进入停止模式 rn");
/* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 KEY1按键下降沿唤醒*/
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
以上最重要的就一句:
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
这是MCU进入低功耗模式的库函数,在stm32f10x_hal_pwr.c中实现,原型如下:
/**
* @brief Enters Stop mode.
* @note In Stop mode, all I/O pins keep the same state as in Run mode.
* @note When exiting Stop mode by using an interrupt or a wakeup event,
* HSI RC oscillator is selected as system clock.
* @note When the voltage regulator operates in low power mode, an additional
* startup delay is incurred when waking up from Stop mode.
* By keeping the internal regulator ON during Stop mode, the consumption
* is higher although the startup time is reduced.
* @param Regulator: Specifies the regulator state in Stop mode.
* This parameter can be one of the following values:
* @arg PWR_MAINREGULATOR_ON: Stop mode with regulator ON
* @arg PWR_LOWPOWERREGULATOR_ON: Stop mode with low power regulator ON
* @param STOPEntry: Specifies if Stop mode in entered with WFI or WFE instruction.
* This parameter can be one of the following values:
* @arg PWR_STOPENTRY_WFI: Enter Stop mode with WFI instruction
* @arg PWR_STOPENTRY_WFE: Enter Stop mode with WFE instruction
* @retval None
*/
void HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry)
{
/* Check the parameters */
assert_param(IS_PWR_REGULATOR(Regulator));
assert_param(IS_PWR_STOP_ENTRY(STOPEntry));
/* Clear PDDS bit in PWR register to specify entering in STOP mode when CPU enter in Deepsleep */
CLEAR_BIT(PWR->CR, PWR_CR_PDDS);
/* Select the voltage regulator mode by setting LPDS bit in PWR register according to Regulator parameter value */
MODIFY_REG(PWR->CR, PWR_CR_LPDS, Regulator);
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* Select Stop mode entry --------------------------------------------------*/
if(STOPEntry == PWR_STOPENTRY_WFI)
{
/* Request Wait For Interrupt */
__WFI();
}
else
{
/* Request Wait For Event */
__SEV();
PWR_OverloadWfe(); /* WFE redefine locally */
PWR_OverloadWfe(); /* WFE redefine locally */
}
/* Reset SLEEPDEEP bit of Cortex System Control Register */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
}
可以选择事件和中断唤醒两种方式,选择哪种方式是根据库函数的第二个参数决定的,笔者这里使用的中断唤醒。
停机模式下MCU唤醒之后,时钟和频率是没有经过倍频的,在F1上,低功耗唤醒之后,是8M频率运行,而正常运行是72M。所以,在唤醒停机模式之后,需要重新配置时钟。如下所示:
/**
* @brief 停机唤醒后配置系统时钟: 使能 HSE, PLL并且选择PLL作为系统时钟.
* @param None
* @retval None
*/
void SYSCLKConfig_STOP(void)
{
/* 使能 HSE */
__HAL_RCC_HSE_CONFIG(RCC_HSE_ON);
/* 等待 HSE 准备就绪 */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET);
/* 使能 PLL */
__HAL_RCC_PLL_ENABLE();
/* 等待 PLL 准备就绪 */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
{
}
/* 选择PLL作为系统时钟源 */
__HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_PLLCLK);
/* 等待PLL被选择为系统时钟源 */
while(__HAL_RCC_GET_SYSCLK_SOURCE() != 0x08)
{
}
}
当然最简单就是直接HAL库函数:
SystemClock_Config(); 好了,我们最后再看看中断唤醒的函数:
/**
* @brief 按键中断回调函数
* @param GPIO_Pin
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY_GPIO_PIN)
{
Delay(0xDFFFF);
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN) == KEY_DOWN_LEVEL)
{
/* 刚从停机唤醒,由于时钟未配置正确,
此printf语句的内容不能正常发送出去 */
printf("n 进入中断 n");
SystemClock_Config();
//SYSCLKConfig_STOP(); //停机唤醒后需要启动HSE
/*由于前面已经重新启动了HSE,
所以本printf语句能正常发出 */
printf("n 退出中断 n");
}
__HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN); //清除中断标志位
}
}
进入中断后主要是重启时钟。
10.3.3待机模式
待机模式的功耗最低。待机模式的具体步骤如下:
1) 使能电源时钟。
因为要配置电源控制寄存器,所以必须先使能电源时钟。在库函数中,使能电源时钟的方法是:
__HAL_RCC_PWR_CLK_ENABLE();
2) 设置 WK_UP 引脚作为唤醒源。
使能时钟之后后再设置 PWR_CSR 的 EWUP 位,使能 WK_UP 用于将 CPU 从待机模式唤醒。在库函数中,设置使能 WK_UP 用于唤醒 CPU 待机模式的函数是:
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); //使能唤醒管脚功能
3) 设置 SLEEPDEEP 位, 设置 PDDS 位,执行 WFI 指令,进入待机模式。
进入待机模式, 首先要设置 SLEEPDEEP 位( 该位在系统控制寄存器( SCB_SCR)的第二位,详见《 CM3 权威指南》), 接着我们通过 PWR_CR 设置 PDDS 位,使得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP中断的到来。在库函数中,进行上面三个功能进入待机模式是在函数HAL_PWR_EnterSTANDBYMode中实现的:
void HAL_PWR_EnterSTANDBYMode (void);
HAL_PWR_EnterSTANDBYMode()函数原型如下所示:
/**
* @brief Enters Standby mode.
* @note In Standby mode, all I/O pins are high impedance except for:
* - Reset pad (still available)
* - TAMPER pin if configured for tamper or calibration out.
* - WKUP pin (PA0) if enabled.
* @retval None
*/
void HAL_PWR_EnterSTANDBYMode(void)
{
/* Select Standby mode */
SET_BIT(PWR->CR, PWR_CR_PDDS);
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM)
__force_stores();
#endif
/* Request Wait For Interrupt */
__WFI();
}
该函数中先配置了 PDDS 寄存器位及 SLEEPDEEP 寄存器位,接着调用__force_stores函数确保存储操作完毕后再调用 WFI 指令,从而进入待机模式。这里值得注意的是,待机模式也可以使用 WFE 指令进入的。
在进入待机模式后,除了被使能了的用于唤醒的 I/O,其余 I/O 都进入高阻态,而待机模式唤醒后,相当于复位 STM32 芯片,程序重新从头开始执行。
4) 最后编写 WK_UP 中断函数。
因为我们通过 WK_UP 中断( PA0 中断)来唤醒MCU,所以我们有必要设置一下该中断函数,同时我们也通过该函数里面进入待机模式。
通过以上几个步骤的设置,我们就可以使用 STM32 的待机模式了,并且可以通过 WK_UP来唤醒 MCU。
主函数如下:
/**
* @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 */
/* 电源管理时钟使能 */
__HAL_RCC_PWR_CLK_ENABLE();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
/* 检测系统是否是从待机模式启动的 */
if (__HAL_PWR_GET_FLAG(PWR_FLAG_WU) == SET)
{
printf("rn 使能后检测,待机唤醒复位 rn");
/* 清除待机标志位 */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
}
else
{
printf("rn 使能后检测,上电复位 rn");
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在循环体外使用库函数 PWR_GetFlagStatus 检测 PWR_FLAG_WU 标志位,当这个标志位为SET 状态的时候,表示本次系统是从待机模式唤醒的复位,否则可能是上电复位。其中PWR_FLAG_WU的标志位在stm32f10x_hal_pwr.h中定义。
中断函数如下:
/**
* @brief 按键中断回调函数
* @param GPIO_Pin
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY_GPIO_PIN)
{
Delay(0xDFFFF);
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN) == KEY_DOWN_LEVEL)
{
if(PWR_Check_Standby())
{
printf("rn 进入待机模式rn");
/* The Following Wakeup sequence is highly recommended prior to each Standby mode entry
mainly when using more than one wakeup source this is to not miss any wakeup event.
- Disable all used wakeup sources,
- Clear all related wakeup flags,
- Re-enable all used wakeup sources,
- Enter the Standby mode.
*/
/* 禁用所有唤醒源: 唤醒引脚PA0 */
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
/* 清除所有唤醒标志位 */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能唤醒引脚:PA0做为系统唤醒输入 */
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
}
}
__HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN); //清除中断标志位
}
}
PWR_Check_Standby()函数用于检测渐渐是否长按,当长按后就进入待机模式。
/**
* @brief 用于检测按键是否被长时间按下
* @param None
* @retval None
*/
uint8_t PWR_Check_Standby(void)
{
uint8_t downCnt =0; //记录按下的次数
uint8_t upCnt =0; //记录松开的次数
while(1) //死循环,由return结束
{
HAL_Delay(20); //延迟一段时间再检测
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN)==KEY_DOWN_LEVEL) //检测到按下按键
{
//点亮所有LED灯
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
downCnt++; //记录按下次数
upCnt=0; //清除按键释放记录
if(downCnt>=100) //按下时间足够
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
return 1; //检测到按键被时间长按下
}
}
else
{
upCnt++; //记录释放次数
if(upCnt>5) //连续检测到释放超过5次
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
return 0; //按下时间太短,不是按键长按操作
}
}
}
}
10.4低功耗实验现象
10.4.1睡眠模式
将程序编译好后下载到板子上,可以看到3个LED依次闪烁,然后熄灭。按下KEY1按键,3个LED再次依次闪烁。然后再次进入睡眠模式。
10.4.2停止模式
将程序编译好后下载到板子上。按下KEY1按键,3个LED依次闪烁。串口依次打印信息如下:
10.4.3待机模式
将程序编译好后下载到板子上。按下按键,3个LED依次闪烁。长按KEY1按键,MCU进入待机模式,LED熄灭,再次按下KEY1按键,3个LED会同时点亮,直到同时熄灭,松开KEY1按键,KEY1按下会使 PA0 引脚产生中断,从而唤醒系统。
系统唤醒后会进行复位,从头开始执行上述过程,与第一次上电时不同的是,这样的复位会使 PWR_FLAG_WU 标志位改为 SET 状态,3个LED再次依次闪烁。串口打印信息如下。
10.3.2停止模式
进入停止模式之后,任何外部中断都可以唤醒低功耗,但是需要重新配置时钟,不然系统将以默认时钟(没有经过倍频)运行。笔者这里还是使用外部中断唤醒。我们先看看主函数:
/**
* @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 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
printf("n 进入停止模式 rn");
/* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 KEY1按键下降沿唤醒*/
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
以上最重要的就一句:
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
这是MCU进入低功耗模式的库函数,在stm32f10x_hal_pwr.c中实现,原型如下:
/**
* @brief Enters Stop mode.
* @note In Stop mode, all I/O pins keep the same state as in Run mode.
* @note When exiting Stop mode by using an interrupt or a wakeup event,
* HSI RC oscillator is selected as system clock.
* @note When the voltage regulator operates in low power mode, an additional
* startup delay is incurred when waking up from Stop mode.
* By keeping the internal regulator ON during Stop mode, the consumption
* is higher although the startup time is reduced.
* @param Regulator: Specifies the regulator state in Stop mode.
* This parameter can be one of the following values:
* @arg PWR_MAINREGULATOR_ON: Stop mode with regulator ON
* @arg PWR_LOWPOWERREGULATOR_ON: Stop mode with low power regulator ON
* @param STOPEntry: Specifies if Stop mode in entered with WFI or WFE instruction.
* This parameter can be one of the following values:
* @arg PWR_STOPENTRY_WFI: Enter Stop mode with WFI instruction
* @arg PWR_STOPENTRY_WFE: Enter Stop mode with WFE instruction
* @retval None
*/
void HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry)
{
/* Check the parameters */
assert_param(IS_PWR_REGULATOR(Regulator));
assert_param(IS_PWR_STOP_ENTRY(STOPEntry));
/* Clear PDDS bit in PWR register to specify entering in STOP mode when CPU enter in Deepsleep */
CLEAR_BIT(PWR->CR, PWR_CR_PDDS);
/* Select the voltage regulator mode by setting LPDS bit in PWR register according to Regulator parameter value */
MODIFY_REG(PWR->CR, PWR_CR_LPDS, Regulator);
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* Select Stop mode entry --------------------------------------------------*/
if(STOPEntry == PWR_STOPENTRY_WFI)
{
/* Request Wait For Interrupt */
__WFI();
}
else
{
/* Request Wait For Event */
__SEV();
PWR_OverloadWfe(); /* WFE redefine locally */
PWR_OverloadWfe(); /* WFE redefine locally */
}
/* Reset SLEEPDEEP bit of Cortex System Control Register */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
}
可以选择事件和中断唤醒两种方式,选择哪种方式是根据库函数的第二个参数决定的,笔者这里使用的中断唤醒。
停机模式下MCU唤醒之后,时钟和频率是没有经过倍频的,在F1上,低功耗唤醒之后,是8M频率运行,而正常运行是72M。所以,在唤醒停机模式之后,需要重新配置时钟。如下所示:
/**
* @brief 停机唤醒后配置系统时钟: 使能 HSE, PLL并且选择PLL作为系统时钟.
* @param None
* @retval None
*/
void SYSCLKConfig_STOP(void)
{
/* 使能 HSE */
__HAL_RCC_HSE_CONFIG(RCC_HSE_ON);
/* 等待 HSE 准备就绪 */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET);
/* 使能 PLL */
__HAL_RCC_PLL_ENABLE();
/* 等待 PLL 准备就绪 */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
{
}
/* 选择PLL作为系统时钟源 */
__HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_PLLCLK);
/* 等待PLL被选择为系统时钟源 */
while(__HAL_RCC_GET_SYSCLK_SOURCE() != 0x08)
{
}
}
当然最简单就是直接HAL库函数:
SystemClock_Config(); 好了,我们最后再看看中断唤醒的函数:
/**
* @brief 按键中断回调函数
* @param GPIO_Pin
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY_GPIO_PIN)
{
Delay(0xDFFFF);
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN) == KEY_DOWN_LEVEL)
{
/* 刚从停机唤醒,由于时钟未配置正确,
此printf语句的内容不能正常发送出去 */
printf("n 进入中断 n");
SystemClock_Config();
//SYSCLKConfig_STOP(); //停机唤醒后需要启动HSE
/*由于前面已经重新启动了HSE,
所以本printf语句能正常发出 */
printf("n 退出中断 n");
}
__HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN); //清除中断标志位
}
}
进入中断后主要是重启时钟。
10.3.3待机模式
待机模式的功耗最低。待机模式的具体步骤如下:
1) 使能电源时钟。
因为要配置电源控制寄存器,所以必须先使能电源时钟。在库函数中,使能电源时钟的方法是:
__HAL_RCC_PWR_CLK_ENABLE();
2) 设置 WK_UP 引脚作为唤醒源。
使能时钟之后后再设置 PWR_CSR 的 EWUP 位,使能 WK_UP 用于将 CPU 从待机模式唤醒。在库函数中,设置使能 WK_UP 用于唤醒 CPU 待机模式的函数是:
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); //使能唤醒管脚功能
3) 设置 SLEEPDEEP 位, 设置 PDDS 位,执行 WFI 指令,进入待机模式。
进入待机模式, 首先要设置 SLEEPDEEP 位( 该位在系统控制寄存器( SCB_SCR)的第二位,详见《 CM3 权威指南》), 接着我们通过 PWR_CR 设置 PDDS 位,使得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP中断的到来。在库函数中,进行上面三个功能进入待机模式是在函数HAL_PWR_EnterSTANDBYMode中实现的:
void HAL_PWR_EnterSTANDBYMode (void);
HAL_PWR_EnterSTANDBYMode()函数原型如下所示:
/**
* @brief Enters Standby mode.
* @note In Standby mode, all I/O pins are high impedance except for:
* - Reset pad (still available)
* - TAMPER pin if configured for tamper or calibration out.
* - WKUP pin (PA0) if enabled.
* @retval None
*/
void HAL_PWR_EnterSTANDBYMode(void)
{
/* Select Standby mode */
SET_BIT(PWR->CR, PWR_CR_PDDS);
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM)
__force_stores();
#endif
/* Request Wait For Interrupt */
__WFI();
}
该函数中先配置了 PDDS 寄存器位及 SLEEPDEEP 寄存器位,接着调用__force_stores函数确保存储操作完毕后再调用 WFI 指令,从而进入待机模式。这里值得注意的是,待机模式也可以使用 WFE 指令进入的。
在进入待机模式后,除了被使能了的用于唤醒的 I/O,其余 I/O 都进入高阻态,而待机模式唤醒后,相当于复位 STM32 芯片,程序重新从头开始执行。
4) 最后编写 WK_UP 中断函数。
因为我们通过 WK_UP 中断( PA0 中断)来唤醒MCU,所以我们有必要设置一下该中断函数,同时我们也通过该函数里面进入待机模式。
通过以上几个步骤的设置,我们就可以使用 STM32 的待机模式了,并且可以通过 WK_UP来唤醒 MCU。
主函数如下:
/**
* @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 */
/* 电源管理时钟使能 */
__HAL_RCC_PWR_CLK_ENABLE();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
/* 检测系统是否是从待机模式启动的 */
if (__HAL_PWR_GET_FLAG(PWR_FLAG_WU) == SET)
{
printf("rn 使能后检测,待机唤醒复位 rn");
/* 清除待机标志位 */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
}
else
{
printf("rn 使能后检测,上电复位 rn");
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在循环体外使用库函数 PWR_GetFlagStatus 检测 PWR_FLAG_WU 标志位,当这个标志位为SET 状态的时候,表示本次系统是从待机模式唤醒的复位,否则可能是上电复位。其中PWR_FLAG_WU的标志位在stm32f10x_hal_pwr.h中定义。
中断函数如下:
/**
* @brief 按键中断回调函数
* @param GPIO_Pin
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY_GPIO_PIN)
{
Delay(0xDFFFF);
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN) == KEY_DOWN_LEVEL)
{
if(PWR_Check_Standby())
{
printf("rn 进入待机模式rn");
/* The Following Wakeup sequence is highly recommended prior to each Standby mode entry
mainly when using more than one wakeup source this is to not miss any wakeup event.
- Disable all used wakeup sources,
- Clear all related wakeup flags,
- Re-enable all used wakeup sources,
- Enter the Standby mode.
*/
/* 禁用所有唤醒源: 唤醒引脚PA0 */
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
/* 清除所有唤醒标志位 */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能唤醒引脚:PA0做为系统唤醒输入 */
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
}
}
__HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN); //清除中断标志位
}
}
PWR_Check_Standby()函数用于检测渐渐是否长按,当长按后就进入待机模式。
/**
* @brief 用于检测按键是否被长时间按下
* @param None
* @retval None
*/
uint8_t PWR_Check_Standby(void)
{
uint8_t downCnt =0; //记录按下的次数
uint8_t upCnt =0; //记录松开的次数
while(1) //死循环,由return结束
{
HAL_Delay(20); //延迟一段时间再检测
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN)==KEY_DOWN_LEVEL) //检测到按下按键
{
//点亮所有LED灯
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
downCnt++; //记录按下次数
upCnt=0; //清除按键释放记录
if(downCnt>=100) //按下时间足够
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
return 1; //检测到按键被时间长按下
}
}
else
{
upCnt++; //记录释放次数
if(upCnt>5) //连续检测到释放超过5次
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
return 0; //按下时间太短,不是按键长按操作
}
}
}
}
10.4低功耗实验现象
10.4.1睡眠模式
将程序编译好后下载到板子上,可以看到3个LED依次闪烁,然后熄灭。按下KEY1按键,3个LED再次依次闪烁。然后再次进入睡眠模式。
10.4.2停止模式
将程序编译好后下载到板子上。按下KEY1按键,3个LED依次闪烁。串口依次打印信息如下:
10.4.3待机模式
将程序编译好后下载到板子上。按下按键,3个LED依次闪烁。长按KEY1按键,MCU进入待机模式,LED熄灭,再次按下KEY1按键,3个LED会同时点亮,直到同时熄灭,松开KEY1按键,KEY1按下会使 PA0 引脚产生中断,从而唤醒系统。
系统唤醒后会进行复位,从头开始执行上述过程,与第一次上电时不同的是,这样的复位会使 PWR_FLAG_WU 标志位改为 SET 状态,3个LED再次依次闪烁。串口打印信息如下。
举报