DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。
模块实物图:
淘宝购买的模块,物美价廉。
DS1302时钟模块,通过单片机设置好时间后,由纽扣电池供电应当一直正常工作。但是单片机掉电再重新上电后,会导致模块重复初始化,使时间回到起点,与实际需求不符。单片机单纯上电直接初始化时间,有些不妥。
方案一:
1.先烧录正常初始化时间的代码。
2.注释初始化时间的代码再次烧录。
点评:此方案可以解决问题,需要重复烧录两次,不够方便,故不作重点讨论。
方案二:
每次设置时间后,使用一个eeprom或者flash等掉电不丢失数据的记忆芯片,写入一个标记,表明已经初始化时间。系统重启只需读取是否这个标记,若有,则不需初始化。反之,则初始化时间。
点评:
此方案可以解决问题,需要额外芯片,有一定的局限性,故也不推荐。
仔细翻阅ds1302手册,发现它本身是支持31字节的ram。在方案二的思路上,使用这些ram做标记就很方便了。
地址C0到FC,读地址是写地址+1,储存数值00~ff之间的任意值、
写地址:c0 c2 c4 … fc
读地址:c1 c3 c5 … fd
如:在c0地址写入3a数值,读c1地址若为3a,则说明上次已经标记。
方案三:
在方案二思路上,使用内部ram空间做标记。单片机重启时,读取有无标记,若有,则不需初始化。反之,则初始化时间。
点评:推荐方案,方便快捷。缺点是代码稍复杂。
笔者使用stm32f1系列单片机亲测,切实可行。
ds1302头文件:
#define WRITE_FLAG_ADDR 0xc0
#define READ_FLAG_ADDR 0xc1
#define FLAG_VAL 0x3a
解释:
定义三个宏,分别是写地址,读地址,和标记数值。
注意:读地址为写地址+1。(数据手册有图)
ds1302头文件:
设置时间函数
void ds1032_set_time(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
year -= 2000; //年默认2000开始
if(year > 100) year = 0;
// 10转为bdc码
year = ((year /10) << 4) + year %10;
mon = ((mon /10) << 4) + mon %10;
day = ((day /10) << 4) + day %10;
hour = ((hour /10) << 4) + hour %10;
min = ((min /10) << 4) + min %10;
sec = ((sec /10) << 4) + sec %10;
// 写入寄存器,同时标记一个地址
ds1302_wirte_rig(0x8e,0x00);//关闭写保护
ds1302_wirte_rig(WRITE_FLAG_ADDR,FLAG_VAL);//写入已经设置时间标记
ds1302_wirte_rig(0x80,sec);//seconds秒
ds1302_wirte_rig(0x82,min);//minutes分
ds1302_wirte_rig(0x84,hour);//hours时
ds1302_wirte_rig(0x86,day);//date日
ds1302_wirte_rig(0x88,mon);//months月
ds1302_wirte_rig(0x8c,(u8) year);//year年
// ds1302_wirte_rig(0x8a,7);// 星期日
ds1302_wirte_rig(0x8e,0x80);//开启写保护
}
初始化时间函数:
void ds1032_init_time(void)
{
if(ds1302_read_rig(READ_FLAG_ADDR)!= FLAG_VAL) // 如果未标记,执行初始化时间
{
ds1032_set_time(2020,12,31,23,59,30);// 2020年12月31日,23:59:30
printf("Y时间初始化完成,标记:%x\r\n",ds1302_read_rig(READ_FLAG_ADDR));
}
else
{
printf("N时间不用重复初始化,标记:%x\r\n",ds1302_read_rig(READ_FLAG_ADDR));
}
}
解释:
先判断有无标记,有则执行ds1032_set_time,否则不执行。