基于51单片机的可调时钟(开源)

这篇具有很好参考价值的文章主要介绍了基于51单片机的可调时钟(开源)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Brief:

        应用DS1302实现在LCD1602上显示时间,包括年月日,时分秒以及星期几的英文缩写,并具有时间可调的功能,调节当前位时闪烁。利用独立按键1实现模式切换,按键2设置时间位选择,按键3当前时间位+1,按键4当前时间位-1,调节时间具有边界判断和自动校正功能,同时会显示当前的工作模式。

        长文预警,三个模块分别为DS1302读取和显示写入的时间设置并修改当前时间

一、DS1302

1、简介和引脚介绍

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

DS1302  是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。 由此得知时间位的越界校正在芯片内部实现,我们只需要写入和读取时间就能实现时钟显示

基于51单片机的可调时钟(开源)

 引脚介绍基于51单片机的可调时钟(开源)

 数据通过I/O引脚与内部数据寄存器进行数据交换

寄存器定义

基于51单片机的可调时钟(开源)

寄存器中的时间数据均使用BCD码保存,前两列为读和写的地址为16进制,也就是命令字

WP为写保护,最高位为1时打开写保护,为0时关闭 

命令字 

基于51单片机的可调时钟(开源) 最高位固定为1,最低位为0时为写数据,为1时读取数据

2、读写时间数据步骤和原理

读写步骤

  • 关闭写保护(输入命令字0x8E,写入0x00) 
  • 向I/O端口输入命令字(地址)字节,确定读/写的数据。

        例如:命令字为0x81,就是告诉芯片我要读取存放秒的寄存器里的数据

                   命令字为0x80,就是高速芯片要往存放秒的寄存器写入数据,下一个字节就是要写入的数据;  

  • 输入要写入的数据 ;或者读取对应地址寄存器里的数据
  • 打开(也可以不打开)写保护

举例:输入(命令字,数据)为(0x82,0x24)就是把分钟的数据置为24(寄存器里是BCD码)

           输入(0x81),就是接下来要读取分钟当前的数据

由于寄存器内部以BCD码存储时间数据,而我们使用LCD1602显示是十进制数,因此我们统一定义写入的时间为十进制数,写入的时候转换为BCD码,读取的时候再将BCD码转为十进制保存显示BCD码转十进制:DEC=BCD/16*10+BCD%16;2BCD

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

读写数据时序定义

基于51单片机的可调时钟(开源)

 写数据 :

  1. 首先将CE置高电平允许读写
  2. 输入的第一位数据到达后在时钟第一个上升沿将其写入,随后将时钟置0
  3. 下一个数据到达,继续给时钟上升沿将其读入,随后置0
  4. 第一个字命令字节写入完毕,确定在哪个寄存器写入数据
  5. 下一个字节为要写入的数据,写入方式和前面一样
  6. CE置0关闭读写
/**
  * @brief DS1302的写数据功能,前八位为要写入的地址,后八位为要写入的数据
  * @param  Commend 命令字确定要写的地址  Data 要写入的数据
  * @retval 无
  */
void DS1302_WriteByte(unsigned char Commend, Data)
{
	unsigned char i;
	DS1302_CE = 1;        //芯片开启读写
	for(i=0;i<8;i++)
	{
		DS1302_IO = Commend & (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;	      //关闭读写功能
}      //关闭读写功能

 读数据 :

  1. 首先将CE置高电平允许读写
  2. 字命令字节写入方式相同,但读数据比写数据少一个时钟周期,时序应为,在第八个时钟上升沿完成第一个字节的写入,在将第八个时钟高电平置0的下降沿开始读取第一位数据,并由单片机将其置于寄存器内给用户保存,在下一个时钟下降沿读取第二位以此类推
  3. 在每一个对应时序把读取到的数据保存
  4. CE置0关闭读写,写入的字命令清0,等待下一次读取在写入对应地址
/**
  * @brief DS1302的读数据功能,前八位为要写入的地址,确定后八位要读的数据在哪
  *			前八个上升沿写地址,后八个下降沿读数据
  * @param  Commend 命令字确定要读数据的地址
  * @retval  读取到的一个字节数据
  */
unsigned char DS1302_ReadByte(unsigned char Commend)
{
	unsigned char Data = 0x00;   //局部变量用于存储读出的数据
	unsigned char i;	
	Commend |= 0x01;
	DS1302_CE = 1;	    
	for(i=0;i<8;i++)
	{
		DS1302_IO = Commend & (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);}   //读取位为1的时候给Data对应位1
	}
	DS1302_CE = 0;			//关闭读写功能	
	DS1302_IO = 0;          //置零等下一次读取
	return Data;            //返回读取的数据
}

这两个是主要的读写函数,下面介绍实现完全实现读写的其他相关函数 

3、DS1302读写相关声明、定义和函数

类型声明和定义

和之前一样 按对应地址自定义声明DS1302的串行时钟、使能、数据输入输出; 宏定义,要写入的地址; 存放要写入时间数据的数组。便于移植和维护,增加可读性

#include <REGX52.H>

//自定义声明DS1302的串行时钟、使能、数据输入输出
sbit DS1302_SCLK = P3^6;
sbit DS1302_CE = P3^5;
sbit DS1302_IO = P3^4;

//宏定义写入地址
#define DS1302_SECOND  0x80
#define DS1302_MINITE  0x82
#define DS1302_HOUR    0x84
#define DS1302_DATE    0x86
#define DS1302_MONTH   0x88
#define DS1302_DAY     0x8A
#define DS1302_YAER    0x8C
#define DS1302_WP      0x8E

char DS1302_Time[] = {22, 12, 5, 0, 21, 58, 1}; //数组存放设置时间

初始化函数DS1302_Init(void)

由于单片机一上电会把时钟和CE使能给置高电平,因此我们要正常使用需要先手动将这俩置0

/**
  * @brief 初始化DS1302的串行时钟和使能端输入
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
	DS1302_SCLK = 0;  //单片机一上电赋高电平,这里手动清零
	DS1302_CE = 0;    //不允许读写状态
}

 设置时间函数DS1302_SetTime(void)

调用写数据函数依次将时间数据写入DS1302,涉及前面提到的,将十进制数转换为BCD码写入

/**
  * @brief 写入存放在数组的时间数据
  * @param  无
  * @retval 无
  */
void DS1302_SetTime(void)
{
	DS1302_WriteByte(DS1302_WP,0x00);//关闭写保护
	
	DS1302_WriteByte(DS1302_YAER,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_MINITE,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);//打开写保护
}

最后这句打开写保护可加可不加,因为我后面要加入修改当前时间的功能所以这里我注释掉了

读取时间函数DS1302_ReadTime(void)

调用读数据函数将时间数据依次读取并存放在数组里,用于显示在LCD1602,读取时将读取到的BCD码转换为十进制数再保存

/**
  * @brief 读取时间数据存放在数组
  * @param  无
  * @retval 无
  */
void DS1302_ReadTime(void)
{
	unsigned char Temp;  //中间变量
	
	Temp = DS1302_ReadByte(DS1302_YAER);
	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_MINITE);
	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使用的所有代码,记得自行封装~

二、显示当前时间

有了使用DS1302写入和读取时间的函数,我们只需要在主函数里调用写入数据函数,再在while循环中不断调用读取时间函数,然后将当前时间显示在LCD1602即可

其中显示星期几需要将对应周一到周日的数字(1~7)转换为英文字符串显示,比较简单

//存放周一到周日的显示字符
unsigned char Day[][6] = {"Mon. ", "Tues.", "Wed. ", "Thur.", "Fri. ", "Sat. ", "Sun. "};



/**
  * @brief 将周内对应数字转化为英文字符串缩写显示
  * @param  无
  * @retval 无
  */
void TranserShowStr_Day(char DayNum)
{
	switch(DayNum)    //将数字转化为对应字符串显示
	{
		case 1: LCD_ShowString(1,12,Day[0]);break;
		case 2: LCD_ShowString(1,12,Day[1]);break;
		case 3: LCD_ShowString(1,12,Day[2]);break;
		case 4: LCD_ShowString(1,12,Day[3]);break;
		case 5: LCD_ShowString(1,12,Day[4]);break;
		case 6: LCD_ShowString(1,12,Day[5]);break;
		case 7: LCD_ShowString(1,12,Day[6]);break;	
	}	
}

将时间数据(年月日时分秒)显示在LCD1602的函数

/**
  * @brief 显示当前的时间包括:年月日时分秒
  * @param  无
  * @retval 无
  */
void ShowTime(void)   //显示年月日时分秒
{
	LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示年
	LCD_ShowChar(1,3,'-');
	LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示月
	LCD_ShowChar(1,6,'-');
	LCD_ShowNum(1,7,DS1302_Time[2],2);	//显示日		
	LCD_ShowNum(2,1,DS1302_Time[3],2);	//显示时
	LCD_ShowChar(2,3,':');				
	LCD_ShowNum(2,4,DS1302_Time[4],2);	//显示分
	LCD_ShowChar(2,6,':');			
	LCD_ShowNum(2,7,DS1302_Time[5],2);	//显示秒
}

主函数比较简单,注意先初始化DS1302和LCD1602

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

void main()
{
    //初始化
	LCD_Init();      
	DS1302_Init();	
	DS1302_SetTime();	 //给DS1302写入时间
	while(1)
	{

        	DS1302_ReadTime();
			TranserShowStr_Day(DS1302_Time[6]);  //将数字转化为对应字符串显示
			ShowTime();                 //显示年月日时分秒
    }
}

到这里就完成了将时间数据写入DS1302并实时读取并将时间显示在LCD1602的功能

 三、设置并修改当前时间

 梳理一下思路

  1. 首先需要按键检测功能完成模式切换,定义Mode在0、1切换
  2. Mode为1时进入设置时间模式,不再读取时间,仅显示进入模式切换时的时间和当前模式
  3. 按键检测为2,时间位索引+1,对应为存放时间数组的索引,用于选择要修改的时间位
  4. 确定了要修改的时间位后按键检测3号键按下,对应时间位数据+1,4号键对应数据-1
  5. 数据增加和减少时的越界判定和校正
  6. 显示修改后的时间
  7. 完成修改后,切换回显示时间模式的时候同时调用设置时间函数将修改后的时间数据写入DS1302并显示
  8. 最后加上在设置时间模式下选中时间位闪烁效果,用于指示当前修改的时间位,在加入这一效果之前可以先用LCD1602把时间位索引显示出来,也可以知道当前修改时间位,闪烁效果更为优化和普遍

思考方向如此,但具体实现以下面为准 

1、按键检测和模式切换

独立按键检测,一号键切换工作模式

//定义键码、设置时间位索引、设置时间闪烁标志、模式(显示时间/设置时间)
unsigned char KeyNum, TimeSet_Sel, TimeSetFlag, Mode;  

void main()
{
    //初始化
	LCD_Init();      
	DS1302_Init();	
	DS1302_SetTime();	 //给DS1302写入时间
	Timer0Init();		 //定时器初始化
	while(1)
	{
		KeyNum = Key();   //接收键码
		if(KeyNum == 1)
		{
			Mode++;
			if(Mode>1)      //使按下按键1切换工作模式
			{
				Mode = 0;   //切换回显示时间模式
				DS1302_SetTime();  //将已修改的时间数据写入芯片
			}
			if(Mode == 1)TimeSet_Sel == 0;    //每次切换到设置时间模式索引清0,固定从“年”开始闪烁
		}
   ......
	if(KeyNum == 2)         //2号按键选择设置的时间位
	{
		TimeSet_Sel++;
		TimeSet_Sel %= 7;	//索引在0~6
	}
	if(KeyNum == 3)         //3号按键使当前时间位+1
	{
		DS1302_Time[TimeSet_Sel]++;
		BoundedCheckUp();   //越上界判断和校正
	}
	if(KeyNum == 4)			//4号键使当前时间位-1
	{
		DS1302_Time[TimeSet_Sel]--;
		BoundedCheckDown();	//越下界判断和校正
	}	

独立按键检测函数

/**
  * @brief 独立按键检测获取键码
  * @param  无
  * @retval 按下按键键码,范围0-4,无按键按下返回0
  */

unsigned char Key()
{
	unsigned char KeyNumber = 0;
	if(P3_1 == 0){Delay(20);while(!P3_1);Delay(20);KeyNumber = 1;}
	if(P3_0 == 0){Delay(20);while(!P3_0);Delay(20);KeyNumber = 2;}
	if(P3_2 == 0){Delay(20);while(!P3_2);Delay(20);KeyNumber = 3;}
	if(P3_3 == 0){Delay(20);while(!P3_3);Delay(20);KeyNumber = 4;}
	
	return KeyNumber;
}

2、 工作模式内容

Mode为0,显示时间模式,正常显示时间;Mode为1,调用TimeSet()函数,相关内容为不再读取时间,进入时保持显示切换时的时间和当前模式 名称

		switch(Mode)
		{
			case 0: LCD_ShowString(2,13,"Show");break;
			case 1: LCD_ShowString(2,13,"Set ");break;
		}
		if(!Mode)    //模式0,显示时间
		{
			DS1302_ReadTime();
			TranserShowStr_Day(DS1302_Time[6]);  //将数字转化为对应字符串显示
			ShowTime();                 //显示年月日时分秒
		}
		else          //模式1,设置时间
			TimeSet();

3、TimeSet()函数

/**
  * @brief 模式1,修改设置当前时间
  * @param  无
  * @retval 无
  */
void TimeSet(void)          //设置时间函数
{
	if(KeyNum == 2)         //2号按键选择设置的时间位
	{
		TimeSet_Sel++;
		TimeSet_Sel %= 7;	//索引在0~6
	}
	if(KeyNum == 3)         //3号按键使当前时间位+1
	{
		DS1302_Time[TimeSet_Sel]++;
		BoundedCheckUp();   //越上界判断和校正
	}
	if(KeyNum == 4)			//4号键使当前时间位-1
	{
		DS1302_Time[TimeSet_Sel]--;
		BoundedCheckDown();	//越下界判断和校正
	}	
	ShowSetTime(); 			//显示当前修改后的时间	
	if(TimeSet_Sel == 6 &&  TimeSetFlag ==1)  //选中时0.5s闪烁,未选中正常显示
		LCD_ShowString(1,12,"     ");
	else
		TranserShowStr_Day(DS1302_Time[6]);    //显示字符缩写是星期几
}

2号键修改的时间位索引+1,3号键按下,对应时间位数据+1,4号键对应数据-1。

4、越界判断和校正

增加时越上界校正

 DS1302显示年份范围为2000~2099,这里只显示0~99,当增加到99在增加变为0

同理月份12月增加变为1,日增加需要考虑大小月,4年一闰的2月为29天(400一闰无需考虑)

小时显示模式是AM,0~23,分秒均为0~59,星期几是1~7,都需要进行逻辑判断,超过置为初值

/**
  * @brief 越上界判断和校正
  * @param  无
  * @retval 无
  */
void BoundedCheckUp(void)   //越上界判断函数
{
	//越上界校正
	if(DS1302_Time[0]>99)DS1302_Time[0] = 0;		//年份越界判断
	if(DS1302_Time[1]>12)DS1302_Time[1] = 1;		//月份越界判断
	//天数越界判断	
	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)       //4年一闰2月29天,时钟寿命仅100年不需要考虑400年一闰
		{
			if(DS1302_Time[2]>29)DS1302_Time[2] = 1;
		}
		else
		{
			if(DS1302_Time[2]>28)DS1302_Time[2] = 1;  //平年2月28天
		}		
	}		
	else 
	{
		if(DS1302_Time[2]>31)DS1302_Time[2] = 1;  //大月
	}	
					
	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(DS1302_Time[6]>7)DS1302_Time[6] = 1;    //周内越界判断	
}

减小时越下界校正

思路和越上界校正几乎一样,只有判断日期的时候,有特殊情况:当月份从大月调成小月的时候,如果这时候日期是31需要将其置为1,同理调到2月也需要这个操作。

说白了就是越下界判断在日期这还要加上越上界判断和校正

/**
  * @brief 越下界判断和校正
  * @param  无
  * @retval 无
  */
void BoundedCheckDown(void)   //越下界判断函数
{
	//越下界校正
	if(DS1302_Time[0]<0)DS1302_Time[0] = 99;		//年份越界判断
	if(DS1302_Time[1]<1)DS1302_Time[1] = 12;		//月份越界判断
	//天数越界判断	
	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)       //4年一闰2月29天,时钟寿命仅100年不需要考虑400年一闰
		{
			if(DS1302_Time[2]<1)DS1302_Time[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;  //平年2月28天
		}		
	}		
	else 
	{
		if(DS1302_Time[2]<1)DS1302_Time[2] = 31;  //大月
		if(DS1302_Time[2]>31)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;   //秒越界判断	
	if(DS1302_Time[6]<1)DS1302_Time[6] = 7;    //周内越界判断
}

 5、LCD显示修改后的时间

在TimeSet()函数里修改时间后就马上显示

没加闪烁效果之前可直接调用之前的显示年月日时分秒的函数ShowTime(void)和显示星期几的函数TranserShowStr_Day(char DayNum),加入了闪烁效果就需要调用具有闪烁功能的显示函数了

ShowTime(); 			//显示当前修改后的时间
TranserShowStr_Day(DS1302_Time[6]);    //显示字符缩写是星期几

6、当前索引位闪烁效果 

需要定义一个标志位TimeSetFlag,当它为0的时候正常显示当前索引位,当它为1时,当前索引位的位置显示相同长度的空字符串

具有闪烁效果的显示时间(年月日时分秒)函数ShowSetTime(void)

当同时满足时间位被选中和 标志位为1的时候,当前索引位的位置显示相同长度的空字符串,否则正常显示数据

/**
  * @brief 设置时间模式下显示当前的时间包括:年月日时分秒,设置位闪烁
  * @param  无
  * @retval 无
  */
void ShowSetTime(void)   //显示年月日时分秒
{
	//特殊符号显示
	LCD_ShowChar(1,3,'-');
	LCD_ShowChar(1,6,'-');
	LCD_ShowChar(2,3,':');
	LCD_ShowChar(2,6,':');	
	
	if(TimeSet_Sel == 0 &&  TimeSetFlag ==1)
		LCD_ShowString(1,1,"  ");
	else
		LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示年
	
	if(TimeSet_Sel == 1 &&  TimeSetFlag ==1)
		LCD_ShowString(1,4,"  ");
	else
		LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示月
	
	if(TimeSet_Sel == 2 &&  TimeSetFlag ==1)
		LCD_ShowString(1,7,"  ");
	else
		LCD_ShowNum(1,7,DS1302_Time[2],2);	//显示日	

	if(TimeSet_Sel == 3 &&  TimeSetFlag ==1)
		LCD_ShowString(2,1,"  ");
	else
		LCD_ShowNum(2,1,DS1302_Time[3],2);	//显示时	

	if(TimeSet_Sel == 4 &&  TimeSetFlag ==1)
		LCD_ShowString(2,4,"  ");
	else
		LCD_ShowNum(2,4,DS1302_Time[4],2);	//显示分					

	if(TimeSet_Sel == 5 &&  TimeSetFlag ==1)
		LCD_ShowString(2,7,"  ");
	else
		LCD_ShowNum(2,7,DS1302_Time[5],2);	//显示秒	
}

利用定时器中断系统使0.5秒标志位取非,从而实现被选中时间位闪烁

1ms的定时器初始化函数

#include <REGX52.H>

/**
  * @brief 定时器初始化,计时时长1MS
  * @param  无
  * @retval 无
  */
  
void Timer0Init()
{
	TMOD = TMOD & 0XF0;    //低四位置零,高四位不变
	TMOD = TMOD | 0X01;    //最低位置1,高四位不变
	TR0 = 1;
	TF0 = 0;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

中断函数

每次进入中断函数计数+1,到达500给标志位取非,即0.5s闪烁

void Timer0_Routine()   interrupt 1
{	static unsigned int T0Count;
	T0Count++;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	if(T0Count == 500)
	{
		T0Count = 0;    
		TimeSetFlag = !TimeSetFlag;   //0时熄灭、1时闪烁
	}	
}

对于设置星期几的闪烁

只需在TimeSet调用TranserShowStr_Day(char DayNum)时完成和前面年月日时分秒相同的操作

	if(TimeSet_Sel == 6 &&  TimeSetFlag ==1)  //选中时0.5s闪烁,未选中正常显示
		LCD_ShowString(1,12,"     ");
	else
		TranserShowStr_Day(DS1302_Time[6]);    //显示字符缩写是星期几

7、将修改后的时间数据写入DS1302

只需要在从设置时间模式切换回显示时间模式的时候调用设置时间函数

整个程序大多使用的是全局变量,在修改数据时很方便,避免了各种函数之间的参数传递很繁琐

		if(KeyNum == 1)
		{
			Mode++;
			if(Mode>1)      //使按下按键1切换工作模式
			{
				Mode = 0;   //切换回显示时间模式
				DS1302_SetTime();  //将已修改的时间数据写入芯片
			}
			if(Mode == 1)TimeSet_Sel == 0;    //每次切换到设置时间模式索引清0,固定从“年”开始闪烁
		}

完整的主程序

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


//存放周一到周日的显示字符
unsigned char Day[][6] = {"Mon. ", "Tues.", "Wed. ", "Thur.", "Fri. ", "Sat. ", "Sun. "};
//定义键码、设置时间位索引、设置时间闪烁标志、模式(显示时间/设置时间)
unsigned char KeyNum, TimeSet_Sel, TimeSetFlag, Mode;  

/**
  * @brief 将周内对应数字转化为英文字符串缩写显示
  * @param  无
  * @retval 无
  */
void TranserShowStr_Day(char DayNum)
{
	switch(DayNum)    //将数字转化为对应字符串显示
	{
		case 1: LCD_ShowString(1,12,Day[0]);break;
		case 2: LCD_ShowString(1,12,Day[1]);break;
		case 3: LCD_ShowString(1,12,Day[2]);break;
		case 4: LCD_ShowString(1,12,Day[3]);break;
		case 5: LCD_ShowString(1,12,Day[4]);break;
		case 6: LCD_ShowString(1,12,Day[5]);break;
		case 7: LCD_ShowString(1,12,Day[6]);break;	
	}	
}


/**
  * @brief 显示当前的时间包括:年月日时分秒
  * @param  无
  * @retval 无
  */
void ShowTime(void)   //显示年月日时分秒
{
	LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示年
	LCD_ShowChar(1,3,'-');
	LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示月
	LCD_ShowChar(1,6,'-');
	LCD_ShowNum(1,7,DS1302_Time[2],2);	//显示日		
	LCD_ShowNum(2,1,DS1302_Time[3],2);	//显示时
	LCD_ShowChar(2,3,':');				
	LCD_ShowNum(2,4,DS1302_Time[4],2);	//显示分
	LCD_ShowChar(2,6,':');			
	LCD_ShowNum(2,7,DS1302_Time[5],2);	//显示秒
}

/**
  * @brief 设置时间模式下显示当前的时间包括:年月日时分秒,设置位闪烁
  * @param  无
  * @retval 无
  */
void ShowSetTime(void)   //显示年月日时分秒
{
	//特殊符号显示
	LCD_ShowChar(1,3,'-');
	LCD_ShowChar(1,6,'-');
	LCD_ShowChar(2,3,':');
	LCD_ShowChar(2,6,':');	
	
	if(TimeSet_Sel == 0 &&  TimeSetFlag ==1)
		LCD_ShowString(1,1,"  ");
	else
		LCD_ShowNum(1,1,DS1302_Time[0],2);  //显示年
	
	if(TimeSet_Sel == 1 &&  TimeSetFlag ==1)
		LCD_ShowString(1,4,"  ");
	else
		LCD_ShowNum(1,4,DS1302_Time[1],2);  //显示月
	
	if(TimeSet_Sel == 2 &&  TimeSetFlag ==1)
		LCD_ShowString(1,7,"  ");
	else
		LCD_ShowNum(1,7,DS1302_Time[2],2);	//显示日	

	if(TimeSet_Sel == 3 &&  TimeSetFlag ==1)
		LCD_ShowString(2,1,"  ");
	else
		LCD_ShowNum(2,1,DS1302_Time[3],2);	//显示时	

	if(TimeSet_Sel == 4 &&  TimeSetFlag ==1)
		LCD_ShowString(2,4,"  ");
	else
		LCD_ShowNum(2,4,DS1302_Time[4],2);	//显示分					

	if(TimeSet_Sel == 5 &&  TimeSetFlag ==1)
		LCD_ShowString(2,7,"  ");
	else
		LCD_ShowNum(2,7,DS1302_Time[5],2);	//显示秒	
}

/**
  * @brief 越上界判断和校正
  * @param  无
  * @retval 无
  */
void BoundedCheckUp(void)   //越上界判断函数
{
	//越上界校正
	if(DS1302_Time[0]>99)DS1302_Time[0] = 0;		//年份越界判断
	if(DS1302_Time[1]>12)DS1302_Time[1] = 1;		//月份越界判断
	//天数越界判断	
	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)       //4年一闰2月29天,时钟寿命仅100年不需要考虑400年一闰
		{
			if(DS1302_Time[2]>29)DS1302_Time[2] = 1;
		}
		else
		{
			if(DS1302_Time[2]>28)DS1302_Time[2] = 1;  //平年2月28天
		}		
	}		
	else 
	{
		if(DS1302_Time[2]>31)DS1302_Time[2] = 1;  //大月
	}	
					
	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(DS1302_Time[6]>7)DS1302_Time[6] = 1;    //周内越界判断	
}

/**
  * @brief 越下界判断和校正
  * @param  无
  * @retval 无
  */
void BoundedCheckDown(void)   //越下界判断函数
{
	//越下界校正
	if(DS1302_Time[0]<0)DS1302_Time[0] = 99;		//年份越界判断
	if(DS1302_Time[1]<1)DS1302_Time[1] = 12;		//月份越界判断
	//天数越界判断	
	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)       //4年一闰2月29天,时钟寿命仅100年不需要考虑400年一闰
		{
			if(DS1302_Time[2]<1)DS1302_Time[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;  //平年2月28天
		}		
	}		
	else 
	{
		if(DS1302_Time[2]<1)DS1302_Time[2] = 31;  //大月
		if(DS1302_Time[2]>31)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;   //秒越界判断	
	if(DS1302_Time[6]<1)DS1302_Time[6] = 7;    //周内越界判断
}


/**
  * @brief 模式1,修改设置当前时间
  * @param  无
  * @retval 无
  */
void TimeSet(void)          //设置时间函数
{
	if(KeyNum == 2)         //2号按键选择设置的时间位
	{
		TimeSet_Sel++;
		TimeSet_Sel %= 7;	//索引在0~6
	}
	if(KeyNum == 3)         //3号按键使当前时间位+1
	{
		DS1302_Time[TimeSet_Sel]++;
		BoundedCheckUp();   //越上界判断和校正
	}
	if(KeyNum == 4)			//4号键使当前时间位-1
	{
		DS1302_Time[TimeSet_Sel]--;
		BoundedCheckDown();	//越下界判断和校正
	}	
	ShowSetTime(); 			//显示当前修改后的时间	
	if(TimeSet_Sel == 6 &&  TimeSetFlag ==1)  //选中时0.5s闪烁,未选中正常显示
		LCD_ShowString(1,12,"     ");
	else
		TranserShowStr_Day(DS1302_Time[6]);    //显示字符缩写是星期几
}


void main()
{
    //初始化
	LCD_Init();      
	DS1302_Init();	
	DS1302_SetTime();	 //给DS1302写入时间
	Timer0Init();		 //定时器初始化
	while(1)
	{
		KeyNum = Key();   //接收键码
		if(KeyNum == 1)
		{
			Mode++;
			if(Mode>1)      //使按下按键1切换工作模式
			{
				Mode = 0;   //切换回显示时间模式
				DS1302_SetTime();  //将已修改的时间数据写入芯片
			}
			if(Mode == 1)TimeSet_Sel == 0;    //每次切换到设置时间模式索引清0,固定从“年”开始闪烁
		}
		switch(Mode)
		{
			case 0: LCD_ShowString(2,13,"Show");break;
			case 1: LCD_ShowString(2,13,"Set ");break;
		}
		if(!Mode)    //模式0,显示时间
		{
			DS1302_ReadTime();
			TranserShowStr_Day(DS1302_Time[6]);  //将数字转化为对应字符串显示
			ShowTime();                 //显示年月日时分秒
		}
		else          //模式1,设置时间
			TimeSet();
	} 
}


void Timer0_Routine()   interrupt 1
{	static unsigned int T0Count;
	T0Count++;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	if(T0Count == 500)
	{
		T0Count = 0;    
		TimeSetFlag = !TimeSetFlag;   //0时熄灭、1时闪烁
	}	
}
	

四、遇到的问题

1、设置时间函数最后的开启写保护那行代码要去掉,不然在模式1里改了的数据不能被写进DS1302

2、刚开始闪烁很粗糙,借鉴了高手的写法

3、设置模式下只是改变了保存时间数组里的数据,并没有写入到DS1302里,所以刚开始设置完返回显示模式也不会有变化

五、可以继续优化的地方

  • 增加设置闹钟功能,调用蜂鸣器
  • 大部分代码写的简单粗暴,代码量很大,部分地方可以考虑优化精简一下
  • 用的是普中51开发板,后续可以考虑用系统板和用到的外设连接做一个拿得出手的小巧时钟

六、作品演示

基于51单片机的可调时钟功能演示文章来源地址https://www.toymoban.com/news/detail-455382.html

到了这里,关于基于51单片机的可调时钟(开源)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于51单片机的数字时钟设计

    目录 一、总体概述 1、计时控制方案 2、主控制器模块 3、显示电路模块 4、调试按键模块 5、电源模块 6、闹钟声光报警模块 二、系统总体结构 1.电路图 三、系统的硬件设计与实现 1、电源电路 2、显示电路 3、单片机基本电路 4、按键电路 四、功能测试及结果分析 五、程序附

    2024年02月07日
    浏览(41)
  • 单片机原理与应用课程设计-基于51单片机的时钟日历

    摘 要 本课程设计是基于51单片机的日历时钟设计。作为嵌入式系统中常用的控制器,单片机在各种电子设备和系统中广泛应用。日历时钟作为一个常见的功能模块,在现代生活中具有重要意义。因此,设计一个基于51单片机的日历时钟,不仅有助于我们掌握单片机编程技术和

    2024年02月20日
    浏览(66)
  • 基于51单片机8位竞赛抢答器_倒计时可调+LED跑马灯

    (程序+proteus仿真+报告) Proteus仿真版本:proteus 7.8 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:Q006 资料下载链接 1、以单片机位核心,设计一个8位抢答器:同时供8名选手比赛,分别用6个按键表示; 2、无人抢答时,8个跑马灯循环点亮,数码管显示00; 3、设置一个

    2024年02月09日
    浏览(34)
  • 基于51单片机的电子时钟(原理图,代码)

    所需要使用的元器件:  代码:(使用的是keil5) #include reg52.h             //调用单片机头文件 #define uchar unsigned char  //无符号字符型 宏定义    变量范围0~255 #define uint  unsigned int     //无符号整型 宏定义    变量范围0~65535   //数码管段选定义      0     1    2    

    2024年02月07日
    浏览(42)
  • 基于51单片机、DS1302时钟模块的电子闹钟设计

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:以下是本篇文章正文内容,下面案例可供参考 DS1302 是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周、时、分、秒进行计时,具有闰年补偿功能,工作

    2024年02月02日
    浏览(63)
  • 基于AT89C51单片机的电子时钟设计

    点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87695258?spm=1001.2014.3001.5503 源码获取 主要内容: 1.设计出电子数字钟的电路,并用protus进行仿真画出对应的电路图 2.设计出电子数字钟的源程序,并用Keil进行编辑生成HEX文件 3.在protus中进行测试。

    2024年02月09日
    浏览(63)
  • 基于 AT89C51 单片机的数字时钟设计

    目录 1.设计目的、作用 2.设计要求 3.设计的具体实现 3.1 设计原理 3.2 硬件系统设计         3.2.1 AT89C51 单片机原理 3.2.2 晶振电路设计 3.2.3 复位电路设计 3.2.4 LED 数码管显示 3.3 系统实现 3.3.1 系统仿真与调试 3.3.2 演示结果 4.总结 附录 附录 1 附录 2 (1 )掌握 51 系列单片机的

    2024年02月01日
    浏览(57)
  • 基于AT89C51单片机的电子时钟设计与仿真

    点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87779867?spm=1001.2014.3001.5503 源码获取 主要内容: 使用DS1302芯片作为计时设备,用6个7段LED数码管或者LCD162作为显示设备,实现时钟功能; 基本要求: (1)可以分别设定小时、分钟和秒,复位后时

    2024年02月06日
    浏览(50)
  • 基于51单片机的数字温度计【开源】

    (1)温度实时显示(LCD2864) (2)温度上限下线调节 (3)万年历功能 (4)超温报警 (5)年月日时分秒可调节 (6)温度测量精度0.0625℃ (7)节日自动判定 STC89c52,DS18B20,DS1302,按键模块。LCD12864 main.c LCD12864.c ( 主要一些延时和LCD12864的驱动) LCD12864.h (主要一些函数的声

    2024年02月10日
    浏览(46)
  • 51单片机数字时钟

    我们学单片机,一般都会写数字时钟编程。它能帮助我们: a. 能够系统性地总结掌握的知识,将单元模块知识有机的结合在一起。 b. 能够充分协调好硬件与软件之间的相互结合,合理设计硬件电路。 c. 掌握单片机 C 语言判断语句、分支语句以及子程序调用等编程知识。

    2024年02月11日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包