电源管理
STM32 HAL库对电源管理提供了完善的函数和命令。
工作模式(高功耗->低功耗):运行、睡眠、停止、待机。若备份域电源正常供电,备份域内的RTC都可以正常运行,备份域内的寄存器的数据会被保存,不受功耗模式影响。
上电复位(POR)和掉电复位(PDR)
当检测到VDD的电压低于阈值VPOR及VPDR时,无需外部电路辅助,STM32芯片会自动保持在复位状态,防止因电压不足强行工作而带来严重的后果。
在刚开始电压低于VPOR时(约1.92V),STM32保持在上电复位状态(POR,Power On Reset)。当VDD电压持续上升至大于VPOR时,芯片开始正常运行。
而在芯片开始正常运行的时候,当检测到VDD电压下降至低于VPDR阈值(约1.88V),会进入掉电复位状态(PDR,Power Down Reset)。
配置PVD监控功能
PVD可监控VDD的电压,当它低于阈值时可产生PVD中断以让系统进行紧急处理,这个阈值可以直接使用库函数PWR_PVDLevelConfig配置成某一个的阈值等级。
WFI和WFE命令
进入各种低功耗模式时都需要调用WFI或WFE命令,实质上都是内核指令,在库文件 core_cm3.h 或 cmsis_armcc.h 中把这些指令封装成了函数。
cpp
/* 等待中断。是一种暂停执行指令,暂停至任意中断产生后被唤醒 */
#define __WFI __wfi
/* 等待事件。是一种暂停执行指令,暂停至任意事件产生后被唤醒 */
#define __WFE __wfe
这两个指令,调用后都能进入低功耗模式,需要使用__WFI();和__WFE();来调用(因为__wfi和__wfe是编译器内置的函数,函数内部调用了相对应的汇编指令)。
具体可查《cortex-CM3/CM4权威指南》。
进入停止模式
直接调用WFI和WFE指令可以进入睡眠模式,而进入停止模式这还需要在调用指令前设置一些寄存器位,STM32 HAL库把这部分的操作封装到HAL_PWR_EnterSTOPMode()。
cpp
/**
* @brief 进入停止模式
* @note 在停止模式下所有I/O都会保持在停止前的状态.
* @note 当使用中断或唤醒事件退出停止模式时,HSI RC振荡器被选择为系统时钟。
* @note 当稳压器在低功率模式下工作时,从停止模式唤醒时会产生额外的启动延迟。
* 通过在停止模式中保持内部稳压器打开,虽然启动时间减少,但消耗更高。
* @param Regulator: 在停止模式下指定稳压器状态。
* @arg PWR_MAINREGULATOR_ON: 稳压器正常运行
* @arg PWR_LOWPOWERREGULATOR_ON: 稳压器低功耗运行
* @param STOPEntry: 指定是否使用WFI或WFE指令进入停止模式。
* @arg PWR_STOPENTRY_WFI: 使用WFI指令进入停止模式
* @arg PWR_STOPENTRY_WFE: 使用WFE指令进入停止模式
* @retval None
*/
void HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry)
{
/* 检查参数 */
assert_param(IS_PWR_REGULATOR(Regulator));
assert_param(IS_PWR_STOP_ENTRY(STOPEntry));
/* 清除PWR寄存器中的PDDS位以指定当CPU进入深度睡眠时进入停止模式 */
CLEAR_BIT(PWR->CR, PWR_CR_PDDS);
/* 根据稳压参数值,通过在PWR寄存器中设置LPDS位来选择稳压模式 */
MODIFY_REG(PWR->CR, PWR_CR_LPDS, Regulator);
/* 设置内核系统控制寄存器的SLEEPDEEP位 */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* 选择停止模式进入 */
if (STOPEntry == PWR_STOPENTRY_WFI)
{
/* 请求等待中断 */
__WFI();
}
else
{
/* 请求等待事件 */
__SEV();
PWR_OverloadWfe(); /* 本地重新定义WFE */
PWR_OverloadWfe(); /* 本地重新定义WFE */
}
/* 以下的程序是当重新唤醒时才执行的,清除内核系统控制寄存器的SLEEPDEEP位 */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
}
进入停止模式后,STM32的所有I/O都保持在停止前的状态,而当它被唤醒时,STM32使用HSI作为系统时钟(8MHz)运行,由于系统是在会影响很多外设的工作状态,所以一般我们在唤醒后会重新开始HSE,把系统时钟设置成原来的状态。
进入待机模式
STM32 HAL库把这部分的操作封装到HAL_PWR_EnterSTANDBYMode()。
cpp
/**
* @brief 进入待机模式
* @note 待机模式下,除以下情况外,所有I/O引脚均为高阻抗::
* - 复位引脚(仍然有效)
* - TAMPER pin if configured for tamper or calibration out.
* - WKUP pin (PA0) (如果使能WKUP唤醒功能).
* @retval None
*/
void HAL_PWR_EnterSTANDBYMode(void)
{
/* 选择待机模式 */
SET_BIT(PWR->CR, PWR_CR_PDDS);
/* 设置内核系统控制寄存器的SLEEPDEEP位*/
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* 存储操作完毕时才能进入待机模式,使用以下语句确保存储操作执行完毕 */
#if defined ( __CC_ARM)
__force_stores();
#endif
/* 请求等待中断 */
__WFI();
}
待机模式也可以使用WFE指令进入的,如果有需要可以自行修改。
在进入待机模式后,除了被使能用于唤醒的I/O,其余I/O都进入高阻态,而从待机模式唤醒后,想防御复位STM32芯片,程序重新从头开始执行。
实验环节1:PWR_PVD监控
实验操作
使用外部可调电源,调节成5V输出,连接到开发板5V和GND排针给板子进行供电;
复位开发板,电压正常时LED为绿色;
向下调节可调电源的电压,大约降到4V时,LED为红色。(程序中控制PVD监控电压约为2.8V,当5V降到4V时,连接STM32的VDD电源会降于2.8V,产生PVD事件,在中断中控制亮红灯)。
注意:其他电源线都拔掉(包括下载器、USB线)。不能远高于5V而导致烧坏开发板。
PVD配置
cpp
void PVD_Config(void)
{
PWR_PVDTypeDef sConfigPVD;
/*使能 PWR 时钟 */
__HAL_RCC_PWR_CLK_ENABLE();
/* 配置 PVD 中断 */
HAL_NVIC_SetPriority(PVD_IRQn, 0 ,0);
HAL_NVIC_EnableIRQ(PVD_IRQn);
/* 配置PVD级别6 (PVD检测电压的阈值为2.8V,VDD电压低于2.8V时产生PVD中断,
具体数据可查询数据手册获知) 具体级别根据自己的实际应用要求配置*/
sConfigPVD.PVDLevel = PWR_PVDLEVEL_6;
sConfigPVD.Mode = PWR_PVD_MODE_IT_RISING_FALLING;
HAL_PWR_ConfigPVD(&sConfigPVD);
/* 使能PVD输出 */
HAL_PWR_EnablePVD();
}
测试环节
cpp
void PVD_IRQHandler(void)
{
HAL_PWR_PVD_IRQHandler();
}
void HAL_PWR_PVDCallback(void)
{
LED红灯
}
void test(void)
{
初始化
LED绿灯
// 配置PVD,当电压过低时,会进入中断服务函数,亮红灯
PVD_Config();
while(1)
{}
}
实验环节2:PWR睡眠模式
实验操作
LED:绿灯正常运行,红灯睡眠状态,蓝灯刚被唤醒。
KEY:key1和key2配置成IO中断模式。
运行一段时间后自动进入睡眠时间,通过按键(key1或key2)唤醒。
睡眠状态下,DAP下载器无法给STM32下载程序,可唤醒后再下载或按复位键使芯片处于复位状态下下载后松开复位键。
测试环节
cpp
int main(void)
{
初始化
while (1)
{
LED绿灯
HAL_Delay(2000);
LED红灯
HAL_SuspendTick(); //暂停滴答时钟,防止通过滴答时钟中断唤醒
//进入睡眠模式
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
//等待中断唤醒 K1或K2按键中断
LED蓝灯
HAL_ResumeTick(); //被唤醒后,恢复滴答时钟
HAL_Delay(2000);
//继续执行while循环
}
}
实验环节3:PWR待机模式
实验操作
LED:绿灯表示本次复位是上电或引脚复位,红灯待机状态,蓝灯刚被唤醒。
KEY:key2配置成输入模式。
长按KEY2按键会进入待机模式,待机模式下KEY1按键可唤醒,唤醒后系统会复位。可通过检测PWR_CSR:WUF标志确定复位来源。
待机模式下,DAP下载器无法给STM32下载程序,可唤醒后再下载。
注意:由于WKUP引脚(PA0)必须使用上升沿才能唤醒待机状态的系统,所以硬件设计PA0引脚连接到KEY1,且按下KEY1时会在PA0引脚产生上升沿,从而可实现唤醒的功能。
测试环节
cpp
/**
* @brief 用于检测按键是否被长时间按下
* @param 无
* @retval 1 :按键被长时间按下 0 :按键没有被长时间按下
*/
static uint8_t KEY2_LongPress(void)
{
uint8_t downCnt = 0; //记录按下的次数
uint8_t upCnt = 0; //记录松开的次数
while (1) //死循环,由return结束
{
HAL_Delay(20); //延迟一段时间再检测
if (HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_PIN) == SET) //检测到按下按键
{
downCnt++; //记录按下次数
upCnt = 0; //清除按键释放记录
if (downCnt >= 100) //按下时间足够
{
return 1; //检测到按键被时间长按下
}
}
else
{
upCnt++; //记录释放次数
if (upCnt > 5) //连续检测到释放超过5次
{
return 0; //按下时间太短,不是按键长按操作
}
}
}
}
int main(void)
{
初始化
/* 使能电源管理单元的时钟,必须要使能时钟才能进入待机模式 */
__HAL_RCC_PWR_CLK_ENABLE();
//检测复位来源
if (__HAL_PWR_GET_FLAG(PWR_FLAG_WU) == SET)
{ // 复位前为待机模式
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
LED蓝灯
}
else
{
// 复位前为正常运行
LED绿灯
}
while (1)
{
// K2 按键长按进入待机模式
if (KEY2_LongPress())
{
LED红灯
HAL_Delay(1000);
/*清除WU状态位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能WKUP引脚的唤醒功能,使能PA0*/
HAL_PWR_EnableWakeUpPin(0x00000100U);
//暂停滴答时钟,防止通过滴答时钟中断唤醒
HAL_SuspendTick();
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
}
}
}
实验现象
开机正常运行绿灯。长按KEY2按键,显红灯,过1s后进入待机模式LED灭。按下KEY1按键退出待机模式自动复位,显蓝灯。按下复位键,重新运行绿灯。
实验环节4:PWR停止模式
实验操作
LED:绿灯正常运行,红灯停止状态,蓝灯刚被唤醒。
KEY:key1和key2配置成IO中断模式。
运行一段时间后自动进入停止时间,通过按键(key1或key2)唤醒。
待机模式下,DAP下载器无法给STM32下载程序,可唤醒后再下载。
注意:由于WKUP引脚(PA0)必须使用上升沿才能唤醒待机状态的系统,所以硬件设计PA0引脚连接到KEY1,且按下KEY1时会在PA0引脚产生上升沿,从而可实现唤醒的功能。
测试环节
cpp
/**
* @brief 从停止模式唤醒后配置系统时钟:启用HSE、PLL并选择PLL作为系统时钟源。
* @param 无
* @retval 无
*/
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) { ; }
}
}
int main(void)
{
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;
初始化
while(1)
{
LED绿灯
HAL_Delay(2000);
// 进入停止模式,亮红灯,按KEY1或KEY2按键可唤醒
LED_RED;
HAL_SuspendTick(); //暂停滴答时钟,防止通过滴答时钟中断唤醒
/* 进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒 */
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
// 等待中断唤醒 K1或K2按键中断
// 被唤醒,亮蓝灯指示
LED蓝灯
SystemCoreClockUpdate(); //根据时钟寄存器的值更新SystemCoreClock变量
//获取唤醒后的时钟状态
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();
/* 从停止模式唤醒后配置系统时钟:启用HSE、PLL*/
/* 选择PLL作为系统时钟源(HSE和PLL在停止模式下被禁用)*/
SYSCLKConfig_STOP();
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();
HAL_Delay(2000);
//继续执行while循环
}
}