一、DS1302主要介绍
1、DS1302 的特点
DS1302 是 DALLAS(达拉斯)公司推出的一款涓流充电时钟芯片。 DS1302 实时时钟芯片广泛应用于电话、传真、便携式仪器等产品领域,它的主要性能 指标如下:
1、DS1302 是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且 还有软件自动调整的能力,可以通过配置 AM/PM 来决定采用 24 小时格式还是 12 小时格式。
2、拥有 31 字节数据存储 RAM。
3、串行 I/O 通信方式,相对并行来说比较节省 IO 口的使用。
4、DS1302 的工作电压比较宽,在 2.0~5.5V 的范围内都可以正常工作。
5、DS1302 这种时钟芯片功耗一般都很低,它在工作电压 2.0V 的时候,工作电流小于 300nA。 6、DS1302 共有 8 个引脚,有两种封装形式,一种是 DIP-8 封装,芯片宽度(不含引脚) 是 300mil,一种是 SOP-8 封装,有两种宽度,一种是 150mil,一种是 208mil。
7、 DS1302 有两个电源输入,一个是主电源,另外一个是备用电源,比如可以用电池或者大电容,这样 做是为了在系统掉电的情况下,我们的时钟还会继续走。如果使用的是充电电池,还可以在 正常工作时,设置充电功能,给我们的备用电池进行充电。
2、DS1302的硬件信息
1 脚 VCC2 是主电源正极的引脚,2 脚 X1 和 3 脚 X2 是晶振输入和输出引脚,4 脚 GND 是负极,5 脚 CE 是使能引脚,接单片机的 IO 口,6 脚 I/O 是数据传输引脚,接单片机的 IO 口,7 脚 SCLK 是通信时钟引脚,接单片机的 IO 口,8 脚 VCC1 是备用电源引脚。考虑到 KST-51 开发板是一套以学习为目的的板子,加上备用电池对航空运输和携带不方便,所以 8 脚没有接备用电池,而是接了一个 10uF 的电容,这个电容就相当于一个电量很小的电池, 经过试验测量得出其可以在系统掉电后仍维持 DS1302 运行 1 分钟左右,如果大家想运行时 间再长,可以加大电容的容量或者换成备用电池,如果掉电后不需要它再维持运行,也可以干脆悬空
3、DS1302的时钟寄存器介绍
寄存器 0:最高位 CH 是一个时钟停止标志位。停止后为1,正常工作为0。如果 Vcc1 悬空或者是电池没电了,当我们下次重新上电时,读取这一位,那这一位就是 1,我们可以通过这一位判断时钟在单片机系统掉电后是否还正常运行。剩下的 7 位高 3 位是秒的十位,低 4 位是秒的个位。
寄存器 1:最高位未使用,剩下的 7 位中高 3 位是分钟的十位,低 4 位是分钟的个位。
寄存器 2:bit7 是 1 的话代表是 12 小时制,0 代表是 24 小时制;bit6 固定是 0,bit5 在 12 小时制下 0 代表的是上午,1 代表的是下午,在 24 小时制下和 bit4 一起代表了小时的十 位,低 4 位代表的是小时的个位。
寄存器 3:高 2 位固定是 0,bit5 和 bit4 是日期的十位,低 4 位是日期的个位。
寄存器 4:高 3 位固定是 0,bit4 是月的十位,低 4 位是月的个位。
寄存器 5:高 5 位固定是 0,低 3 位代表了星期。
寄存器 6:高 4 位代表了年的十位,低 4 位代表了年的个位。请特别注意,这里的 00~ 99 指的是 2000 年~2099 年。
寄存器 7:最高位一个写保护位,如果这一位是 1,那么是禁止给任何其它寄存器或者 那 31 个字节的 RAM 写数据的。因此在写数据之前,这一位必须先写成 0。
二、DS1302的通信时序及代码示例
DS1302 通信有三根线,分别是 CE、I/O 和 SCLK,其中 CE 是使能线, SCLK 是时钟线,I/O 是数据线。我们会发现DS1302的通信方式与SPI很相似,但又不完全一致,接下来我们一起来看看他的通信时序。
单字节写入操作
单字节读操作
大家会发现每次在读或写之前,都有一个字节的数据,那么这一个字节的数据是什么呢?接下来首先解决这个问题。我们讨论过DS1302的时钟寄存器,一共有八个,分别存储着时分秒年月日等信息,如果我们要进行写入秒的操作,我们就要找到对应存储秒数据的寄存器,因此我们就可以理解这一个字节的数据其实就是指明写入数据的地址。
理解了这点之后,我们接下来根据时序图理解一下怎么写入数据。我们根据时序图会发现,写入数据时,对应的有效信号是上升沿,而且数据是先发低位后发高位。下面我们用代码和详细注释来解释。
void DS1302_write(unsigned char bite)//写入
{
unsigned char i;
for(i=0;i<8;i++)
{
SCLK=0;
IO=bite&(0x01<<i);//发送的数据
SCLK=1;
_nop_();
SCLK=0;
}
}
接下来是写入一个完整字节的代码示例,其实写入一个字节就是在写入寄存器地址的基础上再次发送数据就可以了
void DS1302_writebite(unsigned char address,dat)
{
/**电平初始化**/
CE=0;_nop_();
SCLK=0;_nop_();
/**写入**/
CE=1;
DS1302_write(address);
DS1302_write(dat);
CE=0;
}
写入操作看完之后我们来讨论怎样读取一个字节,读取一个字节与写入一个字节相似,都是要先送一个地址指明是哪一个寄存器的,然后就是读取数据,这里有一点需要注意的是,DS1302中的数据都是以BCD码存储的,因此读出来或写入的数据都应该是BCD码,如果我们需要十进制数,我们还需要进行数制转换,代码及详细注释如下
unsigned char DS1302_readbite(unsigned char address)//读取一个字节
{
unsigned char i;
unsigned char dat=0x00;//定义接收数据的变量
/**电平初始化**/
CE=0; _nop_();
SCLK=0; _nop_();
/**写入被操作寄存器地址**/
CE=1; _nop_();
DS1302_write(address);
/**读取数据**/
for(i=0;i<8;i++)//单片机读取DS1302发送的数据
{
SCLK=0;
dat>>=1;
if(IO)
{
dat|=0x80;
}
SCLK=1;
SCLK=0;//下降沿有效
}
CE=0;_nop_();
IO=0;_nop_();//释放数据总线
dat=dat/16*10+dat%16;//将十六进制BCD码转为十进制
return dat;
}
接下来我们写入一组时间
void DS1302_writetime()
{
DS1302_writebite(0x8E,0x00);//关闭芯片写保护
DS1302_writebite(0x80,0x00);//秒寄存器
DS1302_writebite(0x82,0x50);//分寄存器
DS1302_writebite(0x84,0x10);//时寄存器
DS1302_writebite(0x86,0x05);//天寄存器
DS1302_writebite(0x88,0x06);//月寄存器
DS1302_writebite(0x8C,0x23);//年寄存器
DS1302_writebite(0x8E,0x80);//开芯片写保护
}
然后读取时间
void DS1302_readtime()
{
a[0]=DS1302_readbite(0x8D);//读年寄存器
a[1]=DS1302_readbite(0x89);//读月寄存器
a[2]=DS1302_readbite(0x87);//读天寄存器
a[3]=DS1302_readbite(0x85);//读时寄存器
a[4]=DS1302_readbite(0x83);//读分寄存器
a[5]=DS1302_readbite(0x81);//读秒寄存器
}
这样DS1302写入时间和读取时间功能已经完成了。
下面我们想让读取出来的时间显示在LCD1602上,代码如下
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
extern unsigned char a[];
void main()
{
LCD_Init();
DS1302_writetime();
while(1)
{
DS1302_readtime();
LCD_ShowNum(1,1,20,2);//显示20
LCD_ShowNum(1,3,a[0],2);//显示年
LCD_ShowChar(1,5,'-');
LCD_ShowNum(1,6,a[1],2);//显示月
LCD_ShowChar(1,8,'-');
LCD_ShowNum(1,9,a[2],2);//显示日
LCD_ShowNum(2,1,a[3],2);//显示时
LCD_ShowChar(2,3,'-');
LCD_ShowNum(2,4,a[4],2);//显示分
LCD_ShowChar(2,6,'-');
LCD_ShowNum(2,7,a[5],2);//显示秒
}
}
DS1302.C
#include <REGX52.H>
#include <intrins.h>
sbit CE=P3^5;
sbit IO=P3^4;
sbit SCLK=P3^6;
unsigned char a[]={0,0,0,0,0,0};
void DS1302_write(unsigned char bite)//写入
{
unsigned char i;
for(i=0;i<8;i++)//发送数据
{
SCLK=0;
IO=bite&(0x01<<i);
SCLK=1;
_nop_();
SCLK=0;
}
}
void DS1302_writebite(unsigned char address,dat)
{
/**初始化**/
CE=0;_nop_();
SCLK=0;_nop_();
/**写入**/
CE=1;
DS1302_write(address);
DS1302_write(dat);
CE=0;
}
unsigned char DS1302_readbite(unsigned char address)//读取一个字节
{
unsigned char i;
unsigned char dat=0x00;
/**初始化**/
CE=0; _nop_();
SCLK=0; _nop_();
/**写寄存器地址**/
CE=1; _nop_();
DS1302_write(address);
/**读取数据**/
for(i=0;i<8;i++)//单片机读取DS1302发送的数据
{
SCLK=0;
dat>>=1;
if(IO)
{
dat|=0x80;
}
SCLK=1;
SCLK=0;
}
CE=0;_nop_();
IO=0;_nop_();
dat=dat/16*10+dat%16;
return dat;
}
void DS1302_writetime()
{
DS1302_writebite(0x8E,0x00);//关闭芯片写保护
DS1302_writebite(0x80,0x00);//秒寄存器
DS1302_writebite(0x82,0x50);//分寄存器
DS1302_writebite(0x84,0x10);//时寄存器
DS1302_writebite(0x86,0x05);//天寄存器
DS1302_writebite(0x88,0x06);//月寄存器
DS1302_writebite(0x8C,0x23);//年寄存器
DS1302_writebite(0x8E,0x80);//开芯片写保护
}
void DS1302_readtime()
{
a[0]=DS1302_readbite(0x8D);//读年寄存器
a[1]=DS1302_readbite(0x89);//读月寄存器
a[2]=DS1302_readbite(0x87);//读天寄存器
a[3]=DS1302_readbite(0x85);//读时寄存器
a[4]=DS1302_readbite(0x83);//读分寄存器
a[5]=DS1302_readbite(0x81);//读秒寄存器
}
lcd1602.c
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}