在网上看了很久,发现初学者最有兴趣的就是DS1302时钟电路,也很自然,它是个做出来就让你觉得最实用的电路了,但实际上制做上并不简单,首先你要让你的显示部分(不管是数码管还是LCD)调试通过。然后把DS1302接好,调试正确了才能在成功显示时间和日期。下面我们就来说说DS1302的用法。
DS1302的图如下:
DS1302是美国DALLAS公司推出的一种高性能、低功耗的实时时钟芯片,附加31字节静态RAM,采用SPI三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号和RAM数据。实时时钟可提供秒、分、时、日、星期、月和年,一个月小与31天时可以自动调整,且具有闰年补偿功能。工作电压宽达2.5~5.5V。采用双电源供电(主电源和备用电源),可设置备用电源充电方式,提供了对后背电源进行涓细电流充电的能力。
下面是标准的接线电路图:
各引脚功能如下:
引脚号 名称 功能
① Vcc2 主电源
②、③ X1,X2 接32768Hz晶振
④ GND 地线
⑤ RST 复位
⑥ I/0 数据输入输出
⑦ SCLK 串行时钟
⑧ Vccl 后备电源
DS1302有关日历、时间的寄存器共有12个,其中有7个寄存器(读时81h~8Dh,写时80h~8Ch)是存放秒、分,小时、日、月、年、周数据的,存放的数据格式为BCD码形式
它的内部时间寄存器如下:
这张表呢是DS1302内部的7个与时间、日期有关的寄存器图和一个写保护寄存器,我们要做的就是将初始设置的时间、日期数据写入这几个寄存器,然后再不断地读取这几个寄存器来获取实时时间和日期。这几个寄存器的说明如下:
1、秒寄存器(81h、80h)的位7定义为时钟暂停标志(CH)。当初始上电时该位置为1,时钟振荡器停止,DS1302处于低功耗状态;只有将秒寄存器的该位置改写为0时,时钟才能开始运行。
2、小时寄存器(85h、84h)的位7用于定义DS1302是运行于12小时模式还是24小时模式。当为高时,选择12小时模式。在12小时模式时,位5是 ,当为1时,表示PM。在24小时模式时,位5是第二个10小时位
3、控制寄存器(8Fh、8Eh)的位7是写保护位(WP),其它7位均置为0。在任何的对时钟和RAM的写操作之前,WP位必须为0。当WP位为1时,写保护位防止对任一寄存器的写操作。也就是说在电路上电的初始态WP是1,这时是不能改写上面任何一个时间寄存器的,只有首先将WP改写为0,才能进行其它寄存器的写操作。
下面来说说如果对DS1302进行读写:
上面的电路图可以看出,除了电源和接地,DS1302只有三根线和单片机连接,SCLK、I/O和RST(有的也写成CE),先看时序图:
DS1302的数据读写是通过I/O串行进行的。当进行一次读写操作时最少得读写两个字节,第一个字节是控制字节,就是一个命令,告诉DS1302是读还是写操作,是对RAM还是对CLOK寄存器操作,以及操作的地址。第二个字节就是要读或写的数据了。
我们先看单字节写:在进行操作之前先得将CE(也可说是RST)置高电平,然后单片机将控制字的位0放到I/O上,当I/O的数据稳定后,将SCLK置高电平,DS1302检测到SCLK的上升沿后就将I/O上的数据读取,然后单片机将SCLK置为低电平,再将控制字的位1放到I/O上,如此反复,将一个字节控制字的8个位传给DS1302。接下来就是传一个字节的数据给DS1302,当传完数据后,单片机将CE置为低电平,操作结束。
单字节读操作的一开始写控制字的过程和上面的单字节写操作是一样,但是单字节读操作在写控制字的最后一个位,SCLK还在高电平时,DS1302就将数据放到I/O上,单片机将SCLK置为低电平后数据锁存,单机机就可以读取I/O上的数据。如此反复,将一个字节的数据读入单片机。读与写操作的不同就在于,写操作是在SCLK低电平时单片机将数据放到IO上,当SCLK上升沿时,DS1302读取。而读操作是在SCLK高电平时DS1302放数据到IO上,将SCLK置为低电平后,单片机就可从IO上读取数据。
现在我们来看看控制字的内容:
位0就是读写位,当位0为1时,就是告诉DS1302,下面是进行读出操作,而当位0为0时就是写入操作。
位0-位5是要进行操作的DS1302寄存器地址。
位6就是告诉DS1302,是要对RAM进行操作还是对CLK寄存器进行操作,0就是对时间寄存器操作,一般我们都是对时间寄存器进行操作。
位7就是固定的1。为什么是1呢。还记得上面说的单字节读操作吗?在写控制字的最后一个位也就是位7时,DS1302已将它的寄存器数据位0放到IO上了,要是控制字的位7是0的话,DS1302就无法将它的随后的数据放到IO上了。
这样你现在就知道为什么控制字80H是写秒寄存器,而80H是读秒寄存器了吧!80H换成二进制就是10000000。而81H的二进制就是10000001,一个是写操作,另一个是读操作嘛!
好!我们现在来总结一下,如何对DS1302进行操作。
①首先要通过8eH将写保护去掉,这样我们才能将日期,时间的初值写时各个寄存器。
②然后就可以对80H、82H、84H、86H、88H、8AH、8CH进行初值的写入。同时也通过秒寄存器将位7的CH值改成0,这样DS1302就开始走时运行了。
③将写保护寄存器再写为80H,防止误改写寄存器的值。
④不断读取80H-8CH的值,将它们格式化后显示到LCD或数码管上。
这个电路本身还是很简单的,见下图:
元件就是DS1302、一个晶振,一个104的瓷片电源滤波电容。和98ATS52连接的线有电源5V(Vcc)、接地(GND)、SCLK、I/O、CE(RST)。共5根线。
按上面的电路图焊好后别忘了检测一直是否有短路,我检测了我的电路,电源和地之间的静态电流为50uA。
接上单片机后如图:
软件部分的编写,首先得把显示部分调试好,我用的是LCD12864,是已经调试好的可调用子程序12863put.c
下面DS1302程序部分,是根据网上用的非常多的一个DS1302子程序修改的:
/**************************/
/* ds1302实时时钟C程序 */
/**************************/
#include < reg52.h>
#define uchar unsigned char
sbit T_CLK = P1^0; /*实时时钟时钟线引脚 */
sbit T_IO = P1^1; /*实时时钟数据线引脚 */
sbit T_RST = P1^2; /*实时时钟复位线引脚 */
sbit ACC0=ACC^0;
sbit ACC7=ACC^7;
void Init1302(void);
void v_W1302(uchar ucAddr, uchar ucDa);
uchar uc_R1302(uchar ucAddr);
void v_Set1302(uchar *pSecDa);
void v_Get1302(uchar ucCurtime[]);
/******************************************
* 名称: v_RTInputByte
* 说明:
* 功能: 往DS1302写入1Byte数据
* 调用:
* 输入: ucDa 写入的数据
* 返回值: 无
******************************************/
void v_WTInputByte(uchar ucDa)
{
uchar i;
ACC= ucDa;
for(i=8; i>0; i--)
{
T_IO = ACC0; //相当于汇编中的 RRC
T_CLK = 1;
T_CLK = 0;
ACC =ACC>> 1;
}
}
/*****************************************
* 名称: uchar uc_RTOutputByte
* 说明:
* 功能: 从DS1302读取1Byte数据
* 调用:
* 输入:
* 返回值: ACC
******************************************/
uchar uc_RTOutputByte(void)
{
uchar i;
for(i=8; i>0; i--)
{
ACC = ACC>>1; //相当于汇编中的 RRC
ACC7 = T_IO;
T_CLK = 1;
T_CLK = 0;
}
return(ACC);
}
/********************************************
* 名称: v_W1302
* 说明: 先写地址,后写数据
* 功能: 往DS1302写入数据
* 调用: v_RTInputByte()
* 输入: ucAddr: 控制字, ucDa: 要写的数据
* 返回值: 无
*********************************************/
void v_W1302(uchar ucAddr, uchar ucDa)
{
//OE=0;
T_RST = 0;
T_CLK = 0;
T_RST = 1;
v_WTInputByte(ucAddr); /* 地址,命令 */
v_WTInputByte(ucDa); /* 写1Byte数据*/
T_CLK = 1;
T_RST =0;
//OE=1;
}
/*********************************************
* 名称: uc_R1302
* 说明: 先写地址,后读数据
* 功能: 读取DS1302某地址的数据
* 调用: v_RTInputByte() , uc_RTOutputByte()
* 输入: ucAddr: 控制字
* 返回值: ucDa :读取的数据
**********************************************/
uchar uc_R1302(uchar ucAddr)
{
uchar ucDa;
//OE=0;
T_RST = 0;
T_CLK = 0;
T_RST = 1;
v_WTInputByte(ucAddr); /* 地址,命令 */
ucDa = uc_RTOutputByte(); /* 读1Byte数据 */
T_CLK = 1;
T_RST =0;
// OE=1;
return(ucDa);
}
/***************************************
*
* 名称: v_Set1302
* 说明:
* 功能: 设置初始时间
* 调用: v_W1302()
* 输入: pSecDa: 初始时间数组首地址。
* 返回值: 无
****************************************/
void v_Set1302(uchar *pSecDa)
{
uchar i;
uchar ucAddr = 0x80;
v_W1302(0x8e,0x00); // 控制命令,WP=0,允许写操作
for(i =7;i>0;i--)
{
v_W1302(ucAddr,*pSecDa); //秒 分 时 日 月 星期 年
pSecDa++;
ucAddr +=2;
}
v_W1302(0x8e,0x80); // 控制命令,WP=1,写保护
}
/**********************************************
* 名称: v_Get1302
* 说明:
* 功能: 读取DS1302当前时间
* 调用: uc_R1302()
* 输入: ucCurtime: 保存当前时间数据的数组地址
* 返回值: 无
***********************************************/
void v_Get1302(uchar ucCurtime[])
{
uchar i;
uchar ucAddr = 0x81;
for (i=0;i<7;i++)
{
ucCurtime[i] = uc_R1302(ucAddr);//格式为: 秒 分 时 日 月 星期 年
ucAddr += 2;
}
}
/*******************************************
* 名称: Init1302
* 说明:
* 功能: 初始化DS1302
* 调用:
* 输入:
* 返回值: 无
*******************************************/
void Init1302(void)
{
v_W1302(0x8e,0x00); //控制写入WP=0
v_W1302(0x90,0xa5); //辅助电源充电命令
v_W1302(0x80,0x00); //写秒
v_W1302(0x82,0x59); //写分
v_W1302(0x84,0x10); //写时
v_W1302(0x86,0x07); //写日
v_W1302(0x88,0x05); //写月
v_W1302(0x8a,0x04); //写星期
v_W1302(0x8c,0x09); //写年
v_W1302(0x8e,0x80); //写保护WP=1
}
下面是主程序部分DS1302main.c,将DS1302、LCD12864子程序整合在一起。
#include
#define uchar unsigned char
#define uint unsigned int
extern void LcmClear( void ); //清屏
extern void LcmInit( void ); //初始化
extern void LcmPutstr( uchar row,uchar y,uchar * str ); //在设定位置显示字符串
extern void Init1302(void);//初始化DS1302
extern void v_Get1302(uchar ucCurtime[]);//获取DS1302内的7个字节时间数据存入数组中
uchar getTimebuf[7];//用于存放获取的时间数据
uchar setTimebuf[7];//用于存放要设置的时间日期数据
uchar time[]={" : : "};//时间格式字符串
uchar date[]={"20 - - "};//日期格式字符串
uchar daylist[]={"SunMonTueWedThuFriSat"};//星期字符列表
uchar day1[]={" "};//星期格式字符串
//****************
// 主函数
//****************
void Main( void )
{ uchar i,j,k;
Init1302();//初始化DS1302
LcmInit(); //初始化LCD12864
LcmClear();
LcmPutstr( 0,28,"DS1302 TEST" );
LcmPutstr( 3,24,"");
LcmPutstr( 5,0,"BLOG:http://" );
LcmPutstr( 6,18,"hi.baidu.com/txz01" );
LcmPutstr( 7,8,"Email:TXZ001@139.com" );
while(1)
{
v_Get1302(getTimebuf);//获取DS1302内7个时间日期数据存入数组getTimebuf[].
time[6]=(getTimebuf[0])/16+48;//格式化时间秒
time[7]=(getTimebuf[0])%16+48;
time[3]=(getTimebuf[1])/16+48;//格式化时间分
time[4]=(getTimebuf[1])%16+48;
time[0]=(getTimebuf[2])/16+48;//格式化时间小时
time[1]=(getTimebuf[2])%16+48;
date[8]=getTimebuf[3]/16+48;//格式化日期日
date[9]=getTimebuf[3]%16+48;
date[5]=getTimebuf[4]/16+48;//格式化日期月
date[6]=getTimebuf[4]%16+48;
date[2]=getTimebuf[6]/16+48;//格式化日期年
date[3]=getTimebuf[6]%16+48;
day1[0]=daylist[(getTimebuf[5]%10)*3];//格式化星期
day1[1]=daylist[(getTimebuf[5]%10)*3+1];
day1[2]=daylist[(getTimebuf[5]%10)*3+2];
LcmPutstr( 2,0,date);//显示日期
LcmPutstr(2,96,day1);//显示星期
LcmPutstr( 3,36,time);//显示时间
for(i=0;i<5;i++)
for(j=0;j<255;j++)
for(k=0;k<255;k++);
}
}
用Keil将程序编译:
生成HEX文件后下载到AT89S52板上,运行,如下图:
结果并不好,时间数据在乱跳,但我发现秒数基本是一秒一跳,分钟也是一分一跳的,就是说DS1302在正常工作,说明CH修改已正常,只是读取数据不正常。在分析了软件部分都正常后,决定将数据线缩短。因为原来的数据线大约有10多CM。再加上从接口到52芯片的转接线总共大约有30CM长了。因此得将DS1302的数据线剪短,再直接插在52芯片的接口P1上试试。如下图:
上面这张图是示意图,是晚上补做的图,没插电。其结果是基本上能看出是在我设定的时间初值上每秒跳一下,但还是很频繁地有乱跳现象。我于是就想那么短的线难道数据还不稳?莫非是阻抗太高了,使数据线容易受到干扰?我想起那个标准电路中三路数据线是有上拉10K的电阻的,当时我想反正常P1内部也有上拉电阻,就没外接电阻了。现在想起来是不是P1内部的上拉电阻值太大了,使读取数据受外界干扰。我想起我P0上外接的不就是10K的排阻吗?于是将DS1302的数据线插到了P0口的P0.0、P0.1、P0.2上,在软件里修改了数据线接口的定义,然后烧写运行。哈哈!现在就非常稳定了。
总结,其实DS1302的数据引线略长一点是没关系的,但那三根数据线上的上拉电阻一定得要,否则阻抗太高就很容易造成数据传输不稳定,受到外界干扰,造成显示数据乱跳的现象。
DS1302的调试不是很容易,因为当你做好了电路,写好了软件烧写运行后,如果DS1302没有任何反应。你就不太好判断问题出在哪,是晶振不起振还是程序改写秒寄存器CH没成功?我就因此换过晶振,换过DS1302,反复修改过程序。因为DS1302在初始接上电源时晶振是不起振的,就无法检测晶振电路的好坏。这是这个电路的难点。
上一篇:自制单片机之十四……通俗的电子基础课
下一篇:自制单片机之十二……AT89C2051烧写器的制做与调试
推荐阅读最新更新时间:2024-03-16 15:25