GD32F103待机模式与唤醒,本程序使用RTC报警唤醒。
电源管理单元有3种省电模式:睡眠模式,深度睡眠模式和待机模式;
进入待机模式的步骤如下:
若需要RTC闹钟输出,则需要将TAMPER-RTC映射到PC13引脚;
若需要LXTAL晶振32.768KHz,则将OSC32IN映射到PC14引脚,OSC32OUT映射到PC15引脚;现在出厂,都已经默认,不必重新映射。
当LXTAL晶振32.768KHz关闭时,OSC32IN和OSC32OUT可以用作PC14和PC15;
若需要WKUP引脚唤醒,则将WKUP引脚映射到PA0;
进入待机模式后,其他所有I/O都处于高阻态;
1,配置SLEEPDEEP=1
2,配置STBMOD=1
3,配置WUF=0,
4,执行WFI指令或执行2次WFE指令(不清楚原因),命令CPU进入待机模式。
在待机状态,无法烧录程序,必须使用外部复位唤醒,才可以烧录程序。待机前,最好添加延时程序,防止程序无法烧录。
待机模式的结果:
1,需要LDO1.2V供电的电路会被停止供电;
2,内部LDO停止1.2V输出;
3,IRC8M内部RC的8MHz振荡器被关闭;
4,HXTAL外部高速振荡器被关闭;
5,PLL锁相环被关闭;
6,IRC40K内部RC的40KHz振荡器工作,FWDGT独立看门狗定时器启动时会选择IRC40K作为时钟源;
7,LXTAL外部低速振荡器32.768Hz工作,原因是RTC产生报警会让CPU退出待机模式;
待机模式有4个唤醒源:
1,NRST引脚产生产生外部复位信号,CPU会退出待机模式;
2,RTC产生闹钟中断,CPU会退出待机模式;
3,FWDGT独立看门狗定时器产生复位信号,CPU会退出待机模式;
4,WKUP引脚出现上升沿信号,CPU会退出待机模式;
待机模式功耗最低,但唤醒时间最长;
进入待机模式后,SRAM和1.2V电源寄存器的内容会被丢失;
退出待机模式后,CPU产生上电复位;对于GD32芯片,它会从0x80000000地址开始执行代码;
#include "StandbyMode.h"
/*
电源管理单元有3种省电模式:睡眠模式,深度睡眠模式和待机模式;
进入待机模式的步骤如下:
若需要RTC闹钟输出,则需要将TAMPER-RTC映射到PC13引脚;
若需要LXTAL晶振32.768KHz,则将OSC32IN映射到PC14引脚,OSC32OUT映射到PC15引脚;
当LXTAL晶振32.768KHz关闭时,OSC32IN和OSC32OUT可以用作PC14和PC15
若需要WKUP引脚唤醒,则将WKUP引脚映射到PA0;
其他所有I/O都处于高阻态;
1,配置SLEEPDEEP=1
2,配置STBMOD=1
3,配置WUF=0,
4,执行WFI指令或执行2次WFE指令(不清楚原因),命令CPU进入待机模式。
待机模式的结果:
1,需要LDO1.2V供电的电路会被停止供电;
2,内部LDO停止1.2V输出;
3,IRC8M内部RC的8MHz振荡器被关闭;
4,HXTAL外部高速振荡器被关闭;
5,PLL锁相环被关闭;
6,IRC40K内部RC的40KHz振荡器工作,FWDGT独立看门狗定时器启动时会选择IRC40K作为时钟源;
7,LXTAL外部低速振荡器32.768Hz工作,原因是RTC产生报警会让CPU退出待机模式;
待机模式有4个唤醒源:
1,NRST引脚产生产生外部复位信号,CPU会退出待机模式;
2,RTC产生闹钟中断,CPU会退出待机模式;
3,FWDGT独立看门狗定时器产生复位信号,CPU会退出待机模式;
4,WKUP引脚出现上升沿信号,CPU会退出待机模式;
待机模式功耗最低,但唤醒时间最长;
进入待机模式后,SRAM和1.2V电源寄存器的内容会被丢失;
退出待机模式后,CPU产生上电复位;对于GD32芯片,它会从0x80000000地址开始执行代码;
*/
void Enter_StandbyMode0_Use_WFI_CMD(void);
void Enter_StandbyMode0_Use_WFE_CMD(void);
/*
注意:
在待机模式下,除了RESET引脚,配置为RTC功能的PC13,用作LXTAL晶振32.768KHz引脚的PC14和PC15,使能的WKUP引脚,
其他所有I/O都处于高阻态;
从待机唤醒后,除了"电源控制和状态寄存器(PMU_CS)"外,其它所有寄存器均被复位。
从待机模式唤醒后的代码执行等同于复位后的执行,"电源控制和状态寄存器(PMU_CS)"将会指示内核由待机状态退出。
*/
//函数功能:使用WFI命令使CPU进入进入待机模式
void Enter_StandbyMode0_Use_WFI_CMD(void)
{
rcu_periph_clock_enable(RCU_PMU);//使能RCU_PMU外设时钟
pmu_to_standbymode(WFI_CMD);
//SLEEPDEEP=1,STBMOD=1,WURST=1使用WFI命令,使CPU进入待机模式
//唤醒方式:NRST引脚,WKUP引脚,FWDGT复位,RTC闹钟报警
}
//函数功能:使用WFE命令使CPU进入进入待机模式
void Enter_StandbyMode0_Use_WFE_CMD(void)
{
rcu_periph_clock_enable(RCU_PMU);//使能RCU_PMU外设时钟
pmu_to_standbymode(WFE_CMD);
pmu_to_standbymode(WFE_CMD);
//执行2次WFE指令(不清楚原因)后,CPU才会进入待机模式。
//SLEEPDEEP=1,STBMOD=1,WURST=1使用WFE命令,使CPU进入待机模式
//唤醒方式:NRST引脚,WKUP引脚,FWDGT复位,RTC
}
#include "RTC.h"
#include "delay.h"
#include "stdio.h"
//PC13仅可以作为RTC功能引脚
//PC14和PC15可以作为通用I/O口或外部32.768KHz低速晶振引脚
u8 RTC_Alarm;
const u8 table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31}; //平年的月份日期表
_calendar_obj calendar; //时钟结构体
_calendar_obj calendarAlarm; //报警时间结构体
u8 Is_Leap_Year(u16 year);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
u8 RTC_Alarm_Set(u16 year, u8 month, u8 day, u8 hour, u8 min, u8 sec);
u8 RTC_Get(void);
u8 RTC_Alarm_After_xSecond(u16 xSecond);
u8 RTC_Get_Week(u16 year, u8 month, u8 day);
//函数功能:设置RTC的中断优先级
void RTC_NVIC_Configuration(void)
{
//NVIC_PRIGROUP_PRE4_SUB0:抢占优先级为4bit(取值为0~15),子优先级为0bit(没有响应优先级)
//NVIC_PRIGROUP_PRE3_SUB1:抢占优先级为3bit(取值为0~7),子优先级为1bit(取值为0~1)
//NVIC_PRIGROUP_PRE2_SUB2:抢占优先级为2bit(取值为0~3),子优先级为2bit(取值为0~3)
//NVIC_PRIGROUP_PRE1_SUB3:抢占优先级为1bit(取值为0~1),子优先级为3bit(取值为0~7)
//NVIC_PRIGROUP_PRE0_SUB4:抢占优先级为0bit(没有抢占优先级),子优先级为3bit(取值为0~15)
//nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);//设置系统中断优先级"抢占优先级为4bit,子优先级为0bit"
nvic_irq_enable(RTC_IRQn,6,0);
//设置RTC_IRQn的中断优先级,抢占优先级为6,子优先级为0
}
//函数功能:配置RTC,并读取RTC时间保存在结构变量calendar中
u8 RTC_Init(void)
{
u16 temp = 0;
rcu_periph_clock_enable(RCU_BKPI); //使能"RCU_BKPI备份寄存器时钟"
rcu_periph_clock_enable(RCU_PMU); //使能"RCU_PMU电源管理单元时钟"
pmu_backup_write_enable();//使能对备份域寄存器的写访问
if (bkp_data_read(BKP_DATA_0) != 0xA5A5)//发现RTC掉电
{
bkp_deinit();
rcu_osci_on(RCU_LXTAL);//启用"外部32.768KHz晶振"时钟源
temp=0;
while(rcu_flag_get(RCU_FLAG_LXTALSTB) == RESET && temp < 5000)
{
temp++;
delay_ms(1);
}
if(temp >= 5000)return 1;//初始化时钟失败,晶振有问题
rcu_osci_stab_wait(RCU_LXTAL);//等待"外部32.768KHz晶振"时钟源稳定
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);//设置RTC时钟源选择外部32.768KHz
rcu_periph_clock_enable(RCU_RTC);//使能"RCU_RTC"时钟
rtc_register_sync_wait();//等待"寄存器与APB1时钟同步"
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
rtc_interrupt_enable(RTC_INT_SECOND);//秒中断使能
rtc_interrupt_enable(RTC_INT_ALARM);//闹钟中断使能
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
rtc_configuration_mode_enter();//允许配置
rtc_prescaler_set(32767);//将32767的值写入RTC预分频器
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
RTC_Set(2021,3,5,14,20,00); //设置时间
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
rtc_register_sync_wait();//等待"寄存器与APB1时钟同步"
rtc_configuration_mode_exit();//不使能RTC配置
//RTC_Alarm_Set(2021,3,5,14,25,00);//设置5分钟后RTC报警
bkp_data_write(BKP_DATA_0, 0xA5A5);
}
else//系统继续计时
{
rcu_periph_clock_enable(RCU_PMU);//使能RCU_PMU外设时钟
pmu_backup_write_enable();//使能对备份域寄存器的写访问
rtc_register_sync_wait();//等待"寄存器与APB1时钟同步"
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
rtc_interrupt_enable(RTC_INT_SECOND);//秒中断使能
rtc_interrupt_enable(RTC_INT_ALARM);//闹钟中断使能
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
}
RTC_NVIC_Configuration();
RTC_Get();//更新时间
RTC_Alarm_After_xSecond(60);//设置1分钟后RTC报警
return 0;//ok
}
//函数功能:判断是否是闰年
//测试时间:2018年11月8日
u8 Is_Leap_Year(u16 year)
{
if(year % 4 == 0) //必须能被4整除
{
if(year % 100 == 0)
{
if(year % 400 == 0)return 1; //如果以00结尾,还要能被400整除
else return 0;
}
else return 1;
}
else return 0;
}
//函数功能:设置时钟
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount = 0;
if(syear < 1970 || syear > 2099)return 1;
for(t = 1970; t < syear; t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount += 31622400; //闰年的秒钟数
else seccount += 31536000; //平年的秒钟数
}
smon -= 1;
for(t = 0; t < smon; t++) //把前面月份的秒钟数相加
{
seccount += (u32)mon_table[t] * 86400;//月份秒钟数相加
if(Is_Leap_Year(syear) && t == 1)seccount += 86400; //闰年2月份增加一天的秒钟数
}
seccount += (u32)(sday - 1) * 86400; //把前面日期的秒钟数相加
seccount += (u32)hour * 3600; //小时秒钟数
seccount += (u32)min * 60; //分钟秒钟数
seccount += sec; //最后的秒钟加上去
rcu_periph_clock_enable(RCU_BKPI); //使能"RCU_BKPI备份寄存器时钟"
rcu_periph_clock_enable(RCU_PMU); //使能"RCU_PMU电源管理单元时钟"
pmu_backup_write_enable();//使能对备份域寄存器的写访问
rtc_counter_set(seccount);//将预设时间(总秒数值)写入RTC计数器
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
return 0;
}
u8 RTC_Alarm_Set(u16 year, u8 month, u8 day, u8 hour, u8 min, u8 sec)
{
u16 t;
u32 seccount = 0;
if(year < 1970 || year > 2099) return 1;
for(t = 1970; t < year; t++)
{
if(Is_Leap_Year(t)) seccount += 31622400;
else seccount += 31536000;
}
month -= 1;
for(t = 0; t < month; t++)
{
seccount += (u32)mon_table[t] * 86400;
if(Is_Leap_Year(year) && t == 1) seccount += 86400;
}
seccount += (u32)(day - 1) * 86400;
seccount += (u32)hour * 3600;
seccount += (u32)min * 60;
seccount += sec;
rcu_periph_clock_enable(RCU_BKPI); //使能"RCU_BKPI备份寄存器时钟"
rcu_periph_clock_enable(RCU_PMU); //使能"RCU_PMU电源管理单元时钟"
pmu_backup_write_enable();//使能对备份域寄存器的写访问
rtc_configuration_mode_enter();//允许配置
rtc_alarm_config(seccount);//将"闹钟时间"(总秒数值)的值写入RTC闹钟寄存器
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
rtc_configuration_mode_exit();//不使能RTC配置
return 0;
}
//函数功能:获取时钟
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
//RTC->CNTH和RTC->CNTL可以保存110年的总秒数是0xCEC42100
//timecount=RTC->CNTH;//得到计数器中的值(秒钟数)
//timecount<<=16;
//timecount+=RTC->CNTL;
timecount=rtc_counter_get();
calendar.ReadTotalSecond=timecount;//记录总秒数
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp)//超过一天了
{
daycnt=temp;
temp1=1970; //从1970年开始
while(temp>=365)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)temp-=366;//闰年的秒钟数
else break;
}
else temp-=365; //平年
temp1++;//年份加一
}
calendar.w_year=temp1;//保存年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
{
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+1; //得到月份
calendar.w_date=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
calendar.hour=temp/3600; //小时
calendar.min=(temp%3600)/60; //分钟
calendar.sec=(temp%3600)%60; //秒钟
//calendar.msec=(32767-RTC_GetDivider())*1000/32767;//读取毫秒值
temp=rtc_counter_get();//Gets the RTC divider value
temp=32767-temp;
temp=temp*1000;
calendar.msec=temp/32767;
return 0;
}
//函数功能:设置xSecond秒后报警
u8 RTC_Alarm_After_xSecond(u16 xSecond)
{
u32 seccount;
RTC_Get();
seccount=calendar.ReadTotalSecond;
seccount=seccount+xSecond;
rcu_periph_clock_enable(RCU_BKPI); //使能"RCU_BKPI备份寄存器时钟"
rcu_periph_clock_enable(RCU_PMU); //使能"RCU_PMU电源管理单元时钟"
pmu_backup_write_enable();//使能对备份域寄存器的写访问
rtc_configuration_mode_enter();//允许配置
rtc_alarm_config(seccount);//将"闹钟时间"(总秒数值)的值写入RTC闹钟寄存器
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
rtc_configuration_mode_exit();//不使能RTC配置
return 0;
}
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year, u8 month, u8 day)
{
u16 temp2;
u8 yearH, yearL;
yearH = year / 100;
yearL = year % 100;
// 如果为21世纪,年份数加100
if (yearH > 19)yearL += 100;
// 所过闰年数只算1900年之后的
temp2 = yearL + yearL / 4;
temp2 = temp2 % 7;
temp2 = temp2 + day + table_week[month - 1];
if (yearL % 4 == 0 && month < 3)temp2--;
return(temp2 % 7);
}
const char RTC_rn_REG[]="\r\n";
//函数功能:RTC显示时间
void Time_Print_Display(void)
{
printf("%s",RTC_rn_REG);
printf("Date: %0.4d-%0.2d-%0.2d ",calendar.w_year,calendar.w_month,calendar.w_date);
printf("Time: %0.2d:%0.2d:%0.2d:%0.3d",calendar.hour,calendar.min,calendar.sec,calendar.msec);
}
//函数功能:RTC时钟中断
void RTC_IRQHandler(void)
{
if (rtc_flag_get(RTC_FLAG_SECOND) != RESET)
{
RTC_Get();
//BEEP = 1;
}
if (rtc_flag_get(RTC_FLAG_ALARM) != RESET)
{//闹钟标志
RTC_Alarm=1;
RTC_Alarm_After_xSecond(60); //设置1分钟后RTC报警
rtc_flag_clear(RTC_FLAG_ALARM); //清除"闹钟"标志位
rtc_interrupt_flag_clear(RTC_INT_FLAG_ALARM);//清除"闹钟"中断标志
}
rtc_flag_clear(RTC_FLAG_SECOND);//清除"秒标志"位
rtc_interrupt_flag_clear(RTC_INT_FLAG_SECOND);//清除"秒中断标志"
rtc_lwoff_wait();//等待"上次对RTC寄存器写操作完成"
}
#ifndef __RTC_H
#define __RTC_H
#include "sys.h"
//#include "gd32f10x.h" //使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t,bool
extern u8 RTC_Alarm;
typedef struct
{
vu8 hour;
vu8 min;
vu8 sec;
vu16 msec;
vu16 w_year;
vu8 w_month;
vu8 w_date;
vu8 week;
u32 ReadTotalSecond;
}_calendar_obj;
extern _calendar_obj calendar;
extern _calendar_obj calendarAlarm;
extern u8 Is_Leap_Year(u16 year);
extern u8 RTC_Init(void);
extern u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
extern u8 RTC_Get(void);
extern u8 RTC_Alarm_After_xSecond(u16 xSecond);
extern u8 RTC_Get_Week(u16 year, u8 month, u8 day);
extern u8 RTC_Alarm_Set(u16 year, u8 month, u8 day, u8 hour, u8 min, u8 sec);
extern void Time_Print_Display(void);
#endif
main.c如下:
#include "gd32f10x.h" //使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t
#include "delay.h"
#include "stdio.h" //使能printf(),sprintf()
#include "UART3.h"
#include "StandbyMode.h"
#include "RTC.h"
uint8_t MainCnt;
const char CPU_Reset_REG[]="\r\nCPU reset!\r\n";
//PC13仅可以作为RTC功能引脚
//PC14和PC15可以作为通用I/O口或外部32.768KHz低速晶振引脚
int main(void)
{
//MySystemClockInit(1,1);
//NVIC_PRIGROUP_PRE4_SUB0:抢占优先级为4bit(取值为0~15),子优先级为0bit(没有响应优先级)
//NVIC_PRIGROUP_PRE3_SUB1:抢占优先级为3bit(取值为0~7),子优先级为1bit(取值为0~1)
//NVIC_PRIGROUP_PRE2_SUB2:抢占优先级为2bit(取值为0~3),子优先级为2bit(取值为0~3)
//NVIC_PRIGROUP_PRE1_SUB3:抢占优先级为1bit(取值为0~1),子优先级为3bit(取值为0~7)
//NVIC_PRIGROUP_PRE0_SUB4:抢占优先级为0bit(没有抢占优先级),子优先级为3bit(取值为0~15)
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);//设置系统中断优先级"抢占优先级为4bit,子优先级为0bit"
INTX_ENABLE();//开启所有中断
GD32F103_UART3_Init(115200);//初始化UART3
printf("%s",CPU_Reset_REG);//调试串口输出"\r\nCPU reset!\r\n"
delay_init();
RTC_Init();
MainCnt=0;
while(MainCnt<10)
{
MainCnt++;
delay_ms(500);
Time_Print_Display();
if(RTC_Alarm)
{
printf("\r\nRTC_Alarm0");
RTC_Alarm=0;
}
}
printf("\r\nWFI\r\n");
// Enter_StandbyMode0_Use_WFI_CMD(); //使用WFI命令使CPU进入待机模式
Enter_StandbyMode0_Use_WFE_CMD(); //使用WFE命令使CPU进入待机模式
//RTC报警后,退出待机模式后CPU复位
///下面的语句不会执行///
while(1)
{
delay_ms(500);
Time_Print_Display();
if(RTC_Alarm)
{
printf("\r\nRTC_Alarm1");
RTC_Alarm=0;
}
}
}