嵌入式系统中时间是很重要的,在以往的系统中设计者常常使用一种叫RTC的专用芯片来维持时间,这种芯片种类很多接口形式也很多,如常用的DS1302、PCF8563、DS12887等等,虽然用起来方便,但额外增加一个器件和一种驱动程序,这同时也增加了系统的不安全因素,并且很多时候嵌入式工程师常发现这些时钟芯片的走时精度值得怀疑,且难于修改。
现在的MCU或MPU,一般都带有RTC功能,这对嵌入式工程师来说是个好消息,不用再为此扩展一枚芯片了,但通常RTC寄存器中提供的是一个32位的时间刻度值,并没有专用芯片那样分解成时分秒日月年之类的,此处,我将讨论它们的互换算法,其实也很简单。
一般可以设定RTC寄存器每秒加1,这样寄存器中的值使终代表了当前累计的秒数,还好这种RTC结构都是可以由后备电池来维护运行的,不致于断电就没有了。这个32位寄存器值被称为TimerTicker,时间刻度,很是形象。
首先考虑记录时间,32位的整型量每秒加一,其共能记录的时间为,2的32次方秒,即约136.19年,这对于一个产品来说运行时间相当足够了,试想一个产品能运行超过136年是多么恐怖的数字啊。
如何把任意时刻转换成为刻度呢?也就是编码。
先看下面的时间结构:
typedef struct
{
uint8 Year; //年
uint8 Month; //月
uint8 Day; //日
uint8 Hour; //时
uint8 Minute; //分
uint8 Second; //秒
}DateTime;
上面定义的结构实现了最常用的时间结构,对于具体应用,可以添加星期等。其年份可以用年纷的后两位实现,这样可以实现任何世纪,呵呵,理论上。
下面的方法将把此结构编码成为时间刻度:
//Author: 愿陪你一生 QQ380052073
//Note: 本方法时间起点从2000-1-1 0:0:0开始,向后编码150年
uint32 RTC_EncodeTime(DateTime * DT)
{
uint8 T,K;
uint32 T32,TimeData=;
for(T=,K=;T<DT->Year;T++){ //累加年份
K=T&0x03;
TimeData+=K?:;
}
for(T=;T<DT->Month;T++){
if(T!=){ //统计平月
T32=((T+(T>>))&0x01)?:;
}else T32=K?:; //统计闰月
TimeData+=T32;
}
T32=DT->Day-;TimeData+=T32*; //统计天
T32=DT->Hour;TimeData+=T32*; //统计时
T32=DT->Minute;TimeData+=T32*; //统计分
TimeData+=DT->Second; //统计秒
return TimeData;
}
以上方法实现任意时刻编码,其算法思路是先累加年份,从起点年份开始向后计算,每过四年要计算一次闰年,其它为平年,这里运用位运算方法实现了闰年和平年的判断,接下来就计算一年中的月数,这里要对2月份分闰年和平年来讨论,上面的方法*用了闰平年判断的标志,不用再次判断。最后是时分秒的累加过程,这部分最简单了,全部化为秒即可。
同样的思路,可以实现从任意刻度解码到当前时间,即逆变换过程,如下面的方法:
//Author: 愿陪你一生 QQ380052073
//Note: 本方法时间起点从2000-1-1 0:0:0开始,向后解码150年
void RTC_DecodeTime(uint32 TimeData,DateTime * DT)
{
uint8 T,K;
uint32 T16,K16;
K16=TimeData%; //分离时/分/秒
T16=TimeData/; //分离年/月/日
DT->Second=K16%; //计算秒
K16/=;DT->Minute=K16%; //计算分
K16/=;DT->Hour=K16%; //计算时
for(K16=,T=;T<;T++){ //按天累计年份
K16=(T&0x03)?:; //统计闰年
if(T16<K16){K16-=;break;} //计算当年二月份天数
else T16-=K16; //减掉年份
}
DT->Year=T; //计算得到相对年份
for(T=;T<;T++){
K=(T==)?K16:(((T+(T>>))&0x01)+); //统计当月天数
if(T16>=K)T16-=K; //减掉当前月
else break;
}
DT->Month=T; //计算得到当年中的月份
DT->Day=T16+; //计算得到当年中的天
}
上面的算法实现了解码,正好采用相反的方向,先分离出时分秒和年月日,因为闰平年中一天种有24个小时,一小时总是3600秒,而从一月有多少天这里开始出现分岐,所以分离出两部分。秒分时的解码从前部分中得到,依次求余即可,而年月日的分离则需要考虎闰平年和是否2月等。同样先从起点时刻向上累加天数,中间需要区分闰平年,之后即可把年份提取出来,随后再分开月和天,月分的闰年标记同样从年份分离中得到。
以上只是时间刻度编解码的一种方法,经过测试,其效率还是比较高的,运行速度和程序占用空间都满意,适合MCU使用。在分解后的时间结构中,还可以实现类似农历的算法。