STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
由于时钟只需要配置一次,下次开机不需要重新配置(开发板有电池的情况下),所以需要用到备份区域(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<year;i++) { if(Is_LeapYear(i))seccnt+=31622400; else seccnt+=31536000; } mon-=1; for(i=0;i<mon;i++) { seccnt+=(u32)mon_table[i]*86400; if(Is_LeapYear(year)&&i==1) seccnt+=86400; } seccnt+=(u32)(day-1)*86400; seccnt+=(u32)hour*3600; seccnt+=(u32)min*60; seccnt+=(u32)sec; //使能 PWR 和 BKP 外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问 RTC_SetCounter(seccnt); //设置 RTC 计数器的值 RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成 return 0; } //初始化RTC u8 RTC_Init(void) { u8 tem=0; if(BKP_ReadBackupRegister(BKP_DR1)!=0x5050)//如果没配置过 { //使能 PWR 和 BKP外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE);//使能后备寄存器访问 BKP_DeInit();//③复位备份区域 RCC_LSEConfig(RCC_LSE_ON);//设置外部低速晶振(LSE) //检查指定的RCC标志位设置与否,等待低速晶振就绪 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET) { tem++; delay_ms(10); } if(tem>=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(); }
我们是以1970年为基准,往后计时的,即当1970年一月一日00点00分00秒时,计数器从0开始计时。。
说一个比较粗心的地方(导致调试了一晚上都没调好),在写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); } }
通过USMART就可以设置任意时间啦。。
时间: 2024-10-02 04:34:58