1.STM32 RTC介绍:
(1)RTC简介:
STM32的实时时钟(RTC)是一个独立的定时器。STM32的RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
(2)RTC结构框图:
注意:
- 存在:秒中断、闹钟中断;
- 不存在:溢出中断;
- 一般使用外部时钟,32.768K晶振时钟频率;
2.RTC配置步骤:
RTC相关库函数在stm32f10x_rtc.c和stm32f10x_rtc.h文件中;
- 使能电源时钟和后备域时钟,开启RTC后备存储器写访问:RCC_APB1PeriphClockCmd()、PWR_BackupAccessCmd();
- 复位备份区域,开启外部低速振荡器:BKP_DeInit()、RCC_LSEConfig();
- 选择RTC时钟,并使能:RCC_RTCCLKConfig()、RCC_RTCCLKCmd();
- 设置RTC的分频以及配置RTC时钟:RTC_EnterConfigMode()、RTC_ExitConfigMode()、 void RTC_SetPrescaler()、void RTC_ITConfig()、void RTC_SetCounter();
- 更新配置,设置RTC中断分组:RTC_ExitConfigMode()、void BKP_WriteBackupRegister()、uint16_t BKP_ReadBackupRegister();
- 编写RTC中断服务函数:RTC_IRQHandler、FlagStatus RTC_GetFlagStatus()、RTC_ClearITPendingBit();
注意:要访问RTC和RTC备份域,必须先使能电源和后备域的时钟,然后再使能RTC后备域的写访问;
3.RTC实时时钟实验:
功能实现:设置RTC时间日期初值,在RTC秒中断内使用串口打印出RTC日期和时间,LED0指示灯闪烁提示系统运行。
(1)主函数:
#include "delay.h"
#include "led.h"
#include "key.h"
#include "usart1.h"
#include "rtc.h"
int main(){
u8 i=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置分组
delay_init(); //延时初始化
LED_Init();
usart1_Init(9600); //串口通信初始化
RTC_Init(); //RTC初始化
while(1)
{
//系统指示灯闪烁
i++;
if(i%20==0)
{
LED0=!LED0;
}
delay_ms(10);
}
}
(2)头文件:
#ifndef __RTC_H
#define __RTC_H
typedef unsigned char u8;
typedef unsigned short u16;
u8 RTC_Init(void); //RTC初始化
u8 Is_Leap_Year(u16 year); //判断是否是闰年
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); //设置当前时间
u8 RTC_Get(void); //获取当前时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day); //获取当前是星期几
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); //初始化闹钟
#endif
(3)RTC实时时钟功能函数:
#include "stm32f10x.h"
#include "delay.h"
#include "stdio.h"
#include "rtc.h"
//时间结构体
typedef struct
{
u8 hour; //时
u8 min; //分
u8 sec; //秒
u16 w_year; //年
u8 w_month; //月
u8 w_date; //日
u8 week; //周
}_calendar;
_calendar calendar; //定义结构体日历calendar
/*
功能:中断管理
*/
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=RTC_IRQn; //设置中断通道
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2; //子优先级
NVIC_Init(&NVIC_InitStruct);
}
/*
功能:RTC初始化
变量:无
返回值:返回值不为0:表示初始化成功;返回值为0:表示初始化不成功
*/
u8 RTC_Init()
{
u8 time;
//1.使能电源时钟和后备域时钟,开启RTC后备寄存器写访问
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); //使能电源时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE); //使能后备域时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
//2.判断初始化是否完成
if(BKP_ReadBackupRegister(BKP_DR1)!=0xA0A0) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
//3.复位备份区域,开启外部低速振荡器
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //开启外部低速振荡器
//4.判断RTC外部时钟好坏
while((RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)&&(time<100))
{
time++;
delay_ms(10);
}
//5.若出现超时,则晶振可能出现问题
if(time>100)
{
return 1; //初始化时钟失败,晶振存在问题
}
//6.选择RTC时钟,并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
//7.等待APB1时钟和RTC时钟同步
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
//8.设置RTC的分频以及配置RTC时钟
RTC_EnterConfigMode(); //进入配置模式
RTC_SetPrescaler(32767); //设置RTC预分频的值,因为外部低速晶振32.768KHz,计数一次为1s
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
//9.设置初始化时间
RTC_Set(2023,7,10,22,20,50); //设置时间
RTC_ExitConfigMode(); //退出配置模式
//10.写入初始化完成标志值
BKP_WriteBackupRegister(BKP_DR1,0xA0A0); //对后备域写入标志位。方便下一次对RTC进行操作,不必再次设置时间
}
else //已经初始化成功,系统继续计时
{
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
//11.配置中断优先级
RTC_NVIC_Config();
//12.获取当前日历
RTC_Get();
return 0;
}
/*
功能:中断服务函数
变量:无
返回值:无
*/
void RTC_IRQHandler()
{
if(RTC_GetITStatus(RTC_IT_SEC)) //判断是否获取到秒中断标志位
{
RTC_Get(); //每一秒更新时间
printf("RTC TIME:%d-%d-%d %d:%d:%:\r\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec); //打印出日历数据
}
}
/*
功能:判断是否是闰年
变量:year:要判断的年份
返回值:返回1则是闰年,返回0则不是闰年
注意:
月份: 1 2 3 4 5 6 7 8 9 10 11 12
闰年: 31 29 31 30 31 30 31 31 30 31 30 31
非闰年: 31 28 31 30 31 30 31 31 30 31 30 31
*/
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 const 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};
/*
功能:设置日期时间(以1970年1月1日为基准,把输入的时钟转换为秒钟),1970~2099年为合法年份
变量:变量分别为:年、月、日、时、分、秒
返回值:返回值为1则设置失败,返回值为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; //最后的秒钟加上去
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问
RTC_SetCounter(seccount); //设置RTC计数器的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}
/*
功能:初始化闹钟
变量:变量分别为:年、月、日、时、分、秒
返回值:0表示成功,1表示失败
注意: 以1970年1月1日为基准
*/
u8 RTC_Alarm_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; //最后的秒钟加上去
//设置时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
RTC_SetAlarm(seccount);
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}
/*
功能:获取当前时间
变量:无
返回值:0表示成功,1表示失败
*/
u8 RTC_Get()
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC_GetCounter();
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
{
temp1++;
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.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
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);
}
注意:串口需要输入模式需要设置为浮空输入模式: