51单片机从零开始入门教程(DS1302实时时钟篇)

参考教程:[10-1] DS1302实时时钟_哔哩哔哩_bilibili

1、DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片,它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。

2、RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片

3、开发板上的DS1302时钟模块:

引脚名

作用

引脚名

作用

VCC2

主电源

CE

芯片使能

VCC1

备用电池

IO

数据输入/输出

GND

电源地

SLCK

串行时钟

X1、X2

32.768kHz晶振

(提供计数脉冲)

注:在单片机断电后,备用电池启动,时钟会继续计时,直到下次重启单片机时备用电池休息(单片机通电时备用电池充电,时钟由VCC2供电;开发板貌似没有提供备用电池,只是提供了这么一个引脚,备用电池需要自己安装)。

4、内部结构框图:X1、X2提供计数脉冲,实时时钟负责记录时间数据,当CE处于高电平时,实时时钟中的时间数据可以和移位寄存器的数据发生交互(读/写),这个过程和74HC595类似,这里不再赘述;命令控制逻辑用来解释命令字,然后执行相应操作,这个命令字在下面马上就有解释。

5、相关寄存器与命令字:

(1)下面九个寄存器是RTC相关的寄存器(实时时钟下的寄存器):WP置为1,前7个寄存器打开写保护,里面的数据只可读不可改;最后一个寄存器与充电相关,目前可以不予理会;前7个寄存器负责记录年月日时分秒以及星期几(采用的是BCD码,个位和十位分别记录),CH置为1的话,时钟会暂停计时

(2)下图所示的是地址/命令字节,第7位固定为1,第6位置为0可操作时钟(操作RAM则置为1),第5位到第1位则是寄存器地址,第0位如果置为0则进行写操作(如果置为1则进行读操作)这个命令字,其实就是上图中第一列和第二列,比如要对秒寄存器进行读操作,则命令字为0x81。

6、时序:

(1)CE为高电平时才能进行读/写操作

(2)SCLK提供脉冲,每一个上升沿写入一位数据,每一个下降沿读出一位数据

(3)CE置为高电平后,先将8位命令通过I/O写入单片机,单片机根据命令字的解释进行读or写操作,读/写的数据也是通过I/O进行传输,操作完成后CE置为低电平,SCLK也停止提供脉冲。

7、BCD码:BCD码(Binary Coded Decimal‎),用4位二进制数来表示1位十进制数

(1)0001 0011表示13,1000 0101表示85,0001 1010不合法,对应在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法。

(2)BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)

(3)十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)

8、利用DS1302时钟显示当前时间:

(1)项目包含的文件:其中需要重写的都会在下面给出,未给出的沿用旧例出现过的即可(本例需要液晶屏模块的代码文件)。

(2)补充需要重写或新添加的代码文件,然后进行编译。

①DS1302.h文件:

#ifndef __DS1302_H__
#define __DS1302_H__

void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command, Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);

extern unsigned char DS1302_Time[];  //外部可调用该数组

#endif

②DS1302.c文件:

#include <REGX52.H>

sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;

#define DS1302_SECOND     0x80  //这些都是写操作的命令字,要进行读操作+1即可
#define DS1302_MINUTE     0x82
#define DS1302_HOUR       0x84
#define DS1302_DATE       0x86
#define DS1302_MONTH      0x88
#define DS1302_DAY        0x8A
#define DS1302_YEAR       0x8C
#define DS1302_WP         0x8E

unsigned char DS1302_Time[] = 
{23,11,23,12,59,55,4};  //记录当前时间的数组(初始值为初始时间)

void DS1302_Init(void)
{
	DS1302_CE = 0;
	DS1302_SCLK = 0;
}

void DS1302_WriteByte(unsigned char Command, unsigned char Data)
{
	unsigned char i = 0;  //局部变量放在第一行,否则有些编译器可能会报错
	DS1302_CE = 1;  //CE置为高电平,开始操作
	
	for(i = 0; i < 8; i++)  //使用循环将8位命令通过I/O口一位一位写入移位寄存器
	{
		DS1302_IO = Command & (0x01 << i);  //将8位数据一位一位写入I/O口
	
		DS1302_SCLK = 1;  //上升沿(触发上升沿,将I/O的1位数据送到控制逻辑)
		DS1302_SCLK = 0;  //下降沿
		//这里写了一个时钟脉冲,需要查看手册了解单片机能承受的时钟频率
		//如果不能承受过高频率,需要在两条语句间加延时函数
	}
	//控制逻辑接收完整的命令字,进行解释与操作
	
	for(i = 0; i < 8; i++)  //使用循环将8位数据通过I/O口一位一位写入寄存器
	{
		DS1302_IO = Data & (0x01 << i);  //将8位数据一位一位写入I/O口
	
		DS1302_SCLK = 1;  //上升沿(触发上升沿,将I/O的1位数据写进实时时钟下的寄存器)
		DS1302_SCLK = 0;  //下降沿
	}
	
	DS1302_CE = 0;  //操作完成,CE置为低电平
}

unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i = 0;
	unsigned char Data = 0x00;
	
	DS1302_CE = 1;
	
	for(i = 0; i < 8; i++)  //使用循环将8位命令通过I/O口一位一位写入移位寄存器
	{
		DS1302_IO = Command & (0x01 << i);  //使用循环将8位命令通过I/O口一位一位写入寄存器
	
		DS1302_SCLK = 0;  //下降沿
		DS1302_SCLK = 1;  //上升沿(触发上升沿,将I/O的1位数据送到控制逻辑)
		//这里写了一个脉冲,需要查看手册了解单片机能承受的频率
		//如果不能承受过高频率,需要在两条语句间加延时函数
	}
	//控制逻辑接收完整的命令字,进行解释与操作
	
	for(i = 0; i < 8; i++)
	{
		DS1302_SCLK = 1;  //上升沿
		DS1302_SCLK = 0;  //下降沿(触发下降沿,I/O从寄存器中读出一位数据)
		if(DS1302_IO)
		{
			Data = Data | (0x01 << i);  //将I/O读出的一位数据记录在Data变量中
			//Data本身8位都是低电平,遇到高电平数据再进行更改即可
		}
	}
	
	DS1302_CE = 0;  //操作完成,CE置为低电平
	DS1302_IO = 0;  //I/O口清零
	return Data;
}

void DS1302_SetTime(void)
{
	DS1302_WriteByte(DS1302_WP,0x00);  //解除写保护
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);  //设置初始年份
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);  //设置初始月份
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);  //设置初始日
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);  //设置初始时
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);  //设置初始分
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);  //设置初始秒
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);  //初始时间是星期几
	DS1302_WriteByte(DS1302_WP,0x80);  //恢复写保护
}

void DS1302_ReadTime(void)
{
	unsigned char Temp;
	Temp = DS1302_ReadByte(DS1302_YEAR + 1);  //读当前年份
	DS1302_Time[0] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_MONTH + 1);  //读当前月份
	DS1302_Time[1] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_DATE + 1);  //读当前日
	DS1302_Time[2] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_HOUR + 1);  //读当前时
	DS1302_Time[3] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_MINUTE + 1);  //读当前分
	DS1302_Time[4] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_SECOND + 1);  //读当前秒
	DS1302_Time[5] = Temp/16*10+Temp%16;
	Temp = DS1302_ReadByte(DS1302_DAY + 1);  //当前时间是星期几
	DS1302_Time[6] = Temp/16*10+Temp%16;
}

③main.c文件:

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

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	DS1302_Init();
	DS1302_SetTime();  //设置初始时间
	
	while(1)
	{
		DS1302_ReadTime();  //获取当前时间
		LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示年份
		LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示月份
		LCD_ShowNum(1,7,DS1302_Time[2],2);  //显示日
		LCD_ShowNum(2,1,DS1302_Time[3],2);  //显示时
		LCD_ShowNum(2,4,DS1302_Time[4],2);  //显示分
		LCD_ShowNum(2,7,DS1302_Time[5],2);  //显示秒
	}
}

(3)将生成的.hex文件下载到开发板中,可以看到液晶屏上显示当前的时间。

9、利用DS1302时钟显示当前时间,并且可修改当前显示时间:

(1)项目包含的文件:其中需要重写的都会在下面给出,未给出的沿用旧例出现过的即可(本例需要液晶屏模块以及延时函数的代码文件)。

(2)补充需要重写或新添加的代码文件,然后进行编译。

①DS1302.c文件以及DS1302.h文件中的DS1302_Time[]数组改为char类型。

②main.c文件:

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

unsigned char KeyNum, MODE, TimeSetSelect, TimeSetFlashFlag;

void TimeShow(void)
{
	DS1302_ReadTime();  //获取当前时间
	LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示年份
	LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示月份
	LCD_ShowNum(1,7,DS1302_Time[2],2);  //显示日
	LCD_ShowNum(2,1,DS1302_Time[3],2);  //显示时
	LCD_ShowNum(2,4,DS1302_Time[4],2);  //显示分
	LCD_ShowNum(2,7,DS1302_Time[5],2);  //显示秒
}

void TimeSet(void)
{
	if(KeyNum == 2)  //按键2用于选择需要修改的数字
	{
		TimeSetSelect++;  //TimeSetSelect作为DS1302_Time数组的下标,用于访问以及修改DS1302_Time数组
		TimeSetSelect %= 6;  //防止越界(统共有6个选项)
	}
	if(KeyNum == 3)  //按键3用于增加设置值
	{
		DS1302_Time[TimeSetSelect]++;
		if(DS1302_Time[0]>99){DS1302_Time[0] = 0;}
		if(DS1302_Time[1]>12){DS1302_Time[1] = 1;}
		if(DS1302_Time[1] == 1 || DS1302_Time[1] == 3 || DS1302_Time[1] == 5
			|| DS1302_Time[1] == 7 || DS1302_Time[1] == 8 ||DS1302_Time[1] == 10
		  || DS1302_Time[1] == 12)
		{
			if(DS1302_Time[2]>31){DS1302_Time[2] = 1;}
		}
		else if(DS1302_Time[1] == 4 || DS1302_Time[1] == 6 || DS1302_Time[1] == 9
			|| DS1302_Time[1] == 11)
		{
			if(DS1302_Time[2]>30){DS1302_Time[2] = 1;}
		}
		else if(DS1302_Time[1] == 2)
		{
			if((DS1302_Time[0]%4 == 0 && DS1302_Time[0]%100 != 0) 
				|| (DS1302_Time[0]%400 == 0))
			{
				if(DS1302_Time[2]>29){DS1302_Time[2] = 1;}  //闰年2月有29天
			}
			else
			{
				if(DS1302_Time[2]>28){DS1302_Time[2] = 1;}  //平年2月有28天
			}
		}
		if(DS1302_Time[3]>23){DS1302_Time[3] = 0;}
		if(DS1302_Time[4]>59){DS1302_Time[4] = 0;}
		if(DS1302_Time[5]>59){DS1302_Time[5] = 0;}
	}
	if(KeyNum == 4)  //按键4用于减少设置值
	{
		DS1302_Time[TimeSetSelect]--;
		if(DS1302_Time[0]<0){DS1302_Time[0] = 99;}
		if(DS1302_Time[1]<1){DS1302_Time[1] = 12;}
		if(DS1302_Time[1] == 1 || DS1302_Time[1] == 3 || DS1302_Time[1] == 5
			|| DS1302_Time[1] == 7 || DS1302_Time[1] == 8 ||DS1302_Time[1] == 10
		  || DS1302_Time[1] == 12)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2] = 31;}
			if(DS1302_Time[2]>31){DS1302_Time[2] = 1;}
		}
		else if(DS1302_Time[1] == 4 || DS1302_Time[1] == 6 || DS1302_Time[1] == 9
			|| DS1302_Time[1] == 11)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2] = 30;}
			if(DS1302_Time[2]>30){DS1302_Time[2] = 1;}
		}
		else if(DS1302_Time[1] == 2)
		{
			if((DS1302_Time[0]%4 == 0 && DS1302_Time[0]%100 != 0) 
				|| (DS1302_Time[0]%400 == 0))
			{
				if(DS1302_Time[2]<1){DS1302_Time[2] = 29;}  //闰年2月有29天
				if(DS1302_Time[2]>29){DS1302_Time[2] = 1;}
			}
			else
			{
				if(DS1302_Time[2]<1){DS1302_Time[2] = 28;}  //平年2月有28天
				if(DS1302_Time[2]>28){DS1302_Time[2] = 1;}
			}
		}
		if(DS1302_Time[3]<0){DS1302_Time[3] = 23;}
		if(DS1302_Time[4]<0){DS1302_Time[4] = 59;}
		if(DS1302_Time[5]<0){DS1302_Time[5] = 59;}
	}
	//定时器0每隔0.5秒产生一次中断,TimeSetFlashFlag逻辑取反
	//那么TimeSetFlashFlag为1时被选中数字的位置显示空格,为0时正常显示数字,就能达到被选中数字闪烁的效果
	if(TimeSetFlashFlag == 1 && TimeSetSelect == 0)
	{
		LCD_ShowString(1,1,"  ");
	}
	else
	{
		LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示更改后的年份
	}
	if(TimeSetFlashFlag == 1 && TimeSetSelect == 1)
	{
		LCD_ShowString(1,4,"  ");
	}
	else
	{
		LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示更改后的月份
	}
	if(TimeSetFlashFlag == 1 && TimeSetSelect == 2)
	{
		LCD_ShowString(1,7,"  ");
	}
	else
	{
		LCD_ShowNum(1,7,DS1302_Time[2],2);  //显示更改后的日
	}
	if(TimeSetFlashFlag == 1 && TimeSetSelect == 3)
	{
		LCD_ShowString(2,1,"  ");
	}
	else
	{
		LCD_ShowNum(2,1,DS1302_Time[3],2);  //显示更改后的时
	}
	if(TimeSetFlashFlag == 1 && TimeSetSelect == 4)
	{
		LCD_ShowString(2,4,"  ");
	}
	else
	{
		LCD_ShowNum(2,4,DS1302_Time[4],2);  //显示更改后的分
	}
	if(TimeSetFlashFlag == 1 && TimeSetSelect == 5)
	{
		LCD_ShowString(2,7,"  ");
	}
	else
	{
		LCD_ShowNum(2,7,DS1302_Time[5],2);  //显示更改后的秒
	}
	LCD_ShowNum(2,11,TimeSetSelect,2);  //显示当前选中的时间单位
}

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	Timer0_Init();
	DS1302_Init();
	DS1302_SetTime();  //设置初始时间
	
	while(1)
	{
		KeyNum = Key();
		if(KeyNum == 1)  //按下按键1,修改模式和显示模式来回切换
		{
			if(MODE == 0)
			{
				MODE = 1;
			}
			else if(MODE == 1)
			{
				MODE = 0;
				DS1302_SetTime();  //设置显示修改后的时间
			}
		}
		switch(MODE)
		{
			case 0:TimeShow();break;
			case 1:TimeSet();break;
		}
	}
}

void Timer0_Routine()  interrupt 1  //CPU响应中断后执行的函数
{
	static unsigned int T0Count = 0;  //定义计数器
	
	T0Count++;
	
	if(T0Count >= 500)  //每500个中断信号(0.5秒)执行一次下面的代码段
	{
		T0Count = 0;
		TimeSetFlashFlag = !TimeSetFlashFlag;  //是否亮灯变量
	}
	
	//每次中断结束都要重置计数单元
	TH0 = 0xFC;  //定时器0的计数单元高8位
	TL0 = 0x66;  //定时器0的计数单元低8位
}

(3)将生成的.hex文件下载到开发板中,依据代码中的注释进行调试。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值