实时时钟 DS1302
一、实时时钟芯片DS1302
1、DS1302的主要性能指标
(1) DS1302实时时钟能够计算2100年之前的秒、分、时、日、日期、星期、月、年(闰年可自动调整)。 还可以通过配置AM/PM来决定采 用24小时格式还是12小时格式。
(2)内部含有31个字节静态数据存储RAM。
(3)串行I/O通信方式,使得管脚少,简单SPI仅3线接口。
(4)工作电压范围教宽:2.0~5.5V。
(5)工作电流小,功耗低:工作电压为2.0V时,小于300nA。
(6)时钟或RAM数据的读/写有两种传送方式:单字节传送和多字节传送方式。
(7)采用8脚DIP封装(双列直插式封装)或SOIC封装。
(8)供电电压Vcc=5V时,与标准TTL兼容,可与单片机通信。
(9)具有涓流充电能力。
(10)采用主电源和备份电源双电源供应。备用电源可以是电池或者大电容,确保在系统掉电的情况下,时钟还可以走。
DS1302封装图:
2、DS1302的硬件信息
DS1302引脚图:
各引脚功能:
引脚编号 | 引脚名称 | 引脚功能 |
---|---|---|
1 | Vcc2 | 主电源引脚,当Vcc2比Vcc1高0. 2V以上时,DS1302由 Vcc2供电,当Vcc2低于Vcc1时,由Vcc1供电。 |
2 | X1 | 这两个引脚需要接- -个32. 768K的晶振,给DS1302提供一个基准 。 |
3 | X2 | 特别注意,要求这个晶振的引脚负载电容必须是6pF,而不是要加6pF的电容。如果使用有源晶振的话,接到X1上即可,X2悬空。 |
4 | GND | 接地即可 |
5 | CE | DS1302的使能输入引脚。当读写DS1302的时候,这个引脚必须是高电平,DS1302这个引脚内部有一个40k的下拉电阻。 |
6 | I/O | 这个引脚是一个双向通信引脚,读写数据都是通过这个引脚完成。DS1302这个引脚的内部含有一个40k的下拉电阻。 |
7 | SCLK | 输入引脚。SCLK是用来作为通信的时钟信号。DS1302这个引脚.的内部含有- -个40k的下拉电阻。 |
8 | Vcc1 | 备用电源引脚 |
其中只有5脚(CE使能)、6脚(I/O)、7脚(SCLK时钟)需要接到单片机的I/O口上。
DS1302的典型电路如下:
需要注意的是晶振电路。它使用的是32.768K的晶振,晶振外部不需要额外添加其他电容或者电阻。时钟的精度首先取决于晶振的精度以及晶振的引脚负载电容。如果晶振不准或者负载电容过大(小),就会导致时钟误差过大。其次需要考虑晶振的温漂。晶振的精度会随着温度的变化而变化。在实际系统中,常通过校对来减少这种误差的影响。
3、DS1302寄存器介绍
(1) 命令字节
DS1302一条指令一个字节共8位。第7位(最高位)固定为1(0无效);第6位是RAM/CLOCK选择端,这里主要学习CLOCK时钟的使用,所以第6位为0;第5位到第1位是寄存器的地址;第0位是读写控制位,1读0写。命令字节如下:
(2) 寄存器
DS1302有8个和时钟有关的寄存器,如下:
(表格从上到下依次为寄存器0到寄存器7)
寄存器0
:最高位CH是时钟停止标志位。通过该位判断时钟在单片机系统掉电后是否正常运行(0为有备用电源,正常运行)。剩余7位的高3位是秒的十位,低4位是秒的个位。由于DS1302内部是BCD码,秒的十位最大是5,所以3个二进制位足够。寄存器1
:最高位未使用,剩余7位的高3位是分钟的十位,低4位是分钟的个位。寄存器2
:bit7为1代表采用12小时制,为0表示24小时制;bit6固定为0;bit5在12小时制下,0表示上午,1表示下午,在24小时制下,和bit4一起表示小时的十位;低4位表示小时的个位。寄存器3
:高两位固定是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。
4、DS1302通信时序介绍
(1) 单字节写操作时序 (单片机向DS1302内写入)
- 先写入寄存器地址,再写入待写字节
(2)单字节读操作时序(单片机从DS1302读取数据) - 先写入寄存器地址,再读取该寄存器的数据
注:
①该时序图上的箭头方向都是针对DS1302而言的。
②应该先读取I/O线上的数据,再拉高SCK(时钟线)产生上升沿。
5、Burst 模式
DS1302寄存器中,与时间相关的寄存器有7个,分别依次对应秒、分、时、日期、月、星期、年。在读取这7个寄存器的过程中,无论怎样读,都会有时间差,在极端的情况下就会出现错误。例如当前时间是00:00:59,先读秒,读到的秒是59,然后再去读分钟。但是在读完秒准备去读分钟的这段时间内,刚好时间进位了,变成了00:01:59,这个错误就很明显了。
为了解决这个问题,芯片厂家提供了Burst的模式。这里主要学习时钟突发模式(RAM突发模式暂时忽略)。
当向DS1302内写入寄存器地址时,只要将写的5位地址都写1,即读操作用0xBF,写操作用0xBE
,这时DS1302就能识别出是Burst模式,马上把所有的8个字节同时锁存到另外的8个字节的寄存器缓冲区内,这样时钟继续走,而我们读取的是另一个缓冲区内的数据。写数据同理,Burst模式下,我们是先把数据写到这个缓冲区内,最终DS1302会把这个缓冲区内的数据一次性送到它的时钟寄存器内。
从以上叙述不难看出,在DS1302时钟的Burst模式下,必须一次性读或者写8个字节
,要把时钟的寄存器全部读出来或者完全写进去。
二、 注意:DS1302内部寄存器存储的数据类型是BCD码!!!
BCD码介绍:
- 我们时钟日历寄存器使用的是8421码型的BCD码,BCD码还有5421码、2421码等,其中8421码型的BCD码最最常用;
- BCD码是用四位二进制数表示一位十进制数的0-9这十个数简称BCD码;
- 8421码型BCD码最小值为0000 (二进制),最大值为1001 (二进制) : 9
- 一个字节的8421码型BCD码中的低四位用于表示十进制的个位,高四位用于表示十进制的十位,如10 (十进制)的8421码型BCD码=00010000;
BCD码转换
例:把十进制数45转换为8421型的BCD码
unsigned char data1,data2 = 45;//声明两个无符号类型的char型变量data1和data2 并data2赋初值45
data1 = data2/10; // data1 = 4;
data2 = data2%10; // data2 = 5;
data2 = data2 + data1*16; // data2 = 5 + 4*16 = 69 即69为转换的BCD码
把69这个8421型的BCD码换算回十进制数:
data1 = data2/16; // data1 = 4;
data2 = data2%16; // data2 = 5;
data2 = data2 + data1*10; // data2 = 5 + 4*10 = 45
三、简易时钟代码
ds1302.h
#ifndef _DS1302_H_
#define _DS1302_H_
#include <STC15F2K60S2.H>
#define u8 unsigned char
struct sTime{
unsigned char hour;
unsigned char min;
unsigned char sec;
};
sbit DS1302_IO = P2^3;
sbit DS1302_CLK = P1^7;
sbit DS1302_CE = P1^3;
void DS1302Write(unsigned char reg,unsigned char dat);
unsigned char DS1302Read(unsigned char reg);
void DS1302BurstWrite(unsigned char *dat);
void DS1302BurstRead(unsigned char *dat);
void BCD_TO_DAT(unsigned char *dat);
void DAT_TO_BCD(unsigned char *dat);
void GetRealTime(struct sTime *time);
void SetRealTime(struct sTime *time);
void DS1302Init();
#endif
ds1302.c
#include "ds1302.h"
//发送一个字节到DS1302通信总线上
void DS1302ByteWrite(u8 dat)
{
u8 i;
for(i=0;i<8;i++)
{
DS1302_CLK = 0;
DS1302_IO = dat & 0x01;
DS1302_CLK = 1;
dat>>=1;
}
}
//从DS1302通信总线上读取一个字节
u8 DS1302ByteRead()
{
u8 i;
u8 dat = 0;
for(i=0;i<8;i++)
{
DS1302_CLK = 0;//拉低时钟总线,写数据
dat >>= 1;//先读的数据依次右移至最低位
if(DS1302_IO)
{
dat |= 0x80;
}
DS1302_CLK = 1;//拉高时钟总线,让IO读走一位
}
return dat;
}
//向某一寄存器写入一个字节
void DS1302Write(u8 reg,u8 dat)
{
DS1302_CLK = 0; //拉低使能端
DS1302_CLK = 0; //拉低数据总线
DS1302_CE = 1; //拉高使能端 产生上升沿 准备发送数据
DS1302ByteWrite(reg);
DS1302ByteWrite(dat);
DS1302_CE = 0; //拉低使能端,以便下次产生上升沿
}
//从某一寄存器读取一个字节
u8 DS1302Read(u8 reg)
{
u8 dat = 0;
DS1302_CE = 0;
DS1302_CLK = 0;
DS1302_CE = 1; //产生上升沿 准备发送寄存器指令
DS1302ByteWrite(reg);
dat = DS1302ByteRead();
DS1302_CE = 0;
return dat;
}
//用Burst突发模式连续写入8个寄存器数据
void DS1302BurstWrite(u8 *dat)
{
u8 i;
DS1302_CE = 0;
DS1302_CLK = 0;
DS1302_CE = 1;
DS1302ByteWrite(0xBE);
for(i=0;i<8;i++)
{
DS1302ByteWrite(dat[i]);
}
DS1302_CE = 0;
}
//用Burst突发模式连续读取8个寄存器数据
void DS1302BurstRead(u8 *dat)
{
u8 i;
DS1302_CE = 0;
DS1302_CLK = 0;
DS1302_CE = 1;
DS1302ByteWrite(0xBF);
for(i=0;i<8;i++)
{
dat[i] = DS1302ByteRead();
}
DS1302_CE = 0;
}
//将BCD码转换为十进制数
void BCD_TO_DAT(u8 *dat)
{
u8 i;
unsigned char Temp = 0;
for(i=0;i<sizeof(dat);i++)
{
Temp = dat[i]/16;
dat[i] = dat[i]%16;
dat[i] = dat[i]+Temp*10;
}
}
//将十进制数转换为BCD码
void DAT_TO_BCD(u8 *dat)
{
u8 i;
unsigned char Temp = 0;
for(i=0;i<sizeof(dat);i++)
{
Temp = dat[i]/10;
dat[i] = dat[i]%10;
dat[i] = dat[i] + Temp*16;
}
}
//获取时间
void GetRealTime(struct sTime *time)
{
unsigned char buf[8];
DS1302BurstRead(buf);
BCD_TO_DAT(buf);
time ->sec = buf[0];
time ->min = buf[1];
time ->hour = buf[2];
}
//设置时间
void SetRealTime(struct sTime *time)
{
unsigned char buf[8];
buf[0] = time -> sec;
buf[1] = time -> min;
buf[2] = time -> hour;
DAT_TO_BCD(buf);
DS1302BurstWrite(buf);
}
//DS1302初始化
void DS1302Init()
{
struct sTime InitTime;
//初始化设置 8点59分0秒
InitTime.sec = 0;
InitTime.min = 59;
InitTime.hour = 8;
DS1302Write(0x8e,0x00);//关闭写保护
SetRealTime(&InitTime);
}
main.c
#include "sys.h"
#include "ds1302.h"
struct sTime time;
bit DS1302_READ_FLAG;
void main()
{
ALL_Init();
Timer0Init();
DS1302Init();
while(1)
{
DS1302Write(0x8e,0x00);//关闭写保护
if(DS1302_READ_FLAG)//每100ms获取一次时ds1302数据
{
DS1302_READ_FLAG = 0;
GetRealTime(&time);
DS1302Write(0x8e,0x80);//打开写保护
}
Nixie_Drive(&time);//将ds1302数据通过数码管显示
}
}
nixie.c
#include "sys.h"
#include "ds1302.h"
// 0 1 2 3 4 5 6 7
uchar code nixie[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
// 8 9 a b c d e f u
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xc1}; //共阳数码管码字
uchar NixieBuff[] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
uchar smg1,smg2,smg3,smg4,smg5,smg6,smg7,smg8;
uchar code Symbol[] = {0xff,0xbf}; //全灭,-
void Nixie_Scan()
{
static u8 index;
HC138_Set(7);
P0 = 0xff;
HC138_Set(6);
P0 = 0x01 << index;
HC138_Set(7);
P0 = NixieBuff[index];
HC138_Set(0);
index++;
index &= 0x07;
}
void Nixie_Show()
{
NixieBuff[0] = nixie[smg1];
NixieBuff[1] = nixie[smg2];
NixieBuff[2] = Symbol[smg3];
NixieBuff[3] = nixie[smg4];
NixieBuff[4] = nixie[smg5];
NixieBuff[5] = Symbol[smg6];
NixieBuff[6] = nixie[smg7];
NixieBuff[7] = nixie[smg8];
}
void Nixie_Drive(struct sTime *time)
{
smg1 = time->hour/10;
smg2 = time->hour%10;
smg4 = time->min/10;
smg5 = time->min%10;
smg7 = time->sec/10;
smg8 = time->sec%10;
if(time->sec%2==0)
{
smg3 = smg6 = 1;
}else
{
smg3 = smg6 = 0;
}
}
注意:
1、本程序采用了结构体、指针,让程序显得更为复杂,不采用也一样能实现该功能,代码也会更少。
2、 本程序是直接用的burst模式一次性读写7个寄存器