目录
低功耗
低功耗的目的
电源对电子设备的重要性不言而喻,它是保证系统稳定运行的基础,而保证系统能稳定运行后,又有低功耗的要求。 在很多应用场合中都对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久,且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以也很有必要从控制功耗入手,提高设备的续行时间。
如何降低功耗
1、选择低功耗的芯片
STM32F103ZET6 F 标准类的
STM32L051C8T6 L 低功耗类的
2、降低 CPU 的主频
3、将不必要的片上外设,关闭,以及对应的时钟也关闭
4、设备可以设置低功耗模式,可以单片机自带的,睡眠,停止,待机 -- 课程中讲的
5、DHT11 KQM6600 设计电源的开关电路 直接断电
KQM6600 F 引脚 低功耗
6、产品的硬件原理图上的元器件,选择功耗比较低的器件
如何测量功耗
直流电源,显示设备的电压和电流,测试功耗。
万用表,电流档位,串联到电路中,测量电流,计算功耗。
STM32 中的电源系统
ADC 电源及参考电压(VDDA 供电区域)。为了提高转换精度, STM32 的 ADC 配有独立的电源接口,方便进行单独的滤波。ADC 的工作电源使用 VDDA 引脚输入,使用 VSSA 作为独立的地连接, VREF 引脚则为 ADC 提供测量使用的参考电压。
调压器供电电路(VDD/1.8V 供电区域)。在 STM32 的电源系统中调压器供电的电路是最主要的部分,调压器为备份域及待机电路以外的所有数字电路供电,其中包括内核、数字外设以及 RAM,调压器的输出电压约为 1.8V,因而使用调压器供电的这些电路区域被称为 1.8V 域。调压器可以运行在“运行模式”、“停止模式”以及“待机模式”。在运行模式下, 1.8V 域全功率运行;在停止模式下 1.8V 域运行在低功耗状态, 1.8V 区域的所有时钟都被关闭,相应的外设都停止了工作,但它会保留内核寄存器以及 SRAM 的内容;在待机模式下,整个 1.8V 域都断电,该区域的内核寄存器及 SRAM 内容都会丢失(备份区域的寄存器不受影响)。
备份域电路(后备供电区域)。STM32 的 LSE 振荡器、 RTC 及备份寄存器这些器件被包含进备份域电路中,这部分的电路可以通过 STM32 的 VBAT 引脚获取供电电源,在实际应用中一般会使用 3V 的钮扣电池对该引脚供电
后备供电区域如何实现始终有电
如果要实现 备份域电路(后背供电区域)一直有电,需要 VBAT 引脚接纽扣电池
备份域一直有电,可以实现 RTC 时间掉电不丢失
有的开发板:VBAT 引脚没有接纽扣电池
STM32 中的低功耗
按功耗由高到低排列, STM32 具有运行、睡眠、停止和待机四种工作模式。上电复位后 STM32 处于运行状态时,当内核不需要继续运行,就可以选择进入后面的三种低功耗模式降低功耗,这三种模式中,电源消耗不同、唤醒时间不同、唤醒源不同,用户需要根据应用需求,选择最佳的低功耗模式。
睡眠模式
在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设, CM3 核心的外设全都还照常运行。两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是 WFI(wait for interrupt)和 WFE(wait for event),即由等待“中断”唤醒和由“事件”唤醒。
停止模式
在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。停止模式可以由任意一个外部中断(EXTI)唤醒,在停止模式中可以选择电压调节器为开模式或低功耗模式。
待机模式
待机模式,它除了关闭所有的时钟,还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0)引脚的上升沿, RTC 闹钟事件, NRST 引脚的复位和 IWDG(独立看门狗)复位。
三种低功耗模式的编程
进入各种低功耗模式时都需要调用 WFI 或 WFE 命令,它们实质上都是内核指令,在库文件
core_cm3.h 中把这些指令封装成了函数
睡眠模式
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
UART1_Config();
LED_Config();
EXTI_Config();
printf("进入睡眠模式前\r\n");
__WFI();
printf("退出睡眠模式后\r\n");
while(1)
{
}
/*
三种低功耗:睡眠
如何进入:调用__WFI,等待中断
如何退出:任一中断
谁的功耗最低:三者中最高的
退出之后,代码从哪里运行:从原来位置继续运行
IO 的状态:对 IO 口没有影响
使用场景:几乎没啥用
*/
停止模式
直接调用 WFI 和 WFE 指令可以进入睡眠模式,而进入停止模式则还需要在调用指令前设置一些寄存器位,STM32 标准库把这部分的操作封装到 PWR_EnterSTOPMode 函数中了。这个函数有两个输入参数,分别用于控制调压器的模式及选择使用 WFI 或 WFE 停止,代码中先是根据调压器的模式配置 PWR_CR 寄存器,再把内核寄存器的 SLEEPDEEP 位置 1,这样再调用 WFI 或 WFE 命令时, STM32 就不是睡眠,而是进入停止模式了。函数结尾处的语句用于复位 SLEEPDEEP 位的状态,由于它是在 WFI 及 WFE 指令之后的,所以这部分代码是在 STM32 被唤醒的时候才会执行。
要注意的是进入停止模式后, STM32 的所有 I/O 都保持在停止前的状态,而当它被唤醒时, STM32 使用 HSI 作为系统时钟(8MHz)运行,由于系统时钟会影响很多外设的工作状态,所以一般我们在唤醒后会重新开启 HSE,把系统时钟设置回原来的状态。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
UART1_Config();
LED_Config();
EXTI_Config();
printf("进入停止模式前\r\n");
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);
SystemInit();
printf("退出停止模式后\r\n");
while(1)
{
}
/*
三种低功耗:停止
如何进入:PWR_EnterSTOPMode
如何退出:任一外部中断
谁的功耗最低:三者中居中
退出之后,代码从哪里运行:从原来位置继续运行
IO 的状态:对 IO 口没有影响
使用场景:
注意:
1.退出停止之后,选择 HSI 作为系统时钟,需要重新调用时钟初始化,选择 HSE(8M)*9 的 PLLCLK
作为系统时钟
2.设置 PWR 的寄存器,需要开启 PWR 的时钟
*/
待机模式
STM32 标准库也提供了控制进入待机模式的函数:PWR_EnterSTANDBYMode该函数中先配置了 PDDS 寄存器位及 SLEEPDEEP 寄存器位,接着调用__force_stores 函数确保存储操作完毕后再调用 WFI 指令,从而进入待机模式。这里值得注意的是,待机模式也可以使用 WFE 指令进入的,如果您有需要可以自行修改。在进入待机模式后,除了被使能了的用于唤醒的 I/O,其余 I/O 都进入高阻态,而从待机模式唤醒后,相当于复位 STM32 芯片,程序重新从头开始执行。
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SysTick_Config(SystemCoreClock/1000); //core_cm3.h 1827 行
UART1_Config();
KEY_Config();
LED_Config();
printf("准备进入主循环\r\n");
while(1)
{
if(KEY_Period[0]>=KEY_Period[1])
{
KEY_Period[0]=0;
KEY_Handle();
}
if(LED_Period[0]>=LED_Period[1])
{
LED_Period[0]=0;
printf("主循环正在执行\r\n");
}
}
}
void KEY_Handle(void)
{
uint8_t KEY_State = 0;
KEY_State = KEY_SCAN();
switch(KEY_State)
{
case 0: //无按键按下
break;
case 1: //按键 1 短按并松手
break;
case 4: //按键 4 短按并松手
printf("进入待机模式前\r\n");
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
PWR_WakeUpPinCmd(ENABLE); //使能 WakeUp 唤醒功能
PWR_EnterSTANDBYMode();//进入待机模式
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
default:
break;
}
}
/*
三种低功耗:待机
如何进入:PWR_EnterSTANDBYMode
如何退出:WKUP 引脚的上升沿、 RTC 闹钟事件的上升沿、 NRST 引脚上外部复位、 IWDG 复位。
谁的功耗最低:三者中最低
退出之后,代码从哪里运行:从头运行
IO 的状态:将 IO 设置成高阻态(浮空输入)
使用场景:额温枪,把 PA0 按键配置成唤醒按键,当设备 2 分钟无人使用,进入待机模式
户外设备,配置成 RTC 闹钟事件唤醒,唤醒之后,采集数据,上报平台,上报成功,
继续进入待机模式,等待闹钟唤醒。
注意:
1.开 PWR 的时钟
2.使能 PA0 唤醒功能
如何解决代码无法下载:
1.退出低功耗模式,就可以下载
2.一手按复位键,一手点下载,点击下载之后松开复位键,下载成功
*/
RTC 实时时钟
RTC 实时时钟的作用
RTC--实时时钟
本质上:依然是定时器,为什么必须有它,因为它可以实现掉电不丢失,时间能继续运行。
实现实时时钟的必要硬件
做一个日历:让整个设备断电之后,时间不丢失,使用单片机内部的 RTC 功能做实时时钟,必须 VBAT 引脚有电池
STM32 内部的 RTC 介绍
注释:当 VDD 有电的情况下,后备供电区域由 VDD 提供电源,当 VDD 断电之后,由 VBAT 给后备供电区域提供电源。如果 VBAT 引脚没有接电池,VDD 掉电之后,后备供电区域也停止工作了。意味着时间从头运行了。
实时时钟是一个独立的定时器。 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC 模块和时钟配置系统(RCC_BDCR 寄存器)处于后备区域,即在系统复位或从待机模式唤醒后, RTC 的设置和时间维持不变。
系统复位后,对后备寄存器和 RTC 的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和 RTC 的访问:
● 设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位,使能电源和后备接口时钟。
● 设置寄存器 PWR_CR 的 DBP 位,使能对后备寄存器和 RTC 的访问。
RTC 由两个主要部分组成(参见下图)。第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16位寄存器,可通过 APB1 总线对其进行读写操作(参见 16.4 节)。APB1 接口由 APB1 总线时钟驱动,用来与 APB1总线接口。另一部分(RTC 核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是 RTC 的预分频模块,它可编程产生最长为 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20 位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个 TR_CLK 周期中 RTC 产生一个中断(秒中断)。第二个模块是一个 32 位的可编程计数器,可被初始化为当前的系统时间。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。
STM32 内部的 RTC 时钟源介绍
STM32 内部的 RTC 的接口函数
涉及到四章 参考手册 PWR BKP RTC RCC
STM32 内部 RTC 初始化过程
1. 开 BKP 和 PWR 的时钟 -- 参考手册 16.1
2. 允许对后背区域和 RTC 的访问操作 -- 参考手册 16.1
3. 将 RTC 的时钟配置成 LSE
4. 使能 RTC 时钟,并等待 APB1 和 RTC 同步完成 -- 参考手册 16.3.3
5. 对 RTC 进行分频,计数频率设置成 1HZ
6. 设置 RTC 的初始值,把当前时间给他
7. 需要使用秒中断,就配置秒中断
8. 需要使用闹钟,就配置闹钟 -- 不使用
手写算法实现
通过平年闰年手动计算。
如何获取准确时间
通过串口发送给单片机,单片机解析出来时间,跟新到 RTC 中
设备连接网络,获取当前时间,更新到 RTC 中
注意:正常情况下如果 VBAT 引脚接电池,RTC 时间掉电不丢失,不接电池,掉电丢失。
编程实现 RTC 时钟
利用 time 接口实现
mktime();//根据结构体转成秒数
localtime();//将秒数转换成本地时间结构体
时间戳(Unix timestamp)转换工具 - 在线工具
#include "RTC.h"
#include "time.h"
struct tm Set_Time = {0, 15, 14, 22, 5-1, 2025-1900};//待设置的时间
uint32_t Set_Time_Cnt;//待设置的秒数
struct tm *Now_Time = NULL;//结构体指针,当前时间
uint32_t Now_Time_Cnt;//当前时间秒数
uint8_t RTC_Sec_Flag = 0;//秒标志位
void RTC_Config(void)
{
//开启PWR,BKP时钟,位置27,28置1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
//使能PWR_CR的DBP位8置1
PWR_BackupAccessCmd(ENABLE);
//启用低速外部晶振(LSE),它通常用于提供一个稳定的32.768 kHz时钟源,用于RTC的计时。
RCC_LSEConfig(RCC_LSE_ON);
//等待LSE(低速外部晶振)稳定并就绪。RCC_FLAG_LSERDY标志位在LSE稳定时会被置位,代码会等待直到该标志位为1。
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{}
//选择LSE为RTC的时钟源32.768khz
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能RTC时钟
RCC_RTCCLKCmd(ENABLE);
//等待RTC寄存器与APB时钟同步。由于RTC模块是一个独立的外设,它的寄存器更新可能需要一些时间。
RTC_WaitForSynchro();
//检测上次是否操作完成
RTC_WaitForLastTask();
//对RTC分频,内部默认累加1,配置RTC_CRL_CNF
/*
RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)
f=32768/32768=1hz
t=1/f=1s
*/
RTC_SetPrescaler(32767);
//检测上次是否操作完成
RTC_WaitForLastTask();
NVIC_InitTypeDef NVIC_InitStructure = {0};
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//次级优先级
NVIC_Init(&NVIC_InitStructure);
//使能秒中断,CRH
RTC_ITConfig(RTC_IT_SEC, ENABLE);
//等待操作完成
RTC_WaitForLastTask();
//更新待设置的时间到RTC中
Time_Adjust();
}
void Time_Adjust(void)
{
//1.检测上一次是否操作完成 参考手册16.3.4描述 判断参考手册16.4.2的位5是否置1
RTC_WaitForLastTask();
//2.将时间结构体转换成秒数
Set_Time_Cnt = mktime(&Set_Time);//根据结构体转换成秒数
//3.将当前时间戳更新到计数器中 参考手册16.4.5
RTC_SetCounter(Set_Time_Cnt);
//4.检测上一次是否操作完成 参考手册16.3.4描述 判断参考手册16.4.2的位5是否置1
RTC_WaitForLastTask();
}
void RTC_IRQHandler(void)
{
//1.检测中断标志位 判断参考手册16.4.2的位0是否置1
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
//2.执行中断服务函数具体内容
RTC_Sec_Flag = 1;
//3.清除中断标志位 参考手册16.4.2的位0清0
RTC_ClearITPendingBit(RTC_IT_SEC);
//4.检测上一次是否操作完成 参考手册16.3.4描述 判断参考手册16.4.2的位5是否置1
RTC_WaitForLastTask();
}
}