十四、PWR电源控制&减少功耗
十二、PWR电源控制
PWR简介
- PWR(Power Control)电源控制
- PWR负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能
- 可编程电压监测器(PVD)可以监控VDD电源电压,当VDD下降到PVD阀值以下或上升到PVD阀值之上时,PVD会触发中断,用于执行紧急关闭任务
- 低功耗模式包括睡眠模式(Sleep)、停机模式(Stop)和待机模式(Standby),可在系统空闲时,降低STM32的功耗,延长设备使用时间
上电复位和掉电复位
-
当 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
可编程电压检测器
简称 PVD。它的工作流程和上面那个上电复位和掉电复位差不多,都是监测 VDD 和 VDDA 的供电电压
PVD 的区别就是,首先它这个阈值电压是可以使用程序指定的,可以自定义调节
- 调节的范围可以看一下数据手册,内嵌复位和电源控制模块特性的表的上面就是 PVD 的阈值,配置 PLS 寄存器的 3 个位,可以选择右边这么多的阈值,因为这里也同样是迟滞比较,所以有两个阈值,可选范围是 2.2V 到 2.9V 左右,PVD 上限和下限之间的迟滞电压是 100 mV。可以看到,PVD 的电压是比上电掉电复位的电压要高的。
3.3V 是正常的供电,当这个电压降低在 2.9V 到 2.2V 之间,属于 PVD 监测的范围,可以通过 PVD 设置一个警告线,之后再降低到 1.9V,就是复位电路的监测范围,低于 1.9V,直接复位,这是这两个电压监测的工作任务。那当然,PVD 触发之后,芯片还是能正常工作的,只不过是电源电压过低,该提醒一下用户了。
PVD 输出是正逻辑,电压过低时为 1,电压正常时为 0
- 这个信号,可以去申请中断,在上升沿或者下降沿时,触发中断,以此提醒程序进行适当的处理。
- 这个 PVD 的中断申请是通过外部中断实现的,所以如果要使用 PVD 的话,记得要配置外部中断
电源供电框图
-
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 供电。
低功耗模式
低功耗模式有 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.8V 区域的时钟,不仅 CPU 不能运行,外设也运行不了,定时器正在定时的,会暂停,串口收发数据的,也会暂停;
- 没关闭电源,所以 CPU 和外设的寄存器数据都是维持原状的。
- HSI 和 HSE 的振荡器关闭
- LSI 内部低速时钟和 LSE 外部低速时钟,这两个并不会主动关闭,如果开启过这两个时钟,还可以继续运行。
- 电压调节器,这里可以选择是开启,或者处于低功耗模式,无论是开启还是低功耗,都可以维持 1.8V 区域寄存器和存储器的数据内容。区别就是,低功耗模式更省电一些,同时,低功耗模式在唤醒时,要花更多的时间;相反,电压调节器开启的话,就是更耗电一些,唤醒更快了。
主要操作就是,把运行的高速时钟都关了,CPU 和外设,都暂停工作,但是电压调节器并没有关,存储器和寄存器数据可以维持原样。
-
-
待机模式
-
待机模式的开启:SLEEPDEEP 也是置 1, PDDS 置 1,最后调用 WFI 或者 WFE
-
待机模式的唤醒:
只有这几个指定的信号才能唤醒:
- WKUP 引脚的上升沿,引脚定义表里面指示了 WKUP 引脚的位置就是 PA0 的位置。
- RTC 闹钟事件
- NRST 引脚上的外部复位,意思就是按一下复位键
- IWDG 独立看门狗复位
-
待机模式对电路的影响:
基本上是能关的全都关了,1.8V 区域的时钟关闭,两个高速时钟关闭,电压调节器关闭,这个意味着 1.8V 区域的电源关闭,内部的存储器和寄存器的数据全部丢失。
但是不会主动关闭 LSI 和 LSE 两个低速时钟,因为这两个时钟还要维持 RTC 和独立看门狗的运行
-
模式选择
执行 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还是修改前的主频
如何修改主频
修改主频的代码
这里最好结合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
接线图
示例代码
#include "stm32f10x.h" // Device header
#include "OLED.h"
int main()
{
OLED_Init();
// 显示当前主频
OLED_ShowNum(1,1,SystemCoreClock,8);
while(1)
{
}
}
睡眠模式+串口接收
接线图
示例代码
#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);
}
}
停机模式+对射红外传感器
接线图
示例代码
#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);
}
}
待机模式+实时时钟
接线图
示例代码
#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();
}
}