51单片机-DS1302

一、DS1302概述

  • DS1302是由美国DALLAS公司推出的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等功能
  • RTC:实时时钟,是一种集成电路,通常称为时钟芯片,有备用电池,可以掉电运行

  定时器也可以实现实时时钟的功能,采用DS1302是因为定时器实现精度不高、占用单片机CPU的时间且定时器时钟不可以掉电运行

1.1 BCD码

  • RTC内部的数据不是以正常的二进制数来存储时间数据的,而是以BCD码来存储时间数据的
  • BCD码用4位二进制数来表示1位十进制数。8位二进制数,高4位表示十进制的十位低4位表示十进制的个位(例:0001 0011表示13,1000 0101表示85,0001 1010不合法)
  • BCD码转十进制:DEC = BCD/16*10(十位) + BCD/16(个位)
  • 十进制转BCD码:BCD = DEC/10*16 + DEC%10

二、DS1302电路

  下面为DS1302的工作电路

  • VCC2/1:主电源/备用电池
  • GND:接地
  • CE:芯片使能
  • IO:数据输入/输出
  • SCLK:串行时钟
  • X1、X2:32.768KHz晶振,时钟部分

Alt

  下面为DS1302内部结构框图
  内部结构中第一块是电源控制;第二块是X1和X2时钟部分;第三块是RAM存储部分,用于存储时间数据;最后一块是命令控制部分,包括CE使能输入移位寄存器和读写访问

Alt

三、DS1302寄存器和数据时序

3.1 寄存器

  下面为RTC相关的寄存器
  从上到下依次是秒寄存器分寄存器小时寄存器日寄存器月寄存器星期寄存器年寄存器写保护寄存器(WP给1时、写入操作无效)和存储涓流充电寄存器

在这里插入图片描述

  上图前两列是命令字,地址/命令字组成下图所示:总共8位,最高位默认为1,第6位是决定操作RAM还是时钟,A4-A0是地址,最后一位是读写位(秒寄存器为例,秒寄存器地址是00000,读操作时,8位为1000 0001,刚好是81,写操作时,8位位1000 0000,刚好是80)

在这里插入图片描述

3.2 数据时序

  下图为数据时序
  CE给1为使能RTC。SCLK是时钟线,每来一个上升沿,单片机将地址/数据写入到DS1302,每来一个下降沿时,单片机从DS1302里读数据,即DS1302自动将时间数据发送给单片机。IO是数据线,前8位为地址命令字,低位在前,后8位为时间数据

  单字节读:CE使能后,SCLK每来一个上升沿,单片机向DS1302发送地址命令字,确定在哪个地方读;然后每来一个下降沿,DS1302自动把数据放在IO线上,即DS1302发送时间数据给单片机,单片机读时间

  单字节写:CE使能后,SCLK每来一个上升沿,单片机向DS1302发送地址命令字,确定在哪个地方写;然后继续每来一个上升沿,单片机向DS1302发送时间数据,用以设置时间

在这里插入图片描述

四、程序编写

4.1 DS1302时钟

  DS1302显示时钟需要编写两个主要函数
  其一是单字节读,单片机给DS1302发送地址命令字,DS1302再给单片机发送时间数据,单片机读DS1302的时间数据。
  其二是单字节写,用于设置时间,单片机给DS1302发送地址命令字和时间数据。
  由于DS1302的时间数据是BCD码,所以写入时间时,需要转为BCD码写入;单片机读数据时,要将BCD转为十进制数,显示在显示屏上

  下面为DS1302.c

#include <REGX52.H>

sbit DS1302_SCLK = P3^6;//时钟线
sbit DS1302_IO = P3^4;//数据线
sbit DS1302_CE = P3^5;//使能

#define DS1302_SEC    0x80		//秒寄存器
#define DS1302_MIN    0x82		//分寄存器
#define DS1302_HOUR   0x84		//小时寄存器
#define DS1302_DATE   0x86		//日寄存器
#define DS1302_MON    0x88		//月寄存器
#define DS1302_DAY    0x8A		//星期寄存器
#define DS1302_YEAR   0x8C		//年寄存器
#define DS1302_WP     0x8E		//写保护寄存器

unsigned char Time[] = {24,2,18,21,20,50,7};		//时间初始值

/*
函数功能:时钟初始化
*/
void DS1302_Init(void)
{
	DS1302_CE = 0;
	DS1302_SCLK = 0;                                                                                                              
}

/*
函数功能:MCU写地址命令字和时间数据给DS1302
形式参数:地址命令字,时间数据
*/
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE = 1;		//DS1302使能
	for(i=0;i<8;i++)		//写命令字
	{
		DS1302_IO = Command & (0x01<<i);		//低位在前
		DS1302_SCLK = 1;		//上升沿写
		DS1302_SCLK = 0;		//清零
	}
	for(i=0;i<8;i++)		//写时间数据
	{
		DS1302_IO = Data & (0x01<<i);//低位在前
		DS1302_SCLK = 1;
		DS1302_SCLK = 0;
	}
	DS1302_CE = 0;		//使能初始化
}

/*
函数功能:MCU从固定地址里读时间数据
形式参数:地址命令字
返回值:时间数据
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data = 0x00;		//局部变量初始值不一定是0
	Command |= 0x01;		//读命令字,最低位是1
	DS1302_CE = 1;
	for(i=0;i<8;i++)		//写命令字
	{
		DS1302_IO = Command & (0x01<<i);		//上升沿写8位命令字
		DS1302_SCLK = 0;
		DS1302_SCLK = 1;		//先0再1上升沿写
	}
	for(i=0;i<8;i++)		//读数据
	{
		DS1302_SCLK = 1;		//重复给(周期)
		DS1302_SCLK = 0;
		if(DS1302_IO){Data |= (0x01<<i);}		//下降沿读8位数据,IO口上已经有数据(DS1302是自动把数据放在IO线上)
	}
	DS1302_CE = 0;
	DS1302_IO = 0;		//清零
	return Data;
}

/*
函数功能:调用函数表示开始设置时间:年 月 日 小时 分 秒 星期
*/
void DS1302_SetTime(void)
{
	DS1302_WriteByte(DS1302_WP,0x00);		//关闭写保护,写时间时,需要关闭写保护
	DS1302_WriteByte(DS1302_YEAR,Time[0]/10*16+Time[0]%10);		//写入年(十进制转为8位BCD码)
	DS1302_WriteByte(DS1302_MON,Time[1]/10*16+Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,Time[2]/10*16+Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,Time[3]/10*16+Time[3]%10);
	DS1302_WriteByte(DS1302_MIN,Time[4]/10*16+Time[4]%10);
	DS1302_WriteByte(DS1302_SEC,Time[5]/10*16+Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,Time[6]/10*16+Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);//打开写保护
}

/*
函数功能:调用函数表示读时间:年 月 日 小时 分 秒 星期
*/
void DS1302_ReadTime(void)
{
	unsigned char Temp;
	Temp = DS1302_ReadByte(DS1302_YEAR);		//读年寄存器的数据
	Time[0] = Temp/16*10+Temp%16; 		//将读的数据,转为十进制,并存入数组
	Temp = DS1302_ReadByte(DS1302_MON);
	Time[1] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_DATE);
	Time[2] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_HOUR);
	Time[3] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_MIN);
	Time[4] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_SEC);
	Time[5] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_DAY);
	Time[6] = Temp/16*10+Temp%16;
}

  注意其中的Time[]数组要在声明文件中声明,以保证外部可以调用,并且在主函数中显示时间数据时,要注意解除写保护状态

4.2 DS1302可调时钟

  可调时钟需要利用按键去设置时间,所以只需要加上调节年月日等等的逻辑判断,即可实现一个完整的可调时钟
  下面是main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"

unsigned char KeyNum;		//按键键码
unsigned char MODE;		//模式(1为设置模式 0为显示模式)
unsigned char TimeSetSelect;		//设置位数(年 月 日 小时 分 秒 星期)
unsigned char TimeSetFlashFlag;		//设置确定or进入设置状态

/*
函数功能:调用函数,显示时间
*/
void TimeShow(void)
{
	DS1302_ReadTime();		//读取时间,单片机从RTC读
	LCD_ShowNum(1,1,Time[0],2);		//会自动+1
	LCD_ShowNum(1,4,Time[1],2);
	LCD_ShowNum(1,7,Time[2],2);
	LCD_ShowNum(2,1,Time[3],2);
	LCD_ShowNum(2,4,Time[4],2);
	LCD_ShowNum(2,7,Time[5],2);
	LCD_ShowNum(2,10,Time[6],1);
}

/*
函数功能:设置时钟
*/
void TimeSet(void)
{
	if(KeyNum ==2)		//选择设置第几位,年月日时分秒星期
	{
		TimeSetSelect++;
		TimeSetSelect%=7;		//0-6
	}
	if(KeyNum ==3)		//加1
	{
		Time[TimeSetSelect]++;		//数据++
		
//下面要进行合法判断
		
		if(Time[0]>99){Time[0]=0;}		//年合法判断
		if(Time[1]>12){Time[1]=1;}		//月合法判断
		
		if(Time[1]==1 || Time[1]==3 || Time[1]==5 ||
			 Time[1]==7 || Time[1]==8 || Time[1]==10 ||
				Time[1]==12)
		{
			if(Time[2]>31){Time[2]=1;}		//大月份
		}
		
		else if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11)
		{
			if(Time[2]>30){Time[2]=1;}		//小月份
		}
		
		else if(Time[1]==2)		//2月
		{
			if(Time[0]%4==0){if(Time[2]>29){Time[2]=1;}}		//闰年的二月 29天
			else{if(Time[2]>28){Time[2]=1;}}		//平年给28天
		}

		if(Time[3]>23){Time[3]=0;}		//时合法判断
		
		if(Time[4]>59){Time[4]=0;}		//分合法判断
		
		if(Time[5]>59){Time[5]=0;}		//秒合法判断
		
		if(Time[6]>7){Time[6]=1;}		//星期合法判断
		
	}
	if(KeyNum ==4)		//减1
	{
		Time[TimeSetSelect]--;		//数据--
		
//下面要进行合法判断
		
		if(Time[0]<0){Time[0]=99;}		//年合法判断
		if(Time[1]<1){Time[1]=12;}		//月合法判断
		
		if(Time[1]==1 || Time[1]==3 || Time[1]==5 ||
			 Time[1]==7 || Time[1]==8 || Time[1]==10 ||
				Time[1]==12)
		{
			if(Time[2]<1){Time[2]=31;}		//大月份
			if(Time[2]>31){Time[2]=1;}		//由于12月31日直接减月份会减到11月31日,11月没有31号,存在bug
		}
		else if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11)
		{
			if(Time[2]<1){Time[2]=30;}		//小月份
			if(Time[2]>30){Time[2]=1;}		//过大判断
		}
		else if(Time[1]==2)		//如果是2月
		{
			if(Time[0]%4==0)
				{
					if(Time[2]<1){Time[2]=29;}
					if(Time[2]>29){Time[2]=1;}
				}		//闰年的二月 29天
			else
				{
					if(Time[2]<1){Time[2]=28;}
					if(Time[2]>28){Time[2]=1;}
				}		//平年给28天
		}

		if(Time[3]<0){Time[3]=23;}		//时合法判断
		
		if(Time[4]<0){Time[4]=59;}		//分合法判断
		
		if(Time[5]<0){Time[5]=59;}		//秒合法判断
		
		if(Time[6]<1){Time[6]=7;}		//星期合法判断
	}
	
	if(TimeSetSelect ==0 && TimeSetFlashFlag == 1)		//设置位在年,Flag会0 1 500ms循环交替
	{LCD_ShowString(1,1,"  ");}
	else
	{LCD_ShowNum(1,1,Time[0],2);}		//更新年显示(闪烁版)
	
	if(TimeSetSelect ==1 && TimeSetFlashFlag == 1)
	{LCD_ShowString(1,4,"  ");}
	else
	{LCD_ShowNum(1,4,Time[1],2);}		//更新月显示(闪烁版)
	
	if(TimeSetSelect ==2 && TimeSetFlashFlag == 1)
	{LCD_ShowString(1,7,"  ");}
	else
	{LCD_ShowNum(1,7,Time[2],2);}		//更新日显示(闪烁版)
	
	if(TimeSetSelect ==3 && TimeSetFlashFlag == 1)
	{LCD_ShowString(2,1,"  ");}
	else
	{LCD_ShowNum(2,1,Time[3],2);}		//更新时显示(闪烁版)
	
	if(TimeSetSelect ==4 && TimeSetFlashFlag == 1)
	{LCD_ShowString(2,4,"  ");}
	else
	{LCD_ShowNum(2,4,Time[4],2);}		//更新分显示(闪烁版)
	
	if(TimeSetSelect ==5 && TimeSetFlashFlag == 1)
	{LCD_ShowString(2,7,"  ");}
	else
	{LCD_ShowNum(2,7,Time[5],2);}		//更新秒显示(闪烁版)
	
	if(TimeSetSelect ==6 && TimeSetFlashFlag == 1)
	{LCD_ShowString(2,10,"  ");}
	else
	{LCD_ShowNum(2,10,Time[6],1);}		//更新星期显示(闪烁版)
}


void main()
{
	LCD_Init();
	DS1302_Init();
	Timer0_Init();		//初始化
	
	LCD_ShowString(1,3,"-");
	LCD_ShowString(1,6,"-");
	LCD_ShowString(2,3,":");
	LCD_ShowString(2,6,":");
	DS1302_WriteByte(0x8E,0x00);		//解除芯片写保护
	DS1302_SetTime();		//MCU写入时间:将往RTC里写
	
	
	while(1)
	{
		KeyNum = Key();
		if(KeyNum == 1)		//按键1为模式设置(设置or显示)
		{
			if(MODE==0){MODE = 1;TimeSetSelect = 0;}
			else if(MODE ==1){MODE = 0;DS1302_SetTime();}
		}
		switch(MODE)
		{
			case 0:TimeShow();break;		//MODE=0 显示时间
			case 1:TimeSet();break;		//MODE=1 设置时间
		}
	}
}

//1ms
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;
	TH0 = 0xFC;
	T0Count++;
	if(T0Count>=500)		//500ms
	{
		T0Count = 0;
		TimeSetFlashFlag = !TimeSetFlashFlag;
		
	}
}
  • 29
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值