基于单片机的DS1302实时时钟实验 实时时钟芯片DS1302的工作原理

概述在许多系统当中都需要精确的时钟功能,因此时钟芯片孕育而生。其中美国达拉斯 dallas 公司设计的 ds1302 是一款非常流行的数字时钟芯片。ds1302 是一款具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、星期、时、分、秒进行计时,并且具有闰年功能。年计数可达到 2100 年。
15.1 ds1302 功能简介ds1302 内部包含 31 字节的通用 ram,实现设置备用电池功能。采用 3 线制的串行数据通信接口,并且适用于大多数的微处理器。工作电压范围达到 2~5v,与 5vttl 电平完全兼容。支持单字节或多字节时钟、ram 数据读、写操作。当工作电压为 2v 时,工作电流低至 320na。工业级 ds1302 正常工作温度范围为:-40℃ ~85℃,芯片包括直插和贴片两种封装模式,封装示意图如下所示:
ds1302 典型通信电路如下图所示:
如上图所示,只需 3 根线 ce、i/o、sclk 便可实现处理器与 ds1302 之间通信,上图中 x1,x2 之间为外接时钟晶振,vcc2 为电源供电端,vcc1 为备用电池端。各管脚定义及功能如下所示:
15.2 单字节操作模式与 ds1302 进行数据通信时,首先得向 ds1302 传输一个字节的控制指令,控制指令位定义如下所示。
字节的最高位 bit7 必须为 1,否则无法向 ds1302 写入数据。bit6 为 1 时,表示后续对 31 字节的 ram 进行读写操作,为 0 时,表示后续将对时钟寄存器进行读写操作。bit5~bit1 为后续操作的时钟寄存器或 ram 的地址。最低位 bit0 为 1 时,表示读取 ds1302 的数据,为 0 时,表示向 ds1302 写入数据。对 ds1302 进行单字节模式的读、写操作时序如下图所示。
单字节写操作(single-byte write)时序为首先向 ds1302 写入控制指令,紧接着写入一个字节的数据。写入的顺序为低位在前,高位在后的传输方式,要求在时钟上升沿准备好数据。单字节读操作(single-byte read)时序为首先向 ds1302 写入控制指令,紧接着 sclk 的的下降沿 ds1302 有数据 d0 输出,因此在接下来的上升沿前读取稳定的 d0 值,依次类推至 d7。根据上图时序要求,往 ds1302 写入一个字节和读取一个字节的函数如下所示:
//写字节void wrbyte_1302(uchar dat){ uchar j; bit flag; for(j=1;j>1;//将数据移到下一位 } }//读字节uchar rdbyte_1302(void){ uchar dat,flag,j; for(j=1;j>1)|(flag< <7);//读出的值最低位在前面 } return dat; }如上所示,写字节函数 wrbyte_1302()中,要求单片机在时钟 sclk_1302 上升沿前将数据放到数据总线 io_1302 上,然后产生一个 sclk_1302 上升,完成一 bit 数据的写入,同时要求 1byte 的数据低位在前,高位在后依次发送。读字节函数 rdbyte_1302()中,要求在时钟 sclk_1302 下降沿之后,将总线 io_1302 数据读出,同时要求 1byte 的数据低位在前,高位在后依次读取。
在上述函数的基础上,单字节操作模式的读、写函数如下所示:
//单字节写模式void wrsingle_1302(uchar addr,uchar dat){ ce_1302 = 1;//拉高片选 wrbyte_1302(addr);//写入地址及控制指令 wrbyte_1302(dat);//写入数据 ce_1302 = 0;//拉低片选 sclk_1302 = 0;//释放始终总线,满足下次操作时序要求(非常重要)}//单字节读模式uchar rdsingle_1302(uchar addr){ uchar dat; ce_1302 = 1;//拉高片选 wrbyte_1302(addr);//写入地址及控制指令 dat = rdbyte_1302();//读取一个字节数据 ce_1302 = 0;//拉低片选 return dat;}我们这里重点讲述 ds1302 的时钟功能,因此与涓流充电有关的 31 字节 ram 操作这里不做详细的介绍。与实时时钟有关的寄存器如图所示。
与时钟有关的寄存器总共有 9 个如上图所示,前 7 个分别为:秒、分、时、日、月、星期、年,均为 8 位寄存器。以秒寄存器为例介绍时间的表示法,其中 bit6-bit4 为秒的十位,bit3-bit0 为秒的个位。59 秒时,bit6-bit4=“101”,bit3-bit0=“1001”,其它依此类推。另外,设置“时”寄存器的 bit7 可以设置为 12 小时或 24 小时制。上述寄存器的读写控制指令字节分别如图左侧两列所示。
秒寄存器的 ch(bit7)定义为时钟运行标志位,当该位被设置成 1 时,时钟计时停止,并且 ds1302 进入低功耗模式。当设置为 0 时,启动计时。上电初始状态时,该位状态不定,因此在时钟初始化时确保该位被清 0,保证后续时钟芯片正常运行。
第 8 个寄存器为控制寄存器,wp(bit7)为写保护位,当设置为 1 时,无法向 ds1302 写入数据,上电时该位状态不定,因此,需要对 ds1302 其它寄存器进行写操作之前,务必先将 wp 设置为 0。第 9 个寄存器不影响实现时钟功能,暂不做介绍。
因此,在 ds 1302 应用时要对它进行初始化,首先解除写保护,然后将与时间有关的 7 个寄存器赋初值,将初始化的内容放到函数 init_1302(uchar *settime)中。另外,我们将从 ds1302 读取 7 个时间值的操作放到函数 gettime(*currenttime)中,如下代码所示。
//1302初始化void init_1302(uchar *settime){ uchar j; ce_1302 = 0;//初始化通信引脚 sclk_1302 = 0; wrsingle_1302(0x8e,0x00);//解除写保护(wp=0) for(j=0;j<=6;j++) { wrsingle_1302(0x80+2*j,settime[j]);//写入7个时钟数据 } //wrburst_1302(settime);//当采用burst模式时,使用此语句替代上面for循环语句}//获取当前时间值void gettime(uchar *currenttime){ uchar j; ce_1302 = 0;//初始化通信引脚 sclk_1302 = 0; for(j=0;j<=6;j++) { *currenttime = rdsingle_1302(0x81+2*j);//读取7个时钟数据 currenttime++; } //rdburst_1302(currenttime); //当采用burst模式时,使用此语句替代上面for循环语句}到目前为止,我们已经学习了时钟芯片 ds1302 的功能介绍,以及初始化和时钟获取函数的编写,ry-51 单片机开发板上 ds1302 电路原理图如下所示,三根通信线分别接 4.7k 上拉电阻,分别与单片机的 i/o 口相连接。
按照惯例我们将和 ds1302 有关的函数打包放入drive_ds1302.h,drive_ds1302.c中,方便后续调用及移植。drive_ds1302.h代码如下:
#ifndef __ds1302_h__#define __ds1302_h__//1302初始化extern void init_1302(unsigned char *settime);//获取时间extern void gettime(unsigned char *currenttime);//单字节模式写void wrsingle_1302(unsigned char addr,unsigned char dat);//单字节模式读 unsigned char rdsingle_1302(unsigned char addr);//突发模式写void wrburst_1302(unsigned char *settime);//突发模式读void rdburst_1302(unsigned char *currenttime);#endifdrive_ds1302.c代码如下:
#include#includedrive_ds1302.h#define uchar unsigned char#define uint unsigned intsbit ce_1302 = p1^2; //ds1302通信引脚ce,i/o,sclk定义sbit io_1302 = p1^1;sbit sclk_1302 = p1^0; //写字节void wrbyte_1302(uchar dat){ uchar j; bit flag; for(j=1;j>1;//将数据移到下一位 } }//读字节uchar rdbyte_1302(void){ uchar dat,flag,j; for(j=1;j>1)|(flag< <7);//读出的值最低位在前面 } return dat; }//单字节写模式void wrsingle_1302(uchar addr,uchar dat){ ce_1302 = 1;//拉高片选 wrbyte_1302(addr);//写入地址及控制指令 wrbyte_1302(dat);//写入数据 ce_1302 = 0;//拉低片选 sclk_1302 = 0;//释放始终总线,满足下次操作时序要求(非常重要)}//单字节读模式uchar rdsingle_1302(uchar addr){ uchar dat; ce_1302 = 1;//拉高片选 wrbyte_1302(addr);//写入地址及控制指令 dat = rdbyte_1302();//读取一个字节数据 ce_1302 = 0;//拉低片选 return dat;}//突发写模式void wrburst_1302(uchar *settime){ uchar j; ce_1302 = 1;//拉高片选 wrbyte_1302(0xbe);//burst模式写专用指令 for(j=0;j<=6;j++) { wrbyte_1302(settime[j]);//写入7位时钟数据 } ce_1302 = 0;//拉低片选 }//突发读模式void rdburst_1302(uchar *currenttime){ uchar j; ce_1302 = 1;//拉高片选 wrbyte_1302(0xbf);//burst模式读专用指令 for(j=0;j<=6;j++) { *currenttime = rdbyte_1302();//读取一个字节数据; currenttime++; } ce_1302 = 0;//拉低片选 }//1302初始化void init_1302(uchar *settime){ uchar j; ce_1302 = 0;//初始化通信引脚 sclk_1302 = 0; wrsingle_1302(0x8e,0x00);//解除写保护(wp=0) for(j=0;j<=6;j++) { wrsingle_1302(0x80+2*j,settime[j]);//写入7个时钟数据 } //wrburst_1302(settime);//当采用burst模式时,使用此语句替代上面for循环语句}//获取当前时间值void gettime(uchar *currenttime){ uchar j; ce_1302 = 0;//初始化通信引脚 sclk_1302 = 0; for(j=0;j>8; tr0 = 1; //启动定时器 et0 = 1; //允许定时器中断 ea = 1; //开总中断 init_1302(settime);//1302初始化 while(1) { if(t_flag)//500ms定时 { t_flag = 0; gettime(currenttime);//获取时间 str[0] = '2'; str[1] = '0'; str[2] = (currenttime[6] >>4)+'0'; //年 str[3] = (currenttime[6]& 0x0f)+'0'; str[4] = '-'; str[5] = (currenttime[4] >>4)+'0'; //月 str[6] = (currenttime[4]& 0x0f)+'0'; str[7] = '-'; str[8] = (currenttime[3] >>4)+'0'; //日 str[9] = (currenttime[3]& 0x0f)+'0'; str[10] = '�'; str[11] = (currenttime[2] >>4)+'0'; //时 str[12] = (currenttime[2]& 0x0f)+'0'; str[13] = ':'; str[14] = (currenttime[1] >>4)+'0'; //分 str[15] = (currenttime[1]& 0x0f)+'0'; str[16] = ':'; str[17] = (currenttime[0] >>4)+'0'; //秒 str[18] = (currenttime[0]& 0x0f)+'0'; str[19] = ' '; str[20] = (currenttime[5] >>4)+'0'; //星期 str[21] = (currenttime[5]& 0x0f)+'0'; str[22] = '�'; disp_1602_str(1,4,str); //将获得的时间分别显示到1602的第一二行 disp_1602_str(2,3,str+11); } }}//定时器0中断子程序,定时1msvoid timer0() interrupt 1{ static uint t_500ms = 0; tl0 = t_1ms;//重装初始值 th0 = t_1ms >>8; t_500ms++; if(t_500ms >=500)//500ms,置位t_flag { t_500ms = 0; t_flag = 1; }}将程序下查看结果是否与预想的一致吧。
15.3 突发操作模式上面我们讲解的是以单字节的模式,从 1302 中连续读取时间数据。仔细的同学可能会发现一个问题,就是我们连续读 7 个时间寄存器是有先后顺序的,会有读错数据的风险。例如我们要读的时间为 23 时 59 分 59 秒,最开始时我们把 59 秒读出来了,如果刚好在你读完的时候 59 秒变成了 00 秒,59 分变成了 00 分,23 时变成了 00 时,接下来把分、时依次读出来,因此我们读出来的时间为 00 时 00 分 59 秒,很显然这个时间是不对的。下面我们讲解的突发操作模式有效的解决了这个问题。
在突发操作读模式下,当 ds1302 收到突发读数据指令,ds1302 首先会把 8 个时间寄存器的数据同时读出存放在 8 个二级时间寄存器中,然后依次把 8 个二级时间寄存器的数据输出给单片机,突发读专用指令为 0xbf。同样,当我们需要写 ds1302 时,当收到突发写指令后,ds1302 将接收到的 8 个连续数据存储到 8 个二级时间寄存器中,然后同时将 8 个数据写到时间寄存器中,突发写专用指令为 0xbe。根据上述原理,编写突发写模式和突发读模式函数如下代码所示。
//突发写模式void wrburst_1302(uchar *settime){ uchar j; ce_1302 = 1;//拉高片选 wrbyte_1302(0xbe);//burst模式写专用指令 for(j=0;j<=6;j++) { wrbyte_1302(settime[j]);//写入7位时钟数据 } ce_1302 = 0;//拉低片选 }//突发读模式void rdburst_1302(uchar *currenttime){ uchar j; ce_1302 = 1;//拉高片选 wrbyte_1302(0xbf);//burst模式读专用指令 for(j=0;j<=6;j++) { *currenttime = rdbyte_1302();//读取一个字节数据; currenttime++; } ce_1302 = 0;//拉低片选 }如上图所示,首先为向 ds1302 写入突发读或者写指令,然后紧接着是读取或写入 7 个时间数据。前面讲解的都是 8 个连续的数据,我们这里写 7 个的原因是,时间显示这 7 个就足够了。上述完整代码详见完整代码drive_ds1302.h、drive_ds1302.c中。
突发模式的应用与单字节模式类似,只需将如下代码中的for循环语句替换成“rdburst_1302(currenttime)”即可。
//获取当前时间值void gettime(uchar *currenttime){ uchar j; ce_1302 = 0;//初始化通信引脚 sclk_1302 = 0; for(j=0;j<=6;j++) { *currenttime = rdsingle_1302(0x81+2*j);//读取7个时钟数据 currenttime++; } //rdburst_1302(currenttime); //当采用burst模式时,使用此语句替代上面for循环语句}15.4 本章小结本章详细介绍了实时时钟芯片ds1302的工作原理,驱动程序的编写,以及ds1302的简单应用。

STC12C5A60S2-351单片机的电梯防坠梯液压系统设计
消息称Galaxy S23电池容量缩水5%
英特尔力挺触摸屏成为第三代超极本电脑标配
智能电池电量计如何有效改进动态血糖监视仪的电池使用寿命
雷军在微博公布了个好消息:创业9年,小米进入世界500强
基于单片机的DS1302实时时钟实验 实时时钟芯片DS1302的工作原理
基于555的眼睛疲劳消除器原理图
智慧家医优化协同模式助力社区医院分级诊疗
讲解几点关于FIFO IP核使用时的注意事项
关于以色列芯片的性能分析和应用
如何使用OpticStudio对LCD底部照明和边缘照明进行建模呢
富士康新型号iPhone今年将会开始在印度生产
京东把小米,intel,HTC叫到总部一齐开展的天工计划完工时间确定5月22日?
供应R&S罗德与施瓦茨UPV音频分析仪
石墨烯基超级快充电池是黑科技还是大忽悠?电池技术靠谱吗? 市场是否认可?
将FPGA应用到国家仪器公司(NI)的CompactRIO(一款可重新配置的嵌入式测控系统)系统会帮助国家实验室的研究人
冯军细描三年哥窑之梦:主题专卖店将开到巴黎香榭丽舍大街
工业机器人的粘合剂它能起到什么作用
大数据杀熟现象需加强算法应用遏制
【赋能IoT生态系列5之2】羽量级远程智控升级方案