【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第二十八章 低功耗实验

本章,我们将介绍STM32F103的电源控制(PWR),并实现低功耗模式相关功能。我们将通过四个实验来学习并实现低功耗相关功能,分别是PVD电压监控实验、睡眠模式实验、停止模式实验和待机模式实验。
本章分为如下几个小节:
28.1 电源控制(PWR)简介
28.2 PVD电压监控实验
28.3 睡眠模式实验
28.4 停止模式实验
28.5 待机模式实验

28.1 电源控制(PWR)简介

电源控制部分(PWR)概述了不同电源域的电源架构以及电源配置控制器。PWR的内容比较多,我们把它们的主要特性概括为以下3点:
电源系统:VDDA供电区域、VDD供电区域、1.8V供电区域、后备供电区域。
电源监控:POR/PDR监控器、PVD监控器。
电源管理:低功耗模式。
下面将分别对这3个特性进行简单介绍。
28.1.1 电源系统
为了方便对电源系统进行管理,设计者把STM32的内核和外设等器件跟据功能划分了不同的电源区域,具体如图28.1.1.1所示。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第1张图片

图28.1.1.1 电源概述框图
在电源概述框图中我们划分了3个区域①②③,分别是独立的A/D转换器供电和参考电压、电压调节器、电池备份区域。下面分别进行简单介绍:
①独立的A/D转换器供电和参考电压(VDDA供电区域)
VDDA供电区域,主要是ADC电源以及参考电压,STM32的ADC模块配备独立的供电方式,使用了VDDA引脚作为输入,使用VSSA引脚作为独立地连接,VREF引脚为提供给ADC的参考电压。
②电压调节器(VDD /1.8V供电区域)
电压调节器是STM32的电源系统中最核心部分,连接VDD供电区域和1.8供电区域。VDD供电来自于VSS和VDD,给I/O电路以及待机电路供电,电压调节器主要为备份域以及待机电路以外的所有数字电路供电,其中包括内核、数字外设以及RAM,调节器的输出电压约为1.8V,因此由调压器供电的区域称为1.8V供电区域。
电压调节器根据应用方式不同有三种不同的工作模式。在运行模式下,调节器以正常工作模式为内核、内存和外设提供1.8V;在停止模式下,调节器以低功耗模式提供1.8V电源,以保存寄存器和SRAM的内容。在待机模式下,调节器停止供电,除了备用电路和备份域外,寄存器和SRAM的内容全部丢失。
③电池备份区域(后备供电区域)
电池备份区域也就是后备供电区域,使用电池或者其他电源连接到VBAT脚上,当VDD断电时,可以保存备份寄存器的内容和维持RTC的功能。同时VBAT引脚也为RTC和LSE振荡器供电,这保证了当主要电源被切断时,RTC能够继续工作。切换到VBAT供电由复位模块中的掉电复位功能控制。
28.1.2 电源监控
电源监控的部分我们主要关注PVD监控器,此外还需要知道上电复位(POR)/掉电复位(PDR)。其他部分的内容请大家查看《STM32F10xxx参考手册_V10(中文版).pdf》第4.2节(38页)。
上电复位(POR)/掉电复位(PDR)
上电时,当VDD低于指定VPOR阈值时,系统无需外部复位电路便会保持复位模式。一旦VDD电源电压高于VPOR阈值,系统便会退出复位状态,芯片正常工作。掉电时,当VDD低于指定VPDR阈值时,系统就会保持复位模式。如图28.1.2.1所示,RESET为上电复位信号。
注意:POR与PDR的复位电压阈值是固定的,VPOR阈值(典型值)为1.92V,VPDR阈值(典型值)为1.88V。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第2张图片

图28.1.2.1 上电复位/掉电复位波形
可编程电压检测器(PVD)
上面介绍的POR、PDR功能都是设置电压阈值与外部供电电压VDD比较,当VDD低于设置的电压阈值时,就会直接进入复位状态,防止电压不足导致的误操作。
下面介绍可编程电压检测器(PVD),它可以实时监视VDD的电压,方法是将VDD与PWR控制寄存器(PWR_CR)中的PLS[2:0]位所选的VPVD阈值进行比较。其中PWR_CSR寄存器中的PVDO位决定了VDD是高于VPVD还是低于VPVD,本实验中配置的是VDD低于VPVD阈值这个条件。当检测到电压低于VPVD阈值时,如果使能EXTI16线中断,即使能PVD中断,可以产生PVD中断,具体取决于EXTI16线配置为检测上升还是下降沿,然后在复位前,在中断服务程序中执行紧急关闭系统等任务。PVD阀值检测波形,如图28.1.2.2所示。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第3张图片

图28.1.2.2 PVD检测波形
PVD阀值有8个等级,有上升沿和下降沿的区别,具体由PWR_CSR寄存器中的PVDO位决定。PVD阈值等级表具体如表28.1.2.1所示。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第4张图片

表28.1.2.1 PVD阀值等级
28.1.3 电源管理
电源管理的部分我们要关注低功耗模式,在STM32的正常工作中,具有四种工作模式,运行、睡眠、停止以及待机。在上电复位后,STM32处于运行状态时,当内核不需要继续运行,就可以选择进入后面的三种模式降低功耗。这三种低功耗模式电源消耗不同、唤醒时间不同和唤醒源不同,我们要根据自身的需要选择合适的低功耗模式。
下面是低功耗模式汇总介绍,如下表所示。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第5张图片

表28.1.3.1 低功耗模式汇总
下面对睡眠模式、停止模式和待机模式,分开介绍。
1、睡眠模式
进入睡眠模式,Cortex_M3内核停止,所有外设包括Cortex_M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行,有两种进入睡眠模式的模式WFI和WFE。WFI(Wait for interrupt)和WFE(Wait for event)是内核指令,会调用一些汇编指令,我们会使用即可,更详细的描述可以查看《CM3权威指南》。睡眠后唤醒的方式即由等待“中断”唤醒和“事件”唤醒。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第6张图片

表28.1.3.2 睡眠模式进入及退出方法
2、停止模式
进入停止模式,所有的时钟都关闭,所有的外设也就停止了工作。但是VDD电源是没有关闭的,所以内核的寄存器和内存信息都保留下来,等待重新开启时钟就可以从上次停止的地方继续执行程序。
值得注意的是:当电压调节器处于低功耗模式下,当系统从停止模式退出时,将会有一段额外的启动延时。如果在停止模式期间保持内部调节器开启,则退出启动时间会缩短,但相应的功耗会增加。
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第7张图片

3、待机模式
待机模式可实现最低功耗。该模式是在CM3深睡眠模式时关闭电压调节器,整个1.8V供电区域被断电。PLL、HSI和HSE振荡器也被断电。除备份域(RTC寄存器、RTC备份寄存器和备份SRAM)和待机电路中的寄存器外,SRAM 和其他寄存器内容都将丢失。不过如果我们使能了备份区域(备份SRAM、RTC、LSE),那么待机模式下的功耗,将达到3.8uA左右。
那么我们如何进入待机模式呢?其实很简单,只要按表28.1.3.3所示的步骤执行就可以了:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第8张图片

28.2 PVD电压监控实验
本小节我们来学习PVD电压监控实验,该部分的知识点内容请回顾28.1.2电源监控。我们直接从寄存器介绍开始。
28.2.1 PWR寄存器
本实验用到PWR的部分寄存器,在《STM32F10XXX参考手册(中文版)》的4.4小节可以找到PWR寄存器描述。这里我们只介绍PVD电压监控实验用到的PWR控制寄存器(PWR_CR),还有就是我们要用到EXTI16线中断,所以还要配置EXTI相关的寄存器,具体如下:
 PWR控制寄存器(PWR_CR)
PWR控制寄存器描述如图28.2.1.1所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第9张图片

图28.2.1.1 PWR_CR寄存器(部分)
位[7:5] PLS用于设置PVD检测的电压阀值,即前面我们介绍PVD的8个等级阀值选择。
位4 PVDE位,用于使能或者禁止PVD检测,显然我们要使能PVD检测,该位置1。
这个寄存器还有其它的位我们没有列出来,也是跟电源相关的,如待机,掉电等,我们后面的实验再讲解这些功能,这里先跳过。
 EXTI中断屏蔽寄存器(EXTI_IMR)
EXTI中断屏蔽寄存器描述如图28.2.1.2所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第10张图片

图28.2.1.2 EXTI_IMR寄存器
我们要使用到EXTI16线中断,所以MR16位要置1,即开放来自EXTI16线的中断请求。
 EXTI上升沿触发选择寄存器(EXTI_RTSR)
EXTI上升沿触发选择寄存器描述如图28.2.1.3所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第11张图片

图28.2.1.3 EXTI_RTSR寄存器
我们要使用到EXTI16线中断,所以TR16位要置1,即允许EXTI16线的上升沿触发。
 EXTI下降沿触发选择寄存器(EXTI_FTSR)
EXTI下降沿触发选择寄存器描述如图28.2.1.4所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第12张图片

图28.2.1.4 EXTI_FTSR寄存器
我们要使用到EXTI16线中断,所以TR16位要置1,即允许EXTI16线上的下降沿触发。
 EXTI挂起寄存器(EXTI_PR)
EXTI挂起寄存器描述如图28.2.1.5所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第13张图片

图28.2.1.5 EXTI_PR寄存器
EXTI挂起寄存器EXTI_PR管理的是EXTI0线到EXTI19线的中断标志位。在PVD中断服务函数里面,我们记得要对PR16位写1,来清除EXTI16线的中断标志。
28.2.2 硬件设计

  1. 例程功能
    开发板供电正常的话,LCD屏会显示"PVD Voltage OK!"。当供电电压过低,则会通过PVD中断服务函数将LED1点亮;当供电电压正常后,会在PVD中断服务函数将LED1熄灭。LED0闪烁,提示程序运行。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    LED1 – PE5
    2)PVD(可编程电压监测器)
    3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. 原理图
    PVD属于STM32F103的内部资源,只需要软件设置好即可正常工作。我们通过LED0和LCD来指示进入PVD中断的情况。
    28.2.3 程序设计
    28.2.3.1 PWR的HAL库驱动
    PWR在HAL库中的驱动代码在stm32f1xx_hal_pwr.c文件(及其头文件)中。
  4. HAL_PWR_ConfigPVD函数
    PVD的初始化函数,其声明如下:
    void HAL_PWR_ConfigPVD (PWR_PVDTypeDef *sConfigPVD);
    函数描述:
    用于初始化PWR。
    函数形参:
    形参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边沿检测模式。
函数返回值:

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中编写,详见本例程源码。
28.2.3.2 程序流程图
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第14张图片

图28.2.3.2.1 PVD电压监控实验程序流程图
28.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.2V;
 * @arg       	PWR_PVDLEVEL_1,2.3V;
 * @arg       	PWR_PVDLEVEL_2,2.4V;
 * @arg       	PWR_PVDLEVEL_3,2.5V;
 * @arg       	PWR_PVDLEVEL_4,2.6V;
 * @arg       	PWR_PVDLEVEL_5,2.7V;
 * @arg       	PWR_PVDLEVEL_6,2.8V;
 * @arg       	PWR_PVDLEVEL_7,2.9V;
 * @retval      	无
 */
void pwr_pvd_init(uint32_t pls)
{
    PWR_PVDTypeDef pwr_pvd = {0};

    __HAL_RCC_PWR_CLK_ENABLE();                      /* 使能PWR时钟 */
    
pwr_pvd.PVDLevel = pls;                          /* 检测电压级别 */
/* 使用中断线的上升沿和下降沿双边缘触发 */
    pwr_pvd.Mode = PWR_PVD_MODE_IT_RISING_FALLING;   
    HAL_PWR_ConfigPVD(&pwr_pvd);

    HAL_NVIC_SetPriority(PVD_IRQn, 3 ,3);
    HAL_NVIC_EnableIRQ(PVD_IRQn);
    HAL_PWR_EnablePVD();                             /* 使能PVD检测 */
}
这里需要注意的就是PVD中断线选择的是上升沿和下降沿双边沿触发,其他的内容前面已经讲过。
下面介绍的是PVD中断服务函数及其回调函数,函数定义如下:
/**
 * @brief      	PVD中断服务函数
 * @param      	无
 * @retval     	无
 */
void PVD_IRQHandler(void)
{
    HAL_PWR_PVD_IRQHandler();
}

/**
 * @brief      	PVD中断服务回调函数
 * @param      	无
 * @retval     	无
 */
void HAL_PWR_PVDCallback(void)
{
   if (__HAL_PWR_GET_FLAG(PWR_FLAG_PVDO))	/* 电压比PLS所选电压还低 */
{
/* LCD显示电压低 */
        lcd_show_string(30, 130, 200, 16, 16, "PVD Low Voltage!", RED); 
        LED1(0);    							/* 点亮LED1, 表明电压低了 */
    }
    else
{
/* LCD显示电压正常 */
        lcd_show_string(30, 130, 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(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                        		/* 延时初始化 */
    usart_init(115200);                  		/* 串口初始化为115200 */
    led_init();                           		/* 初始化LED */
    lcd_init();                           		/* 初始化LCD */
    pwr_pvd_init(PWR_PVDLEVEL_7);      		/* PVD 2.9V检测 */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "PVD TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    /* 默认LCD显示电压正常 */
    lcd_show_string(30, 130, 200, 16, 16, "PVD Voltage OK! ", BLUE);

    while (1)
    {
        if ((t % 20) == 0)
        {
            LED0_TOGGLE();              	/* 每200ms,翻转一次LED0 */
        }

        delay_ms(10);
        t++;
    }
}

这里我们选择PVD的检测电压阀值为2.9V,其他的代码很好理解,最后下载验证一下。
28.2.4 下载验证
下载代码后,默认LCD屏会显示"PVD Voltage OK!“,当供电电压过低,则LED1会点亮,并且LCD屏会显示PVD Low Voltage!。当开发板供电正常,LED1会熄灭,LCD屏会继续显示"PVD Voltage OK!”。

28.3 睡眠模式实验
本小节我们来学习睡眠模式实验,该部分的知识点内容请回顾28.1.2.3电源管理。我们直接从寄存器介绍开始。
28.3.1 EXTI寄存器
本实验我们用到外部中断来唤醒睡眠模式。进入睡眠模式很简单,直接调用内核指令WFI(参考28.1.3电源管理对这个指令的讲解)即可进入。用外部中断唤醒,就要在进入睡眠模式前,先对外部中断进行配置。例如,使能中断线(EXTI_IMR),使用何种触发模式(EXTI_FTSR/EXTI_RTSR)。当中断触发(即EXTI_PR中某位能查询到1),跳转到中断服务函数里,最后还得手动清除该标记即(EXTI_PR中对某位进行置1处理)。
 EXTI中断屏蔽寄存器(EXTI_IMR)
EXTI中断屏蔽寄存器描述如图28.3.1.1所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第15张图片

图28.3.1.1 EXTI_IMR寄存器
本实验使用WK_UP(PA0)唤醒,即EXTI0线中断,所以在外部中断服务函数要把MR0位置1。
EXTI上升沿触发选择寄存器(EXTI_RTSR)
EXTI上升沿触发选择寄存器描述如图28.3.1.2所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第16张图片

图28.3.1.2 EXTI_RTSR寄存器
我们要使用到EXTI0线中断,所以TR0位要置1,即EXTI0使用的是上升沿进行触发。
EXTI挂起寄存器(EXTI_PR)
EXTI挂起寄存器描述如图28.3.1.3所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第17张图片

图28.3.1.3 EXTI_PR寄存器
在EXTI0中断服务函数里面,需要清除EXTI0中断标记,即对PR0位写1。
28.3.2 硬件设计

  1. 例程功能
    LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入睡眠模式,此时LED0不再闪烁,说明已经进入睡眠模式。按下按键WK_UP后,LED1熄灭,提示退出睡眠模式,此时LED0继续闪烁,说明已经退出睡眠模式。
  2. 硬件资源
    1)LED灯
    LED0 – PB5 LED1 – PE5
    2)独立按键
    KEY0 – PE4 WK_UP - PA0
    3)电源管理(低功耗模式 - 睡眠模式)
    4)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. 原理图
    PWR属于STM32F103的内部资源,只需要软件设置好即可正常工作。我们通过KEY0让CPU进入睡眠模式,再通过WK_UP 触发EXTI中断来唤醒CPU。LED0指示程序是否执行,LED1指示CPU是否进入睡眠模式。
    28.3.3 程序设计
    28.3.3.1 PWR的HAL库驱动
  4. HAL_PWR_EnterSLEEPMode函数
    进入睡眠模式函数,其声明如下:
    void HAL_PWR_EnterSLEEPMode (uint32_t Regulator, uint8_t SLEEPEntry);
    函数描述:
    用于设置CPU进入睡眠模式。
    函数形参:
    形参1指定稳压器的状态。有两个选择,PWR_MAINREGULATOR_ON表示稳压器处于正常模式,PWR_LOWPOWERREGULATOR_ON表示稳压器处于低功耗模式。对应的是PWR_CR寄存器的LPDS位的设置(该形参在该函数中没有实质用处)。
    形参2指定进入睡眠模式的方式。有两个选择,PWR_SLEEPENTRY _WFI表示使用WFI指令,PWR_SLEEPENTRY_WFE表示使用WFE指令。我们选择前者,这两个指令的区别,请参考本教程28.1.3电源管理的描述。
    函数返回值:

    睡眠模式配置步骤
    1)配置唤醒睡眠模式的方式
    这里我们用外部中断的方式唤醒睡眠模式,所以这里需要配置一个外部中断功能,我们用WK_UP按键作为中断触发源,接下来就是配置PA0(连接按键WK_UP)。
    通过__HAL_RCC_GPIOA_CLK_ENABLE函数使能GPIOA的时钟。
    通过HAL_GPIO_Init函数配置PA0为上升沿触发检测的外部中断模式,开启下拉电阻等。
    通过HAL_NVIC_EnableIRQ函数使能EXTI0中断。
    通过HAL_NVIC_SetPriority函数设置中断优先级。
    编写EXTI0_IRQHandle中断函数,在中断服务函数中调用HAL_GPIO_EXTI_IRQHandler函数。
    最后编写HAL_GPIO_EXTI_Callback回调函数。由于前面已经介绍过外部中断的配置步骤,这里就介绍到这里,详见本例程源码。
    2)进入CPU睡眠模式
    通过HAL_PWR_EnterSLEEPMode函数进入睡眠模式。
    3)通过按下按键触发外部中断唤醒睡眠模式
    在本实验中,通过按下KEY0按键进入睡眠模式,然后通过按下WK_UP按键触发外部中断唤醒睡眠模式。
    28.3.3.2 程序流程图
    【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第18张图片

图28.3.3.2.1 睡眠模式实验程序流程图
28.3.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。睡眠模式实验代码在电压监控实验源码后追加。
首先看本实验在pwr.h头文件定义的几个宏定义:
/* PWR WKUP 按键 引脚和中断 定义

  • 我们通过WK_UP按键唤醒 MCU, 因此必须定义这个按键及其对应的中断服务函数
 */
#define PWR_WKUP_GPIO_PORT      		GPIOA
#define PWR_WKUP_GPIO_PIN        	GPIO_PIN_0
#define PWR_WKUP_GPIO_CLK_ENABLE()	do{ __HAL_RCC_GPIOA_CLK_ENABLE();}while(0) 

#define PWR_WKUP_INT_IRQn       		EXTI0_IRQn
#define PWR_WKUP_INT_IRQHandler  	EXTI0_IRQHandler
这些定义是WK_UP按键的相关宏定义,以及其对应的外部中断线0的相关定义。
pwr.h头文件就介绍这部分的程序,下面是pwr.c文件,先看低功耗模式下的按键初始化函数,其定义如下:
/**
 * @brief     	低功耗模式下的按键初始化(用于唤醒睡眠模式/停止模式)
 * @param      	无
 * @retval     	无
 */
void pwr_wkup_key_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    PWR_WKUP_GPIO_CLK_ENABLE();                              	/* WKUP时钟使能 */

    gpio_init_struct.Pin = PWR_WKUP_GPIO_PIN;              	/* WKUP引脚 */
    gpio_init_struct.Mode = GPIO_MODE_IT_RISING;           	/* 中断,上升沿 */
    gpio_init_struct.Pull = GPIO_PULLDOWN;                  	/* 下拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;        	/* 高速 */
    HAL_GPIO_Init(PWR_WKUP_GPIO_PORT, &gpio_init_struct);	/* WKUP引脚初始化 */

    HAL_NVIC_SetPriority(PWR_WKUP_INT_IRQn, 2, 2); /* 抢占优先级2,子优先级2 */
    HAL_NVIC_EnableIRQ(PWR_WKUP_INT_IRQn); 
}
该函数初始化WK_UP按键(PA0),并设置上升沿触发的外部中断线0,最后设置中断优先级并使能外部中断线0。
下面介绍的是进入CPU睡眠模式函数,其定义如下:
/**
 * @brief      	进入CPU睡眠模式
 * @param      	无
 * @retval     	无
 */
void pwr_enter_sleep(void)
{
/* 进入睡眠模式 */
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
}
函数内直接调用HAL_PWR_EnterSLEEPMode函数使用WFI指令进入睡眠模式。
下面介绍的是WK_UP按键外部中断服务函数及其回调函数,函数定义如下:
/**
 * @brief     	WK_UP按键 外部中断服务程序
 * @param      	无
 * @retval    	无
 */
void PWR_WKUP_INT_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(PWR_WKUP_GPIO_PIN);
}

/**
 * @brief      	外部中断回调函数
 * @param      	GPIO_Pin:中断线引脚
 * @note      	此函数会被PWR_WKUP_INT_IRQHandler()调用
 * @retval     	无
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == PWR_WKUP_GPIO_PIN)
    {
        /* HAL_GPIO_EXTI_IRQHandler()函数已经为我们清除了中断标志位,
所以我们进了回调函数可以只关注中断时的控制逻辑,不用再操作寄存器 */
    }
}

在WK_UP按键外部中断服务函数中我们调用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(RCC_PLL_MUL9); 	/* 设置时钟, 72Mhz */
    delay_init(72);                         	/* 延时初始化 */
    usart_init(115200);                    	/* 串口初始化为115200 */
    led_init();                             		/* 初始化LED */
    lcd_init();                             		/* 初始化LCD */
    key_init();                             		/* 初始化按键 */
    pwr_wkup_key_init();                  		/* 唤醒按键初始化 */

    lcd_show_string(30, 50,  200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70,  200, 16, 16, "SLEEP TEST", RED);
    lcd_show_string(30, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Enter SLEEP MODE", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY_UP:Exit SLEEP MODE", RED);

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES)
        {
            LED1(0);                  	/* 点亮绿灯,提示进入睡眠模式 */
            pwr_enter_sleep();    	/* 进入睡眠模式 */
            LED1(1);                  	/* 关闭绿灯,提示退出睡眠模式 */
        }

        if ((t % 20) == 0)
        {
            LED0_TOGGLE();      		/* 每200ms,翻转一次LED0 */
        }

        delay_ms(10);
        t++;
    }
}

该部分程序,功能就是按下KEY0后,点亮LED1,进入睡眠模式。然后一直等待外部中断唤醒,当按下按键WK_UP,就触发外部中断,睡眠模式就被唤醒,然后继续执行后面的程序,关闭LED1等。
28.3.4 下载验证
下载代码后,LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入睡眠模式,此时LED0不再闪烁,说明已经进入睡眠模式。按下按键WK_UP后,LED1熄灭,提示退出睡眠模式,此时LED0继续闪烁,说明已经退出睡眠模式。

28.4 停止模式实验
本小节我们来学习停止模式实验,该部分的知识点内容请回顾28.1.2.3电源管理。我们直接从寄存器介绍开始。
28.4.1 PWR寄存器
本实验我们用到外部中断来唤醒停止模式。我们用到WFI指令进入停止模式,这个后面会讲,进入停止模式后,使用外部中断唤醒。外部中断部分内容参照睡眠模式即可,都是共用同样的配置。
下面主要介绍PWR_CR寄存器相关位。
 PWR控制寄存器(PWR_CR)
PWR的控制寄存器描述如图28.4.1.1所示:
在这里插入图片描述
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第19张图片

图28.4.1.1 PWR_CR寄存器
通过PDDS位选择进入停止模式还是待机模式,停止模式即对PDDS位置0即可。在停止模式下,电压调节器有两种模式:开启或者低功耗,选择低功耗模式,即LPDS置1。
系统控制寄存器(SCB_SCR)
系统控制寄存器描述如图28.4.1.2所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第20张图片

图28.4.1.2 系统控制寄存器
该寄存器存在于ARM内核中,详细描述可查阅《Cortex-M3权威指南》,在本实验中,我们需要把SLEEPDEEP位置1,这样子后面调用WFI命令时,进入的就是停止模式了。在唤醒后,需要清除SLEEPDEEP位,进行置0。
28.4.2 硬件设计

  1. 例程功能
    LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入停止模式,此时LED0不再闪烁,说明已经进入停止模式。按下按键WK_UP后,LED1熄灭,提示退出停止模式,此时LED0继续闪烁,说明已经退出停止模式。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    LED1 – PE5
    2)独立按键
    KEY0 – PE4
    WK_UP - PA0
    3)电源管理(低功耗模式–停止模式)
    4)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. 原理图
    PWR属于STM32F103的内部资源,只需要软件设置好即可正常工作。我们通过KEY0让CPU进入停止模式,再通过WK_UP 触发EXTI中断来唤醒CPU。LED0指示程序是否执行,LED1指示CPU是否进入停止模式。
    28.4.3 程序设计
    28.4.3.1 PWR的HAL库驱动
  4. HAL_PWR_EnterSTOPMode函数
    进入停止模式函数,其声明如下:
    void HAL_PWR_EnterSTOPMode (uint32_t Regulator, uint8_t STOPEntry);
    函数描述:
    用于设置CPU进入停止模式。
    函数形参:
    形参1指定稳压器在停止模式下的状态。有两个选择,PWR_MAINREGULATOR_ON表示稳压器处于正常模式,PWR_LOWPOWERREGULATOR_ON表示稳压器处于低功耗模式。对应的是PWR_CR1寄存器的LPDS位的设置。
    形参2指定用WFI还是WFE指令进入停止模式。有两个选择,PWR_STOPENTRY_WFI表示使用WFI指令,PWR_STOPENTRY_WFE表示使用WFE指令。我们选择前者,不了解这两种指令的区别,可以回看28.1.3小节的知识。
    函数返回值:

    停止模式配置步骤
    1)配置唤醒停止模式的方式
    这里我们用外部中断的方式唤醒停止模式,所以这里需要配置一个外部中断功能,我们用WK_UP按键作为中断触发源,接下来就是配置PA0(连接按键WK_UP)。
    通过__HAL_RCC_GPIOA_CLK_ENABLE函数使能GPIOA的时钟。
    通过HAL_GPIO_Init函数配置PA0为上升沿触发检测的外部中断模式,开启下拉电阻等。
    通过HAL_NVIC_EnableIRQ函数使能EXTI0中断。
    通过HAL_NVIC_SetPriority函数设置中断优先级。
    编写EXTI0_IRQHandle中断函数,在中断服务函数中调用HAL_GPIO_EXTI_IRQHandler。
    最后编写HAL_GPIO_EXTI_Callback回调函数。由于前面已经介绍过外部中断的配置步骤,这里就介绍到这里,详见本例程源码。
    2)进入CPU停止模式
    通过HAL_PWR_EnterSTOPMode函数进入停止模式。
    3)通过按下按键触发外部中断唤醒停止模式
    在本实验中,通过按下KEY0按键进入停止模式,然后通过按下WK_UP按键触发外部中断唤醒停止模式。
    28.4.3.2 程序流程图
    【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第21张图片

图28.4.3.2.1 停止模式实验程序流程图
28.4.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。停止模式实验代码在睡眠模式实验源码后追加。
首先看pwr.h头文件,因为我们还是用到WK_UP对应的外部中断线来唤醒停止模式的CPU,pwr.h头文件的WK_UP按键对应的宏定义我们也是用到的,上个实验已经讲过,这里不再赘述。下面是pwr.c文件,WK_UP按键的相关函数我们还是用上个实验的,我们主要介绍进入停止模式函数,其定义如下:

/**
 * @brief     	进入停止模式
 * @param       	无
 * @retval     	无
 */
void pwr_enter_stop(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    /* 进入停止模式,设置稳压器为低功耗模式,等待中断唤醒 */
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}

该函数因为涉及对电源控制寄存器的操作,所以先调用__HAL_RCC_PWR_CLK_ENABLE函数使能PWR时钟,然后调用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(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                      		/* 延时初始化 */
    usart_init(115200);                 		/* 串口初始化为115200 */
    led_init();                           		/* 初始化LED */
    lcd_init();                           		/* 初始化LCD */
    key_init();                           		/* 初始化按键 */
pwr_wkup_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, "KEY_UP:Exit STOP MODE", RED);

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES)
        {
            LED1(0);                            		/* 点亮绿灯,提示进入停止模式 */
            pwr_enter_stop();             			/* 进入停止模式 */

            /* 从停止模式唤醒, 需要重新设置系统时钟, 72Mhz */
            sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟, 72Mhz */
delay_init(72);                       	/* 延时初始化 */
            LED1(1);                       		  	/* 关闭绿灯,提示退出停止模式 */
        }

        if ((t % 20) == 0)
        {
            LED0_TOGGLE();                    		/* 每200ms,翻转一次LED0 */
        }
        delay_ms(10);
        t++;
    }
}

该部分程序,功能就是按下KEY0后,点亮LED1,进入停止模式。然后一直等待外部中断唤醒,当按下按键WK_UP,就触发外部中断,停止模式就被唤醒,然后继续执行后面的程序,重新设置系统时钟72MHZ和延时初始化,关闭LED1等。
28.4.4 下载验证
下载代码后,LED0闪烁,表明代码正在运行。按下按键KEY0后,LED1点亮,提示进入停止模式,此时LED0不再闪烁,说明已经进入停止模式。按下按键WK_UP后,LED1熄灭,提示退出停止模式,此时LED0继续闪烁,说明已经退出停止模式。

28.5 待机模式实验
本小节我们来学习待机模式实验,该部分的知识点内容请回顾28.1.2.3电源管理。我们直接从寄存器介绍开始。
28.5.1 PWR寄存器
本实验是先对相关的电源控制寄存器配置待机模式的参数,然后通过WFI指令进入待机模式,使用WKUP引脚的上升沿来唤醒(这是特定的唤醒源)。
下面主要介绍本实验用到的寄存器,系统控制寄存器的部分与上一实验一致,就不介绍了
 PWR控制寄存器(PWR_CR)
PWR的控制寄存器描述如图28.5.1.1所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第22张图片

图28.5.1.1 PWR_CR寄存器
这里我们通过设置PDDS位,使CPU进入深度睡眠时进入待机模式,同时,我们需要通过CWUF位清除之前的唤醒位。
 电源控制/状态寄存器(PWR_CSR)
电源控制/状态寄存器描述如图28.5.1.2所示:
【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第23张图片

图28.5.1.1 PWR_CSR寄存器
该寄存器我们只关心EWUP位,设置EWUP为1,即WKUP引脚作为待机模式的唤醒源。
28.5.2 硬件设计

  1. 例程功能
    LED0闪烁,表明代码正在运行。按下按键KEY0后,进入待机模式,待机模式下大部分引脚处于高阻态,所以说这时候LED0会熄灭,TFTLCD也会熄灭。按下WK_UP按键后,退出待机模式(相当于复位操作),程序重新执行,LED0继续闪烁,TFTLCD屏点亮。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)独立按键
    KEY0 – PE4
    WK_UP - PA0
    3)电源管理(低功耗模式 – 待机模式)
    4)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. 原理图
    PWR属于STM32F103的内部资源,只需要软件设置好即可正常工作。我们通过KEY0让CPU进入待机模式,再通过WK_UP上升沿来唤醒CPU。LED0指示程序是否执行。
    28.5.3 程序设计
    28.5.3.1 PWR的HAL库驱动
  4. HAL_PWR_EnableWakeUpPin函数
    使能唤醒引脚函数,其声明如下:
    void HAL_PWR_EnableWakeUpPin (uint32_t WakeUpPinPolarity);
    函数描述:
    用于使能唤醒引脚。
    函数形参:.
    形参1取值范围:PWR_WAKEUP_PIN1。
    函数返回值:

    注意事项:
    禁止某个唤醒引脚使用的函数如下:
    void HAL_PWR_DisableWakeUpPin (uint32_t WakeUpPinPolarity);
  5. HAL_PWR_EnterSTANDBYMode函数
    进入待机模式函数,其声明如下:
    void HAL_PWR_EnterSTANDBYMode (void);
    函数描述:
    用于使CPU进入待机模式,进入待机模式,首先要设置SLEEPDEEP位,接着我们通过PWR_CR设置PDDS位,使得CPU进入深度睡眠时进入待机模式,最后执行WFI指令开始进入待机模式,并等待WK_UP上升沿的到来。
    函数形参:

    函数返回值:

    待机模式配置步骤
    1)进入CPU待机模式
    在进入待机模式之前我们需要做一些准备:
    涉及到操作PWR寄存器的内容,所以首先先进行PWR时钟的初始化,用__HAL_RCC_PWR_CLK_ENABLE函数实现。
    通过HAL_PWR_EnableWakeUpPin函数使能WKUP的唤醒功能。
    通过__HAL_PWR_CLEAR_FLAG函数清除唤醒标记,详看源码。
    通过HAL_PWR_EnterSTANDBYMode函数进入待机模式。
    2)通过WKUP引脚上升沿触发唤醒睡眠模式
    在本实验中,通过按下KEY0按键进入待机模式,然后通过按下WK_UP按键,使用待机模式中的WKUP引脚上升沿的唤醒信号,而不是普通的外部中断,唤醒待机模式。
    28.5.3.2 程序流程图
    【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2_第24张图片

图28.4.3.2.1 待机模式实验程序流程图
28.5.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWR源码包括两个文件:pwr.c和pwr.h。待机模式实验代码在停止模式实验源码后追加。
pwr.h头文件上的宏定义我们是没有用到的,这里我们使用的是特定唤醒源,与外部中断无关。下面是pwr.c文件,我们主要介绍进入待机模式函数,其定义如下:

/**
 * @brief      	进入待机模式
 * @param      	无
 * @retval     	无
 */
void pwr_enter_standby(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();               	/* 使能电源时钟 */

    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);	/* 使能KEY_UP引脚的唤醒功能 */
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);        	/* 需要清此标记,否则将保持唤醒状态 */
    HAL_PWR_EnterSTANDBYMode();               	/* 进入待机模式 */
}

该函数首先是调用__HAL_RCC_PWR_CLK_ENABLE来使能PWR时钟,然后调用函数HAL_PWR_EnableWakeUpPin用来设置WK_UP引脚作为唤醒源。在进入待机模式前,还得调用__HAL_PWR_CLEAR_FLAG函数清除一下唤醒标志,要不然会保持唤醒状态。最后调用函数HAL_PWR_EnterSTANDBYMode进入待机模式。
最后在main.c里面编写如下代码:

int main(void)
{
    uint8_t t = 0;
    uint8_t key = 0;

    HAL_Init();                             		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);		/* 设置时钟, 72Mhz */
    delay_init(72);                        		/* 延时初始化 */
    usart_init(115200);                   		/* 串口初始化为115200 */
    led_init();                            		/* 初始化LED */
    lcd_init();                             		/* 初始化LCD */
    key_init();                             		/* 初始化按键 */
    pwr_wkup_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函数进入待机模式,然后等待按下WK_UP按键产生WKUP上升沿唤醒CPU。注意待机模式唤醒后,系统会进行复位。
28.5.4 下载验证
下载代码后,LED0闪烁,表明代码正在运行。按下按键KEY0后,TFTLCD屏熄灭,此时LED0不再闪烁,说明已经进入待机模式。按下按键WK_UP后,TFTLCD屏点亮,LED0闪烁,说明系统从待机模式中唤醒相当于复位。

你可能感兴趣的:(stm32,单片机,嵌入式硬件)