《DMG474开发指南_V1.1》第二十四章 低功耗实验

第二十四章 低功耗实验


       本章,我们将介绍STM32G474的电源控制(PWR),并实现低功耗模式相关功能。我们将通过四个实验来学习并实现低功耗相关功能,分别是PVD电压监控实验、睡眠模式实验、停止模式实验和待机模式实验。

       本章分为如下几个小节:

       24.1 电源控制(PWR)简介

       24.2 PVD电压监控实验

       24.3 睡眠模式实验

       24.4 停止模式实验

       24.5 待机模式实验


        24.1 电源控制(PWR)简介

       电源控制部分(PWR)概述了不同电源域的电源架构以及电源配置控制器。PWR的内容比较多,我们把它们的主要特性概括为以下3点:

       电源系统:内核域(VCORE)、VDD域、备份域、模拟域(VDDA)。

       电源监控:POR/PDR监控器、BOR监控器、PVD监控器、AVD监控器、VBAT阈值、温度阈值。

       电源管理:VBAT电池充电、工作模式、电压调节控制、低功耗模式。

       下面将分别对这3个特性进行简单介绍。


       24.1.1 电源系统 

       为了方便对电源系统进行管理,设计者把STM32的内核和外设等器件根据功能划分了不同的电源区域,具体如图24.1.1.1所示:


图24.1.1.1 电源概述框图


       在电源概述框图中我们划分了4个区域,分别是:①模拟域;②VDD域;③内核域;④备份域。下面分别进行简单介绍:

       ① 模拟域

       V DDA为ADC、DAC、OPAMP、比较器和电压参考缓冲器供电的外部模拟电源。该电源独立于所有其它电源。

       V SSA是独立的模拟和参考电压地。

       V REF+ 是ADC和DAC的外部参考电压。

       ② VDD域 

       V DD为I/O和系统模拟模块(如复位、电源管理和时钟)供电的外部电源。

       ③ 内核域

       内核域的Vcore受电压调节器的控制,其为内核域(核心、内存和数字外设)提供全部电源。

       ④ 备份域

       内备份域的电源自来VDD域的V DD或者V BAT。在备份域中,包含LSE、RTC、备份寄存器、RTC域控制寄存器等。


       24.1.2 电源监控 

       电源监控的部分我们主要关注PVD监控器,此外还需要知道上电复位(POR)/掉电复位(PDR)。其他部分的内容请大家查看《STM32G4xx参考手册_V7(英文版).pdf》(第6.2节236页)。

       l 上电复位(POR)/掉电复位(PDR)/欠压复位(BOR)

       上电期间,B OR保持设备复位,直到电源电压V DD达到规定的V BORx阈值,复位解除。当VDD低于选定的阈值时,将产生复位。当V DD超过V BORx上限时,设备复位被解除,系统可以启动,具体波形如图24.1.2.1所示:


图24.1.2.1 上电复位/掉电复位/欠压复位波形


       l 可编程电压检测器(PVD)

       上面介绍的POR、PDR以及BOR功能都是设置电压阈值与外部供电电压V DD比较,当V DD低于设置的电压阈值时,就会直接进入复位状态,防止电压不足导致的误操作。

       下面介绍可编程电压检测器(PVD),它可以实时监视VDD的电压,方法是将V DD与PWR控制寄存器1(PWR_CR1)中的PLS[2:0]位所选的V PVD阈值进行比较。当检测到电压低于V PVD阈值时,如果使能EXTI16线中断(即使能PVD& AVD中断),可以产生PVD中断,具体取决于EXTI16线配置为检测上升还是下降沿,然后在复位前,在中断服务程序中执行紧急关闭系统等任务。PVD阀值检测波形,如图24.1.2.2所示:


图24.1.2.2 PVD检测波形


       24.1.3 电源管理

       电源管理的部分我们要关注低功耗模式,在STM32的正常工作中,具有几种工作模式:运行、睡眠、低功率运行、低功率睡眠、停止以及待机。在上电复位后,STM32处于运行状态时,当内核不需要继续运行,就可以选择进入后面的几种模式降低功耗。这几种低功耗模式电源消耗不同、唤醒时间不同和唤醒源不同,我们要根据自身的需要选择合适的低功耗模式。

       STM32G474的低功耗模式非常多,下面是低功耗模式汇总介绍,如下表所示:


表24.1.3.1 低功耗模式汇总


       由上表可知,STM32G474支持的低功耗模式很多,但我们常用到的只有一部分,下面仅对睡眠模式、停止模式和待机模式进行介绍。

       1、睡眠模式(Sleep)

       进入睡眠模式,CPU时钟关闭,但是其他所有的外设仍可以运行,所以任何中断或事件都可以唤醒睡眠模式。有两种方式进入睡眠模式,这两种方式进入的睡眠模式唤醒的方法不同,分别是 WFI(wait for interrupt)和 WFE(wait for event),即由等待“中断”唤醒和“事件”唤醒。下面我们看看睡眠模式进入及退出方法:


表24.1.3.2 睡眠模式进入及退出方法


       2、停止模式(Stop)

       进入停止模式,所有的时钟都关闭,所有的外设也就停止了工作。但是VDD电源是没有关闭的,所以内核的寄存器和内存信息都保留下来,等待重新开启时钟就可以从上次停止的地方继续执行程序。

       值得注意的是:当电压调节器处于低功耗模式下,当系统从停止模式退出时,将会有一段额外的启动延时。如果在停止模式期间保持内部调节器开启,则退出启动时间会缩短,但相应的功耗会增加。

       3、待机模式(Standby)

       待机模式是在CM4深睡眠模式时关闭电压调节器,整个1.8V供电区域被断电。PLL、HSI和HSE振荡器也被断电。除备份域(RTC寄存器、RTC备份寄存器和备份SRAM)和待机电路中的寄存器外,SRAM和其他寄存器内容都将丢失。


        24.2 PVD电压监控实验

       本小节我们来学习PVD电压监控实验,该部分的知识点内容请回顾24.1.2电源监控。我们直接从寄存器介绍开始。


       24.2.1 PWR寄存器 

       本实验用到PWR的部分寄存器,在《STM32G4xx参考手册_V7(英文版).pdf》手册的第6.4小节(256页)可以找到PWR的寄存器描述。这里我们只介绍PVD电压监控实验我们用到的PWR的控制寄存器(PWR_CR),还有就是我们要用到EXTI16线中断,所以还要配置EXTI相关的寄存器,具体如下:

       l PWR控制寄存器(PWR_CR2)

       PWR控制寄存器2描述如图24.2.1.1所示:


图24.2.1.1 PWR_CR2寄存器(部分)


       位[3:1]PLS用于设置PVD检测的电压阀值,有8个等级阀值可以选择。注意:当位[3:1]为111时,将和外部输入的模拟电压进行比较。

       PVDE位,用于使能或者禁止PVD检测,显然我们要使能PVD检测,该位置1。

       l EXTI中断屏蔽寄存器(EXTI_IMR1)

       EXTI中断屏蔽寄存器描述如图24.2.1.2所示:


图24.2.1.2 EXTI_IMR1寄存器


       我们要使用到EXTI16线中断,所以IM16位要置1,即开放来自EXTI16线的中断请求。

       l EXTI上升沿触发选择寄存器(EXTI_RTSR1)

       EXTI上升沿触发选择寄存器描述如图24.2.1.3所示:


图24.2.1.3 EXTI_RTSR1寄存器


       我们要使用到EXTI16线中断,所以RT16位要置1,即允许EXTI16线上的下降沿触发。

       l EXTI下降沿触发选择寄存器(EXTI_FTSR1)

       EXTI下降沿触发选择寄存器描述如图24.1.4所示:


图24.2.1.4 EXTI_FTSR1寄存器


       我们要使用到EXTI16线中断,所以FT16位要置1,即允许EXTI16线上的下降沿触发。

       l EXTI挂起寄存器(EXTI_PR1)

       EXTI挂起寄存器描述如图24.2.1.5所示:


图24.2.1.5 EXTI_PR1寄存器


       EXTI挂起寄存器EXTI_PR1管理的是EXTI0~EXTI17、EXTI19~EXTI22和EXTI29~EXTI31线的中断标志位。在PVD中断服务函数里面,我们记得要对PIF16位写1,来清除EXTI16线的中断标志。


       24.2.2 硬件设计


       1. 例程功能

       开发板供电正常的话,LCD屏会显示"PVD Voltage OK!"。当供电电压过低,则会通过PVD中断服务函数将LED1点亮;当供电电压正常,会在PVD中断服务函数将LED1熄灭。LED0闪烁,提示程序运行。


       2. 硬件资源


       1)LED灯

              LED0 – PE0

              LED1 – PE1

       2)PVD(可编程电压监测器)

       3)正点原子1.3寸TFTLCD模块(SPI接口)


       3. 原理图

       PVD属于STM32G474的内部资源,只需要软件设置好即可正常工作。我们通过LED0和LCD来指示进入PVD中断的情况。


       24.2.3 程序设计

       24.2.3.1 PWR的HAL库驱动

       PWR在HAL库中的驱动代码在stm32g4xx_hal_pwr.c文件(及其头文件)中。

       1. HAL_PWR_ConfigPVD函数

       PVD的初始化函数,其声明如下:

void HAL_PWR_ConfigPVD(PWR_PVDTypeDef *sConfigPVD);

       l 函数描述:

       用于初始化PWR。

       l 函数形参:

       形参1是PWR_PVDTypeDef结构体类型变量,其定义如下:

typedef struct
{
  uint32_t PVDLevel; /* 指定PVD检测级别 */
  uint32_t Mode;      /* 指定PVD的EXTI检测模式 */
}PWR_PVDTypeDef;

       1)PVDLevel:指向PVD检测级别,对应PWR_CR寄存器的PLS位的设置,取值范围PWR_PVDLEVEL_0到PWR_PVDLEVEL_7,共八个级别。

       2)Mode:指定PVD的EXTI边沿触发模式。

       l 函数返回值:

       


       PVD电压监控配置步骤

       1)配置PVD,使能PVD时钟。

       调用HAL_PWR_ConfigPVD函数配置PVD,包括检测电压级别、使用中断线触发方式等。

       2)使能PVD检测,配置PVD/AVD中断优先级,开启PVD中断。

       通过HAL_PWR_EnablePVD函数使能PVD检测。

       通过HAL_NVIC_EnableIRQ函数使能PVD中断。

       通过HAL_NVIC_SetPriority函数设置中断优先级。

       3)编写中断服务函数。

       PVD中断服务函数为PVD_IRQHandler,当发生中断的时候,程序就会执行中断服务函数。HAL库有专门的PVD中断处理函数,我们只需要在PVD中断服务函数里面调用HAL_PWR_PVD_IRQHandler()函数,然后逻辑代码在PVD中断服务回调函数HAL_PWR_PVDCallback中编写,详见本例程源码。


       24.2.3.2 程序流程图

       下面看看本实验的程序流程图:


图24.2.3.2.1 PVD电压监控实验程序流程图


       24.2.3.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。本章节有四个实验,每一个实验的代码都是在上一个实验后面追加。

       pwr.h头文件只有函数声明,下面直接开始介绍pwr.c的程序,首先是PVD初始化函数。

/**
 * @brief       初始化PVD电压监视器
 * @param       pls: 电压等级(PWR_PVD_detection_level)
 *   @arg       PWR_PVDLEVEL_0,2.0V;
 *   @arg       PWR_PVDLEVEL_1,2.2V;
 *   @arg       PWR_PVDLEVEL_2,2.4V;
 *   @arg       PWR_PVDLEVEL_3,2.5V;
 *   @arg       PWR_PVDLEVEL_4,2.6V;
 *   @arg       PWR_PVDLEVEL_5,2.8V;
 *   @arg       PWR_PVDLEVEL_6,2.9V;
 *   @arg       PWR_PVDLEVEL_7,外部模拟电压输入;
 * @retval      无
 */
void pwr_pvd_init(uint32_t pls)
{
    PWR_PVDTypeDef pvd_handle = {0};
 
    __HAL_RCC_PWR_CLK_ENABLE();    /*使能PWR时钟*/
    
pvd_handle.PVDLevel = pls;
/* 使用中断线的上升沿和下降沿双边缘触发 */
    pvd_handle.Mode = PWR_PVD_MODE_IT_RISING_FALLING; 
    HAL_PWR_ConfigPVD(&pvd_handle);         /* 开启PWR对应的lin16上的中断 */
 
    HAL_NVIC_SetPriority(PVD_IRQn, 3 ,3);   /* 抢占优先级3,子优先级3 */
    HAL_NVIC_EnableIRQ(PVD_IRQn);
    HAL_PWR_EnablePVD();
}

       这里需要注意的就是PVD中断线选择的是上升沿和下降沿双边缘触发,其他的内容前面已经讲过。

       下面介绍的是PVD中断服务函数及其回调函数,函数定义如下:

/**
 * @brief       PVD中断服务函数
 * @param       无
 * @retval      无
 */
void PVD_PVM_IRQHandler(void)
{
    HAL_PWREx_PVD_PVM_IRQHandler();
}
 
/**
 * @brief       PVD中断服务回调函数
 * @param       无
 * @retval      无
 */
void HAL_PWR_PVDCallback(void)
{
   if (__HAL_PWR_GET_FLAG(PWR_FLAG_PVDO)) /* 电压比PLS所选电压还低 */
{
/* LCD显示电压低 */
        lcd_show_string(10, 110, 200, 16, 16, "PVD Low Voltage!", RED);
        LED1(0);    /* 点亮LED1, 表明电压低了 */
    }
    else
{
/* LCD显示电压正常 */
        lcd_show_string(10, 110, 200, 16, 16, "PVD Voltage OK! ", BLUE);
        LED1(1);    /* 灭掉绿灯 */
    }
}

       HAL_PWR_PVDCallback回调函数中首先是判断VDD电压是否比PLS所选电压还低,是的话,就在LCD显示PVD Low Voltage!并且点亮LED1,否则,在LCD显示PVD Voltage OK!并且关闭LED1。

       在main函数里面编写如下代码:

int main(void)
{
    uint8_t t=0;
 
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟, 170Mhz */
    delay_init(170);                        /* 延时初始化 */
    usart_init(115200);                    /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    lcd_init();                             /* 初始化LCD */
    pwr_pvd_init(PWR_PVDLEVEL_6);        /* PVD 2.9V检测 */
 
    lcd_show_string(10, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(10, 70, 200, 16, 16, "PVD TEST", RED);
    lcd_show_string(10, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    /* 默认LCD显示电压正常 */
    lcd_show_string(10, 110, 200, 16, 16, "PVD Voltage OK! ", BLUE);
 
    while (1)
    {
        if ((t % 20) == 0)
        {
            LED0_TOGGLE();                /* 每200ms,翻转一次LED0 */
        }
 
        delay_ms(10);
        t++;
    }
}

       这里我们选择PVD的检测电压阀值为2.9V,其他的代码很好理解,最后下载验证一下。


       24.2.4 下载验证

       下载代码后,默认LCD屏会显示"PVD Voltage OK!",当供电电压过低,则LED1会点亮,并且LCD屏会显示PVD Low Voltage!。当开发板供电正常,LED1会熄灭,LCD屏会继续显示"PVD Voltage OK!"。


        24.3 睡眠模式实验

       本小节我们来学习睡眠模式实验,该部分的知识点内容请回顾24.1.3电源管理。我们直接从寄存器介绍开始。


       24.3.1 EXTI寄存器 

       本实验我们用到外部中断来唤醒睡眠模式。我们用到WFI指令进入睡眠模式,这个后面会讲,进入睡眠模式后,使用外部中断唤醒。进入外部中断后,EXTI_IMR1寄存器的值会自动清零,我们需要对对应的外部中断线位置1,取消屏蔽,相当于其他中断的中断标志位进入中断后硬件自动置1,需要手动清零。

       l EXTI中断屏蔽寄存器(EXTI_IMR1)

       EXTI中断屏蔽寄存器描述如图24.3.1.1所示:


图24.3.1.1 EXTI_IMR1寄存器


       本实验使用KEY1(PE13)唤醒,即EXTI13线中断,所以在外部中断服务函数要把IM13位置1。

       l EXTI上升沿触发选择寄存器(EXTI_RTSR1)

       EXTI上升沿触发选择寄存器描述如图24.3.1.2所示:


图24.3.1.2 EXTI_RTSR1寄存器


       我们要使用到EXTI13线中断,所以RT13位要置1,即EXTI13使用的是上升沿进行触发。

       l EXTI挂起寄存器(EXTI_PR1)

       EXTI挂起寄存器描述如图24.3.1.3所示:


图24.3.1.3 EXTI_PR1寄存器


       在EXTI13中断服务函数里面,需要清除EXTI13中断标记,即对PIF13位写1。


       24.3.2 硬件设计


       1. 例程功能

       LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入睡眠模式,此时LED0不再闪烁,说明已经进入睡眠模式。按下按键KEY1后,LED1熄灭,提示退出睡眠模式,此时LED0继续闪烁,说明已经退出睡眠模式。


       2. 硬件资源


       1)LED灯

              LED0 – PE0

              LED1 – PE1

       2)独立按键

              KEY0 – PE12

              KEY1 – PE13

       3)电池管理(睡眠模式)

       4)正点原子1.3寸TFTLCD模块(SPI接口)


       3. 原理图

       PWR属于STM32G474的内部资源,只需要软件设置好即可正常工作。我们通过KEY0让CPU进入睡眠模式,再通过KEY1 触发EXTI中断来唤醒CPU。LED0指示程序是否执行,LED1指示CPU是否进入睡眠模式。


       24.3.3 程序设计 


       24.3.3.1 PWR的HAL库驱动


       1. HAL_PWR_EnterSLEEPMode函数

       进入睡眠模式函数,其声明如下:

void HAL_PWR_EnterSLEEPMode(uint32_t Regulator, uint8_t SLEEPEntry);

       l 函数描述:

       用于设置CPU进入睡眠模式。

       l 函数形参:

       形参1指定稳压器的状态。有两个选择,PWR_MAINREGULATOR_ON表示稳压器处于正常模式,PWR_LOWPOWERREGULATOR_ON表示稳压器处于低功耗模式(该形参在该函数中没有实质用处)。

       形参2指定进入睡眠模式的方式。有两个选择,PWR_SLEEPENTRY _WFI表示使用WFI指令,PWR_SLEEPENTRY_WFE表示使用WFE指令看,我们选择前者。

       l 函数返回值:无


       睡眠模式配置步骤

       1)配置唤醒睡眠模式的方式

       这里我们用外部中断的方式唤醒睡眠模式,所以这里需要配置一个外部中断功能,我们用KEY1按键作为中断触发源,接下来就是配置PE13(连接按键KEY1)。

       通过__HAL_RCC_GPIOE_CLK_ENABLE函数使能GPIOE的时钟。

       通过HAL_GPIO_Init函数配置PE13为具有下降边缘触发检测的外部中断模式,开启上拉电阻等。

       通过HAL_NVIC_EnableIRQ函数使能EXTI13中断。

       通过HAL_NVIC_SetPriority函数设置中断优先级。

       编写EXTI13_IRQHandle中断函数,在中断服务函数中调用HAL_GPIO_EXTI_IRQHandler函数。

       最后编写HAL_GPIO_EXTI_Callback回调函数。由于前面已经介绍过外部中断的配置步骤,这里就介绍到这里,详见本例程源码。

       2)进入CPU睡眠模式

       通过HAL_PWR_EnterSLEEPMode函数进入睡眠模式。

       3)通过按下按键触发外部中断唤醒睡眠模式

       在本实验中,通过按下KEY0按键进入睡眠模式,然后通过按下KEY1按键触发外部中断唤醒睡眠模式。

24.3.3.2 程序流程图


图24.3.3.2.1 睡眠模式实验程序流程图


       24.3.3.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。睡眠模式实验代码在电压监控实验源码后追加。

       首先看本实验在pwr.h头文件定义的几个宏定义:

/* PWR KEY 按键 引脚和中断 定义 
 * 我们通过KEY1按键唤醒 MCU,  因此必须定义这个按键及其对应的中断服务函数 
 */
#define PWR_KEY1_GPIO_PORT        GPIOE
#define PWR_KEY1_GPIO_PIN         GPIO_PIN_13
/* PE口时钟使能 */
#define PWR_KEY1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE();}while(0)
 
#define PWR_KEY1_INT_IRQn         EXTI15_10_IRQn
#define PWR_KEY1_INT_IRQHandler   EXTI15_10_IRQHandler

       这些定义是KEY1按键的相关宏定义,以及其对应的外部中断线13的相关定义。

       pwr.h头文件就介绍这部分的程序,下面是pwr.c文件,先看低功耗模式下的按键初始化函数,其定义如下:

/**
 * @brief       低功耗模式下的按键初始化(用于唤醒睡眠模式/停止模式/待机模式)
 * @param       无
 * @retval      无
 */
void pwr_key_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    PWR_KEY1_GPIO_CLK_ENABLE();                               /* KEY1时钟使能 */
 
    gpio_init_struct.Pin = PWR_KEY1_GPIO_PIN;               /* KEY1引脚 */
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;            /* 中断,下降沿 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;         /* 高速 */
    HAL_GPIO_Init(PWR_KEY1_GPIO_PORT, &gpio_init_struct); /* KEY1引脚初始化 */
 
    HAL_NVIC_SetPriority(PWR_KEY1_INT_IRQn, 2, 2); /* 抢占优先级2,子优先级2 */
    HAL_NVIC_EnableIRQ(PWR_KEY1_INT_IRQn); 
}

       该函数初始化KEY1按键(PE13),并设置下降沿触发的外部中断线13,最后设置中断优先级并使能外部中断线13。

       下面介绍的是进入CPU睡眠模式函数,其定义如下:

/**
 * @brief       进入CPU睡眠模式
 * @param       无
 * @retval      无
 */
void pwr_enter_sleep(void)
{
    HAL_SuspendTick();  /* 暂停滴答时钟,防止通过滴答时钟中断唤醒 */
 
/* 进入睡眠模式 */
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}

       函数内直接调用HAL_PWR_EnterSLEEPMode函数使用WFI指令进入睡眠模式。

       下面介绍的是KEY1按键外部中断服务函数及其回调函数,函数定义如下:

/**
 * @brief       KEY1按键 外部中断服务程序
 * @param       无
 * @retval      无
 */
void PWR_KEY1_INT_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(PWR_KEY1_GPIO_PIN);
}
 
/**
 * @brief       外部中断回调函数
 * @param       GPIO_Pin:中断线引脚
 * @note        此函数会被PWR_KEY1_INT_IRQHandler()调用
 * @retval      无
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == PWR_KEY1_GPIO_PIN)
    {
        /* HAL_GPIO_EXTI_IRQHandler()函数已经为我们清除了中断标志位,
所以我们进了回调函数可以不做任何事 */
    }
}

       在KEY1按键外部中断服务函数中我们调用HAL库的HAL_GPIO_EXTI_IRQHandler函数来处理外部中断。该函数会调用__HAL_GPIO_EXTI_CLEAR_IT函数取消屏蔽对应的外部中断线位,这里是EXTI_IMR寄存器相应位,还有其他寄存器控制其他外部中断线。我们只是唤醒睡眠模式而已,不需要其他的逻辑程序,所以HAL_GPIO_EXTI_Callback回调函数可以什么都不用做,甚至也可以不重新定义这个回调函数(屏蔽该回调函数也可以)。

       最后在main.c里面编写如下代码: 

int main(void)
{
    uint8_t t=0;
    uint8_t key = 0;
 
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟, 170Mhz */
    delay_init(170);                         /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                               /* 初始化LED */
    lcd_init();                               /* 初始化LCD */
    key_init();                               /* 初始化按键 */
    pwr_key_init();                       /* 唤醒按键初始化 */
 
    lcd_show_string(10, 50,  200, 16, 16, "STM32", RED);
    lcd_show_string(10, 70,  200, 16, 16, "SLEEP TEST", RED);
    lcd_show_string(10, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(10, 110, 200, 16, 16, "KEY0:Enter SLEEP MODE", RED);
    lcd_show_string(10, 130, 200, 16, 16, "KEY1:Exit SLEEP MODE", RED);
 
    while (1)
    {
        key = key_scan(0);
        if (key == KEY0_PRES)
        {
            LED1(0);                   /* 点亮绿灯,提示进入睡眠模式 */
            pwr_enter_sleep();       /* 进入睡眠模式 */
            HAL_ResumeTick();   /* 恢复滴答时钟 */
            LED1(1);                   /* 关闭绿灯,提示退出睡眠模式 */
        }
 
        if ((t % 20) == 0)
        {
            LED0_TOGGLE();        /* 每200ms,翻转一次LED0 */
        }
        delay_ms(10);
        t++;
    }
}

       该部分程序,功能就是按下KEY0后,点亮LED1,进入睡眠模式。然后一直等待外部中断唤醒,当按下按键KEY1,就触发外部中断,睡眠模式就被唤醒,然后继续执行后面的程序,关闭LED1等。


       24.3.4 下载验证

       下载代码后,LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入睡眠模式,此时LED0不再闪烁,说明已经进入睡眠模式。按下按键KEY1后,LED1熄灭,提示退出睡眠模式,此时LED0继续闪烁,说明已经退出睡眠模式。


        24.4 停止模式实验

       本小节我们来学习停止模式实验,该部分的知识点内容请回顾24.1.2电源管理。我们直接从寄存器介绍开始。


       24.4.1 EXTI寄存器 

       本实验我们用到外部中断来唤醒停止模式。我们用到WFI指令进入停止模式,进入停止模式后,使用外部中断唤醒。外部中断部分内容参照睡眠模式即可,都是共用同样的配置。


       24.4.2 硬件设计


       1. 例程功能

       LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入停止模式,此时LED0不再闪烁,说明已经进入停止模式。按下按键KEY1后,LED1熄灭,提示退出停止模式,此时LED0继续闪烁,说明已经退出停止模式。


       2. 硬件资源


       1)LED灯

              LED0 – PE0

              LED1 – PE1

       2)独立按键

              KEY0 – PE12

              KEY1 – PE13

       3)电池管理(停止模式)

       4)正点原子1.3寸TFTLCD模块(SPI接口)


       3. 原理图

       PWR属于STM32G474的内部资源,只需要软件设置好即可正常工作。我们通过KEY0让CPU进入停止模式,再通过KEY1 触发EXTI中断来唤醒CPU。LED0指示程序是否执行,LED1指示CPU是否进入停止模式。


       24.4.3 程序设计 


       24.4.3.1 PWR的HAL库驱动


       1. HAL_PWR_EnterSTOPMode函数

       进入睡眠模式函数,其声明如下:

void HAL_PWR_EnterSTOPMode (uint32_t Regulator, uint8_t STOPEntry);

       l 函数描述:

       用于设置CPU进入停止模式。

       l 函数形参:

       形参1指定稳压器在睡眠模式下的状态。有两个选择,PWR_MAINREGULATOR_ON表示稳压器处于正常模式,PWR_LOWPOWERREGULATOR_ON表示稳压器处于低功耗模式。

       形参2指定用WFI还是WFE指令进入停止模式。有两个选择,PWR_STOPENTRY_WFI表示使用WFI指令,PWR_STOPENTRY_WFE表示使用WFE指令,我们选择前者。

       l 函数返回值:

       


       停止模式配置步骤


       1)配置唤醒停止模式的方式

       这里我们用外部中断的方式唤醒停止模式,所以这里需要配置一个外部中断功能,我们用WK_UP按键作为中断触发源,接下来就是配置PE13(连接按键KEY1)。

       通过__HAL_RCC_GPIOE_CLK_ENABLE函数使能GPIOE的时钟。

       通过HAL_GPIO_Init函数配置PE13为具有下降边缘触发检测的外部中断模式,开启上拉电阻等。

       通过HAL_NVIC_EnableIRQ函数使能EXTI13中断。

       通过HAL_NVIC_SetPriority函数设置中断优先级。

       编写EXTI13_IRQHandle中断函数,在中断服务函数中调用HAL_GPIO_EXTI_IRQHandler函数。

       最后编写HAL_GPIO_EXTI_Callback回调函数。由于前面已经介绍过外部中断的配置步骤,这里就介绍到这里,详见本例程源码。

       2)进入CPU停止模式

       通过HAL_PWR_EnterSTOPMode函数进入停止模式。

       3)通过按下按键触发外部中断唤醒停止模式

       在本实验中,通过按下KEY0按键进入停止模式,然后通过按下KEY1按键触发外部中断唤醒停止模式。


       24.4.3.2 程序流程图


图24.4.3.2.1 停止模式实验程序流程图


       24.4.3.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。停止模式实验代码在睡眠模式实验源码后追加。

       首先看pwr.h头文件,因为我们还是用到KEY1对应的外部中断线来唤醒停止模式的CPU,pwr.h头文件的KEY1按键对应的宏定义我们也是用到的,上个实验已经讲过,这里不再赘述。下面是pwr.c文件,KEY1按键的相关函数我们还是用上个实验的,我们主要介绍进入停止模式函数,其定义如下:

/**
 * @brief       进入停止模式
 * @param       无
 * @retval      无
 */
void pwr_enter_stop(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_SuspendTick();  /* 暂停滴答时钟,防止通过滴答时钟中断唤醒 */
    /* 进入停止模式,掉电并进入低功耗模式并待机 */
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}

       该函数先调用__HAL_RCC_PWR_CLK_ENABLE函数使能PWR时钟,然后调用HAL_SuspendTick函数暂停滴答时钟,最后调用HAL_PWR_EnterSTOPMode函数进入停止模式,形参1选择PWR_LOWPOWERREGULATOR_ON表示设置稳压器为低功耗模式,形参2则是选择WFI指令。

       最后在main.c里面编写如下代码:

int main(void)
{
    uint8_t t=0;
    uint8_t key = 0;
 
    HAL_Init();                            /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟, 170Mhz */
    delay_init(170);                         /* 延时初始化 */
    usart_init(115200);                   /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    lcd_init();                             /* 初始化LCD */
    key_init();                             /* 初始化按键 */
    pwr_key_init();                        /* 唤醒按键初始化 */
    
    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "STOP TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Enter STOP MODE", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY1:Exit STOP MODE", RED);
 
    while (1)
    {
        key = key_scan(0);
 
        if (key == KEY0_PRES)
        {
            LED1(0);         /* 点亮绿灯,提示进入停止模式 */
            pwr_enter_stop(); /* 进入停止模式 */
 
            /* 从停止模式唤醒, 需要重新设置系统时钟, 170Mhz */
            sys_stm32_clock_init(85, 2, 2, 4, 8);
delay_init(170); /* 延时初始化 */
            LED1(1);           /* 关闭绿灯,提示退出停止模式 */
        }
 
        if ((t % 20) == 0)
        {
            LED0_TOGGLE();    /* 每200ms,翻转一次LED0 */
        }
 
        delay_ms(10);
        t++;
    }
}

       该部分程序,功能就是按下KEY0后,点亮LED1、暂停滴答时钟并进入停止模式。然后一直等待外部中断唤醒,当按下按键KEY1,就触发外部中断,停止模式就被唤醒,然后继续执行后面的程序,重新设置系统时钟170Mhz和延时初始化,关闭LED1等。


       24.4.4 下载验证

       下载代码后,LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入停止模式,此时LED0不再闪烁,说明已经进入停止模式。按下按键KEY1后,LED1熄灭,提示退出停止模式,此时LED0继续闪烁,说明已经退出停止模式。


       24.5 待机模式实验

       本小节我们来学习待机模式实验,该部分的知识点内容请回顾24.1.2电源管理。我们直接从寄存器介绍开始。


       24.5.1 PWR寄存器 

       本实验是先对相关的电源控制寄存器配置待机模式的参数,然后通过WFI指令进入待机模式,使用PA0引脚的上升沿来唤醒。

       下面主要介绍PWR_CR3寄存器相关位。

       l PWR控制寄存器(PWR_CR3)

       PWR的控制寄存器描述如图24.5.1.1所示:


图24.5.1.1 PWR_CR3寄存器(部分)


       这里我们只需要关注EWUP1位,该位用于使能唤醒引脚,我们需要置1。


       24.5.2 硬件设计


       1. 例程功能

       LED0闪烁,表明代码正在运行。按下按键KEY0后,进入待机模式,待机模式下大部分引脚处于高阻态,所以这时候LED0会熄灭。当PA0被拉高后,退出待机模式(相当于复位操作),程序重新执行,LED0继续闪烁。


       2. 硬件资源


       1)LED灯

              LED0 – PE0

       2)独立按键

              KEY0 – PE12

       3)电池管理(待机模式)

       4)正点原子1.3寸TFTLCD模块(SPI接口)

       5)IO

       PA0(开发板PA0没有接按键,需要自己接地测试)


       3. 原理图

       PWR属于STM32G474的内部资源,只需要软件设置好即可正常工作。我们通过KEY0让CPU进入待机模式,再通过PA0上升沿来唤醒CPU,LED0指示程序是否执行。


       24.5.3 程序设计


       24.5.3.1 PWR的HAL库驱动


       1. HAL_PWR_EnableWakeUpPin函数

       使能唤醒引脚函数,其声明如下:

void HAL_PWR_EnableWakeUpPin(uint32_t WakeUpPinPolarity);

       l 函数描述:

       用于使能唤醒引脚。

       l 函数形参:

       形参1取值范围:PWR_WAKEUP_PIN1~5,我们这里用PWR_WAKEUP_PIN1。

       l 函数返回值:

       


       2. HAL_PWR_DisableWakeUpPin函数

       禁止某个唤醒引脚使用的函数如下:

void HAL_PWR_DisableWakeUpPin(uint32_t WakeUpPinPolarity);


       3. HAL_PWR_EnterSTANDBYMode函数

       进入待机模式函数,其声明如下:

void HAL_PWR_EnterSTANDBYMode(void);

       l 函数描述:

       用于使CPU进入待机模式。要进入待机模式,首先要设置CR1的LPMS[2:0]位为011,接着设置SCB_SCR的SLEEPDEEP位为1,使得CPU进入待机模式,最后执行WFI指令开始进入待机模式,并等待唤醒信号上升沿的到来。

       l 函数形参:

       

       l 函数返回值:

       


       待机模式配置步骤

       1)进入CPU停止模式

       在进入待机模式之前我们需要做一些准备:

       涉及到操作PWR寄存器的内容,所以首先先进行PWR时钟的初始化,用__HAL_RCC_PWR_CLK_ENABLE函数实现。

       通过HAL_PWR_EnableWakeUpPin函数使能WKUP的唤醒功能。

       通过__HAL_PWR_CLEAR_FLAG函数清除唤醒标记,详看源码。

       通过HAL_PWR_EnterSTANDBYMode函数进入待机模式。

       2)通过WKUP引脚上升沿触发唤醒睡眠模式

       在本实验中,通过按下KEY0按键进入待机模式,然后通过PA0的上升沿唤醒待机模式。


       24.5.3.2 程序流程图


图24.5.3.2.1 待机模式实验程序流程图


       24.5.3.3 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。待机模式实验代码在停止模式实验源码后追加。

       首先看pwr.h头文件,因为我们用到WKUP引脚上升沿来唤醒待机模式的CPU,pwr.h头文件的WK_UP按键对应的宏定义我们也是用到的,WKUP定义如下。

#define PWR_WKUP_GPIO_PORT   PIOA
#define PWR_WKUP_GPIO_PIN   GPIO_PIN_0
/* PA口时钟使能 */
#define PWR_WKUP_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

       下面是pwr.c文件,我们主要介绍进入待机模式函数,其定义如下:

/**
 * @brief       进入待机模式
 * @param       无
 * @retval      无
 */
void pwr_enter_standby(void)
{
    __HAL_RCC_AHB1_FORCE_RESET();                       /* 复位所有IO口 */
 
    while(WKUP_KD); /* 等待WK_UP按键松开(在有RTC中断时,必须等WK_UP松开再进入待机) */
 
    __HAL_RCC_PWR_CLK_ENABLE();                         /* 使能PWR时钟 */
    __HAL_RCC_BACKUPRESET_FORCE();                      /* 复位备份区域 */
    HAL_PWR_EnableBkUpAccess();                         /* 后备区域访问使能 */  
 
    /* STM32G4,当开启了RTC相关中断后,必须先关闭RTC中断,再清中断标志位,然后重新设置
     * RTC中断,再进入待机模式才可以正常唤醒,否则会有问题
     */
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
    __HAL_RTC_WRITEPROTECTION_DISABLE(&g_rtc_handle);   /* 关闭RTC写保护 */
 
    /* 关闭RTC相关中断,可能在RTC实验打开了 */
    __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&g_rtc_handle, RTC_IT_WUT);
    __HAL_RTC_TIMESTAMP_DISABLE_IT(&g_rtc_handle, RTC_IT_TS);
    __HAL_RTC_ALARM_DISABLE_IT(&g_rtc_handle, RTC_IT_ALRA|RTC_IT_ALRB);
 
    /* 清除RTC相关中断标志位 */
    __HAL_RTC_ALARM_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_ALRAF|RTC_FLAG_ALRBF);
    __HAL_RTC_TIMESTAMP_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_TSF); 
    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_WUTF);
 
    __HAL_RCC_BACKUPRESET_RELEASE();     /* 备份区域复位结束 */
    __HAL_RTC_WRITEPROTECTION_ENABLE(&g_rtc_handle);    /* 使能RTC写保护 */
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);     /* 清除Wake_UP标志 */
 
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);   /* 设置WKUP用于唤醒 */
    HAL_PWR_EnterSTANDBYMode();       /* 进入待机模式 */
}

       该函数内容较多,关于RTC的部分设置,是为了避免出现意外唤醒的情况。接下来看待机模式相关的部分:首先是调用__HAL_RCC_PWR_CLK_ENABLE来使能PWR时钟,然后调用函数HAL_PWR_EnableWakeUpPin 来设置 WK_UP 脚作为唤醒源。最后调用函数HAL_PWR_EnterSTANDBYMode进入待机模式。

       最后在main.c里面编写如下代码:

int main(void)
{
    uint8_t t=0;
    uint8_t key = 0;
 
    HAL_Init();                               /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟, 170Mhz */
    delay_init(170);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                              /* 初始化LED */
    lcd_init();                               /* 初始化LCD */
    key_init();                               /* 初始化按键 */
    pwr_key_init();                     /* 唤醒按键初始化 */
 
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "STANDBY TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Enter STANDBY MODE", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY_UP:Exit STANDBY MODE", RED);
 
    while (1)
    {
        key = key_scan(0);
 
        if (key == KEY0_PRES)
        {
            pwr_enter_standby();        /* 进入待机模式 */
            /* 从待机模式唤醒相当于系统重启(复位), 因此不会执行到这里 */
        }
 
        if ((t % 20) == 0)
        {
            LED0_TOGGLE();              /* 每200ms,翻转一次LED0 */
        }
 
        delay_ms(10);
        t++;
    }
}

       该部分程序,经过一系列初始化后,判断到KEY0按下就调用pwr_enter_standby函数进入待机模式,然后等待PA0产生WKUP上升沿唤醒CPU。注意:待机模式唤醒后,系统会进行复位。


       24.5.4 下载验证

       下载代码后,LED0闪烁,表明代码正在运行。按下按键KEY0后,TFTLCD屏熄灭,提示进入停止模式,此时LED0不再闪烁,说明已经进入待机模式。当PA0被拉高后,TFTLCD屏点亮,提示退出待机模式,此时LED0继续闪烁,说明已经退出待机模式。


请使用浏览器的分享功能分享到微信等