DS1302实时时钟


前言

  本次编程实验以IAP15F2K61S2为单片机主控芯片,其编程使用与STC15F2K60S2完全相同,头文件为STC15F2K60S2.H。若用于51系列单片机,以reg52.h为头文件,则读者需将程序中可能涉及的定时器初始化程序和LED亮灭程序和数码管显示程序,根据自身所用单片机原理图和手册进行修改。


一、DS1302简介

  DS1302是DALLAS公司推出的涓流充电时钟芯片,内含有两块存储器:日历时钟寄存器和静态RAM存储器(31字节),后者可用于用户自定义编程。DS1302采用SPI三线接口与单片机进行通信,可向用户提供秒分时日月年的信息,且可通过 AM/PM 指示决定采用 24 或 12 小时格式,每月的天数和闰年的天数可自动调整。

二、DS1302引脚定义和原理图

1.引脚定义

DS1302引脚图
  Vcc1和Vcc2:电源供电管脚。其中Vcc1作为主电源,Vcc2作为备用电源。当Vcc2>Vcc1+0.2V时,由Vcc2向DS1302供电;当Vcc2< Vcc1时,由Vcc1向DS1302供电。
  X1和X2:32.768KHz 晶振管脚
  SCLK:串行时钟输入。控制数据的输入和输出。
  I/O: 数据输入和输出引脚
  CE:使能信号,在读、写数据期间,必须为高电平。

2.原理图

DS1302原理图
  由原理图可知,SCLK引脚为P1^ 7 ; I /O引脚为P2^ 3;CE引脚为P1^ 3。

三、DS1302编程原理

1.时钟日历寄存器定义

DS1302时钟寄存器
  时钟日历寄存器包含在 7 个读/写寄存器内,相关数据以BCD码显示存放。BCD码是二进制十进制代码,是一种二进制的数字编码形式,利用四个位元来储存一个十进制的数码,可以快速转换。如10对应的BCD码为0x10,25对应0x25,简单来说就是在十进制前加0x(十六进制标志)即可。时钟日历寄存器从第一行至第七行,每一行分别为秒分时日月星期年寄存器。第八行为写保护寄存器。此外,第一列为读控制字节,第二列为写控制字节(后文介绍)。
  7 个读/写寄存器的每一位,并不都是用于存放数据:
  1.秒寄存器的 BIT7 定义为时间暂停位,当 BIT1 为 1 时,时钟振荡器停止工作,DS1302 进入低功耗模式,电源消耗小于 100 微安;当 BIT1 为 0 时,时钟振荡器启动,DS1302 正常工作。
  2.小时寄存器的 BIT7 定义为 12 或 24 小时工作模式选择位,当 BIT7 为高时,为 12 小时工作模式,此时 BIT5 为 AM/PM 位,低电平标示 AM,高电平标示PM,在 24 小时模式下,BIT5 为第二个 10 小时位标示(20~23 时)。
  在对上述寄存器进行数据写入时,必须先将写保护寄存器的 BIT7(即WP,写保护位)置低电平,其他位都置为0;否则不能对任何时钟日历寄存器进行写操作,但可以进行读操作。

2.控制字节定义

  在单片机与DS1302进行数据通信时,需要发送一个控制字节,控制字节的意义在于:让DS1302知道在何处如何对数据进行处理(存放或读取)。
DS1302控制字定义  位7:必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。
  位6:如果为0,则表示存取日历时钟数据,为1表示存取RAM数据。
  位5-位1:指示操作单元的地址。
  位0(最低有效位):为0,表示要进行写操作,写入数据;为1,表示进行读操作,读出数据。
  以对秒寄存器进行写操作为例,控制字节为1000 0000,即0x80,符合时钟日历寄存器中秒寄存器的写控制字节。

3.时序图

  写时序:首先CE置高电平,开始的 8 个 SCLK 周期,每迎来一次上升沿,输入写控制字节的一位;数据字节在后 8 个 SCLK 周期的
上升沿输入;最后CE置低电平,结束通信。写控制字节输入和数据字节输入均从位 0 开始。

DS1302写时序图
  读时序:首先CE置高电平,开始的 8 个 SCLK 周期,每迎来一次上升沿,输入读控制字节的一位;数据字节在后 8 个 SCLK 周期的
下降沿输出;最后CE置低电平,结束通信。读控制字节输入和数据字节输出均从位 0 开始。需要注意的是:在读控制字节位7输入后,SCLK将迎来一次下降沿,数据字节将从此次下降沿开始发送。
DS1302读时序

4.具体过程

  先向DS1302写入数据,设定起始时间:首先关闭写保护,然后写入写控制字节及数据字节,最后打开写保护。向DS1302读取数据时:直接写入读控制字节,即可读出数据。

四、编程实现

1.ds1302.h

#ifndef __DS1302_H
#define __DS1302_H

#include <STC15F2K60S2.H>
#include <intrins.h>

//对DS1302引脚进行I/O口定义
sbit SCK=P1^7;		//串行时钟线
sbit SDA=P2^3;		//数据线
sbit RST = P1^3;   // DS1302使能线

extern unsigned char read_time[]; //声明外部变量

void DS1302_Write(unsigned char temp);  //向DS1302写入一个字节
void DS1302_Write_Byte( unsigned char address,unsigned char dat ); //向指定寄存器写入数据
unsigned char DS1302_Read_Byte( unsigned char address );   //从指定寄存器读出数据
void DS1302_set(unsigned char DS1302_set_addr[],unsigned char ds1302_set_time[]);  //DS1302初始时间设定
void DS1302_read(unsigned char DS1302_set_addr[]);  //DS1302时间读取

#endif

2.ds1302.c

#include "ds1302.h"		

unsigned char read_time[7]={0};  //DS1302时间存储数组

/*
  *  @brief     DS1302通信初始化
  *  @param    
  *  @reval      
  *  @note:    
*/

void DS1302_Start()
{
 	RST=0;	_nop_();  //DS1302复位
 	SCK=0;	_nop_();  //拉低串行时钟线
 	RST=1; 	_nop_();  //禁止复位
}
/*
  *  @brief     向DS1302写入一个字节
  *  @param     byte:要写入的字节
  *  @reval      
  *  @note:    
*/
void DS1302_Write(unsigned  char byte) 
{
	unsigned char i=0; 
	for (i=0;i<8;i++)     	
	{ 
		SCK=0;    //拉低时钟线
		SDA=byte&0x01;   //取出写入字节的位0,放到数据线上
		byte>>=1;      //byte字节右移1位,从而位1变成位0,位2变成位1...从而低位先写入
		SCK=1;   //拉高时钟线,产生上升沿,发送一位数据
	}
}   
/*
  *  @brief     向DS1302寄存器写入数据
  *  @param     address:要写入数据的寄存器;dat:要写入的数据
  *  @reval      
  *  @note:    
*/

void DS1302_Write_Byte( unsigned char address,unsigned char dat )     
{
	DS1302_Start();   //DS1302通信初始化
 	DS1302_Write(address);	//写入控制字节,即指明寄存器和读/写操作
 	DS1302_Write(dat);   //写入数据
 	RST=0; 		//通信结束
}
/*
  *  @brief     从DS1302寄存器读取数据
  *  @param     address:待读取数据的寄存器
  *  @reval     temp:读出的数据
  *  @note:    
*/

unsigned char DS1302_Read_Byte ( unsigned char address )
{
 	unsigned char i=0,temp=0x00;
  DS1302_Start();   //DS1302通信初始化
 	DS1302_Write(address);	//写入控制字节,即指明寄存器和读/写操作,此行代码运行结束后,SCK=1
 	for (i=0;i<8;i++) 	
 	{		
		SCK=0;    //拉低时钟线,产生下降沿,从而开始发送一位数据
		temp>>=1;	//temp右移一位,清零位7
 		if(SDA)      //与下一行代码搭配,如果SDA=1,则temp位7为1;反之为0,从而读出数据。
 		temp|=0x80;	 //读出一位,并赋值给temp的位7,下次循环时,temp右移一位,使得位7变成位6,位6变成位5...从而低位先读出
 		SCK=1;   //拉高时钟线
	} 
 	RST=0;	_nop_();   //结束通信
 	SCK=0;	_nop_();   //拉低时钟线
	SCK=1;	_nop_();   //拉高时钟线,实质是复位到高电平
	SDA=0;	_nop_();   
	SDA=1;	_nop_();   //复位数据线到高电平
	return (temp);		//返回读出的数据字节
}
/*
  *  @brief     DS1302初始时间设定
  *  @param     DS1302_set_addr[]:时钟日历7个写寄存器地址;DS1302_set_time[]:预设的时间(BCD码)
  *  @reval      
  *  @note:    
*/

void DS1302_set(unsigned char DS1302_set_addr[],unsigned char DS1302_set_time[])
{
	unsigned char i=0;
	DS1302_Write_Byte(0x8e,0x00);    //关闭写保护
	for(i=0;i<7;i++)
	{
		DS1302_Write_Byte(DS1302_set_addr[i],DS1302_set_time[i]);//向时钟日历寄存器写入相应数据
	}
	DS1302_Write_Byte(0x8e,0x80);    //打开写保护
}
/*
  *  @brief     DS1302时间读取
  *  @param     DS1302_set_addr[]:时钟日历7个写寄存器地址
  *  @reval      
  *  @note:    
*/

void DS1302_read(unsigned char DS1302_set_addr[])
{
	unsigned char i=0;
	for(i=0;i<7;i++)
	{
		read_time[i]=DS1302_Read_Byte(DS1302_set_addr[i]+0x01);//注意到每个时钟日历读寄存器地址比写寄存器地址大1,所以读寄存器地址在写寄存器地址基础上加1
	}

}

3.main.c

#include <STC15F2K60S2.H>
#include "ds1302.h"

typedef unsigned char u8;

#define outputp0(y,x)  P0=x,P2&=0x1f,P2|=y,P2&=0x1f;//P2高三位用于选择P0输出的通道,P0用于数据输出

u8 code DS1302_write_addr[]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};  //时钟日历7个写寄存器地址
u8 code DS1302_write_time[]={0x45,0x59,0x23,0x31,0x12,0x07,0x99};  //预设的时间,依次为秒分时日月星期年

unsigned char code Seg_Table[]={   //共阳数码管
0xc0,  
0xf9,
0xa4,
0xb0,
0x99,
0x92,
0x82,
0xf8,
0x80,
0x90,
0xff,
0xbf  //'-'
};

void Delay2ms(void)	//@11.0592MHz
{
	unsigned char data i, j;

	_nop_();
	_nop_();
	i = 22;
	j = 128;
	do
	{
		while (--j);
	} while (--i);
}
/*
  *  @brief     1位数码管显示函数
  *  @param     pos:位选;dat:显示数字;dot:小数点选择位,1有0无
  *  @reval      
  *  @note:    
*/

void showbit(unsigned char pos,dat,dot)
{
	outputp0(0xc0,0x01<<pos);    //位选
	outputp0(0xe0,Seg_Table[dat]+0x80*dot);	  //段选
	Delay2ms();   //延时

	outputp0(0xc0,0x01<<pos);      //位选
	outputp0(0xe0,Seg_Table[10]);   //段选,这两行用于消影
}
void test()   //测试程序
{
	DS1302_read(DS1302_write_addr);	  //读出DS1302时间
  //时间显示,可通过更改read_time[]索引值显示秒分时日月星期年
	showbit(0,read_time[2]/16,0); //由于BCD码相当于将十进制十位乘以16加上个位获得,因此对其除以16获得十位
	showbit(1,read_time[2]%16,0);  //对其取余获得个位
	showbit(2,11,0);
	showbit(3,read_time[1]/16,0);
	showbit(4,read_time[1]%16,0);
	showbit(5,11,0);
	showbit(6,read_time[0]/16,0);
	showbit(7,read_time[0]%16,0);
}	
void main()
{
	outputp0(0x80,0xff);  //关闭8位LED
	outputp0(0xa0,0x00);  //关闭蜂鸣器、继电器等
	DS1302_set(DS1302_write_addr,DS1302_write_time);  //预设DS1302时间
	
	while(1)
	{
		test();	
	}	
}

五、测试现象

  上电显示“23-59-45”,15秒后变成“00-00-00”。


总结

  DS1302通过SPI与单片机进行通信,其通信时序相对复杂,期间不能被打断,一般来说无法使用定时器扫描数码管的方式来显示时间。当然,通过某些方式也可以实现二者并存,请读者自行思考。

  有任何问题和补充,欢迎私信或评论区交流。

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值