51单片机DS1302可调时钟


前言

  本次编程实验以IAP15F2K61S2为单片机主控芯片,头文件为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模块

  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

  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
	}
}

2.独立按键模块

  key.h

#ifndef __KEY_H
#define __KEY_H

#include <STC15F2K60S2.H>

sbit key1=P3^0;  //按键连接的IO口定义
sbit key2=P3^1;
sbit key3=P3^2;
sbit key4=P3^3;

unsigned char key_state();  //按键状态获取函数
unsigned char key_send(unsigned char mode);  //按键值发送函数

#endif

  key.c

#include "key.h"

unsigned char key_state()  //得到按键状态
{
	unsigned char keyget=0;
	
	if(!key1)keyget=1;
	if(!key2)keyget=2;
	if(!key3)keyget=3;
	if(!key4)keyget=4;
	
	return keyget;
}
/*
  *  @brief     按键值发送函数
  *  @param     mode:按键长短按模式标志,1长按0短按
*  @reval       keyend:最终返回的按键值
  *  @note    
*/
unsigned char key_send(unsigned char mode)  
{
	static unsigned char keynow=0,keylast=0; //按键当前状态;按键上一次状态
	unsigned char keyend=0;            //最终要发送的按键值
	
	keylast=keynow;       //不断刷新按键上一次状态和当前状态
	keynow=key_state();
	if(keylast==1&&keynow==(1*mode))  //如果按键1按下
		keyend=1;
	if(keylast==2&&keynow==(2*mode))  //如果按键2按下
		keyend=2;
	if(keylast==3&&keynow==(3*mode))  //如果按键3按下
		keyend=3;
	if(keylast==4&&keynow==(4*mode))  //如果按键4按下
		keyend=4;
	
	return keyend;
}

3.定时器0模块

  timer0.h

#ifndef __TIMER0_H
#define __TIMER0_H

#include <STC15F2K60S2.H>

void Timer0_Init(void);		//1毫秒@11.0592MHz

#endif

  timer0.c

#include "timer0.h"

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x01;			//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	
	ET0 = 1;				//使能定时器0中断
	EA=1;
}

4.主函数

  main.c

#include <STC15F2K60S2.H>
#include "timer0.h"
#include "key.h"
#include "ds1302.h"
#define outputp0(y,x) P0=x,P2&=0x1f,P2=y,P2&=0x1f;

typedef unsigned char u8;
u8 code DS1302_write_addr[]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};  //时钟日历7个写寄存器地址
u8 DS1302_write_time[]={0x45,0x59,0x23,0x31,0x12,0x07,0x99};  //预设的时间(BCD码)
u8 DS1302_write_time_temp[]={45,59,23,31,12,07,9};  //预设时间的中间缓存变量(十进制)

u8 keynum=0,keytemp=0,setflag=0,showflag=0;
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()  //测试程序
{
		ET0 = 0;   //关闭定时器0中断
		DS1302_read(DS1302_write_addr);	  //读出DS1302时间
		ET0 = 1;   //打开定时器0中断
		if(keynum==4)   //如果key4按下
		{
			showflag=!showflag;   //切换显示页面和设置模式
			keynum=0;		  //清零按键值
		}
		if(keynum<4&&keynum>0) //如果key1/2/3按下
		{
			u8 i=0;
			ET0 = 0;		//关闭定时器0中断
			DS1302_read(DS1302_write_addr);	  //读出DS1302当前时间,并赋值给写入时间缓存变量,使时间更改在当前时间基础上进行
			for(i=0;i<7;i++)
				DS1302_write_time_temp[i]=read_time[i]/16*10+read_time[i]%16;  //二-十六进制转二-十进制,方便后续处理
			if(showflag==0)   //如果是秒分时设置模式
			{
				switch(keynum)  //如果直接对DS1302_write_time[]中的数据加一,则0x19加一变成0x1A,但DS1302数据以BCD码形式存放,不存在“0xA”
				{
					case 1:DS1302_write_time_temp[0]++;break; //key1按下使秒加一
					case 2:DS1302_write_time_temp[1]++;break; //key2按下使分加一
					case 3:DS1302_write_time_temp[2]++;break; //key3按下使时加一
				}	
				DS1302_write_time_temp[0]%=60;  //范围限定
				DS1302_write_time_temp[1]%=60;
				DS1302_write_time_temp[2]%=24;

				DS1302_write_time[0]=DS1302_write_time_temp[0]/10*16+DS1302_write_time_temp[0]%10;  //二-十进制转二-十六进制,方便写入时间到DS1302
				DS1302_write_time[1]=DS1302_write_time_temp[1]/10*16+DS1302_write_time_temp[1]%10;
				DS1302_write_time[2]=DS1302_write_time_temp[2]/10*16+DS1302_write_time_temp[2]%10;		
			}
			else if(showflag==1)  //如果是年月日设置模式
			{
				switch(keynum)
				{
					case 1:DS1302_write_time_temp[3]++;break; //key1按下使日加一
					case 2:DS1302_write_time_temp[4]++;break; //key2按下使月加一
					case 3:DS1302_write_time_temp[6]++;break; //key3按下使年加一
				}			
				if(DS1302_write_time_temp[4]>12)DS1302_write_time_temp[4]=1;  //月范围限制
				DS1302_write_time_temp[6]%=100;  //年范围限制,只显示年份后两位(00-99)
				//下面是根据年份和月对日范围进行限制,请读者举一反三,自行思考代码含义
				if(DS1302_write_time_temp[4]<8)
				{
					if(DS1302_write_time_temp[4]%2==0)
						DS1302_write_time_temp[3]%=31;
					else if(DS1302_write_time_temp[4]%2==1)
						DS1302_write_time_temp[3]%=32;
				}
				else if(DS1302_write_time_temp[4]==8)
					DS1302_write_time_temp[3]%=32;
				else if(DS1302_write_time_temp[4]>8)
				{
					if(DS1302_write_time_temp[4]%2==0)
						DS1302_write_time_temp[3]%=32;
					else if(DS1302_write_time_temp[4]%2==1)
						DS1302_write_time_temp[3]%=31;
				}
				if(DS1302_write_time_temp[4]==2)
				{
					if(((2000+DS1302_write_time_temp[6])%4)==0)
						DS1302_write_time_temp[3]%=29;
					else if(((2000+DS1302_write_time_temp[6])%4)!=0)
						DS1302_write_time_temp[3]%=30;
				}
				if(DS1302_write_time_temp[3]==0)DS1302_write_time_temp[3]=1;
				 
				DS1302_write_time[3]=DS1302_write_time_temp[3]/10*16+DS1302_write_time_temp[3]%10; //二-十进制转二-十六进制,方便写入时间到DS1302
				DS1302_write_time[4]=DS1302_write_time_temp[4]/10*16+DS1302_write_time_temp[4]%10;
				DS1302_write_time[6]=DS1302_write_time_temp[6]/10*16+DS1302_write_time_temp[6]%10;
			}
		
			DS1302_set(DS1302_write_addr,DS1302_write_time);  //预设DS1302时间
			DS1302_read(DS1302_write_addr);	  //读出DS1302时间		
			keynum=0;		//清零按键值
			ET0 = 1;		//打开定时器0中断
		}

	if(showflag==0)   //显示秒分时
	{
		showbit(0,read_time[2]/16,0);
		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);
	}
	else if(showflag==1)  //显示年月日
	{
		showbit(0,read_time[6]/16,0);  
		showbit(1,read_time[6]%16,0);
		showbit(2,11,0);
		showbit(3,read_time[4]/16,0);
		showbit(4,read_time[4]%16,0);
		showbit(5,11,0);
		showbit(6,read_time[3]/16,0);
		showbit(7,read_time[3]%16,0);
	}

}
void main()
{
	outputp0(0x80,0xff);  //关闭8位LED
	outputp0(0xa0,0x00);  //关闭蜂鸣器继电器等
	Timer0_Init();  //定时器0初始化
	ET0 = 0;   //关闭定时器0中断
	DS1302_set(DS1302_write_addr,DS1302_write_time);  //预设DS1302时间
	ET0 = 1;   //打开定时器0中断
	while(1)
	{
		test();	
	}
}
	
void Timer0_Isr(void) interrupt 1
{
	static u8 longflag=0;      
	static int tkey=0,tlongflag=0,tlongkey=0;
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值

	tkey++;          //按键短按时间计数
	if(tkey>=(500*longflag+25*(!longflag)))   //短按每25ms检测一次按键,长按时防止松手瞬间触发短按
	{
		if(!(key1&key2&key3&key4))     //长按检测,每25ms检测按键是否仍在按下,如果是
		{
			tlongflag++;						//按键按下时间计数(以25ms为单位)
			if(tlongflag>=80)       //如果持续按下2s
			{
				tlongflag=80;         //防止溢出
				longflag=1;           //切换为长按模式
			}
		}
		else if(key1&key2&key3&key4)		//长按检测,每25ms检测按键是否仍在按下,如果不是
		{
			tlongflag=0;							//清零按键按下时间计数
			longflag=0;								//切换为短按模式
		}
		
		tkey=0;             //清零短按时间计数
		if(longflag==0)         //如果是短按模式
		{
			keytemp=key_send(0);  //按键以短按模式检测
			if(keytemp)
			{
				keynum=keytemp;
				setflag=1;
			}
		}
	}
	
	tlongkey++;           //按键长按时间计数
	if(tlongkey>=500)     //长按时每500ms检测一次按键
	{
		tlongkey=0;						  //清零按键长按时间计数
		if(longflag==1)         //如果是长按模式
		{
			keytemp=key_send(1);  //按键以长按模式检测
			if(keytemp)
			{
				keynum=keytemp;
				setflag=1;
			}
		}
	}
}

六、测试现象

  上电显示“23-59-45”,正常读取时间;按下key1/2/3,相应数据改变;上电后15秒内按下key4,切换显示,显示“99-12-31”。若在上电后15秒后切换到年月日显示,由于初始设定时间为“99-12-31-23-59-45”,相当于过去了一天,因此年月日显示为“00-01-01”。需要注意的是:设置时间时,DS1302停止工作,即秒停止自增;显示年月日时,并不妨碍秒分时的改变,DS1302正常工作。


总结

  此次编程实验实现了定时器扫描按键和具有严格时序通信(此次实验为SPI)并存。方法为在通信前关闭中断,在通信完成后打开中断。根据此种方法,可以在通过中断提高MCU工作效率的同时,使中断不干扰具有严格时序的通信(如I2C,One-Wire,SPI等)。

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值