十四、PWR电源控制&减少功耗

十二、PWR电源控制

PWR简介

  • PWR(Power Control)电源控制
  • PWR负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能
  • 可编程电压监测器(PVD)可以监控VDD电源电压,当VDD下降到PVD阀值以下或上升到PVD阀值之上时,PVD会触发中断,用于执行紧急关闭任务
  • 低功耗模式包括睡眠模式(Sleep)、停机模式(Stop)和待机模式(Standby),可在系统空闲时,降低STM32的功耗,延长设备使用时间

上电复位和掉电复位

image

  • 当 VDD 或者 VDDA 电压过低时,内部电路直接产生复位,这个复位和不复位的界限之间,设置了一个 40 mV 迟滞电压,大于上限 POR(Power On Reset)时解除复位,小于下限 PDR(Power Down Reset)时复位。

  • 这是一个典型的迟滞比较器,设置两个阈值的作用,就是防止电压在某个阈值附近波动时,造成输出也来回抖动。

  • 下面的复位信号 Reset,是低电平有效

STM32 数据手册,在 5.3.3 内嵌复位和电源控制模块特性里有个表,这里写了上电或掉电复位阈值

  • 下降沿,也就是 PDR 掉电复位的阈值下限,典型值是 1.88V;

  • 上升沿,也就是 PDR 上电复位的阈值上限,典型值是 1.92V;

  • 1.92 - 1.88 V 就是迟滞的阈值:40 mV。所以如果忽略迟滞的话,简单来说,就是大于 1.9V 上电,低于 1.9V 掉电

  • 最后一行,是 TRSTTEMPO 复位持续时间,典型值是 2.5 ms

image
image

可编程电压检测器

image

简称 PVD。它的工作流程和上面那个上电复位和掉电复位差不多,都是监测 VDD 和 VDDA 的供电电压

PVD 的区别就是,首先它这个阈值电压是可以使用程序指定的,可以自定义调节

  • 调节的范围可以看一下数据手册,内嵌复位和电源控制模块特性的表的上面就是 PVD 的阈值,配置 PLS 寄存器的 3 个位,可以选择右边这么多的阈值,因为这里也同样是迟滞比较,所以有两个阈值,可选范围是 2.2V 到 2.9V 左右,PVD 上限和下限之间的迟滞电压是 100 mV。可以看到,PVD 的电压是比上电掉电复位的电压要高的。

image

image

3.3V 是正常的供电,当这个电压降低在 2.9V 到 2.2V 之间,属于 PVD 监测的范围,可以通过 PVD 设置一个警告线,之后再降低到 1.9V,就是复位电路的监测范围,低于 1.9V,直接复位,这是这两个电压监测的工作任务。那当然,PVD 触发之后,芯片还是能正常工作的,只不过是电源电压过低,该提醒一下用户了。

PVD 输出是正逻辑,电压过低时为 1,电压正常时为 0

  • 这个信号,可以去申请中断,在上升沿或者下降沿时,触发中断,以此提醒程序进行适当的处理。
  • 这个 PVD 的中断申请是通过外部中断实现的,所以如果要使用 PVD 的话,记得要配置外部中断

电源供电框图

image

  • VDDA 供电区域,主要负责模拟部分的供电

    其中包括 AD 转换器、温度传感器、复位模块、PLL 锁相环,这些电路的供电正极是 VDDA,负极是 VSSA。

    其中 AD 转换器还有两根参考电压的供电脚,叫做 VREF+ 和 VREF-,这两个脚在引脚多的型号里会单独引出来。在引脚少的型号,比如我们这个 C8T6,VREF+ 和 VREF- 在内部就已经分别接到了 VDDA 和 VSSA 了。

  • 中间是数字部分供电,包括两块区域,VDD 供电区域和 1.8V 供电区域。

    VDD 供电区域,包括 IO 电路、待机电路、唤醒逻辑和独立看门狗;

    右边部分是 VDD 通过电压调节器,降压到 1.8V,提供给后面这一块的 1.8V 供电区域,1.8V 区域包括 CPU 核心、存储器和内置数字外设

  • 后备供电,叫做 VBAT(V Battery)

    VBAT 后备供电区域其中包括 LSE 32K 晶体振荡器、后备寄存器、RCC BDCR 寄存器和 RTC。

    RTC BDCR 是 RCC 的寄存器,叫备份域控制寄存器,也是和后备区域有关的寄存器,所以也可以由 VBAT 供电。

    上面有个低电压检测器,可以控制开关,VDD 有电时,由 VDD 供电;VDD 没电时,由 VBAT 供电。

低功耗模式

image

低功耗模式有 3 种:睡眠、停机和待机。这 3 种模式,从上到下,关闭的电路越来越多,对应的,从上到下,是越来越省电,同时,从上到下,也是越来越难唤醒的

  • 睡眠模式:直接调用 WFI 或者 WFE,即可进入睡眠模式,这两个东西是内核的指令,对应库函数里,也有对应的函数。

    • WFI 的意思是 Wait For Interrupt,等待中断,对应的唤醒条件是任一中断

      调用 WFI 进入的睡眠模式,任何外设发生任何中断时,芯片就会立刻醒来,因为中断发生了,所以醒来之后的第一件事,一般就是处理中断函数

    • WFE的意思是 Wait For Event,等待事件,对应的唤醒条件是,唤醒事件

      这个事件可以是外部中断配置为事件模式,也可以是使能了中断,但是没有配置 NVIC

      调用 WFE 进入的睡眠模式,产生唤醒事件时,会立刻醒来,醒来之后,一般不需要进中断函数,直接从睡的地方继续运行

    • 睡眠模式对电路的影响

      对 1.8V 区域时钟的影响是,CPU 时钟关,对其他时钟和 ADC 时钟无影响;

      对 VDD 区域时钟的影响是,无;

      对电压调节器的操作是,开。

      所以睡眠模式对电路的影响就是只把 CPU 时钟关了,对其他电路没有任何操作。CPU 时钟关了,程序就会暂停,不会继续运行了,CPU 不运行,芯片功耗就会降低。

  • 停机模式

    • 停机模式开启:首先 SLEEPDEEP 为设置为 1,进入深度睡眠模式;PDDS = 0,进入停机模式;LPDS 用来设置个电压调节器,LPDS = 0,电压调节器开启,LPDS = 1,电压调节器进入低功耗。最后,当我们把这些位提前设置好了,最后再调用 WFI 或者 WFE,芯片就可以进入停止模式了。

    • 停机模式的唤醒:任一外部中断。PVD、RTC 闹钟、USB 唤醒、ETH 唤醒,借道了外部中断,所以这 4 个信号,也可以唤醒停止模式,另外这里并没有区分 WFI 和 WFE,其实也可以想象得到,WFI 要用外部中断的中断模式唤醒,WFE 要用外部中断的事件模式唤醒,这是对应的。

    • 停机模式对电路的影响:

      1. 关闭所有 1.8V 区域的时钟,不仅 CPU 不能运行,外设也运行不了,定时器正在定时的,会暂停,串口收发数据的,也会暂停;
      2. 没关闭电源,所以 CPU 和外设的寄存器数据都是维持原状的。
      3. HSI 和 HSE 的振荡器关闭
      4. LSI 内部低速时钟和 LSE 外部低速时钟,这两个并不会主动关闭,如果开启过这两个时钟,还可以继续运行。
      5. 电压调节器,这里可以选择是开启,或者处于低功耗模式,无论是开启还是低功耗,都可以维持 1.8V 区域寄存器和存储器的数据内容。区别就是,低功耗模式更省电一些,同时,低功耗模式在唤醒时,要花更多的时间;相反,电压调节器开启的话,就是更耗电一些,唤醒更快了。

      主要操作就是,把运行的高速时钟都关了,CPU 和外设,都暂停工作,但是电压调节器并没有关,存储器和寄存器数据可以维持原样。

  • 待机模式

    • 待机模式的开启:SLEEPDEEP 也是置 1, PDDS 置 1,最后调用 WFI 或者 WFE

    • 待机模式的唤醒:

      只有这几个指定的信号才能唤醒:

      1. WKUP 引脚的上升沿,引脚定义表里面指示了 WKUP 引脚的位置就是 PA0 的位置。
      2. RTC 闹钟事件
      3. NRST 引脚上的外部复位,意思就是按一下复位键
      4. IWDG 独立看门狗复位
    • 待机模式对电路的影响:

      基本上是能关的全都关了,1.8V 区域的时钟关闭,两个高速时钟关闭,电压调节器关闭,这个意味着 1.8V 区域的电源关闭,内部的存储器和寄存器的数据全部丢失。

      但是不会主动关闭 LSI 和 LSE 两个低速时钟,因为这两个时钟还要维持 RTC 和独立看门狗的运行

模式选择

image

执行 WFI(Wait For Interrupt,等待中断)或者 WFE(Wait For Event,等待事件)指令后,STM32进入低功耗模式。就是说这两个指令是最终开启低功耗模式的触发条件,配置其他的寄存器,都要在这两个指令之前

  • SLEEPDEEP:

    SLEEPDEEP = 0,对应的就是睡眠模式;

    SLEEPDEEP = 1,对应的是停机或者待机模式

  • SLEEPONEXIT:

    SLEEPONEXIT = 0,无论程序在哪里调用 WFI/WFE,都会立刻进入睡眠;SLEEPONEXIT = 1,执行 WFI/WFE 之后,它会等待中断退出,等所有中断处理完成之后,再进入睡眠

    这个可能考虑到中断还有一些紧急的任务,最好不要被睡眠打断了

  • PDDS:

    PDDS = 0,就进入的是停机模式;

    PDDS = 1,就进入的是待机模式。

  • LPDS:

    LPDS = 0,就是停机模式且电压调节器开启;

    LPDS = 1,就是停机模式且电压调节器低功耗,电压调节器低功耗的特性就是:更省电,但是唤醒延迟更高。

低功耗模式的特性

睡眠模式

  • 执行完WFI/WFE指令后,STM32进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
  • SLEEPONEXIT位决定STM32执行完WFI或WFE后,是立刻进入睡眠,还是等STM32从最低优先级的中断处理程序中退出时进入睡眠
  • 在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态
  • WFI指令进入睡眠模式,可被任意一个NVIC响应的中断唤醒
  • WFE指令进入睡眠模式,可被唤醒事件唤醒

停机模式

  • 执行完WFI/WFE指令后,STM32进入停止模式,程序暂停运行,唤醒后程序从暂停的地方继续运行
  • 1.8V供电区域的所有时钟都被停止,PLL、HSI和HSE被禁止,SRAM和寄存器内容被保留下来
  • 在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态
  • 当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟(8MHz)。需要调用SystemInit()函数重新启动HSE
  • 当电压调节器处于低功耗模式下,系统从停止模式退出时,会有一段额外的启动延时
  • WFI指令进入停止模式,可被任意一个EXTI中断唤醒
  • WFE指令进入停止模式,可被任意一个EXTI事件唤醒

待机模式

  • 执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行
  • 整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
  • 在待机模式下,所有的I/O引脚变为高阻态(浮空输入)
  • WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式

修改主频

可调用的函数与变量

system_stm32f10x.h文件对外开放了两个函数和一个变量。其具体作用如下

// 设置微控制器系统初始化嵌入式Flash接口,PLL和更新SystemCoreClock变量。
extern void SystemInit(void);

// 根据时钟寄存器值更新SystemCoreClock变量。
extern void SystemCoreClockUpdate(void);
extern uint32_t SystemCoreClock;	// 当前系统主时钟频率频
注意:主频修改后都需要调用一下SystemCoreClockUpdate函数,否则SystemCoreClock还是修改前的主频

如何修改主频

image

修改主频的代码

这里最好结合RCC时钟树这张图来看

当前使用的启动文件为MD结尾的,下面代码中与MD系列无关的代码都删除了方便观看

SystemInit函数最后调用了SetSysClock函数来设置主频

void SystemInit (void)
{
  /* 将RCC时钟配置重置为默认重置状态 */
  /* 设置HSION位 */
  RCC->CR |= (uint32_t)0x00000001;

  /* 复位SW, HPRE, PPRE1, PPRE2, ADCPRE和MCO位 */
  RCC->CFGR &= (uint32_t)0xF0FF0000;
  
  /* 重置HSEON, CSSON和PLLON位 */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* 重置HSEBYP位 */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* 复位PLLSRC, PLLXTPRE, PLLMUL和USBPRE/OTGFSPRE位 */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

  /* 禁用所有中断并清除挂起的位  */
  RCC->CIR = 0x009F0000;

  /* 配置系统时钟频率、HCLK、PCLK2和PCLK1预分频器 */
  /* 配置Flash Latency周期,并开启预取缓冲 */
  SetSysClock();
}

根据用户没有注释的宏定义来执行不同函数

默认情况下 SYSCLK_FREQ_72MHz 是解除注释的,所以SetSysClock函数会调用SetSysClockTo72();函数将主频配置为72MHz

/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000

static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif
 
 /* 如果上述定义均未启用,则使用HSI作为系统时钟源(重置后的默认值) */ 
}

这个函数就是配置72MHz主频的具体底代码

其他频率的主频也是类似的函数,只有在配置PLL锁相环的时倍频系数不同

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
   
  /* 使能 HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 等待HSE准备好,如果超时则到达退出 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  /* 如果HSEState=1,表示晶振启动成功,进入if */
  if (HSEStatus == (uint32_t)0x01)
  {
    /* 启用预取缓冲区 */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2等待状态 */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

    /* HCLK = SYSCLK。配置AHB的时钟频率等于SYSCLK。SYSCK的时钟频率等于PLL锁相环的时钟频率 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK。配置APB2的时钟频率等于AHB */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK。配置ABP1的时钟频率等于AHB的二分之一 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

    /*  PLL锁相环时钟频率配置: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
      
    /* 使能 PLL锁相环 */
    RCC->CR |= RCC_CR_PLLON;

    /* 等到PLL锁相环准备好 */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* 选择“PLL”作为系统时钟源 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* 等到PLL锁相环作为系统时钟源 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* 如果HSE启动失败,应用程序将出现错误时钟配置。用户可以在这里添加一些代码来处理此错误 */
  }
}

PWR库函数

// 复位PWR
void PWR_DeInit(void);

// 开启RTC和备份区域的访问权限
void PWR_BackupAccessCmd(FunctionalState NewState);

// 使能PVD电压检测
void PWR_PVDCmd(FunctionalState NewState);

// 设置PVD电压检测的阈值
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);

// 使能WakeUP引脚,上升沿可以唤醒待机模式下的STM32 
void PWR_WakeUpPinCmd(FunctionalState NewState);

// STM32进入停机模式
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);

// STM32进入待机模式
void PWR_EnterSTANDBYMode(void);

// 获取PWR相关的标志位
FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);

// 清除PWR相关的标志位
void PWR_ClearFlag(uint32_t PWR_FLAG);

案例

修改主频

将system_stm32f10x.c文件中的第112行代码解除注释,115行的代码注释,即可修改主频为36MHz

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"

int main()
{
	OLED_Init();
	// 显示当前主频
	OLED_ShowNum(1,1,SystemCoreClock,8);
	while(1)
	{
		
	}
}

睡眠模式+串口接收

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

// 接收串口发送来的数据
uint16_t receive;

/**
  * @brief  配置USART1的发送和接收功能,波特率:9600
  * @param  无
  * @retval 无
  */
void Serial_Init()
{
	// 开启USART和GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	
	// 根据引脚图可知USART1的TX端是PA9
	// 初始化PA9为复用推挽输出
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 根据引脚图可知USART1的RX端是PA10
	// 初始化PA10为上拉输入
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置USART1的发送和接收数据
	USART_InitTypeDef USART_InitStruct;
	USART_InitStruct.USART_BaudRate = 9600;											// 波特率9600
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	// 不使用硬件流控制
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;					// 开启USART的发送和接收功能
	USART_InitStruct.USART_Parity = USART_Parity_No;								// 无校验位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;								// 一位停止位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;						// 一帧8个数据位
	USART_Init(USART1, &USART_InitStruct);
	
	// 开启USART1的RXNE的中断通道
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	// NVIC优先级分组2
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	// 联通USART1的NVIC的通道
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;			// 选择USART1的中断通道
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;			// 开启通道
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;	// 抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;			// 响应优先级
	NVIC_Init(&NVIC_InitStruct);
	
	// 启动USART1
	USART_Cmd(USART1, ENABLE);
}

int main()
{
	Serial_Init();
	OLED_Init();
	while(1)
	{
		// 显示接收到的数据
		OLED_ShowHexNum(1, 1, receive, 5);
		
		// 显示字符串Run表示当前程序正在运行
		OLED_ShowString(2,1,"Run");
		Delay_ms(100);
		OLED_ShowString(2,1,"   ");
		Delay_ms(100);
		
		// 进入睡眠模式。SLEEPDEEP、SLEEPONEXIT默认为0,需要配置内核中的寄存器来修改
		__WFI();
	}
}

// USART1的中断函数
void USART1_IRQHandler()
{
	// 检测USART1的RXNE是否产生中断
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		// 读取USART_DR寄存器的值
		receive = USART_ReceiveData(USART1);
		// 清除中断标志位,在读取USART_DR寄存器的值后硬件会自动清除,所以这条代码可以不写
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}


停机模式+对射红外传感器

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

// 无符号16位整数,用来记录红外线被遮挡的次数
uint16_t count = 0;

int main()
{
	// 开启GPIOB和AFIO的外设时钟。EXTI和NVIC的时钟不用管
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	// 定义GPIO_InitTypeDef类型的结构体
	GPIO_InitTypeDef GPIO_InitStructer;
	// 通过STM32F10xx参考手册可知,GPIO作为外部中断输入时,浮空、上拉、下拉输入都可以
	GPIO_InitStructer.GPIO_Mode = GPIO_Mode_IPD;					// 上拉输入
	GPIO_InitStructer.GPIO_Pin = GPIO_Pin_14;						// 选择引脚14
	GPIO_InitStructer.GPIO_Speed = GPIO_Speed_50MHz;				// 速度50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructer);							// 根据结构体中的成员属性初始GPIOB
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);	// 将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
	
	// 定义EXTI_InitTypeDef类型的结构体
	EXTI_InitTypeDef EXTI_InitStructer;
	EXTI_InitStructer.EXTI_Line = EXTI_Line14;						// 要启用或禁用的EXTI通道,通道14
	EXTI_InitStructer.EXTI_LineCmd = ENABLE;						// 是否启用通道,启用
	EXTI_InitStructer.EXTI_Mode = EXTI_Mode_Interrupt;				// 中断响应还是事件响应,中断响应
	EXTI_InitStructer.EXTI_Trigger = EXTI_Trigger_Falling;			// 触发方式,上升沿、下降沿和双边沿,下降沿
	EXTI_Init(&EXTI_InitStructer);									// 根据结构体中的成员属性初始EXTI
	
	// 设置NVIC优先级分组,分组2,抢占优先级2个bit位,响应优先级2个bit位
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	// 定义NVIC_InitTypeDef类型的结构体
	NVIC_InitTypeDef NVIC_InitStructer;
	NVIC_InitStructer.NVIC_IRQChannel = EXTI15_10_IRQn;				// 选择中断通道,外部中断15~10
	NVIC_InitStructer.NVIC_IRQChannelCmd = ENABLE;					// 是否开启通道,开启
	NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority = 1;		// 设置抢占优先级,1
	NVIC_InitStructer.NVIC_IRQChannelSubPriority = 1;				// 设置响应优先级,1
	NVIC_Init(&NVIC_InitStructer);									// 根据结构体中的成员属性初始化NVIC
	
	OLED_Init();
	OLED_ShowString(1,1,"Count:");
	
	while(1)
	{
		OLED_ShowNum(1, 7, count, 5);
		
		// 显示一下字符串表示唤醒成功
		OLED_ShowString(2,1,"Run");
		Delay_ms(100);
		OLED_ShowString(2,1,"   ");
		Delay_ms(100);
		
		/* 停机模式的操作 */
		// 开启PWR时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
		// 进入停机模式。电压调节器开,WFI外部中断唤醒
		PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
		// 设备从停机模式唤醒后,系统使用HSI(8MHz)作为系统时钟,所以需要调用SystemInit函数重新启动HSE作为系统时钟
		SystemInit();
	}
}

// 外部中断15~10通道的中断函数
void EXTI15_10_IRQHandler(void)
{
	// 判断是否是外部中断14号线触发的中断
	if(EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		count++;
		// 清除外部中断14号线的中断标志位
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

待机模式+实时时钟

接线图

image

示例代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include <time.h>
#include "Delay.h"

// 存放时间戳
time_t time_cnt;
// 时间结构体
struct tm time_date;
// 年月日字符串
char Year_To_Date[11];
// 时分秒字符串
char Hour_Min_Sec[9];

void RTC_Init()
{
	// 开启BKP和PWR的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
	
	// 开启RTC和备份寄存器的访问权限
	PWR_BackupAccessCmd(ENABLE);
	
	// 检测备份寄存器的值是否复位,如果没有复位说明备用电源有电,RTC还在走时,就不需要初始化RTC
	if(BKP_ReadBackupRegister(BKP_DR1) == 0x0000)
	{
		
		
		// 开启LSE外部低速时钟
		RCC_LSEConfig(RCC_LSE_ON);
		// 等待RCC_FLAG_LSERDY标志位置1,LSE才开启成功
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
		
		// RTCCLK选择LSE作为时钟源
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		// 使能RTCCLK
		RCC_RTCCLKCmd(ENABLE);
		
		// 等待RTC寄存器同步完成
		RTC_WaitForSynchro();
		// 等待写操作完成
		RTC_WaitForLastTask();
		
		// 设置预分频值。LSE的时钟频率为32.768KHz,经过32768分频刚好为1Hz
		RTC_SetPrescaler(32768 - 1);
		// 等待写操作完成
		RTC_WaitForLastTask();
		
		// 写入时间戳。写RTC_CNT寄存器
		RTC_SetCounter(1672588795);		// 该时间戳对应北京时间2023-1-1 23:59:55
		// 等待写操作完成
		RTC_WaitForLastTask();
		
		// 随便给BKP的DR1备份寄存器一个值,用于检测主电源断电后再上电时备用电源是否掉电
		BKP_WriteBackupRegister(BKP_DR1, 0x7788);
	}
	// RTC不需要初始化,但是STM32上电后最好还是等待RTC寄存器同步
	else
	{
		// 等待RTC寄存器同步完成
		RTC_WaitForSynchro();
		// 等待写操作完成
		RTC_WaitForLastTask();
	}
}

// 获取当前年月日的时间字符串
void RTC_Get_YearToDay()
{
	// 读取RTC_CNT获取时间戳。因为STM32无法判断处于那个时区,所以localtime和gmtime的返回值都是伦敦时间,所以需要加上8小时的偏移
	time_cnt = RTC_GetCounter() + (8*60*60);
	// 将时间戳转化为结构体类型
	time_date = *localtime(&time_cnt);
	// 使用自定义显示时间格式的函数
	strftime(Year_To_Date, 11, "%Y/%m/%d", &time_date);
}

// 获取当前时分秒的时间字符串
void RTC_Get_HourMinSec()
{
	// 读取RTC_CNT获取时间戳。因为STM32无法判断处于那个时区,所以localtime和gmtime的返回值都是伦敦时间,所以需要加上8小时的偏移
	time_cnt = RTC_GetCounter() + (8*60*60);
	// 将时间戳转化为结构体类型
	time_date = *localtime(&time_cnt);
	// 使用自定义显示时间格式的函数
	strftime(Hour_Min_Sec, 9, "%H:%M:%S", &time_date);
}

int main()
{
	OLED_Init();
	RTC_Init();
	while(1)
	{
		RTC_Get_YearToDay();
		RTC_Get_HourMinSec();
		OLED_ShowString(1,1,Year_To_Date);
		OLED_ShowString(2,1,Hour_Min_Sec);
		OLED_ShowNum(3,1,RTC_GetCounter(),10);
		
		/* 开启待机模式的操作 */
		// 显示字符串表示唤醒成功
		OLED_ShowString(4,1,"Run");
		Delay_ms(100);
		OLED_ShowString(4,1,"   ");
		Delay_ms(100);
		
		// 设置闹钟,当前时间加10,也就是每10秒闹钟响一次
		RTC_SetAlarm(RTC_GetCounter() + 10);
		// 等待写入完成
		RTC_WaitForLastTask();
		
		// NVIC优先级分组2
		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
		// 初始化NVIC
		NVIC_InitTypeDef NVIC_InitStruct;
		NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn;				// 选择RTC的中断通道
		NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;			// 使能选择的中断通道
		NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
		NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
		NVIC_Init(&NVIC_InitStruct);
		
		// 开启PWR的时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
		// 进入待机模式。待机模式唤醒后程序直接从头开始运行,也就是该函数后面的程序永远不会运行
		PWR_EnterSTANDBYMode();
	}
}

posted @ 2024-03-05 03:40  7七柒  阅读(512)  评论(0)    收藏  举报