当前位置:网站首页>基于51单片机的电子时钟设计

基于51单片机的电子时钟设计

2022-07-05 16:40:00 少年、潜行

基于51单片机的电子时钟设计

0 功能介绍

1、从DS1302中读取时间显示

2、一共4个按键,按键1按下进入修改时间模式,再按下切换修改的时间变量,这个时候第2和3个按键就是修改时间变量的按键,修改完毕后,点击按键4确认修改

3、不是修改模式下,按下按键2,可以切换时间和日期的显示

4、修改模式下,对应修改的时间变量会闪烁

1 软件平台及代码开源

仿真软件:Proteus 8.9

代码编写:Keil5

百度网盘链接:

链接:https://pan.baidu.com/s/1RP_8MkZIqHt7WFPc6na3sQ
提取码:y2fn
–来自百度网盘超级会员V4的分享

Gitee链接:

51单片机项目学习: 这个仓库拿来保存一些51单片机项目学习 (gitee.com)

2 仿真硬件选型

DS1302获取当前时间,8位共阴极数码管,74HC138作位选,74HC573作段选,4个按键。

在这里插入图片描述

3 代码编写

3.1 整体设计思路

​ 从DS1302中读取出当前时间显示,在修改时间的时候,先把当前时间存入一篇缓存中,然后修改缓存中的数据,等到修改完毕后,再把缓存中的数据写入DS1302中。

3.2 软件代码设计

3.2.1 数码管显示

​ 数码管的显示将放置在一个5ms的定时器中实现,每隔5ms,换一个显示的位次,通过74HC138的编码来达到显示位的改变。segCode就是存放的数码管段码,缓存,segBuff的实际值为多少,就是谁的段码,如果值为10,我设置的segCode为0x00,即为不显示。

void SegShow()
{
    
	static u8 segSelectCount = 0;//位选变量
	
	segSelectCount ++;
	if(segSelectCount > 7)
		segSelectCount = 0;

	SEG = 0x00;//消隐
	switch (segSelectCount)
	{
    
		case 0: HCC = 0;HCB = 0;HCA = 0;SEG = segCode[segBuff[0]];break;
		case 1: HCC = 0;HCB = 0;HCA = 1;SEG = segCode[segBuff[1]];break;
		case 2: HCC = 0;HCB = 1;HCA = 0;SEG = 0x40               ;break;
		case 3: HCC = 0;HCB = 1;HCA = 1;SEG = segCode[segBuff[2]];break;
		case 4: HCC = 1;HCB = 0;HCA = 0;SEG = segCode[segBuff[3]];break;
		case 5: HCC = 1;HCB = 0;HCA = 1;SEG = 0x40               ;break;
		case 6: HCC = 1;HCB = 1;HCA = 0;SEG = segCode[segBuff[4]];break;
		case 7: HCC = 1;HCB = 1;HCA = 1;SEG = segCode[segBuff[5]];break;
		default:HCC = 0;HCB = 0;HCA = 0;SEG = segCode[segBuff[0]];break;
	}
}

3.2.2 DS1302中数据的处理

往DS1302寄存器中写值

void ds1302write(u8 addre,u8 dat)
{
    
	u8 i;
	RST=0;
	_nop_();
	SCK=0;
	_nop_();
	RST=1;
	_nop_();
	for (i=0;i<8;i++)
	{
    
		IO=addre&0x01;
		addre>>=1;
		SCK=1;
		_nop_();
		SCK=0;
		_nop_();
	}
	for (i=0;i<8;i++)
	{
    
		IO=dat&0x01;
		dat>>=1;
		SCK=1;
		_nop_();
		SCK=0;
		_nop_();
	}
	RST=0;
}

从DS1302中读值

u8 ds1302read(u8 addre)
{
    
	u8 i,dat1,dat;
	RST=0;
	_nop_();
	SCK=0;
	_nop_();
	RST=1;
	_nop_();
	for (i=0;i<8;i++)
	{
    
		IO=addre&0x01;
		addre>>=1;
		SCK=1;
		_nop_();
		SCK=0;
		_nop_();
	}
	_nop_();
	for (i=0;i<8;i++)
	{
    
		dat1=IO;
		dat=(dat>>1)|(dat1<<7);
		SCK=1;
		_nop_();
		SCK=0;
		_nop_();
	}
	RST=0;
	_nop_();
	SCK=1;
	_nop_();
	IO=1;
	_nop_();
	IO=0;
	_nop_();
	return dat;
}

DS1302的初始化,如果在文件开头定义的FIRST_WRITE的值为1,那么在初始化的时候就会往DS1302中写入time[]中设置的时间,在第一次写入可以将这块代码打开,后面就可以关闭。

void ds1302init()
{
    
#if FIRST_WRITE == 1
	u8 i;
	ds1302write(0x8e,0x00);
	for (i=0;i<7;i++)
	{
    
		ds1302write(writeaddre[i],time[i]);
	}
	ds1302write(0x8e,0x80);
#endif
}

DS1302读取时间,将数据从DS1302寄存器中读取出来,然后再做一个BCD码转10进制的转换。

void ds1302readtime()
{
    
	u8 i;
	for (i=0;i<7;i++)
	{
    
		time[i]=ds1302read(readaddre[i]);
	}
	second = (time[0]/16)*10+(time[0]&0x0f);
	minute = (time[1]/16)*10+(time[1]&0x0f);
	hour   = (time[2]/16)*10+(time[2]&0x0f);
	day    = (time[3]/16)*10+(time[3]&0x0f);
	month  = (time[4]/16)*10+(time[4]&0x0f);
	week   = (time[5]/16)*10+(time[5]&0x0f);
	year   = (time[6]/16)*10+(time[6]&0x0f);
}

DS1302写入时间,将要写入的变量先做10进制转BCD码,然后写入相应的寄存器。

void ds1302writetime()
{
    
	u8 i;
	ds1302write(0x8e,0x00);
	time[0] = (((secondTemp/10)<<4) + (secondTemp%10));
	time[1] = (((minuteTemp/10)<<4) + (minuteTemp%10));
	time[2] = (((hourTemp/10)<<4)   + (hourTemp%10));
	time[3] = (((dayTemp/10)<<4)    + (dayTemp%10));
	time[4] = (((monthTemp/10)<<4)  + (monthTemp%10));
	time[5] = (((weekTemp/10)<<4)   + (weekTemp%10));
	time[6] = (((yearTemp/10)<<4)   + (yearTemp%10));
	
	for (i=0;i<7;i++)
	{
    
		ds1302write(writeaddre[i],time[i]);
	}
	ds1302write(0x8e,0x80);
}

3.2.3 按键扫描

​ 按键扫描参考正点原子的写法,但是我的延时不是按照他的用delay延时,而是使用的定时器延时,这样的好处是延时时不占用CPU资源。

void KeyScan(u8 mode)
{
    
    static int keyCount = 0;
    static int keyState = 0;
    if(mode == 1) keyState=0;
    if (keyState == 0 && (KEY0 == 0||KEY1 == 0||KEY2 == 0||KEY3 == 0))
    {
    
        keyCount++;
        if(keyCount>2)
        {
    
            keyState = 1;
            keyCount=0;
            if(KEY0 == 0) isKey0 = 1;
            else if(KEY1 == 0) isKey1 = 1;
			else if(KEY2 == 0) isKey2 = 1;
			else if(KEY3 == 0) isKey3 = 1;
        }
    } else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && KEY3 == 1)
    {
    
        keyState = 0;
    }
}

3.2.4 根据显示模式修改显示变量

根据显示状态变量,修改现在的显示变量

void SegBuffChange()
{
    
	if(showMode == 0)
	{
    
		segBuff[5] = second%10;
		segBuff[4] = second/10;
		segBuff[3] = minute%10;
		segBuff[2] = minute/10;
		segBuff[1] = hour%10;
		segBuff[0] = hour/10;
	}else if(showMode == 1)
	{
    
		segBuff[5] = day%10;
		segBuff[4] = day/10;
		segBuff[3] = month%10;
		segBuff[2] = month/10;
		segBuff[1] = year%10;
		segBuff[0] = year/10;
	}else if(showMode == 2)
	{
    
		segBuff[5] = secondTemp%10;
		segBuff[4] = secondTemp/10;
		segBuff[3] = minuteTemp%10;
		segBuff[2] = minuteTemp/10;
		segBuff[1] = hourTemp%10;
		segBuff[0] = hourTemp/10;
	}else if(showMode == 3)
	{
    
		segBuff[5] = dayTemp%10;
		segBuff[4] = dayTemp/10;
		segBuff[3] = monthTemp%10;
		segBuff[2] = monthTemp/10;
		segBuff[1] = yearTemp%10;
		segBuff[0] = yearTemp/10;
	}
}

3.2.5 闪烁函数

​ 在按键修改模式下,将闪烁状态值置1,由于该函数放置在segBuff修改的后面,所以在实际显示前,会以这里改变的为实际显示,就可以达到一个闪烁的效果。

void dataBlink()
{
    
	static u8  blinkCount = 0;
	static bit blinkState = 0;
	if(changeOrNormalState == 1)//在按键修改模式下
	{
    
		blinkCount++;
		if(blinkCount == 80)//每隔80*5 闪烁状态值转换
		{
    
			blinkState = !blinkState;
			blinkCount = 0;
		}
	}else
	{
    
		blinkState = 0;
	}
	if(blinkState == 1)//假如闪烁状态值为1,该位次变量数码管不显示,我在seg.c中有定义,10就是不显示
	{
    
		segBuff[(2-changeCount%3)*2+1] = 10;
		segBuff[(2-changeCount%3)*2]   = 10;
	}
}

3.2.6 按键执行函数

按键按下后的执行函数,可以看代码注释

void ClockChangeFunction()
{
    
	if(isKey0 == 1)
	{
    
		isKey0 = 0;
		if(changeCount == 0 && changeOrNormalState == 0)//修改的位选为0,即为秒,同时显示状态为正常显示状态下
		{
    
			changeOrNormalState = 1;//显示状态改为修改时间模式
			DataTempGet();//获取修改前的时间变量值
			showMode = 2;//显示模式为显示修改时间下的时分秒
		}else if(changeOrNormalState == 1)//假如为显示模式
		{
    
			changeCount++;//按键按下位次++
			if(changeCount > 5)
				changeCount = 0;
			if(changeCount > 2)//时分秒是0、1、2,大于2就要换成年月日显示
				showMode = 3;//显示模式为显示修改时间下的年月日
			else
				showMode = 2;
		}
	}else if(isKey1 == 1)
	{
    
		isKey1 = 0;
		if(changeOrNormalState == 1)//显示状态为修改时间模式
		{
    
			dataAdd();//对应位次时间变量增加
		}else //正常显示模式
		{
    
			if(showMode == 0)//切换时间和日期的显示
				showMode = 1;
			else if(showMode == 1)
				showMode = 0;
		}
	}else if(isKey2 == 1)
	{
    
		isKey2 = 0;
		if(changeOrNormalState == 1)//和增加一样
		{
    
			dataSub();
		}
	}else if(isKey3 == 1)
	{
    
		isKey3 = 0;
		if(changeOrNormalState == 1)//在修改模式下按下
		{
    
			changeOrNormalState = 0;//变为正常显示模式
			ds1302writetime();//写入修改后的时间
			showMode = 0;//显示模式为时分秒
			changeCount = 0;//修改位次归零
		}
	}
}

3.2.7 时间变量增加函数

这部分代码,在按键执行函数中执行,但是由于 这一部分太多,所以将其封装函数,主要是拿来改变时间变量。主要是有个平闰年的处理,其他都比较正常。

void dataAdd()
{
    
	if(changeCount == 0)
	{
    
		secondTemp ++;
		if(secondTemp > 59)
			secondTemp = 0;
	}else if(changeCount == 1)
	{
    
		minuteTemp ++;
		if(minuteTemp > 59)
			minuteTemp = 0;
	}else if(changeCount == 2)
	{
    
		hourTemp ++;
		if(hourTemp > 23)
			hourTemp = 0;
	}else if(changeCount == 3)
	{
    
		dayTemp ++;
		if(monthTemp == 1 || monthTemp == 3 || monthTemp == 5 || monthTemp == 7 || monthTemp == 8 || monthTemp == 10 || monthTemp == 12)
		{
    
			if(dayTemp > 31)
				dayTemp = 1;
		}else if(monthTemp == 3 || monthTemp == 6 || monthTemp == 9 || monthTemp == 11)
		{
    
			if(dayTemp > 30)
				dayTemp = 1;
		}else if(monthTemp == 2)
		{
    
			if((2000+year)%400==0)
			{
    
				if(dayTemp > 29)
					dayTemp = 1;
			}
			else
			{
    
				if((2000+year)%4==0&&(2000+year)%100!=0)
				{
    
					if(dayTemp > 29)
						dayTemp = 1;
				}else
				{
    
					if(dayTemp > 28)
						dayTemp = 1;
				}
			}
		}
	}else if(changeCount == 4)
	{
    
		monthTemp ++;
		if(monthTemp > 12)
			monthTemp = 1;
	}else if(changeCount == 5)
	{
    
		yearTemp ++;
		if(yearTemp > 99)
			yearTemp = 0;
	}
}

4 总结

​ 这个设计功能比较简单,也许有些地方也有一些小BUG,但是我目前没有发现,如果有人发现,欢迎交流。

原网站

版权声明
本文为[少年、潜行]所创,转载请带上原文链接,感谢
https://blog.csdn.net/wan1234512/article/details/125593532