您现在的位置是:首页 >技术交流 >51单片机(十)DS1302实时时钟网站首页技术交流

51单片机(十)DS1302实时时钟

乘凉~ 2024-06-17 00:01:02
简介51单片机(十)DS1302实时时钟

❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。
☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋。
?专栏目标:实现从零基础入门51单片机和STM32单片机,力求在玩好单片机的同时,能够了解一些计算机的基本概念,了解电路及其元器件的基本理论等。

⭐️ 专栏主要内容: 主要学习51单片机的功能、各个模块、单片机的外设、驱动等,最终玩好单片机和单片机的外设,全程手敲代码,实现我们所要实现的功能。
? 专栏说明 :如果文章知识点有错误的地方,欢迎大家随时在文章下面评论,我会第一时间改正。让我们一起学习,一起进步。
?专栏主页:http://t.csdn.cn/HCD8v

本学习过程参考:https://space.bilibili.com/383400717

单片机安装软件、各种资料以及源码的路径:
https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng
提取码:gdzf

本节主要介绍学习DS1302实时时钟的相关知识,包括DS1302实时时钟基础知识介绍、本节目标等;并利用两个小实验来写程序进行练习,分别是DS1302时钟以及DS1302可调时钟,最后附上相关代码。

一、DS1302实时时钟和本节目标

1.1 DS1302基础知识

在这里插入图片描述

DS1302在开发版上的位置:

在这里插入图片描述

一定要学会通过看手册来学习和使用一款新的芯片,这里以DS1302为例,来学习一下看手册:

DS1302手册的路径:51单片机入门教程资料开发板资料包HC6800-ES V2.0开发板芯片资料DS1302中文手册.pdf

DS1302芯片是用来计时的,引脚定义和应用电路如下图所示

在这里插入图片描述

DIP是直插式,直接通过引脚插在MCU上的;SO是贴片式,贴在MCU上的;虽然方式不一样,但是内部芯片是一样的。
在这里插入图片描述

Vcc2是主电源,由MCU通电后对其进行供电;Vcc1是备用电源,MCU掉电时使用电池进行备用使用。

X1、X2接的是32.768KHz晶振;晶振给负责给实时时钟系统提供稳定的计数脉冲;

CE、I/O、SCLK三个引脚是通信引脚,MCU通过这三个引脚对时钟系统进行时间的读取和写入。

内部结构框图

在这里插入图片描述
在这里插入图片描述

CE为时钟使能开关,当CE为高电平时,对时钟的读写等操作才有效。

寄存器定义

在这里插入图片描述

上图中每个寄存器都有一个地址,各个寄存器的功能如下图所示:

在这里插入图片描述

而左边的READ和WRITE则表示读和写的命令字;例如,对于秒控制器,当命令字设置为0x81时,表示要对秒寄存器进行读操作;反之,当命令字设置为0x80,则表示对秒寄存器进行写操作。

那什么是命令字呢?

在这里插入图片描述

在这里插入图片描述

在时钟芯片上,我们如果想要对以上寄存器进行读写时操作,需要有三个条件,一是在哪里,二是什么操作,三是读/写什么;也就是说要确认,在哪个寄存器进行什么操作(读呢还是写呢?),读/写的内容是什么;

那么命令字就可以解决前两个条件,通过命令字就可以确认时在哪个寄存器进行什么操作,比如命令字设置为0x81,则表示对秒寄存器进行读操作;再比如命令字设置为0x84,则表示对时寄存器进行写操作。

至于第三个条件,读/写的内容怎么确认?命令字和写入的数据是怎么对应起来的呢?就要通过时序图:

在这里插入图片描述

时序图中是对CE(操作使能)、SCLK(时钟)、I/O(数据)三个引脚的操作,我们要学会看时序图,上面是单字符读的时序图,下面是单字符写的时序图;

以写的时序图为例,来解释一下,

可以看到,再下面写的时序图里,CE引脚,开始写时从零变为1,然后全程是高电平的,写完后右边为低电平0;

SCLK时钟引脚,是一个高电平,一个低电平,再一个高电平,再一个低电平,如此循环,即时钟信号先来一个上升沿,在一个下降沿,再一个上升沿,再一个下降沿,如此往复;且时钟里规定,在SCLK的上升沿,I/O口将写入一个对应的数据;在SCLK的下降沿,DS1302就会将数据输出到MCU;总结一句话就是,在SCLK的上升沿,MCU向时钟写入数据,在SCLK的下降沿,时钟向MCU写入数据,也等价于MCU向时钟读取数据;这种方式跟之前提到的SPI通信方式很类似。

I/O引脚,左边半部分是表示命令字,从左到右是命令字八个位的从低位到高位;右边半部分表示要写入的字节,从左到右也是字节的从低位到高位。

下面详细介绍一下时序图的过程(以写数据为例):

首先将CE置为高电平1;然后对于写数据来说,需要给到时钟输入两个字节,第一个字节是命令字,第二个字节表示要写入的字节数据;
在这里插入图片描述
我们首先取第一个字节的最低位,然后将SCLK置为高电平1(对应上升沿),此时就将第一个字节的最低位给到了R/W,然后将SCLK置为低电平0(对应下降沿);然后取第一个字节的第二位,然后将SCLK置为高电平1(对应上升沿),此时就将第二个字节的第二位给到了A0,然后将SCLK置为低电平0(对应下降沿);如此循环8次,直到将第一个字节的所有位都给到了命令字;然后同样的方法,将第二个字节的每一位写入到D0-D7;此时就完成了整个单字节写的过程。

1.2 本节目标

目标1:实现一个DS1302时钟,显示在LCD1602上,效果如下:
在这里插入图片描述
目标2:实现一个DS1302时钟,并能通过按键对时间进行修改

其中第一个按键功能是在显示模式和调节时间模式间进行切换

比如当按下按键第一个按键时,时钟由显示模式改为了调节时间模式,即年时间在闪烁:
在这里插入图片描述
第二个按键的功能是选择调节的位,比如上图已经到了调节年,按下第二个键时,可以发现到了调节月,即月时间在闪烁:
在这里插入图片描述
同样的,再按下第二个按键时,可以发现到了调节日,即日时间在闪烁:

在这里插入图片描述

第三个按键是对时间进行加操作,第四个按键是对时间进行减操作。

并且逻辑里加入了闰年、闰月、每个月是28天还是29天还是30天还是31天等等逻辑进行了判断,所以逻辑比较复杂。

二、DS1302时钟

代码路径:51单片机入门教程资料课件及程序源码程序源码KeilProject10-1 DS1302时钟
具体代码:

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

void main()
{
	LCD_Init();
	DS1302_Init();
	LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
	LCD_ShowString(2,1,"  :  :  ");
	
	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);//显示秒
	}
}

DS1302.c:

#include <REGX52.H>

//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

//寄存器写入地址/指令定义
#define DS1302_SECOND		0x80
#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

//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};

/**
  * @brief  DS1302初始化
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
	DS1302_CE=0;
	DS1302_SCLK=0;
}

/**
  * @brief  DS1302写一个字节
  * @param  Command 命令字/地址
  * @param  Data 要写入的数据
  * @retval 无
  */
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	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;
}

/**
  * @brief  DS1302读一个字节
  * @param  Command 命令字/地址
  * @retval 读出的数据
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	Command|=0x01;	//将指令转换为读指令
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;
	}
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	DS1302_CE=0;
	DS1302_IO=0;	//读取后将IO设置为0,否则读出的数据会出错
	return Data;
}

/**
  * @brief  DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
  * @param  无
  * @retval 无
  */
void DS1302_SetTime(void)
{
	DS1302_WriteByte(DS1302_WP,0x00);
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);//十进制转BCD码后写入
	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);
}

/**
  * @brief  DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
  * @param  无
  * @retval 无
  */
void DS1302_ReadTime(void)
{
	unsigned char Temp;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
	Temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
}

三、DS1302可调时钟

代码路径:51单片机入门教程资料课件及程序源码程序源码KeilProject10-2 DS1302可调时钟
具体代码:

#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++;//设置选择位加1
		TimeSetSelect%=6;//越界清零
	}
	if(KeyNum==3)//按键3按下
	{
		DS1302_Time[TimeSetSelect]++;//时间设置位数值加1
		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)
			{
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}//闰年2月
			}
			else
			{
				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月
			}
		}
		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)//按键3按下
	{
		DS1302_Time[TimeSetSelect]--;//时间设置位数值减1
		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)
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=29;}//闰年2月
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
			}
			else
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月
				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;}//秒越界判断
	}
	//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
	if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
	else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
	if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
	else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
	if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
	else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
	if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
	else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
	if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
	else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
	if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
	else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
}

void main()
{
	LCD_Init();
	DS1302_Init();
	Timer0Init();
	LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();//设置时间
	
	while(1)
	{
		KeyNum=Key();//读取键码
		if(KeyNum==1)//按键1按下
		{
			if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
			else if(MODE==1){MODE=0;DS1302_SetTime();}
		}
		switch(MODE)//根据不同的功能执行不同的函数
		{
			case 0:TimeShow();break;
			case 1:TimeSet();break;
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500)//每500ms进入一次
	{
		T0Count=0;
		TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
	}
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。