STM32 PWR电源控制 与 低功耗模式 详解

news/2024/9/19 12:23:18/ 标签: 单片机, 嵌入式硬件

目录

STM32 PWR电源控制 与 低功耗模式 详解

1. PWR 电源控制 简介

2. PWR 电源控制 框图

3. 上电复位和掉电复位 与 可编程电压检测器(PVD)

3.1 内嵌复位与电源控制模块特性图

3.2 上电复位和掉电复位

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

3.4 总结来说可以看这个图来理解。

4. 低功耗模式介绍

睡眠模式简介

停机模式简介

待机模式简介

5. 低功耗模式选择框图

6. 低功耗三种模式总结

6.1 睡眠模式

6.2 停机模式

6.3 待机模式

7. STM32各个状态下的电量消耗

7.1 睡眠模式

7.2 停机模式和待机模式

8. 修改系统主频的方法

7.1 system_stm32f10x.c中可调用的函数

7.2 system_stm32f10x.c中的宏定义

[扩展]STM32配置系统时钟时都做了什么

9. 编写:睡眠模式+串口收发数据

9.1 为什么一定是睡眠模式呢?

9.2 代码框架介绍

main.c

Serial.c

Serial.h

10. PWR.h 电源控制函数介绍

11. 编写:停止模式+外部中断计次

11.1 为什么是停止模式呢?

11.2 注意事项

11.3 代码框架介绍

main.c

CountSensor.c

CountSensor.h

12. 编写:待机模式+实时时钟

12.1 为什么是待机模式呢?

12.2 注意事项

12.3 代码框架介绍

main.c

MyRTC.c

MyRTC.h


STM32 PWR电源控制 与 低功耗模式 详解

1. PWR 电源控制 简介

PWR(Power Control)电源控制

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

2. PWR 电源控制 框图

  1. 最上面是模拟供电VDDA
    • 主要为AD转换器、温度传感器、复位模块、PLL锁相环供电
    • 其中AD转换器,还有两根参考电压的供电引脚:VREF+、VREF-(在C8T6中直接接到了VDDA和VSSA了。其他引脚比较多的情况下会直接引出去)
  2. 中间为数字部分供电,分为两部分:VDD供电、1.8V供电
    • 左边 部分为 VDD供电,其中包括IO供电、待机电路、唤醒逻辑、独立看门狗
    • 右边 部分为 VDD通过电压调节器降压到1.8V,提供给1.8V供电区域。 1.8V区域包括CPU核心、存储器、内置的 数字外设。
  3. 下面为后备供电区域。VBAT
    • 其中包括LSE 32K晶体振荡器、后备寄存器BKP、RCC_BOCR寄存器(是RCC的寄存器,是备份域控制寄存器)、RTC
    • 低电压检测器是控制供电的。当VDD有电时,这块区域由VDD供电,没电才用VBAT供电。

3. 上电复位和掉电复位 与 可编程电压检测器(PVD)

3.1 内嵌复位与电源控制模块特性图

这个是内嵌的上电掉电复位和可编程电压检测器的框图。填写寄存器对应的值,会有对应的上下限电压阈值。(以典型值为准)

3.2 上电复位和掉电复位

  1. 当VDD或VDDA电压过低时,内部电路直接产生复位,让stm32复位住,不要乱操作

  2. 图中的40mV的迟滞。是用来定义上下限的。用来复位和解除复位。

    这是一个典型的迟滞比较器。迟滞比较器设置两个阈值比设置单一的阈值要好。可以防止电压在某个阈值附近波动时,造成输出也来抖动

    • 当电压小于下线PDR时,复位。
    • 当电压大于上限POR时,解除复位
  3. 由上表可知,在STM32中 复位的下限为1.88V、上限为1.92V、每次复位持续时间为2.5ms)

  4. 可以看到:在低于下限电压时,复位信号一直为0,代表复位(低电平有效)。 而高于上限时,复位信号为1,代表不复位。

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

电压检测器和上电复位和掉电复位电路。都是用来监测VDD和VDDA的供电电压的。

而PVD的区别是:

  1. 阈值电压可以用程序指定
  2. 范围在数据手册中可以看到,可选范围(大概)是2.2V ~ 2.9V
  3. PVD的上限和下限的迟滞电压为100mv,是比上电掉电复位的电压要高的。

PVD的工作逻辑:

  1. 电压超过设置的阈值时,代表正常。PVD输出为0, 而电压低于设置的阈值时,代表电压过低。此时 PVD输出为1。开始警告

  2. 产生的这个上升沿和下降沿时可以在外部中断申请中断。在中断中可以进行相关操作。

3.4 总结来说可以看这个图来理解。

PVD是来检测2.9V~2.2V左右的电压,可以在这个范围内设置一个警告线,如果低于则触发警告。可以进入中断做一些事情。

上电掉电复位则是提供一个最低的阈值,如果低于这个阈值则复位。

4. 低功耗模式介绍

进入停机或者待机模式之后,需要按住复位再下载程序,否则下载失败 (我这里不行,我会断电重上电,在未进入待机模式前开始烧录代码)

在理解睡眠、停机、待机模式时,参考PWR电源控制框图 效果更佳

  • 低功耗模式有 睡眠、 停机、 待机、
    1. 从上到下,关闭的电路越来越多。
    2. 从上到下,越来越省电
    3. 从上到下,越来越难唤醒

睡眠模式简介

  • 睡眠模式有两种进入方式:

    1. 直接调用WFI进入:WFI(Wait For Interrupt)等待中断,有中断才能被唤醒

      这个一般是为了进入中断处理中断函数。

    2. 直接调用WFE进入:WFE(Wait For Event)等待事件,有事件才能被唤醒

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

      这个一般是不需要进入中断的,直接从睡的地方继续运行。

  • 睡眠模式对电路的影响:

    1. 对 1.8V区域的时钟 影响是 CPU时钟关、对其他时钟和ADC时钟无影响、 对 VDD区域的时钟 无影响 对 电压调节器 操作为开 (电压调节器是VDD通过调节后的1.8V,给后备区域用的)

    **总结就是,睡眠模式只把CPU时钟关闭,对其他电路没有任何操作 CPU时钟关闭之后,程序就会暂停。但是寄存器的数据都还在 注意:睡眠进入不了的可能原因:**系统定时器SysTick一直产生中断

停机模式简介

  • 停机模式要操控的标志位:
    1. PDDS位 :设置停机还是待机模式**。0为停机模式**、设置为1为待机模式
    2. LPDS位 : 设置电压调节器的开关,0为开启。1为进入低功耗 电压调节器是否关闭,1.8V区域的寄存器仍然能保持寄存器的数据。不过开启后更省电,但唤醒更慢罢了。
    3. SLEEPDEEP位 : 设置是否进入深度睡眠模式。 1 为进入。
    4. 最后调用WFI或者WFE,芯片就可以进入停机模式了。
  • 对电路有何影响:
    1. 关闭1.8V区域的时钟。 这就代表CPU时钟和内置数字外设的时钟都会停止。比如定时器、串口… 不过没关闭电源。寄存器的数据都还在。(原因见PWR电源框图)
    2. HSI和HSE的振荡器关闭, 因为外设都关了。所以要高速时钟也没用了。所以会关闭HSI内部高速时钟、HSE外部高速时钟。两个低速时钟不会主动关闭,因为这两个时钟要维持RTC和独立看门狗的运行。
  • 如何唤醒
    1. 在停机模式下。任一外部中断(在外设中断寄存器中设置)就可以唤醒

待机模式简介

  • 停机模式要操控的标志位:
    1. PDDS位 :设置停机还是待机模式。0为停机模式、设置为1为待机模式
    2. SLEEPDEEP位 : 设置是否进入深度睡眠模式**。 1 为进入。**
    3. 最后调用WFI或者WFE,芯片就可以进入待机模式了。
  • 对电路有何影响:
    1. 关闭1.8V区域的时钟。 这就代表CPU时钟和内置数字外设的时钟都会停止。比如定时器、串口… 不过没关闭电源。寄存器的数据都还在。(原因见PWR电源框图)
    2. HSI和HSE的振荡器关闭, 因为外设都关了。所以要高速时钟也没用了。所以会关闭HSI内部高速时钟、HSE外部高速时钟。两个低速时钟不会主动关闭,因为这两个时钟要维持RTC和独立看门狗的运行。
    3. 强制关闭电压调节器,1.8V电源直接关断,意味着内部的寄存器数据全部丢失
  • 如何唤醒
    1. WKUP的引脚产生上升沿、
    2. RTC闹钟事件
    3. NRST引脚上的外部复位
    4. IWDG复位

5. 低功耗模式选择框图

执行WFI(Wait For Interrupt)或者 WFE(Wait For Event)指令后,STM32进入低功耗模式。(这两条指令为最终开启低功耗模式的触发条件)

可以看到。

一旦WFI 或者 WFE 执行了。 芯片是通过判断这些标志位来进入低功耗的三种模式的。

  • SLEEPDEEP 位 决定是否进入深度睡眠。0为浅睡眠模式(睡眠模式)、1为深度睡眠模式(停机或待机)
    • 在睡眠模式时

      SLEEPONEXIT位的0、1标志可以决定立刻睡眠,还是等待中断事件处理结束后才睡眠。一般不用,只有在中断中调用睡眠模式,才会考虑这个标志位。

    • 在深度睡眠模式时

      • 判断PDDS位,为0则进入停机。为1则进入待机模式
        • 停机模式下,会继续判断LPDS位,为0则开启电压调节器。为1则电压调节器开启低功耗(更省电,但唤醒时间更长)

使用睡眠模式时,一般是直接使用使用__WFI来配置为睡眠模式,对于刚才所说的SLEEPDEEP = 0和 SLEEPONEXIT 的位 使用默认的就可以了。所以是不需要配置的。直接输入__WFI 而停止模式和待机模式,都有对应的函数可以直接一键配置

要是真想去配置的话,输入SCB->SCR = 0x….; 就可以配置这些位了

6. 低功耗三种模式总结

6.1 睡眠模式

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

6.2 停机模式

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

6.3 待机模式

  • 执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行
  • 整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,**SRAM和寄存器内容丢失,**只有备份的寄存器和待机电路维持供电
  • 在待机模式下,所有的I/O引脚变为高阻态(浮空输入)
  • WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
  • 在进入待机模式之前,一般都需要把所有的外设 都关闭,比如屏幕、电机等等。这样才能达到 最最最省电的效果

7. STM32各个状态下的电量消耗

7.1 睡眠模式

睡眠模式下,各个频率的电流 是mA级别的电流。

7.2 停机模式和待机模式

电流变为了μA级别。这是非常省电的。

对于一个300mAh的电池:睡眠(10h),停机(1w h),待机(10w h), 的使用时间能相差很大!

备份区域的供电 消耗电量也很小,仅仅1μA

8. 修改系统主频的方法

修改主频可以有效的降低功耗。并且在停机模式下唤醒,也需要修改主频到72MHZ

这个需要去system_stm32f10x.csystem_stm32f10x.h 中查看学习

7.1 system_stm32f10x.c中可调用的函数

  • system_stm32f10x.c 文件中有两个外部可调用的函数和一个外部可调用的变量.(可以在头文件中看到。)

    两个函数是SystemInit()SystemCoreClockUpdate()

    一个变量是SystemCoreClock

  • SystemInit() 是用来配置时钟树的,他是在执行main函数之前,在启动文件中调用的。

  • SystemCoreClock 表示主频频率的值。如果我们要显示当前频率就可以直接调用显示

  • SystemCoreClockUpdate() 更新上边的SystemCoreClock 的值。

7.2 system_stm32f10x.c中的宏定义

解除对应的注释,来选择想要的系统主频(带有钥匙的文件无法修改,需要取消文件的只读属性)

这个if和 lese的意思是:只要你不是VL(超值系列)。就可以配置那么多的主频(lese下边的)

[扩展]STM32配置系统时钟时都做了什么

系统在进入main函数之前,会由system_stm32f10x.c函数来配置系统时钟。

首先system会启动HSI内部高速时钟

然后会进行各种恢复缺省配置(比如)

最后调用SetSysClock函数(这是一个分配函数,根据我们之前解除的宏定义,来选择执行不同的配置函数。比如SetSysClockTo72、To56、To48…..)

在执行的配置函数中。才会真正的对stm32的寄存器开始配置。

比如To72的配置为:选择HSE外部告诉始终作为锁相环输入,锁相环进行9倍频 再选择锁相环输出作为主频。

9. 编写:睡眠模式+串口收发数据

9.1 为什么一定是睡眠模式呢?

  • 因为在停机模式和待机模式下。会关闭1.8V区域的时钟。这就导致了USART外设不能接收数据而产生中断。 并且停机模式和待机模式只能是由外部中断唤醒的。

所以只能使用睡眠模式,因为睡眠模式仅仅是关闭了CPU的时钟。使程序不再往下运行。1.8V供电区域和电压调节器都是开着的。可以使用USART的中断进行唤醒。

9.2 代码框架介绍

  • main.c 仅仅添加了一行代码:__WFI 中断睡眠。 其余的不用配置,使用默认为0的就行(睡眠模式0 + 立刻睡眠0) 另外还加入了Running来验证程序是否进入睡眠模式
  • 执行流程:__WFI()后,睡眠,串口发送数据,产生中断,唤醒cpu,执行中断,执行主函数,遇到__WFI() ,睡眠,
  • Serial.c 串口收发数据
  • Serial.h 串口收发数据头文件
  • 注意事项:如果Delay.c中的延时实现是靠SysTick滴答定时器中断实现的。这个中断也会触发唤醒操作。需要关闭SysTick的中断

main.c

#include "stm32f10x.h"                  // Device header
#include "led.h"
#include "delay.h"
#include "Key.h"
#include "Serial.h"
#include "OLED.h"uint8_t RxData;int main()
{OLED_Init();//初始化OLEDDelay_Init();//初始化延时函数Serial_Init();//初始化串口OLED_ShowString(1,1,"RxData:");while(1){if(Serial_GetRxfalg() == 1){RxData = Serial_GetRxData();Serial_SendByte(RxData);OLED_ShowHexNum(1,8,RxData,2);}OLED_ShowString(2,1,"Running");Delay_ms(100);OLED_ShowString(2,1,"       ");Delay_ms(100);__WFI();}
}

Serial.c

#include "Serial.h"//初始化
void Serial_Init(void)
{//使能A9 A10所在的GPIORCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能A9 A10的复用外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//配置PA9端口为复用推挽输出GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;       //9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化//这里对于F4芯片可以单独 分开的设置复用以及上下拉。不用分开配置,GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//复用推挽模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;       //9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate = 9600;//波特率。会自动算好填入BRR寄存器USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制、不使用.USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//用| 使用两个功能USART_InitStructure.USART_Parity = USART_Parity_No; //校验位。USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位1位。USART_InitStructure.USART_WordLength = USART_WordLength_8b; //不需要校验,所以字长选择8位字长。USART_Init(USART1,&USART_InitStructure);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NCIC_InitStructure;NCIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NCIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NCIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NCIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//优先级NVIC_Init(&NCIC_InitStructure);USART_Cmd(USART1,ENABLE);}//发送字节
void Serial_SendByte(uint16_t Byte)
{USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//如果写标志位没有置1(没有发送完),那么就等
}//发送数组
void Serial_SendArray(uint8_t* Array,uint16_t len)
{uint16_t i = 0;for(i = 0; i < len; i++){Serial_SendByte(Array[i]);}
}//发送字符串
void Serial_SendString(char* String)//有结束的\\0 所以不用再传长度了,
{uint16_t i = 0;for(i = 0; (String[i] != '\\0'); i++){Serial_SendByte(String[i]);}
}//求次方函数
uint32_t Serial_Pow(uint32_t X,uint8_t Y)
{int Num = 1;while(Y--){Num *= X;}return Num;
}//发送字符形式的数字
void Serial_SendNumber(uint32_t Number,uint8_t len)
{//从高位像低位取数字然后输出uint8_t i = 0;for(i = 0; i < len; i++){Serial_SendByte(Number / Serial_Pow(10,len - i - 1) %10 + '0');}
}//下边没学过,直接搬过来的。
/*** 函    数:使用printf需要重定向的底层函数* 参    数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数return ch;
}/*** 函    数:自己封装的prinf函数* 参    数:format 格式化字符串* 参    数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100];				//定义字符数组va_list arg;					//定义可变参数列表数据类型的变量argva_start(arg, format);			//从format开始,接收参数列表到arg变量vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);					//结束变量argSerial_SendString(String);		//串口发送字符数组(字符串)
}uint8_t Serial_RxData;
uint8_t Serial_RxFlag;uint8_t Serial_GetRxfalg(void)
{if(Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}return 0;
}uint8_t Serial_GetRxData(void)
{return Serial_RxData;
}void USART1_IRQHandler(void)//中断
{if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET){Serial_RxData = USART_ReceiveData(USART1);//读取Serial_RxFlag = 1;USART_ClearITPendingBit(USART1,USART_FLAG_RXNE);//手动清除标志位}}

Serial.h

#ifndef __SERIAL_H #include "stm32f10x.h"
#include "stdio.h"
#include "stdarg.h"#define __SERIAL_H //初始化串口
void Serial_Init(void);//发送字节
void Serial_SendByte(uint16_t Byte);//发送数组
void Serial_SendArray(uint8_t* Array,uint16_t len);//发送字符串
void Serial_SendString(char* String);//发送字符形式的数字
void Serial_SendNumber(uint32_t Number,uint8_t len);//移植printf
void Serial_Printf(char *format, ...);//获取输入的RXNE标志位
uint8_t Serial_GetRxfalg(void);//获取输入的字符
uint8_t Serial_GetRxData(void);#endif

10. PWR.h 电源控制函数介绍

void PWR_DeInit(void);

  • 恢复缺省配置

void PWR_BackupAccessCmd(FunctionalState NewState);

  • 使能后备区域的访问

void PWR_PVDCmd(FunctionalState NewState);

  • 配置PVD阈值电压

void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);

  • 使能PVD功能

void PWR_WakeUpPinCmd(FunctionalState NewState);

  • 使能位于PA0位置的WKUP引脚。(配合待机模式使用)

void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);

  • 进入停止模式
  • 指定电压调节器在停止模式中的状态、使用WFI或WFI指令进入

void PWR_EnterSTANDBYMode(void);

  • 进入待机模式
  • 使用WFI或WFI指令进入

FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);

  • 获取标志位

void PWR_ClearFlag(uint32_t PWR_FLAG);

  • 清除标志位

11. 编写:停止模式+外部中断计次

11.1 为什么是停止模式呢?

  • 刚才我们已经验证过睡眠模式了。而停止模式只能由外部中断来触发。所以只能选择外部中断类型的代码来验证
  • 在停止模式下, 1.8V的区域的时钟关闭,CPU和外设都没有时钟了。
  • 但是外部中断的工作是不需要时钟过的

11.2 注意事项

  • 我们需要使用PWR外设,来操作进入停止模式、待机模式。它在APB1总线上

  • 在停止模式被唤醒之后,会默认使用内部高速时钟HSI。

    需要手动调整时钟为外部高速HSE。如果不设置,在退出停止模式之后程序运行速度会变慢。重新启动主频函数为:SystemInit();

11.3 代码框架介绍

  • main.c 测试停止模式工作,使用running闪烁来判断是否停止
  • 执行流程:复位后初始化、进入主循环、进入停止模式、外部中断发生时,唤醒停止模式(此时主频被设置为HSI内部高速时钟)、进入中断执行中断内容、继续从停止位置继续执行。设置主频为72MHZ、继续执行后续函数、然后进入停止
  • CountSensor.c 外部中断计次
  • CountSensor.h 外部中断计次头文件

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "oled.h"
#include "Delay.h"
#include "CountSensor.h"int main()
{OLED_Init();//初始化OLED;CountSensor_Init();//初始化RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//开启PWR外设OLED_ShowString(1,1,"Count:");while(1){OLED_ShowSignedNum(1, 7,CountSenSor_Get(),5);OLED_ShowString(2,1,"Running");Delay_ms(100);OLED_ShowString(2,1,"       ");Delay_ms(100);PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);//开启电压调节器, WFI模式进入SystemInit();//重新启动主频}
}

CountSensor.c

#include "stm32f10x.h"                  // Device header//引脚为A2  //计次变量
uint16_t CountSensor_Count = 0;//初始化
void CountSensor_Init(void)
{//开启A2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启挂载在APB2外设的AFIO外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//EXTI时钟和NVIC的时钟不用手动打开。//配置GPIOGPIO_InitTypeDef GPIO_InitStructrue;GPIO_InitStructrue.GPIO_Mode = GPIO_Mode_IPU;        //上拉输入模式GPIO_InitStructrue.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructrue.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_Init(GPIOA,&GPIO_InitStructrue);//配置AFIO (F1中在GPIO.h文件中)(目的是为了把GPIOA的PIN2引脚映射到AFIO中。)GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource2);//这里需要根据PIn和GPIOx来选择//配置EXTIEXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line = EXTI_Line2;          //选择中断线路,这里是PIN2 所以为2   EXTI_InitStructure.EXTI_LineCmd = ENABLE;           //是否使能指定的中断线路EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断或响应模式   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//上升或下降或边沿触发EXTI_Init(&EXTI_InitStructure);//配置NVIC(因为NVIC属于内核,所以被分配到内核的杂项中去了,在misc.c)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置抢占和响应优先级NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;       //在stm32f10x.h文件里。让你找IRQn_Type里的一个中断通道。这里使用的是md的芯片(如果引脚是15-10或者9-5则需要去找对应的那个)这里我是PIN2.所以找2NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //是否使能指定的中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级(这里可以看表。看范围,)NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;       //指定响应优先级NVIC_Init(&NVIC_InitStructure);    }//获取当前的计数
uint16_t CountSenSor_Get(void)
{return CountSensor_Count;
}//在STM32中,中断的函数都是固定的。他们在启动文件中存放xxx.s 
//以IRQHandler结尾的就是中断函数的名字。
//在这里需要找到对应的中断函数,我这里是2
void EXTI2_IRQHandler(void)//中断函数是无参无返回值的。中断函数必须写对,写错就进不去
{//在进入中断后,一般要判断一下这个是不是我们想要的那个中断源触发的中断。//但是在这里。我是GPIOA的PIN2引脚,所以不用写。//如果是5-9 10-15的引脚。他们EXTI到NVIC是几个共用的。//所以需要根据EXTI输入时的16根引脚。来判断是16根引脚的那一根发送的中断请求。//这里规范写的话需要加上去//查找标志位函数在exit.h中。if(EXTI_GetITStatus(EXTI_Line2) == SET)//第一个参数是行数.判断这个线的标志位是不是== SET。是则是我们想要的{CountSensor_Count++;EXTI_ClearITPendingBit(EXTI_Line2);//中断结束后,要调用清除标志位的函数。如果你不清除,程序会一直进入中断}
}

CountSensor.h

#ifndef __COUNTSENSOR_H
#define __COUNTSENSOR_H//初始化旋转编码计次
void CountSensor_Init(void);//获取当前计数值
uint16_t CountSenSor_Get(void);#endif

12. 编写:待机模式+实时时钟

12.1 为什么是待机模式呢?

  • 刚才我们已经验证过睡眠模式、停止模式了。 待机模式只能由WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位来退出待机模式。
  • 在待机模式恢复后,会重头开始执行命令。所以不需要配置时钟了。
  • 在待机模式下, 1.8V的区域的时钟关闭,电压调节器也关闭。这会使除了备份区域外的所有寄存器清除数据,并且IO口对外呈现为高阻态。

12.2 注意事项

  • 在进入待机模式之前,一般都需要把所有的外设 都关闭,比如屏幕、电机等等。 否则就无法极度省电
  • 待机模式会重头开始执行命令,所以不需要再配置时钟了。
  • 待机模式烧录代码可以按住复位再烧录 (我这里不行,我会断电重上电,在未进入待机模式前开始烧录代码)
  • 测试WKUP引脚时,只需要函数:PWR_WakeUpPinCmd(ENABLE); 就可以了。程序会自动帮我们把WKUP(PA0)引脚设置为下拉输入的配置,不需要GPIO初始化。

12.3 代码框架介绍

  • main.c 测试停止模式工作,使用running闪烁来判断是否进入待机模式
  • 执行流程:复位后初始化、进入主循环、进入待机模式(所有IO口浮空、寄存器重置、CPU核心停止。但是后备区域不会受到影响)、唤醒待机模式、程序重头开始运行。然后再次进入待机模式
  • MyRTC.c RTC实时时钟
  • MyRTC.h RTC实时时钟头文件

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{/*模块初始化*/OLED_Init();        //OLED初始化MyRTC_Init();       //RTC初始化/*显示静态字符串*/OLED_ShowString(1, 1, "CNT:");  //时间戳计数器OLED_ShowString(2, 1, "ALR:");  //闹钟值OLED_ShowString(3, 1, "ALRF:"); //闹钟标志位PWR_WakeUpPinCmd(ENABLE);//存下要显示的闹钟值(闹钟寄存器只可写不可读,所以要存下,方便显示)uint32_t Alarm = RTC_GetCounter() + 10;//设定闹钟为十秒后RTC_SetAlarm(Alarm);//显示闹钟OLED_ShowNum(2, 5, Alarm, 10);//开启PWR时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);while (1){OLED_ShowNum(1, 5, RTC_GetCounter(), 10);                   //显示32位的秒计数器OLED_ShowNum(3, 6, RTC_GetFlagStatus(RTC_FLAG_ALR), 1);     //显示32位的秒计数器OLED_ShowString(4,1,"Running");Delay_ms(100);OLED_ShowString(4,1,"       ");Delay_ms(100);//在进入待机模式之前,一般都需要把所有的外设 都关闭,比如屏幕、电机等等。//这里清个屏表示一下OLED_Clear();PWR_EnterSTANDBYMode();//进入待机模式}
}

MyRTC.c

#include "stm32f10x.h"                  // Device header
#include <time.h>uint16_t MyRTC_Time[] = {2024, 8, 18, 23, 46, 0};   //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒void MyRTC_SetTime(void);           //函数声明/*** 函    数:RTC初始化* 参    数:无* 返 回 值:无*/
void MyRTC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);     //开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);     //开启BKP的时钟/*备份寄存器、RTC访问使能*/PWR_BackupAccessCmd(ENABLE);                            //使用PWR开启对备份寄存器和RTC的访问if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)          //通过写入备份寄存器的标志位,判断RTC是否是第一次配置//if成立则执行第一次的RTC初始化{RCC_LSEConfig(RCC_LSE_ON);                          //开启LSE时钟while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);  //等待LSE准备就绪RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);             //选择RTCCLK来源为LSERCC_RTCCLKCmd(ENABLE);                              //RTCCLK使能RTC_WaitForSynchro();                               //等待同步RTC_WaitForLastTask();                              //等待上一次操作完成RTC_SetPrescaler(32768 - 1);                        //设置RTC预分频器,预分频后的计数频率为1HzRTC_WaitForLastTask();                              //等待上一次操作完成MyRTC_SetTime();                                    //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);           //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置}else                                                    //RTC不是第一次配置{RTC_WaitForSynchro();                               //等待同步RTC_WaitForLastTask();                              //等待上一次操作完成}
}
/*** 函    数:RTC设置时间* 参    数:无* 返 回 值:无* 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路*/
void MyRTC_SetTime(void)
{time_t time_cnt = 0;    //定义秒计数器数据类型struct tm time_date;    //定义日期时间数据类型time_date.tm_year = MyRTC_Time[0] - 1900;       //将数组的时间赋值给日期时间结构体time_date.tm_mon = MyRTC_Time[1] - 1;time_date.tm_mday = MyRTC_Time[2];time_date.tm_hour = MyRTC_Time[3];time_date.tm_min = MyRTC_Time[4];time_date.tm_sec = MyRTC_Time[5];time_cnt = mktime(&time_date) - 8 * 60 * 60;    //调用mktime函数,将日期时间转换为秒计数器格式//- 8 * 60 * 60为东八区的时区调整RTC_SetCounter(time_cnt);                       //将秒计数器写入到RTC的CNT中RTC_WaitForLastTask();                          //等待上一次操作完成
}/*** 函    数:RTC读取时间* 参    数:无* 返 回 值:无* 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组*/
void MyRTC_ReadTime(void)
{time_t time_cnt;        //定义秒计数器数据类型struct tm time_date;    //定义日期时间数据类型time_cnt = RTC_GetCounter() + 8 * 60 * 60;      //读取RTC的CNT,获取当前的秒计数器//+ 8 * 60 * 60为东八区的时区调整time_date = *localtime(&time_cnt);              //使用localtime函数,将秒计数器转换为日期时间格式MyRTC_Time[0] = time_date.tm_year + 1900;       //将日期时间结构体赋值给数组的时间MyRTC_Time[1] = time_date.tm_mon + 1;MyRTC_Time[2] = time_date.tm_mday;MyRTC_Time[3] = time_date.tm_hour;MyRTC_Time[4] = time_date.tm_min;MyRTC_Time[5] = time_date.tm_sec;
}

MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_Hextern uint16_t MyRTC_Time[];void MyRTC_Init(void);void MyRTC_SetTime(void);void MyRTC_ReadTime(void);#endif


http://www.ppmy.cn/news/1511870.html

相关文章

[星瞳科技]OpenMV是否属于单片机?

文件系统 MicroPyhon的文件系统是FatFS。 根目录 路径都是以根目录为起点。 当插入sd卡后&#xff0c;根目录就是SD卡&#xff1b;不插入sd卡&#xff0c;根目录就是内置的Flash。 如果需要&#xff0c;你可以在SD卡上&#xff0c;新建一个空文件&#xff1a;/flash/SKIPS…

ES6随笔

ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript的一个重大更新&#xff0c;它引入了许多新特性和语法糖&#xff0c;使得JavaScript的开发更加高效和灵活。下面是一些ES6中新增的主要特性及其代码示例&#xff1a; 1. 模板字符串&#xff08;Template Strings&#…

stable-fast-3d的部署,在服务器Ubuntu22.04系统下——点动科技

在服务器Ubuntu22.04系统下&#xff0c;stable-fast-3d的部署 一、ubuntu22.04基本环境配置1.1 更换清华Ubuntu镜像源1.2 更新包列表&#xff1a;2. 安装英伟达显卡驱动2.1 使用wget在命令行下载驱动包2.2 更新软件列表和安装必要软件、依赖2.2 卸载原有驱动2.3 安装驱动2.4 安…

【Java学习】实现图书管理系统

所属专栏&#xff1a;Java学习 &#x1f341;1. 功能演示 用户分为普通用户和管理员&#xff0c;登录进系统之后可以对图书进行一系列操作&#xff0c;此时我们要明白&#xff0c;对图书的操作是通过书架来执行的&#xff0c;我们平常在图书馆上借书就是在书架上 &#x1f…

单元测试、系统测试、集成测试知识总结

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、单元测试的概念 单元测试是对软件基本组成单元进行的测试&#xff0c;如函数或一个类的方法。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0c;有…

爬取豆瓣TOP250电影详解

一.分析网页DOM树结构 1.分析网页结构及简单爬取 豆瓣&#xff08;Douban&#xff09;是一个社区网站&#xff0c;创立于2005年3月6日。该网站以书影音起家&#xff0c;提供关于书籍、电影、音乐等作品的信息&#xff0c;其作品描述和评论都是由用户提供&#xff08;User-Gen…

【element-ui】 统一全局配置size和z-index属性

Vue.use(Element, { size: small, zIndex: 3000 });参考&#xff1a; element ui 统一全局配置size和z-index属性

使用SSH协议远程连接Ubuntu

1.切换到root用户 sudo -i 2.安装openssh-server apt update apt install openssh-server 3.启动ssh服务 service ssh start 4.查看ssh状态 &#xff08;q键: 退出&#xff09; service ssh status 5.检查ssh服务是否启动成功 ps -e | grep ssh 6.开机自启动 systemctl enable …

Webpack高级配置(干货2)

目录 39.CodeSplit 优化代码运行性能40.CodeSplit按需加载&#xff0c;性能优化41.eslint 不支持动态倒入语法&#xff0c;需要引入import的plugin42.图片&#xff0c;字体等命名规则&#xff0c;可以提取复用43.preload/prefatch 39.CodeSplit 优化代码运行性能 entry由字符串…

libvirt bridge network configure

If you want to configure all the parameters of your virtual machine, you can issue the command like this: virsh edit ubuntu22.04-test In the GUI of NIC configuration, you can choose a configuration item from a drop box,such as “default”,‘bridged-network…

Apache Doris 中Compaction问题分析和典型案例

说明 此文档主要说明一些常见compaction问题的排查思路和临时处理手段。这些问题包括 Compaction socre高Compaction失败compaction占用资源多Compaction core 如果问题紧急&#xff0c;可联系社区同学处理 如果阅读中有问题&#xff0c;可以反馈给社区同学。 1 compaction …

机器学习--常见算法总结

有监督学习算法 1. 线性回归算法 概念&#xff1a;线性回归是一种统计方法&#xff0c;用于预测一个变量&#xff08;因变量&#xff09;与一个或多个自变量&#xff08;特征变量&#xff09;之间的关系。目标是通过线性方程建立自变量和因变量之间的关系模型。 作用&#x…

Linux执行脚本报错:-bash: ./mylife.sh: /bin/sh^M: bad interpreter: Text file busy

这个错误信息 -bash: ./mylife.sh: /bin/sh^M: bad interpreter: Text file busy 实际上包含了两个主要问题&#xff0c;但“Text file busy”这部分通常不是真实的错误&#xff0c;可能是显示错误或者与之前的操作冲突。更常见的问题是前面的 /bin/sh^M&#xff0c;这里的 ^M …

ZooKeeper Watcher 机制详解

ZooKeeper Watcher 机制详解 1、特点2、注册与触发3、注意事项 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; ZooKeeper 的 Watcher 机制是分布式系统中用于事件通知的重要功能。 1、特点 一次性触发&#xff1a; Watcher 被触发后立即从…

Python自动化测试工具selenium使用指南

概述 selenium是网页应用中最流行的自动化测试工具&#xff0c;可以用来做自动化测试或者浏览器爬虫等。官网地址为&#xff1a;相对于另外一款web自动化测试工具QTP来说有如下优点&#xff1a; 免费开源轻量级&#xff0c;不同语言只需要一个体积很小的依赖包支持多种系统&a…

《现代情报》

《现代情报》简介 《现代情报》&#xff08;原名《情报知识》&#xff09;杂志是由吉林省科学技术厅主管&#xff0c;吉林省科学技术信息研究所、中国科学技术情报学会联合主办的信息资源管理领域综合性学术期刊。该刊于1980年创刊&#xff0c;每年12期。重点报道服务于国家战略…

上传文件,文件类型限制语法,各种媒体视频文件的Content-Type

各种媒体视频文件的Content-Type “application/x-apple-diskimage”: “DMG”, “application/epubzip”: “EPUB”, “application/java-archive”: “JAR”, “video/x-matroska”: “MKV”, “text/html”: “HTML|HTM”, “text/css”: “CSS”, “text/javascript…

Ps:首选项 - 单位与标尺

Ps菜单&#xff1a;编辑/首选项 Edit/Preferences 快捷键&#xff1a;Ctrl K Photoshop 首选项中的“单位与标尺” Units & Rulers选项卡允许用户根据工作需求定制 Photoshop 的测量单位和标尺显示方式。这对于保持工作的一致性和精确性&#xff0c;尤其是在跨设备或跨平台…

USB3.2 摘录(四)

系列文章目录 USB3.2 摘录&#xff08;一&#xff09; USB3.2 摘录&#xff08;二&#xff09; USB3.2 摘录&#xff08;三&#xff09; USB3.2 摘录&#xff08;四&#xff09; 文章目录 系列文章目录8 协议层&#xff08;Protocol Layer&#xff09;8.8 三个参数地址信息&…

软考高级第四版备考---第四十一天(软件工程-软件设计)

一、结构化设计 结构化设计&#xff08;Structured Design,SD&#xff09;是一种面向数据流的方法&#xff0c;它以SRS和SA阶段所产生的DFD和数据字典等文档为基础&#xff0c;是一个自顶向下、逐步求精和模块化的过程 二、面向对象设计 2.1面相对象设计&#xff08;OOD&…