文章目录
- 需求
- 一、低功耗模式
- 1.睡眠模式
- 2.停止模式
- 3.待机模式
- 二、RTC实现实时时钟
- 1.寄存器配置流程
- 2.标准库开发
- 3.主函数调用
- 三、需求实现代码
需求
1.实现睡眠模式、停止模式和待机模式。
2.实现RTC实时时间显示。
一、低功耗模式
电源对电子设备的重要性不言而喻,它是保证系统稳定运行的基础,而保证系统能稳定运行后,又有低功耗的要求。
在很多应用场合中都对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久,且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以也很有必要从控制功耗入手,提高设备的续行时间。
因此, STM32 有专门的电源管理外设监控电源并管理设备的运行模式, 确保系统正常运行,并尽量降低器件的功耗。
简而言之,低功耗模式就是为了保证移动设备的长时间运行。
从主控芯片角度上来说,如何降低功耗:
1、降低CPU的主频 72M->48M。
2、将不必要的片上外设关闭,对应的时钟也关闭。
3、设备可以设置低功耗模式。
1.睡眠模式
首先,我们先看最简单的睡眠模式。
想要设置睡眠模式,只用直接在主函数中根据需求添加__WFI();和__WFE();函数就行。
若是想用中断唤醒就用__WFI();
想用事件唤醒就用__WFE();
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
Delay_ms(2000);
KEY1_Exti_PA0_init();
printf("睡眠1\r\n");
__WFI();
printf("睡眠2\r\n");
while(1)
{
}
return 0;
}
在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设, CM3 核心的外设全都还照常运行。
WFI和WFE 命令,它们实质上都是内核指令,只是在库文件 core_cm3.h 中把这些指令封装成了函数。
2.停止模式
想进入停止模式需要在调用指令前设置一些寄存器位。
而STM32 标准库把这部分的操作封装到 PWR_EnterSTOPMode 函数中了。所以我们直接使用即可。
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "string.h"
#include "key.h"
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
Delay_ms(2000);
KEY1_Exti_PA0_init();
printf("停止1\r\n");
PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);
SystemInit();
printf("停止2\r\n");
while(1)
{
}
return 0;
}
在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。
停止模式可以由任意一个外部中断(EXTI)唤醒(此处用的就是key1使能EXTI来唤醒),在停止模式中可以选择电压调节器为开模式或低功耗模式。
进入停止模式后, STM32 的所有 I/O 都保持在停止前的状态,而当它被唤醒时, STM32 使用 HSI 作为系统时钟(8MHz)运行,由于系统时钟会影响很多外设的工作状态,所以一般我们在唤醒后会重新开启 HSE,把系统时钟设置回原来的状态。
3.待机模式
待机模式库函数也有封装好的代码,直接用就行。
待机模式,它除了关闭所有的时钟,还把 1.8V 区域的电源也完全关闭了。(除了被使能了的用于唤醒的 I/O,其余 I/O 都进入高阻态)
简单来说,就是从待机模式唤醒后,会从头开始执行程序,类似于复位。
它有四种唤醒方式:
- WKUP(PA0)引脚的上升沿。
- RTC 闹钟事件。
- NRST 引脚的复位。
- IWDG(独立看门狗)复位。
本例程使用的是KEY1上升沿唤醒。
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "string.h"
#include "key.h"
uint8_t key3flag,cntt;
void EnterStanbdy(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
//选择WKUP唤醒
PWR_WakeUpPinCmd(ENABLE);
//进入待机模式
PWR_EnterSTANDBYMode();
//退出待机模式之后,由于SRAM电压关了,所以清空了,退出之后代码需要重新运行
}
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
SysTick_Config(72000);
Led_Init();
key_Init();
while(1)
{
if(ledcnt[0]>=ledcnt[1]){//过去500ms
ledcnt[0]=0;
/***LED1闪烁任务***/
Led_Toggle(1);
printf(" LED闪烁 \r\n");
cntt++;
cntt%=100;
}
if(keycnt[0]>=keycnt[1]){//过去10ms
keycnt[0]=0;
//按键非阻塞检测
key3flag = key_value();
if(key3flag == 2)
{
printf("进入待机模式\r\n");
EnterStanbdy();
}
}
}
return 0;
}
二、RTC实现实时时钟
实时时钟是一个独立的定时器。 RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后, RTC的设置和时间维持不变。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:
● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟。
● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
RTC内部电路
1.寄存器配置流程
RTC 从配置上分两大部分:时钟的配置和定时器的配置。
时钟的配置:可以直接访问,直接由RCC的BDCR来配置时钟:时钟源的选择。
定时器的配置:不可以直接访问,因为定时器相关的寄存器在备份区域。
1、 使能备份区域访问— PWREN、BKPEN
a) 开电源控制器以及备份区的时钟
b) 电源PWR_CR的DBP置1
2、 配置分频。
3、 设置计数器计数值。
4、 需要开中断,就开不需要就不开。
5、 需要设置闹钟,就设置闹钟。
2.标准库开发
由于该模块标准库有现成的,所以此处我们使用标准库开发。
该部分在此处:
然后创建RTC.c和RTC.h编写好基础部分。
将标准库的void RTC_Configuration(void)直接整个函数copy过来。
接下来一行一行看:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
该段代码就是使能PWR和BKP的时钟并使能,使备份访区能够访问。
/* Reset Backup Domain */
BKP_DeInit();//强制后备区域复位
此处可有可无,是为了防止之前有人设置过备份区,保险起见的话就加上。
/* Enable LSE */
RCC_LSEConfig(RCC_LSE_ON);//使能外部低速时钟
/* Wait till LSE is ready */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)//等待低速时钟就绪
{}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//选择RTC的时钟源
RCC_RTCCLKCmd(ENABLE);//使能RTC时钟
时钟源的配置:1.打开LSE。2.等待低速时钟稳定。3.配置RTC时钟源为LSE。
RTC_WaitForSynchro();//等待APB1和RTC时钟同步
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();//等待上次写操作完成
RTC_EnterConfigMode();//进入配置模式
配置完RTC后需要等待一会儿使时钟源同步。
最后等待上次完上次写操作后就可以配置了。
RTC_SetPrescaler(32767);
RTC_WaitForLastTask();
将频率配置为1ms一次。(每操作一次就等待一下)
RTC_SetCounter(ret);
设置一下初始值
int ret;
struct tm info;
info.tm_year = 2024 - 1900;
info.tm_mon = 7 - 1;
info.tm_mday = 1;
info.tm_hour = 4;
info.tm_min = 10;
info.tm_sec = 0;
info.tm_isdst = -1;
ret = mktime(&info);
if(ret==-1)
{
printf("时间获取出错\r\n");
return;
}
printf("获取到秒数为%d\r\n",ret);
上面是初始值的由来
最后退出并保存
RTC_ExitConfigMode();
RTC_WaitForLastTask();
3.主函数调用
在主函数中直接定义一个32位的值承接一下秒数,然后用localtime()函数转换为标准时间显示格式打印即可。
三、需求实现代码
main.c
#include "stm32f10x.h"
#include "usart.h"
#include "stdio.h"
#include "delay.h"
#include "string.h"
#include "pwm.h"
#include "adc.h"
#include "su03t.h"
#include "dht11.h"
#include "kqm.h"
#include "key.h"
#include "RTC.h"
uint8_t key3flag,cntt;
uint32_t sec=0;
void EnterStanbdy(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
//选择WKUP唤醒
PWR_WakeUpPinCmd(ENABLE);
//进入待机模式
PWR_EnterSTANDBYMode();
//退出待机模式之后,由于SRAM电压关了,所以清空了,退出之后代码需要重新运行
}
int main()
{
NVIC_SetPriorityGrouping(5);//两位抢占两位次级
Usart1_Config();
SysTick_Config(72000);
Led_Init();
key_Init();
while(1)
{
sec = RTC_GetCounter();
time_t seconds=sec;
struct tm *info = localtime(&seconds);
if(ledcnt[0]>=ledcnt[1]){//过去500ms
ledcnt[0]=0;
/***LED1闪烁任务***/
Led_Toggle(1);
printf(" LED闪烁 \r\n");
printf("本地时间:%d-%02d-%02d %02d:%02d:%02d\r\n",
info->tm_year + 1900, info->tm_mon + 1, info->tm_mday,
info->tm_hour, info->tm_min, info->tm_sec);
cntt++;
cntt%=100;
}
if(keycnt[0]>=keycnt[1]){//过去10ms
keycnt[0]=0;
//按键非阻塞检测
key3flag = key_value();
if(key3flag == 2)
{
printf("进入待机模式\r\n");
EnterStanbdy();
}
}
}
return 0;
}
RTC.c
#include "RTC.h"
void RTC_Configuration(void)
{
int ret;
struct tm info;
info.tm_year = 2024 - 1900;
info.tm_mon = 7 - 1;
info.tm_mday = 1;
info.tm_hour = 4;
info.tm_min = 10;
info.tm_sec = 0;
info.tm_isdst = -1;
ret = mktime(&info);
if(ret==-1)
{
printf("时间获取出错\r\n");
return;
}
printf("获取到秒数为%d\r\n",ret);
/* Enable PWR and BKP clocks */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//使能PWR和BKP的时钟
/* Allow access to BKP Domain */
PWR_BackupAccessCmd(ENABLE);//使能后备区域访问
/* Reset Backup Domain */
BKP_DeInit();//强制后备区域复位
/* Enable LSE */
RCC_LSEConfig(RCC_LSE_ON);//使能外部低速时钟
/* Wait till LSE is ready */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)//等待低速时钟就绪
{}
/* Select LSE as RTC Clock Source */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//选择RTC的时钟源
/* Enable RTC Clock */
RCC_RTCCLKCmd(ENABLE);//使能RTC时钟
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();//等待APB1和RTC时钟同步
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();//等待上次写操作完成
RTC_EnterConfigMode();//进入配置模式
//RTC->CRL |= 0x01<<4;
/* Enable the RTC Second */
//RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
// RTC_WaitForLastTask();
/* Set RTC prescaler: set RTC period to 1sec */
RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
RTC_SetCounter(ret);
RTC_ExitConfigMode();
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
RTC.h
#ifndef _RTC_H_
#define _RTC_H_
#include "stm32f10x.h"
#include "time.h"
#include "stdio.h"
void RTC_Configuration(void);
#endif
led.c
#include "stm32f10x.h"
void Led_Init()
{
//配置好模式,然后全灭
//开APB2时钟
RCC->APB2ENR |= 0X01 << 6;
//配置PE2--PE5为通用推挽输出
GPIOE->CRL &=~(0X0F << 20);//PE5
GPIOE->CRL |= 0X03 << 20;
GPIOE->CRL &=~(0X0F << 16);//PE4
GPIOE->CRL |= 0X03 << 16;
GPIOE->CRL &=~(0X0F << 12);//PE3
GPIOE->CRL |= 0X03 << 12;
GPIOE->CRL &=~(0X0F << 8);//PE2
GPIOE->CRL |= 0X03 << 8;
//4个引脚均输出高电平
GPIOE->ODR |= (0x0F << 2);
}
//开关灯
void Led1_Ctrl(int flag)
{
if(!!flag)
{
GPIOE->ODR &= ~(0x0F << 2);
}
else
{
GPIOE->ODR |= (0x0F << 2);
}
}
void Led_Toggle(int flag)
{
GPIOE->ODR ^= 0x01<<(flag+1);
}
key.c
#include "stm32f10x.h"
#include "stdio.h"
//PA0
void KEY1_Exti_PA0_init()
{
RCC->APB2ENR |= 0x05;//打开GPIO和AFIO时钟
GPIOA->CRL &=~(0X0F << 0);//PC4 key2
GPIOA->CRL |= 0X04 << 0;
AFIO->EXTICR[0] &= ~(0x0F);//配置GPIO映射EXTI线
EXTI->RTSR &= ~(0x1); //关闭上升沿检测
EXTI->FTSR |= 0x01; //打开下降沿检测
EXTI->IMR |= 0x01; //打开exti的屏蔽位
NVIC_SetPriority(EXTI0_IRQn,2);NVIC设置优先级
NVIC_EnableIRQ(EXTI0_IRQn); //NVIC使能中断通道
}
//exti0的中断服务函数
void EXTI0_IRQHandler(void)
{
//判断中断标志是否被置位
if((EXTI->PR&(0x1<<0))!=0){
//如果置位,就清理标志位
EXTI->PR |= 0x1<<0;//写1是清除
printf("按键1触发中断\r\n");
}
}
//PC4
void KEY2_Exti_PC4_init()
{
RCC->APB2ENR |= (0x01<<4); //打开GPIOC时钟
RCC->APB2ENR |= 0x01; //AFIO时钟
GPIOC->CRL &=~(0X0F<<16);//配置PC4 key2
GPIOC->CRL |= (0X04<<16);//浮空输入
AFIO->EXTICR[1] |= 0x02;//配置GPIOC映射EXTI线,外部中断配置寄存器
EXTI->RTSR |= (0x01<<4) ; //打开上升沿检测
EXTI->FTSR &= ~(0x1<<4); //关闭下降沿检测
EXTI->IMR |= (0x01<<4); //打开exti的屏蔽位
NVIC_SetPriority(EXTI4_IRQn,2);NVIC设置优先级
NVIC_EnableIRQ(EXTI4_IRQn); //NVIC使能中断通道
}
void EXTI4_IRQHandler(void)
{
//判断中断标志是否被置位
if((EXTI->PR&(0x1<<4))!=0){
//如果置位,就清理标志位
printf("按键2触发中断\r\n");
EXTI->PR |= (0x1<<4);//写1是清除
}
}
void key_Init()
{
//开时钟
RCC->APB2ENR |= 0x01<<4;//PC
RCC->APB2ENR |= 0x01<<2;//PA
//配置模式
GPIOC->CRL &=~(0X0F << 24);//PC6 key4
GPIOC->CRL |= 0X04 << 24;
GPIOC->CRL &=~(0X0F << 20);//PC5 key3
GPIOC->CRL |= 0X04 << 20;
GPIOC->CRL &=~(0X0F << 16);//PC4 key2
GPIOC->CRL |= 0X04 << 16;
GPIOA->CRL &=~0X0F;//PA0 key1
GPIOA->CRL |= 0X04;
}
int Get_Key_Val(void)
{
int key_val = 0;
if(!!(GPIOA->IDR &(0X01 << 0))==1)
key_val = 1;
if(!!(GPIOC->IDR &(0X01 << 4))==0)
key_val = 2;
if(!!(GPIOC->IDR &(0X01 << 5))==0)
key_val = 3;
if(!!(GPIOC->IDR &(0X01 << 6))==0)
key_val = 4;
return key_val;
}
//PC5
void KEY3_Exti_PC5_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure={0};
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource5);
EXTI_InitTypeDef EXTI_InitStructure={0};
EXTI_InitStructure.EXTI_Line = EXTI_Line5;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
NVIC_SetPriority(EXTI9_5_IRQn,2);NVIC设置优先级00 10
NVIC_EnableIRQ(EXTI9_5_IRQn); //NVIC使能中断通道
}
//PC6
void KEY4_Exti_PC6_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure={0};
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource6);
EXTI_InitTypeDef EXTI_InitStructure={0};
EXTI_InitStructure.EXTI_Line = EXTI_Line6;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
NVIC_SetPriority(EXTI9_5_IRQn,2);NVIC设置优先级00 10
NVIC_EnableIRQ(EXTI9_5_IRQn); //NVIC使能中断通
}
void EXTI9_5_IRQHandler(void)
{
//判断中断标志是否被置位
if(EXTI_GetITStatus(EXTI_Line5) != RESET)
{
printf("按键3触发中断\r\n");
//如果置位,就清理标志位
EXTI_ClearITPendingBit(EXTI_Line5);
}
//判断中断标志是否被置位
if(EXTI_GetITStatus(EXTI_Line6) != RESET)
{
printf("按键4触发中断\r\n");
//如果置位,就清理标志位
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
//非阻塞按键检测
uint8_t key_value(void)//10ms执行一次
{
static uint16_t keycnt = 0;//用来计数,本函数,进来一次加一次,
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0){
keycnt++;
}
else if(keycnt>=200){//等待按键松开,如果按下时间超过200*10,认为是长按
printf(" 按键3长按 \r\n");
keycnt = 0;
return 2;
}
else if(keycnt>=2){//等待按键松开,如果按下时间超过2*10ms,认为是短按
printf(" 按键3短按 \r\n");
keycnt = 0;
return 1;
}
else{//等待按键松开如果按下时间不足20ms,认为这是一次无效按下,或者没有按下
return 0;
}
return 0;
}
delay.c
#include "stm32f10x.h"
#include "delay.h"
uint32_t systicktime=0;
uint16_t ledcnt[2]={0,1000};//500ms 每个任务执行的时间
uint16_t led2cnt[2]={0,2000};//700ms
uint16_t keycnt[2]={0,10};//10ms检测一次
void SysTick_Handler(void)//1ms调用一次
{
//不需要清中断挂起位
systicktime++;
ledcnt[0]++;
led2cnt[0]++;
keycnt[0]++;
}
void Delay_ms(uint32_t time)
{
uint32_t nowtime = systicktime;
while(systicktime < time+nowtime);
}
void Delay_nus(uint32_t time)
{
uint32_t i=0;
for(i=0;i<time;i++){
delay1us();
}
}
void Delay_nms(uint32_t time)
{
uint32_t i=0;
for(i=0;i<time;i++){
Delay_nus(1000);//延时1ms
}
}