
STM32单片机芯片与内部23 电源管理——待机模式、PVD检测 标准库函数、HAL库函数
介绍了待机模式的注意事项,进入方式、唤醒方式与原因判断,标准库与HAL库的工程实现。介绍了PVD的电压检测,中断配置,中断服务函数等,最终对PVD检测和ADC电压检测做了分析原理 硬件支持,响应速度与实时性,精度与应用场景,功耗与持续监测等。
目录
一、待机模式——标准库函数
1、注意事项
待机模式,它除了关闭所有的时钟,还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0) 引脚的上升沿, RTC 闹钟事件, NRST 引脚的复位和 IWDG(独立看门狗) 复位。虽然默认还是WFI,但是不会回到前面待机的部分了,而是从代码的头部开始。
2、待机模式进入
void PWR_EnterSTANDBYMode(void)
{
/* Clear Wake-up flag */
PWR->CR |= PWR_CR_CWUF;
/* Select STANDBY mode */
PWR->CR |= PWR_CR_PDDS;
/* Set SLEEPDEEP bit of Cortex System Control Register */
SCB->SCR |= SCB_SCR_SLEEPDEEP;
/* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM )
__force_stores();
#endif
/* Request Wait For Interrupt */
__WFI();
}
3、唤醒复位原因
(1)、WKUP_PA0唤醒
有人会问,PA0是否需要初始化配置等,最终设置为浮空或者下拉输入呢?不需要,这个是硬件层面的代码。
void PWR_WakeUpPinCmd(FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_FUNCTIONAL_STATE(NewState));
*(__IO uint32_t *) CSR_EWUP_BB = (uint32_t)NewState;
}
(2)、复位判断
如下函数放在main的开始,即可每次复位后判断当前复位状态,可以选择设置PWR_FLAG_WU、PWR_FLAG_SB、PWR_FLAG_PVDO三个状态。
FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG)
{
FlagStatus bitstatus = RESET;
/* Check the parameters */
assert_param(IS_PWR_GET_FLAG(PWR_FLAG));
if ((PWR->CSR & PWR_FLAG) != (uint32_t)RESET)
{
bitstatus = SET;
}
else
{
bitstatus = RESET;
}
/* Return the flag status */
return bitstatus;
}
二、待机模式——HAL库
1、待机模式进入
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();
}
2、唤醒复位原因
(1)、WKUP_PA0唤醒
void HAL_PWR_EnableWakeUpPin(uint32_t WakeUpPinx)
{
/* Check the parameter */
assert_param(IS_PWR_WAKEUP_PIN(WakeUpPinx));
/* Enable the EWUPx pin */
*(__IO uint32_t *) CSR_EWUP_BB(WakeUpPinx) = (uint32_t)ENABLE;
}
(2)、复位判断
/** @brief Check PWR flag is set or not.
* @param __FLAG__: specifies the flag to check.
* This parameter can be one of the following values:
* @arg PWR_FLAG_WU: Wake Up flag. This flag indicates that a wakeup event
* was received from the WKUP pin or from the RTC alarm
* An additional wakeup event is detected if the WKUP pin is enabled
* (by setting the EWUP bit) when the WKUP pin level is already high.
* @arg PWR_FLAG_SB: StandBy flag. This flag indicates that the system was
* resumed from StandBy mode.
* @arg PWR_FLAG_PVDO: PVD Output. This flag is valid only if PVD is enabled
* by the HAL_PWR_EnablePVD() function. The PVD is stopped by Standby mode
* For this reason, this bit is equal to 0 after Standby or reset
* until the PVDE bit is set.
* @retval The new state of __FLAG__ (TRUE or FALSE).
*/
#define __HAL_PWR_GET_FLAG(__FLAG__) ((PWR->CSR & (__FLAG__)) == (__FLAG__))
三、待机模式——用户侧
int main(void)
{
/* 使能电源管理单元的时钟,必须要使能时钟才能进入待机模式 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR , ENABLE);
LED_GPIO_Config();
/*初始化USART1*/
USART_Config();
/*初始化按键,不需要中断,仅初始化KEY2即可,只用于唤醒的PA0引脚不需要这样初始化*/
Key_GPIO_Config();
printf("\r\n 欢迎使用野火 STM32 开发板。\r\n");
printf("\r\n 野火STM32 待机模式例程\r\n");
printf("\r\n 实验说明:\r\n");
printf("\r\n 1.本程序中,绿灯表示本次复位是上电或引脚复位,红灯表示即将进入待机状态,蓝灯表示本次是待机唤醒的复位\r\n");
printf("\r\n 2.长按KEY2按键后,会进入待机模式\r\n");
printf("\r\n 3.在待机模式下,按KEY1按键可唤醒,唤醒后系统会进行复位,程序从头开始执行\r\n");
printf("\r\n 4.可通过检测WU标志位确定复位来源\r\n");
printf("\r\n 5.在待机状态下,DAP下载器无法给STM32下载程序,需要唤醒后才能下载");
//检测复位来源
if(PWR_GetFlagStatus(PWR_FLAG_WU) == SET)
{
LED_BLUE;
printf("\r\n 待机唤醒复位 \r\n");
}
else
{
LED_GREEN;
printf("\r\n 非待机唤醒复位 \r\n");
}
while(1)
{
// K2 按键长按进入待机模式
if(KEY2_LongPress())
{
printf("\r\n 即将进入待机模式,进入待机模式后可按KEY1唤醒,唤醒后会进行复位,程序从头开始执行\r\n");
LED_RED;
Delay(0xFFFFFF);
/*清除WU状态位*/
PWR_ClearFlag (PWR_FLAG_WU);
/* 使能WKUP引脚的唤醒功能 ,使能PA0*/
PWR_WakeUpPinCmd (ENABLE);
/* 进入待机模式 */
PWR_EnterSTANDBYMode();
}
}
}
int main(void)
{
/* 初始化系统时钟为72MHz */
SystemClock_Config();
/* 初始化LED */
LED_GPIO_Config();
/* 初始化调试串口,一般为串口1 */
DEBUG_USART_Config();
/*初始化按键,不需要中断,仅初始化KEY2即可,只用于唤醒的PA0引脚不需要这样初始化*/
Key_GPIO_Config();
printf("\r\n 欢迎使用野火 STM32 F103 开发板。\r\n");
printf("\r\n 野火F103 待机模式例程\r\n");
printf("\r\n 实验说明:\r\n");
printf("\r\n 1.本程序中,绿灯表示本次复位是上电或引脚复位,红灯表示即将进入待机状态,蓝灯表示本次是待机唤醒的复位\r\n");
printf("\r\n 2.长按KEY2按键后,会进入待机模式\r\n");
printf("\r\n 3.在待机模式下,按KEY1按键可唤醒,唤醒后系统会进行复位,程序从头开始执行\r\n");
printf("\r\n 4.可通过检测WU标志位确定复位来源\r\n");
printf("\r\n 5.在待机状态下,DAP下载器无法给STM32下载程序,需要唤醒后才能下载");
//检测复位来源
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == SET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
LED_BLUE;
printf("\r\n 待机唤醒复位 \r\n");
}
else
{
LED_GREEN;
printf("\r\n 非待机唤醒复位 \r\n");
}
while(1)
{
// K2 按键长按进入待机模式
if(KEY2_LongPress())
{
printf("\r\n 即将进入待机模式,进入待机模式后可按KEY1唤醒,唤醒后会进行复位,程序从头开始执行\r\n");
LED_RED;
HAL_Delay(1000);
/*清除WU状态位*/
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* 使能WKUP引脚的唤醒功能 ,使能PA0*/
HAL_PWR_EnableWakeUpPin( 0x00000100U);
//暂停滴答时钟,防止通过滴答时钟中断唤醒
HAL_SuspendTick();
/* 进入待机模式 */
HAL_PWR_EnterSTANDBYMode();
}
}
}
四、PVD管理——标准库函数
1、PVD设定
由之前手册介绍和代码比对可以看到,PVD可以设定电压的范围。
/**
* @brief Configures the voltage threshold detected by the Power Voltage Detector(PVD).
* @param PWR_PVDLevel: specifies the PVD detection level
* This parameter can be one of the following values:
* @arg PWR_PVDLevel_2V2: PVD detection level set to 2.2V
* @arg PWR_PVDLevel_2V3: PVD detection level set to 2.3V
* @arg PWR_PVDLevel_2V4: PVD detection level set to 2.4V
* @arg PWR_PVDLevel_2V5: PVD detection level set to 2.5V
* @arg PWR_PVDLevel_2V6: PVD detection level set to 2.6V
* @arg PWR_PVDLevel_2V7: PVD detection level set to 2.7V
* @arg PWR_PVDLevel_2V8: PVD detection level set to 2.8V
* @arg PWR_PVDLevel_2V9: PVD detection level set to 2.9V
* @retval None
*/
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel)
{
uint32_t tmpreg = 0;
/* Check the parameters */
assert_param(IS_PWR_PVD_LEVEL(PWR_PVDLevel));
tmpreg = PWR->CR;
/* Clear PLS[7:5] bits */
tmpreg &= CR_PLS_MASK;
/* Set PLS[7:5] bits according to PWR_PVDLevel value */
tmpreg |= PWR_PVDLevel;
/* Store the new value */
PWR->CR = tmpreg;
}
2、PVD中断配置
占用EXTI16中断线。
/*使能 PWR 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 使能 PVD 中断 */
NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 配置 EXTI16线(PVD 输出) 来产生上升下降沿中断*/
EXTI_ClearITPendingBit(EXTI_Line16);
EXTI_InitStructure.EXTI_Line = EXTI_Line16;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
3、中断服务函数
可以提醒电量低等。
void PVD_IRQHandler(void)
{
/*检测是否产生了PVD警告信号*/
if(PWR_GetFlagStatus (PWR_FLAG_PVDO)==SET)
{
/* 亮红灯,实际应用中应进入紧急状态处理 */
LED_RED;
}
/* 清除中断信号*/
EXTI_ClearITPendingBit(EXTI_Line16);
}
五、PVD管理——HAL库
1、PVD设定
#define PWR_PVDLEVEL_0 PWR_CR_PLS_2V2
#define PWR_PVDLEVEL_1 PWR_CR_PLS_2V3
#define PWR_PVDLEVEL_2 PWR_CR_PLS_2V4
#define PWR_PVDLEVEL_3 PWR_CR_PLS_2V5
#define PWR_PVDLEVEL_4 PWR_CR_PLS_2V6
#define PWR_PVDLEVEL_5 PWR_CR_PLS_2V7
#define PWR_PVDLEVEL_6 PWR_CR_PLS_2V8
#define PWR_PVDLEVEL_7 PWR_CR_PLS_2V9
#define PWR_PVD_MODE_NORMAL 0x00000000U /*!< basic mode is used */
#define PWR_PVD_MODE_IT_RISING 0x00010001U /*!< External Interrupt Mode with Rising edge trigger detection */
#define PWR_PVD_MODE_IT_FALLING 0x00010002U /*!< External Interrupt Mode with Falling edge trigger detection */
#define PWR_PVD_MODE_IT_RISING_FALLING 0x00010003U /*!< External Interrupt Mode with Rising/Falling edge trigger detection */
#define PWR_PVD_MODE_EVENT_RISING 0x00020001U /*!< Event Mode with Rising edge trigger detection */
#define PWR_PVD_MODE_EVENT_FALLING 0x00020002U /*!< Event Mode with Falling edge trigger detection */
#define PWR_PVD_MODE_EVENT_RISING_FALLING 0x00020003U /*!< Event Mode with Rising/Falling edge trigger detection */
typedef struct
{
uint32_t PVDLevel; /*!< PVDLevel: Specifies the PVD detection level.
This parameter can be a value of @ref PWR_PVD_detection_level */
uint32_t Mode; /*!< Mode: Specifies the operating mode for the selected pins.
This parameter can be a value of @ref PWR_PVD_Mode */
}PWR_PVDTypeDef;
void HAL_PWR_ConfigPVD(PWR_PVDTypeDef *sConfigPVD)
{
/* Check the parameters */
assert_param(IS_PWR_PVD_LEVEL(sConfigPVD->PVDLevel));
assert_param(IS_PWR_PVD_MODE(sConfigPVD->Mode));
/* Set PLS[7:5] bits according to PVDLevel value */
MODIFY_REG(PWR->CR, PWR_CR_PLS, sConfigPVD->PVDLevel);
/* Clear any previous config. Keep it clear if no event or IT mode is selected */
__HAL_PWR_PVD_EXTI_DISABLE_EVENT();
__HAL_PWR_PVD_EXTI_DISABLE_IT();
__HAL_PWR_PVD_EXTI_DISABLE_FALLING_EDGE();
__HAL_PWR_PVD_EXTI_DISABLE_RISING_EDGE();
/* Configure interrupt mode */
if((sConfigPVD->Mode & PVD_MODE_IT) == PVD_MODE_IT)
{
__HAL_PWR_PVD_EXTI_ENABLE_IT();
}
/* Configure event mode */
if((sConfigPVD->Mode & PVD_MODE_EVT) == PVD_MODE_EVT)
{
__HAL_PWR_PVD_EXTI_ENABLE_EVENT();
}
/* Configure the edge */
if((sConfigPVD->Mode & PVD_RISING_EDGE) == PVD_RISING_EDGE)
{
__HAL_PWR_PVD_EXTI_ENABLE_RISING_EDGE();
}
if((sConfigPVD->Mode & PVD_FALLING_EDGE) == PVD_FALLING_EDGE)
{
__HAL_PWR_PVD_EXTI_ENABLE_FALLING_EDGE();
}
}
2、PVD中断配置
/*使能 PWR 时钟 */
__HAL_RCC_PWR_CLK_ENABLE();
/* 配置 PVD 中断 */
/*中断设置,抢占优先级0,子优先级为0*/
HAL_NVIC_SetPriority(PVD_IRQn, 0 ,0);
HAL_NVIC_EnableIRQ(PVD_IRQn);
3、中断服务函数
void PVD_IRQHandler(void)
{
HAL_PWR_PVD_IRQHandler();
}
void HAL_PWR_PVDCallback(void)
{
LED_RED;;
}
六、PVD管理——用户侧
无需其他设置,通过PVD_Config即可。
void PVD_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/*使能 PWR 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 使能 PVD 中断 */
NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 配置 EXTI16线(PVD 输出) 来产生上升下降沿中断*/
EXTI_ClearITPendingBit(EXTI_Line16);
EXTI_InitStructure.EXTI_Line = EXTI_Line16;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* 配置PVD级别PWR_PVDLevel_2V6 (PVD检测电压的阈值为2.6V,VDD电压低于2.6V时产生PVD中断) */
/*具体级别根据自己的实际应用要求配置*/
PWR_PVDLevelConfig(PWR_PVDLevel_2V6);
/* 使能PVD输出 */
PWR_PVDCmd(ENABLE);
}
void PVD_Config(void)
{
PWR_PVDTypeDef sConfigPVD;
/*使能 PWR 时钟 */
__HAL_RCC_PWR_CLK_ENABLE();
/* 配置 PVD 中断 */
/*中断设置,抢占优先级0,子优先级为0*/
HAL_NVIC_SetPriority(PVD_IRQn, 0 ,0);
HAL_NVIC_EnableIRQ(PVD_IRQn);
/* 配置PVD级别5 (PVD检测电压的阈值为2.8V,
VDD电压低于2.8V时产生PVD中断,具体数据
可查询数据手册获知) 具体级别根据自己的
实际应用要求配置*/
sConfigPVD.PVDLevel = PWR_PVDLEVEL_5;
sConfigPVD.Mode = PWR_PVD_MODE_IT_RISING_FALLING;
HAL_PWR_ConfigPVD(&sConfigPVD);
/* 使能PVD输出 */
HAL_PWR_EnablePVD();
}
PVD检测电压的阈值根据不同级别可以设定,那么可以检测VDD电压从而产生PVD中断,例如可以提醒电量低,这个和用ADC采集,用户层面判断低电压有什么区别嘛。
PVD (Power Voltage Detector) 和 ADC (模数转换器) 采集电压用于检测电压水平,它们的主要区别在于工作原理、响应速度、使用场景和应用场合。以下是二者的一些关键区别:
1、工作原理和硬件支持
-
PVD (Power Voltage Detector):
- PVD 是一个硬件电压监测电路,可以监测电源电压(如 VDD)并比较它与预设的阈值。
- 当 VDD 电压下降到低于设定阈值时,PVD 会触发中断或其他事件。这个过程是硬件级别的,即当电压低于阈值时,硬件自动处理,立即生成中断或报警。
- PVD 可以设置不同的电压阈值,以便在电压达到危险水平时提醒系统,如低电量提醒。
- 优点:响应速度快、可靠性高,因为是硬件级别的检测,且不需要软件参与。可以持续地在低功耗模式下进行电压监测,适用于实时电压监控。
-
ADC (Analog-to-Digital Converter):
- ADC 是一个将模拟电压(如 VDD)转换为数字信号的模块,通常用于测量较为精确的电压值。
- 用户可以通过 ADC 获取当前 VDD 电压值,然后在软件中判断是否低于设定的阈值,来决定是否执行低电量相关的操作。
- 优点:ADC 可以提供更高的精度和可编程性,适合进行精确的电压测量。
- 缺点:由于是软件驱动的,通常需要定期或在某些条件下手动触发 ADC 采样,可能会有较长的延迟。且 ADC 的采样速度不如 PVD 快,可能会错过一些快速变化的电压状态。
2、响应速度和实时性
-
PVD:
- PVD 是硬件级别的电压检测,它的响应速度非常快。当电压低于设定阈值时,PVD 立即产生中断,不需要软件的参与。
- 由于是硬件中断,它的响应时间基本为中断处理时间,通常是微秒级别。
-
ADC:
- ADC 需要采样电压并将模拟信号转换为数字信号,通常具有较长的采样周期(比如几个微秒到几十微秒),并且结果需要通过软件进行处理才能判断电压是否低于阈值。
- 在一些低功耗应用中,ADC 可能不频繁触发,导致响应速度相对较慢。
3、精度和应用场景
-
PVD:
- PVD 提供的是一个比较粗略的电压检测(通常是固定的几个电压阈值,如 2.4V、2.8V 等),它不能提供非常精确的电压值,仅适合判断电源是否低于某个安全阈值。
- 通常用于判断电池电量是否足够或者是否需要进入低功耗模式。
-
ADC:
- ADC 提供的电压值更加精确,可以测量更细微的电压变化(比如毫伏级的变化),适合用于电池电量的精确测量和动态监测。
- 适合用在对电压变化较为敏感的应用,如电池电量精确管理、动态电压调节等场合。
4、功耗和持续监测
-
PVD:
- PVD 是一个非常低功耗的硬件功能,通常不会占用太多资源。它可以在系统处于低功耗模式时运行,持续监控电压。
- 适合需要长时间连续监测电源电压的场景,尤其是低功耗设备。
-
ADC:
- ADC 通常需要更高的功耗,尤其是高精度的 ADC。频繁采样时会导致较高的功耗,这对于低功耗应用不太适合。
- 如果需要实时或定期监控电压,ADC 会相对消耗更多的能量,尤其是在低功耗模式下需要唤醒和采样。
5、应用举例
-
PVD 适用场景:
- 电池电量监控:当电池电压下降到某个阈值时,触发中断,提醒系统电量低,或者触发设备进入省电模式。
- 系统电源监控:电池或电源电压低于设定的安全阈值时,触发保护或关闭系统。
-
ADC 适用场景:
- 精确电池电量测量:通过 ADC 获取电池电压值,然后通过软件算法推算剩余电量,适用于需要精确电量估算的场景。
- 电压调节:例如,动态电压调节(DVS),通过实时监测 VDD 电压来调整系统电压,以优化功耗或性能。
总结:
- PVD 是硬件级别的电压检测器,快速且适合用于低功耗设备中的实时电压监测,特别是用于电池电量低的预警。
- ADC 提供更精确的电压测量,但需要软件判断,响应速度较慢,适合用于需要精确电压监控的场合。
如果需求只是检测电压是否低于某个阈值,使用 PVD 会更加简单高效。如果你需要精确的电池电量估算,或者更灵活的电压监控,ADC 会更加适用。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)