RTC实时时钟通常是指一个集成电路,RTC本质上是一个独立的定时器,通常情况下需要外接一个32.768KHZ的晶振和匹配电容(10~33pf),由于时间是不停止的,为了满足这一要求,所以RTC实时时钟有两种供电方式:
1)在设备正常运行的时候,RTC实时时钟模块是由MCU主电源进行供电。
2)在主电源停止供电的时候,RTC实时时钟由备份电源(纽扣电池)来进行供电,保证当MCU停止供电的情况下,RTC不受影响,保持正常工作。
1)基于Windows操作系统:1900年1月1日00:00:00
2)基于Linux操作系统:1970年1月1日00:00:00
STM32F40x实时时钟(RTC)模块是一个独立的BCD码定时器/计数器,除了可以正常的提供日历功能外,还可以对MCU进行唤醒。并且在MCU复位后,RTC的寄存器是不允许正常访问的(无法对RTC寄存器进行写操作,但可以进行读操作寄存器)。
(1)可以直接提供,秒,分钟,小时(12/24小时制)、星期几、日期、月份、年份的日历
(2)具有闹钟功能,并且可以对闹钟进行日期编程。
(3)具有自动唤醒单元,可以周期性的更新事件显示
(4)RTC模块的中断源为:闹钟A,闹钟B,唤醒,时间戳以及入侵检测
(5)RTC模块具有独立备份区域,可以对发生入侵事件的时间进行保存。
1)、在系统复位后,需要把电源控制寄存器(PWR_CR)的DBP位置1,以使能RTC寄存器的写访问。
2)、上电复位后,需要早通过向写保护寄存器(RTC_WPR)写入0XCA和0x53,来解除寄存器的写保护,写入一个错误的数值(除了0xCA和0x53)会再次激活写保护。
1)、首先需要把初始化状态寄存器(RTC_ISR)中的INIT位置1,进入初始化模式,在次模式下,日历计数器将停止工作并且寄存器中的值是可以被更新的。
2)、配置为初始化模式后,RTC寄存器不能立即进入初始化状态,所以在配置为初始化模式后,必须轮询等待初始化寄存器(RTC_ISR)中的INIT位置1,才可以更新时间和日期。
3)、设置RTC_PRER寄存器中的同步预分频器和异步预分频器,把时钟的频率设置为1HZ。
4)、设置RTC_TR,RTC_DR寄存器中的时间和日期,并在RTC_CR寄存器中的FMT位设置时间的格式(12小时制或24小时制)
5)、对初始化寄存器(RTC_ISR)中的INIT位清0则退出初始化模式,当初始化模式序列完成后,日历开始计数。
1)、把控制寄存器(RTC_CR)中的闹钟A和闹钟B的使能位清零,关闭闹钟A和闹钟B。
2)、轮询等待初始化状态寄存器(RTC_ISR)寄存器中的闹钟写入标志位置1,进入闹钟的编程模式。
3)、根据需要,对闹钟A寄存器(RTC_ALRMAR)和闹钟B寄存器(RTC_ALRMBR)的闹钟值和产生闹钟的条件进行编译。
4)、把控制寄存器(RTC_CR)中的闹钟A(ALRAE)和闹钟B(ALRBE)的使能位置1,使能闹钟A和闹钟B。
5)、设置闹钟中断。
6)、编写闹钟中断服务函数。
由于日历和时间寄存器都存在影子寄存器,所以在读取时间和日历值之前,必须保证影子寄存器的数据和上层寄存器的值同步(等待日历和时间标志位被置1,RTC_ISR[5]),才能读取时间和日历寄存器。
存放RTC模块的时间数据,存储方式是以BCD码的格式来存储。
存放RTC模块的日期数据,存储方式是以BCD码的格式来存储。
设置RTC模块的工作模式
存放RTC模块的初始化状态标志位
存放RTC模块预分频值,这个寄存器包含异步预分频器和同步预分频器。如果更改其中一个分频值,都必须对异步和同步预分频器单独操作一次。
存放唤醒计数重装载值。
存放闹钟A的闹钟值,存储方式都是以BCD码存储。
存放闹钟B的闹钟值,存储方式都是以BCD码存储。
解除RTC模块的写保护,以及激活写保护。
后备区域存储器,用户可对这些寄存器写入数据或者读出数据,可以通过在备份寄存器中写入国定的数据来判断芯片是否是第一次使用RTC,在系统复位后运行RTC初始化时,提示是否需要配置时间。
这个寄存器再使用RTC模块时,只使用到了第8位(DBP)。
RTC模块时钟源选择以及使能RTC模块时钟。
#include "rtc.h"
/*
函数名称:u8 Rtc_Init(void)
函数功能:RTC 初始化
函数形参:无
函数返回值:1----失败;0-----成功
*/
char *time = __TIME__;
char *date = __DATE__;
char *month[12] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
_time my_time;
u8 year1,month1,day1;
u8 buff1[9];
u8 Rtc_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
u32 temp = 0;
RCC->APB1ENR |= 1 <<28;//开启电源接口时钟
PWR->CR |= 1 << 8;//开启备份域访问
//选择时钟源
RCC->BDCR &= ~(3 << 8);
RCC->BDCR |= 1 << 8;//选择外部低速时钟
RCC->BDCR |= 1 << 0;//开启外部低速时钟使能
//等待外部时钟就绪
while(!(RCC->BDCR & (1 << 1)))
{
temp++;
TIM3_us(1);
if(temp >= 100000)
{
return 1;//起振失败
}
}
RCC->BDCR |= 1 << 15;//开启RTC时钟使能
RTC_WaitForLastTask();//等待标志位RTOFF置1(等待上次写操作完成)
RTC_EnterConfigMode();//进入配置模式
RTC_SetPrescaler(32768-1); //设置预分频系数
//获取时间
my_time.hour = (time[0] - '0') * 10 + (time[1] - '0');
my_time.min = (time[3] - '0') * 10 + (time[4] - '0');
my_time.sec = (time[6] - '0') * 10 + (time[7] - '0');
//获取日期
my_time.year = (date[9] - '0') * 10 + (date[10] - '0') + 2000;
if(date[4] != ' ')
{
my_time.day = (date[4] - '0') * 10 + (date[5] - '0');
}
else
{
my_time.day = (date[5] - '0');
}
for(u8 i = 1;i <= 12;i++)
{
if(strncmp(date,month[i - 1],3) == 0)
{
my_time.mon = i;
break;
}
}
year1 = (date[9] - '0') * 10 + (date[10] - '0');
month1 = my_time.mon;
day1 = my_time.day;
//设置时间
RTC_Set_Time(my_time.year,my_time.mon,my_time.day,my_time.hour,my_time.min,my_time.sec);
RTC_WaitForLastTask();//等待写操作完成
RTC_ExitConfigMode();//退出配置模式
printf(" RTC 初始化成功\r\n");
RTC_WaitForSynchro();//等待 RTC 与 APB1 同步
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//设置为秒中断
RTC_ITConfig(RTC_IT_SEC,ENABLE);
return 0;
}
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
RTC_ClearITPendingBit(RTC_IT_SEC);
RTC_Get_Time();
sprintf((char *)buff1," %d-%d %d:%d:%d ",month1,day1,my_time.hour,my_time.min,my_time.sec);
OLED_String_Disp(0,0,(char *)buff1 );
printf("date:%d-%d-%d %d:%d:%d\r\n",my_time.year,month1,day1,my_time.hour,my_time.min,my_time.sec);
}
}
/*******************************************************************
* 函数名: Is_Leap_Year()
* 功能描述 : 判断年份是否为闰年
* 返回值说明: 1:闰年 0:平年
*******************************************************************/
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;
}
/*******************************************************************
* 函数名: RTC_Set_Time()
* 功能描述 : 设置时间
* 返回值说明: 0:成功;其他:失败
*******************************************************************/
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};//平年每个月的天数
u8 RTC_Set_Time(u16 year,u8 month,u8 day,u8 hour,u8 min,u8 sec)
{
u16 i;
u32 second_cnt=0;
if(year<1970||year>2099) return 1;
/********************计算年份共有多少秒*****************************/
for(i=1970;i=365)
{
if(Is_Leap_Year(my_time.year))day_cnt-=366;
else day_cnt-=365;
my_time.year +=1;
}
/******************计算剩下的秒中共有多少月*********************/
t=0;
while(day_cnt>=mon_table[t])
{
if((Is_Leap_Year(my_time.year))&&(t==1))day_cnt-=(mon_table[t]+1);
else day_cnt-=mon_table[t];
t++;
}
temp=second_cnt%86400;
my_time.mon =t+1;
my_time.day =day_cnt+1;
my_time.hour=temp/3600;
my_time.min =(temp%3600)/60;
my_time.sec =(temp%3600)%60;
}