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

news2024/9/22 11:41:12

目录

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();//初始化OLED
    Delay_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所在的GPIO
    RCC_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;       //9
    GPIO_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;       //9
    GPIO_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;					//定义可变参数列表数据类型的变量arg
	va_start(arg, format);			//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);					//结束变量arg
	Serial_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的时钟不用手动打开。
    
    //配置GPIO
    GPIO_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来选择

    
    //配置EXTI
    EXTI_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.所以找2
    NVIC_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来源为LSE
        RCC_RTCCLKCmd(ENABLE);                              //RTCCLK使能
                                        
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        RTC_SetPrescaler(32768 - 1);                        //设置RTC预分频器,预分频后的计数频率为1Hz
        RTC_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_H

extern uint16_t MyRTC_Time[];

void MyRTC_Init(void);

void MyRTC_SetTime(void);

void MyRTC_ReadTime(void);

#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2055492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蚁群算法原理与实战(Python、MATLAB、C++)

蚁群算法 1.蚁群算法来源 蚁群算法&#xff08;Ant Colony Optimization&#xff0c;简称ACO&#xff09;是一种模拟自然界中蚂蚁寻找食物路径行为的优化算法&#xff0c;主要用于解决组合优化问题。它的灵感来源于意大利学者Marco Dorigo在1992年提出的蚂蚁系统模型。 蚁群算…

脑网络相似性:方法与应用

摘要 图论方法已被证明是理解、表征和量化复杂大脑网络的有效工具。然而&#xff0c;定量比较两个图形的方法却较少受到关注。在一些网络神经科学应用中&#xff0c;比较大脑网络确实是必不可少的。在这里&#xff0c;本研究讨论了近年来用于比较大脑网络的技术现状、挑战以及…

Android常见界面控件(二)

目录 前言 一、 RadioButton控件 设置RadioGroup的监听事件 二、CheckBox控件 三、Toast类 改变Toast窗口的提示位置 前言 在上一篇中&#xff0c;我们讲解了三个常见的控件&#xff1a;TextView控件、Button控件、ImageView控件&#xff0c;那么本篇我们就接着讲剩下的…

Selenium实战:深度解析Python中嵌套Frame与iFrame的定位与切换技巧,解决Selenium定位不到的问题

在Web自动化测试中&#xff0c;处理网页中的Frame和iFrame是常见的挑战之一。这些元素在网页中扮演着承载独立HTML文档的角色&#xff0c;使得直接定位或操作其中的元素变得复杂。Python的Selenium库提供了强大的工具来应对这些挑战&#xff0c;本文将详细介绍如何使用Selenium…

SFP光模块、gt口、PMD、PMA、PCS之间的关系

ZYNQ内部的GT&#xff08;高速收发器&#xff09;接口包含了PCS&#xff08;物理编码子层&#xff09;与PMA&#xff08;物理介质接入层&#xff09;。这两个层在高速数据传输中起着至关重要的作用。 PCS层&#xff08;物理编码子层&#xff09; PCS层位于协调子层&#xff0…

Ubuntu虚拟机服务器的搭建

01.VMware安装 略。 02.Ubuntu虚拟机安装 略。 03.配置Ubuntu虚拟机网络 参考视频&#xff1a; Ubutu虚拟机网络配置&#xff08;桥接&#xff09;https://www.bilibili.com/video/BV1bG411V72A/?spm_id_from333.999.0.0&vd_sourced1fd4bcc46805ab35cc8bbb5a8bf318f…

win11如何查看串口的名字

1、右击win然后点击设备管理器 2、点击端口然后右击串口点击属性 3、进入窗口后点击Port information即可看见Port name属性就是串口名字

uniapp/vue如何实现一个子表单及子表单作用

子表单是一个辅助表单或一个表&#xff0c;它允许在主表单中添加多个行式项目&#xff0c;以处理与主记录相关联的多个辅助项目或数据。子表单在多种应用场景中发挥着重要作用&#xff0c;特别是在需要处理一对多关系的数据时。 以下是对子表单的详细解析&#xff1a; 定义与特…

苍穹外卖day10

苍穹外卖day10 Spring Task订单状态定时处理WebSocket应用&#xff08;弹幕&#xff0c;网页聊天&#xff0c;体育实况更新&#xff0c;股票基金实时更新&#xff09; 来单题型代码实现需求分析 客户催单 Spring Task 链接: 在线生成器 在线生成器 订单状态定时处理 每分钟检…

木舟0基础学习Java的第二十五天(JavaWeb)

XML 概念和体系 XML指可扩展标记语言&#xff08;EXtensible Markup Language&#xff09; XML没有预定义标签 需要自定义标签 <标签></标签> XML特点 XML数据以纯文本格式存储 实现不同应用程序之间的数据通信 实现不同平台的数据通信 实现不同平台的数据共…

Linux------Cortex-A架构的处理器运行模型与其寄存器组

寄存器组分为 外设寄存器组 比如&#xff1a;和总线相连的io寄存器&#xff0c;usart配置寄存器&#xff0c;spi配置寄存器等等 内核寄存器组&#xff1a;R0-R15 CPSR SPSR一共18个寄存器组&#xff0c;内核寄存器组用来记录当前程序地址状态&#xff0c;当前执行指令等&a…

8月来得及|1000/660/880题45天带刷计划!

刷题不在于多&#xff0c;而在于精 你选的这几本题集没有问题&#xff0c;660题专门训练客观题&#xff0c;880题和1000题都是不错的综合性题集&#xff0c;特别适合在强化阶段进行刷题训练&#xff0c;但是问题是你做这么多题&#xff0c;也不一定能起到多好的作用。 刷题的…

Linux | 进程概念详解:冯诺依曼体系结构、进程基本概念、PCB、进程组织、fork()创建子进程、进程运行逻辑

文章目录 进程概念1、冯诺依曼体系结构2、进程2.1基本概念2.2描述进程-PCB2.3组织进程2.4查看进程2.5通过系统调用获取进程标识符2.6通过系统调用创建进程-fork初识头文件与返回值fork函数的调用逻辑和底层逻辑 进程概念 1、冯诺依曼体系结构 目前我们认识的计算机中&#xff…

dm 到 dm 的 HS 同步部署

一、数据库部署 完成两节点数据库部署并初始化&#xff0c;配置参数如下&#xff1a; 节点 192.168.2.132 192.168.2.133 数据库版本 DM8 DM8 实例名 DM1 DM2 端口号 5236 5236 数据文件路径 /home/dmdba/dmdata /home/dmdba/dmdata 二、 dmhs 部署 1. 部署…

户外无线麦克风哪个牌子好,领夹麦克风十大品牌排行榜推荐

​为了在数字时代的浪潮中有效传达自己的思想和感情&#xff0c;选择合适的声音工具变得尤为重要。无线领夹麦克风就是这样一种工具&#xff0c;它能够帮助我们在任何环境下都保持最佳的语音传递效果。经过慎重考虑和市场调研&#xff0c;我为大家推荐几款性能卓越且价格合理的…

测试报告----功能测试

目录 一、项目背景 二、项目功能 &#xff08;1&#xff09;页面1&#xff1a;用户注册 &#xff08;2&#xff09;页面2&#xff1a;登陆页面 &#xff08;3&#xff09;页面3&#xff1a;信息主页 &#xff08;4&#xff09;页面4&#xff1a;详情页面 ​编辑&#xf…

获取当前路由器的外网IP(WAN IP)

GPT-4o (OpenAI) 获取当前路由器的外网IP&#xff08;WAN IP&#xff09;可以通过以下几种方法&#xff1a; 1. 访问路由器管理页面&#xff1a; - 通常路由器的管理页面可以通过在浏览器中输入路由器的IP地址来访问&#xff08;例如&#xff0c;192.168.0.1 或 192.168.1…

接口基础知识9:详解response body(响应体)

课程大纲 一、定义 HTTP响应体&#xff08;HTTP Response Body&#xff09;&#xff1a;服务器返回给客户端的数据部分&#xff0c;‌它包含了服务器对客户端请求的响应内容&#xff08;如客户端请求的资源、客户端请求的执行结果&#xff09;。 与请求类似&#xff0c;HTTP …

使用python实现3D聚类图

实验记录&#xff0c;在做XX得分预测的实验中&#xff0c;做了一个基于Python的3D聚类图&#xff0c;水平有限&#xff0c;仅供参考。 一、以实现三个类别聚类为例 代码&#xff1a; import pandas as pd import numpy as np from sklearn.decomposition import PCA from sk…

【Prometheus】监控系统

目录 一.Prometheus概述 1.Prometheus的认识与了解 2.Prometheus的特点 3.Prometheus存储引擎 TSDB 的特点优势 4.Prometheus 的生态组件 Prometheus server Client Library Exporters Service Discovery Alertmanager Pushgateway Grafana 5.Prometheus 的…