由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(BKP)来标记是否配置过时钟
简单介绍BKP:备份寄存器是 42 个 16 位的寄存器( Mini 开发板就是大容量的),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里, 当 VDD 电源被切断,他们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。此外, BKP 控制寄存器用来管理侵入检测和 RTC 校准功能。
简单说一下我对时钟工作原理的理解:一个32位的计数器,从0向上计数的话,假设每加一就是1秒,那么一个32位的计数器跑到溢出需要100多年。。已经很长了,这里时钟自带一个秒中断,当每加一的时候就会触发一次秒中断,我们通过往秒中断里写更新时间的函数来达到时间同步的效果
由于rtc.c里函数很多。。我就贴一下说几个比较重要的吧。。
#include "rtc.h"
_calendar_obj calendar;//时钟结构体
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
const u8 table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
u8 Is_LeapYear(u16 year)
{
return (year%4==0&&year%100!=0)||year%400==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);
}
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//得到当前的时间,结果保存在calendar结构体里面
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=RTC_GetCounter();
u32 daynum=timecount/86400;
u16 tem=0;
if(daycnt!=daynum)//大于1天
{
daycnt=daynum;
tem=1970;
while(daynum>=365)
{
if(Is_LeapYear(tem))
{
if(daynum>=366)daynum-=366;
else break;
}
else daynum-=365;
tem++;
}
calendar.w_year=tem;//年
tem=0;
while(daynum>=28)
{
if(Is_LeapYear(calendar.w_year)&&tem==1)
{
if(daynum>=29)daynum-=29;
else break;
}
else
{
if(daynum>=mon_table[tem])daynum-=mon_table[tem];
else break;
}
tem++;
}
calendar.w_month=tem+1;//月
calendar.w_date=daynum+1;//日
}
daynum=timecount%86400;
calendar.hour=daynum/3600;//时
calendar.min=(daynum%3600)/60;//分
calendar.sec=(daynum%3600)%60;//秒
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);
return 0;
}
//设置时钟
//把输入的时钟转换为秒钟
//以 1970 年 1 月 1 日为基准
//1970~2099 年为合法年份
//返回值:0,成功;其他:错误代码.
//平年的月份日期表
//year,mon,day,hour,min,sec:年月日时分秒
//返回值:设置结果。 0,成功; 1,失败。
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
u16 i;u32 seccnt=0;
if(year<1970||year>2099)return 1;
for(i=1970;i=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//设置RTC时钟
RCC_RTCCLKCmd(ENABLE);//使能 RTC 时钟
RTC_WaitForLastTask();//等待最近一次对 RTC 寄存器的写操作完成
RTC_WaitForSynchro();//等待 RTC 寄存器同步
RTC_ITConfig(RTC_IT_SEC,ENABLE);//使能 RTC 秒中断
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_SetPrescaler(32767); //设置 RTC 预分频的值
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_Set(2009,12,2,10,0,55); //设置时间
RTC_ExitConfigMode(); //退出配置模式
//向指定的后备寄存器中写入用户程序数据 0x5050
BKP_WriteBackupRegister(BKP_DR1,0X5050);
}
else
{
RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能 RTC 秒中断
RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成
}
RTC_NVIC_Config(); //RCT 中断分组设置
RTC_Get(); //更新时间
return 0; //ok
}
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)//秒中断
{
RTC_Get();//更新时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!=RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR);//清闹钟中断
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);//清中断
RTC_WaitForLastTask();
}
说一个比较粗心的地方(导致调试了一晚上都没调好),在写RTC_Get()函数(更新时间)的时候不小心将一个16位的变量当成32的用了。。结果是一晚上都忙的热火朝天还没找到是哪错了,早晨来到一直到中午才弄好。。sad
rtc.h
#ifndef _RTC_H
#define _RTC_H
#include "sys.h"
#include "delay.h"
#include "usart.h"
typedef struct
{
vu8 hour;
vu8 min;
vu8 sec;
//公历日月年周
vu16 w_year;
vu8 w_month;
vu8 w_date;
vu8 week;
}_calendar_obj;
extern _calendar_obj calendar;//日历结构体
//void Disp_Time(u8 x,u8 y,u8 size); //在制定位置开始显示时间
//void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
u8 RTC_Init(void); //初始化 RTC,返回 0,失败;1,成功;
u8 Is_LeapYear(u16 year); //平年,闰年判断
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
#endif
主函数
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "usmart.h"
#include "rtc.h"
void init(void)
{
NVIC_Configuration();
delay_init();
uart_init(9600);
LED_Init();
LCD_Init();
usmart_dev.init(72);//初始化SMART组件
}
int main(void)
{
u8 t;
init();
POINT_COLOR=RED;
while(RTC_Init()) //RTC初始化 ,一定要初始化成功
{
LCD_ShowString(60,130,200,16,16,"RTC ERROR! ");
delay_ms(800);
LCD_ShowString(60,130,200,16,16,"RTC Trying...");
}
//显示时间
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,130,200,16,16," - - ");
LCD_ShowString(60,162,200,16,16," : : ");
while(1)
{
if(t!=calendar.sec)
{
t=calendar.sec;
LCD_ShowNum(60,130,calendar.w_year,4,16);
LCD_ShowNum(100,130,calendar.w_month,1,16);
LCD_ShowNum(124,130,calendar.w_date,2,16);
switch(calendar.week)
{
case 0:
LCD_ShowString(60,148,200,16,16,"Sunday ");
break;
case 1:
LCD_ShowString(60,148,200,16,16,"Monday ");
break;
case 2:
LCD_ShowString(60,148,200,16,16,"Tuesday ");
break;
case 3:
LCD_ShowString(60,148,200,16,16,"Wednesday");
break;
case 4:
LCD_ShowString(60,148,200,16,16,"Thursday ");
break;
case 5:
LCD_ShowString(60,148,200,16,16,"Friday ");
break;
case 6:
LCD_ShowString(60,148,200,16,16,"Saturday ");
break;
}
LCD_ShowNum(60,162,calendar.hour,2,16);
LCD_ShowNum(84,162,calendar.min,2,16);
LCD_ShowNum(108,162,calendar.sec,2,16);
LED0=!LED0;
}
delay_ms(10);
}
}