五一假期已过,你们是否还像五一五二五三那样快乐呢??答案就交给大家本身寻找了哈、、说到五1、、就从五一开始的那一刻起、、就开始计时着、、到五一假期结束、、呵呵、、在这里,智商和情商比我高的人估计又猜到我要说什么了、、关于日期,关于时间、STM32也提供了强大的RTC模块、、至于RTC模块究竟是哪三个英文单词的缩写,我就不说了、好了,言归正传、这个语气、请你们想一想周星驰唐伯虎点秋香当中华安与对穿王的对白:我俩、、、言归正传:算法
在这里、先别急,我们来看看通常的RTC模块(芯片):编程
一、 描述:RTC芯片是一种能提供日历(年月日)/时钟(时分秒)、数据存储等功能的专用集成电路;数组
二、应用环境:一般使用后备电池,以保证其在系统掉电的状况下运行;函数
其时钟源由外部32.768KHz晶振提供,能够实现闹钟功能。字体
三、做用:应用于某些系统的时钟信号源和参数设置的存储电路。spa
RTC具备计时准确、耗电低和体积小等特色,特别是在各类嵌入式系统中用于记录事件发生的时间和相关信息, 如通讯工程、电力自动化、工业控制等领域。code
再来看看STM32中RTC有哪些区别呢(概述,具体分析请别急哈、):blog
一、不具备提供日历/时钟功能;接口
二、可以提供一个精确的秒周期信号:配置RTCCLK以及RTC_DIV,使得预分频器产生频率为1秒的秒脉冲(TR_CLK),做为RTC的时钟基准。事件
三、具备闹钟功能。
4:特色:(1)保护寄存器---防止误操做(请注意这一点哈,很是关键)
(2)3个事件/中断源:秒、溢出、闹钟(连外部中断线17上,用于将MCU从中止模式唤醒)
(3)RTC内核和时钟的设置位于备份区域(也请注意这一点哈,由于要使RTC能记忆,就靠它了):
有独立的VBAT供电;
只能由备份区域复位才能将其复位;
从待机模式唤醒后,RTC的设置仍被保留。
啊哈、、以上摘自STM3210X+其他模块培训资料、、在这里摆出来就是为了给你们有一个比较全面的认识先、、莫告我盗版哈、、好了、、接下来,来看看时钟吧、、
翻开“葵花宝典”第STM32篇之RTC参考手册能够看出:能够选择如下三种RTC的时钟源:(直接看图哈)这下就不说是美丽的涂鸦了、、、
在这里,咱们选择独立的32.768KHz晶振(LSE)来做为外部时钟源、提早说下:只要在RTC的预分频转载寄存器中写入0x7fff(也就是32767),就能够产生以秒为单位的信号了、、
接下来,咱们来看看参考手册里的一段话:(RTC核心)由一组可编程计数器组成,分红两个主要模块。
第一个模块是RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC预分频器)。若是在RTC_CR寄存器中设置了相应的容许位,则在每一个TR_CLK周期中RTC产生一个中断(秒中断)。
第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,若是RTC_CR控制寄存器中设置了相应容许位,比较匹配时将产生一个闹钟中断。
接下来请允许我介绍下几位“大神”:
从红色区域咱们能够看出咱们这里要实现以秒为单位的计时,就要设置RTC_CRH的最低位为1;
接下来请看(请注意红色区域,再对照前面红色字体的标注):
还有几个寄存器在此就不作具体介绍了,你们能够参考中文手册,在此仅列出:
RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)
RTC计数器寄存器 (RTC_CNTH / RTC_CNTL)
注:由于RTC是由独立的时钟源提供,不挂载在ABP桥上,可是,奇怪的是;软件又是经过ABP1桥来访问RTC的寄存器,因为可读寄存器只在RTC ABP1桥时钟进行同步的RTC时钟上升沿被更新,可读标志位也是这样,这就涉及到一个概念:同步,假如ABP1被刚刚开启,在第一次的寄存器更新以前,从ABP1桥上读取的RTC的寄存器就有可能被破坏了,为了不此种状况的发生,在读取寄存器的时候必定要等待他们同步、、至于如何操做、、请看上图寄存器的红色标记
好了、、人须要记忆、、不然失忆了,对彼此都不是一件好事、、固然、RTC也同样、、你不但愿RTC在系统复位或者一些意外后而是RTC失效、、因此、、RTC也要有记忆的功能、至于如何记忆呢?STM32也为咱们提供了BKP模块:翻开“葵花宝典”第STM32篇之BKP备份寄存器有这么一段描述:
备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
因此,咱们在这就要开启BKP的时钟、、
然而、、事情并非那么的简单、、在这里、、仅仅打开BKP的时钟还不够、、由于某种特殊缘由、、请看
由于系统复位后,RTC和后备寄存器处于被保护状态以防意外写入、、因此在此也要打开PWR的时钟,在这里,咱们须要取消保护,向后备寄存器写入一个字节0x5050或者0x0505都行,以标注时钟已经配置过了、、到时检查这个字节来决定是否要从新配置、、
而BKP PWR时钟都是挂载在ABP1桥上的:
那咱们要怎么来操做呢:请看代码注释:
1 u8 RTC_Init(void) 2 { 3 //检查是否是第一次配置时钟 4 u8 temp=0; 5 6 if(BKP_ReadBackupRegister(BKP_DR1)!= 0x5050)//从指定的后备寄存器中读出数据,读出了与写入的指定数据不相同,这时候须要初始化 7 { 8 //1/经过使能PER和BKP外设时钟来打开电源盒后备接口的时钟使其能对后备寄存器和RTC的访问 11 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能时钟 12 PWR_BackupAccessCmd(ENABLE); //取消备份域写保护13 //2/复位备份域,开启外部低速振荡器 14 /* Reset the BKP registers */ BKP_DeInit(); 15 /* Enable the HSE */ RCC_LSEConfig(RCC_LSE_ON); 16 17 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)//等待低速时钟准备就绪,在选择时钟前必定要等待时钟就绪、、不然、后果严重 18 { 19 temp++; 20 delay_ms(10); 21 } 22 if(temp >= 250) 23 { 24 return 1; 25 } 26 //3/选择时钟和使能时钟 27 /* Select the LSE as RTC clock source */RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); 28 /* Enable the RTC clock */RCC_RTCCLKCmd(ENABLE); 29 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操做完成。注:必定要等写操做完成 30 RTC_WaitForSynchro(); //等待RTC寄存器同步 31 32 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能秒中断 33 34 RTC_WaitForLastTask(); //等待最近一次对RTC的写操做完成 35 RTC_EnterConfigMode();/// 容许配置 36 37 RTC_SetPrescaler(32767); //设置RTC的预分频值 38 39 RTC_WaitForLastTask(); //等待最近一次对RTC写操做的完成 40 41 RTC_Set(2014,5,4,12,0,5); //设置时间 42 43 RTC_ExitConfigMode(); //退出配置 44 45 BKP_WriteBackupRegister(BKP_DR1, 0X5050); // 向指定的后备域写入一字节 46 47 } 48 else //不然,系统继续计时 49 { 50 RTC_WaitForSynchro(); 51 RTC_ITConfig(RTC_IT_SEC, ENABLE); //开启秒中断 52 RTC_WaitForLastTask(); 53 } 54 RTC_NVIC_Config(); 55 RTC_Get();//更新时间 56 return 0; 57 }
能够看出、、若是咱们要配置下一次,必定要等待上次对RTC的操做是否完成,若是没有,就必须等待上次结束才能开始下一次的操做、、同时,若是要对相关的寄存器进行写操做。、必定要进入配置,配置完以后记得退出配置//..
接下来,给中秒中断的中断服务函数,相信你们也比较清楚了:
3 void RTC_IRQHandler(void) 4 { 5 if (RTC_GetITStatus(RTC_IT_SEC) != RESET) 6 { 7 RTC_Get(); 8 } 9 if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) 10 { 11 RTC_ClearITPendingBit(RTC_IT_ALR); 12 } 13 RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); 14 RTC_WaitForLastTask(); 15 }
注:对于相关的库函数,因为篇幅的缘由,在此就不仔细列出来了、请你们谅解、、你们能够参照固件库、、
在这里给出战舰原子的时间算法程序:就不细讲了、、你们好好研究、、说实话、、我也有些不太懂、因此请你们多多交流:
//判断是不是闰年函数//月份 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) // { if(year%100==0) { if(year%400==0)return 1; 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 表示这个数组里的值不容许改变 //平年的月份日期表 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31}; 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_BackupAccessCmd(ENABLE); RTC_SetCounter(seccount); RTC_WaitForLastTask(); //注意、、此时也要等待、、 return 0; } //返回当前的时间 u8 RTC_Get(void) { 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; 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) { 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; } //得到是星期几 为了帮助理解,在原子论坛里找出一份,(别告我侵权哈,我在这里引用哈) 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; temp2=yearL+yearL/4; temp2=temp2%7; temp2=temp2+day+table_week[month-1]; if (yearL%4==0&&month<3)temp2--; return(temp2%7); }
有时候,想知道公元某年某月某日是星期几,能够用下面的公式算出来:
x:这一年是公元多少年。
y:这一天是这一年的第几天。
s:星期几。不过要先除以7,再取余数。没有余数是星期日,余数是1、2、3、4、5、6,
分别是星期1、星期2、星期3、星期4、星期5、星期六。
好比,今年国庆节(2010年10月1日)是星期几?
x=2010。
y=31+28+31+30+31+30+31+31+30+1=31×5+30×3+28+1=274。
s=2010-1+502-20+5+274=2770,2770÷7余5。
因此,今年国庆节是星期五。
若是,你只想知道这个公式怎样用,到这儿就能够了。而要想知道这个公式的道理是什么,那可就说来话长了。
“星期制”是公元321年3月7日,古罗马皇帝君士坦丁宣布开始实行的,而且规定这一天为星期一。实际上,就是把公元元年元旦(公元1年1月1日)规定为星期一。(至关于公式中的x=1,y=1,因此s=1。)
一般1年有365天,365÷7=52……1,就是说比52个星期多1天。因此,同一个日期,下一年是星期几,就要比上一年向后推1天。好比,上一年元旦是星期三,下一年元旦就是星期四。
“一般,每过1年,同一日期是星期几就要向后推1天”,是理解这个公式的关键。
要想知道某年某月某日是星期几,首先,要知道这一年元旦以公元元年元旦是星期一为起点,已经把星期几向后推了多少天,还要知道这一天是这一年的第几天。而要知道这一年元旦已经把星期几向后推了多少天,能够从公元元年到这一年已通过了多少年算起,先按1年向后推1天计算,再根据闰年的规定进行调整。
闰年的规定是:年份是4的倍数的通常都是闰年,其中,年份是整百数的通常不是闰年,只有年份是400的倍数的才是闰年。
如今,能够解释公式中各部分的含义了。
这样一来,s就是在公元元年元旦是星期一的基础上,须要把这一天是星期几向后推的总天数。因此,s除以7取余数,就能说明这一天是星期几。(摘抄,敬请谅解哈)
到这里、、也接近尾声了、、看了一下时间、、呵呵、、又忘了睡觉了、、待会还要上课、、好吧、、篇幅有点多、、但愿你们耐心的看完、、相信看到前面一句话,也算是差很少看完了、、但愿能对大家和之后的我有一个很好的理解上的帮助、、有写得不对的地方但愿能告诉我、、旁观者清嘛、、谢谢你们、、五一事后、、你们仍需努力、、为生活而奋斗、、、