基于AT89S52单片机的多功能电子万年历

1、 项目介绍(设计内容)

基于AT89S52单片机的多功能电子万年历的硬件结构和软硬件设计方法。本设计由数据显示模块、温度采集模块、时间处理模块和调整设置模块四个模块组成。系统以AT89S52单片机为控制器,以串行时钟日历芯片DS1302记录日历和时间,它可以对年、月、日、时、分、秒进行计时,还具有闰年补偿等多种功能。温度采集选用DS18B20芯片,万年历采用直观的数字显示,数据显示采用1602液晶显示模块,可以在LCD1602上同时显示年、月、日、周日、时、分、秒,还具有时间校准等功能。

2、 项目的总体设计

系统的功能往往决定了系统采用的结构,经过成本,性能,功耗等多方面的考虑决定用三个8位74LS164串行接口外接LED显示器,RESPACK-8对单片机AT89S52进行供电,时间芯片DS1302连接单片机AT89S52。从而实现电子万年历的功能。
按照系统设计的要求,初步确定系统由电源模块、时钟模块、显示模块、键盘接口模块、温度测量模块和闹钟模块共六个模块组成,电路系统构成框图如图1所示。
基于AT89S52单片机的多功能电子万年历_第1张图片
图1 硬件电路框图

基于AT89S52单片机的多功能电子万年历_第2张图片

图2 主程序流程图

3、 设计思路方法及实现步骤(包括硬件设计和软件设计两个部分)

3.1 Proteus仿真图

单片机电子万年历的制作有多种方法,可供选择的器件和运用的技术也有很多种。所以,系统的总体设计方案应在满足系统功能的前提下,充分考虑系统使用的环境,所选的结构要简单使用、易于实现,器件的选用着眼于合适的参数、稳定的性能、较低的功耗以及低廉的成本。
系统的功能往往决定了系统采用的结构,经过成本,性能,功耗等多方面的考虑决定用三个8位74LS164串行接口外接LED显示器,RESPACK-8对单片机AT89S52进行供电,时间芯片DS1302连接单片机AT89S52。从而实现电子万年历的功能。
按照系统设计的要求,初步确定系统由电源模块、时钟模块、显示模块、键盘接口模块、温度测量模块和闹钟模块共六个模块组成,电路系统构成框图如图1所示。
基于AT89S52单片机的多功能电子万年历_第3张图片

				图3 硬件电路框图 

3.2 DS1302读写程序设计

本系统的时间读取主要来源于单片机对DS1302的操作,在硬件上时钟芯片DS1302与单片机的连接需要三条线,即SCLK(7)、I/O(6)、RST(5),具体连接图见系统硬件设计原理图。读取写程序设计如下:
函 数 名:RTInputByte()
功 能:实时时钟写入一字节
说 明:往DS1302写入1Byte数据 (内部函数)
入口参数:d 写入的数据
返 回 值:无

void RTInputByte(uchar d) 
{ 
    uchar i;
    ACC = d;
    for(i=8; i>0; i--)
    {
        T_IO = ACC0;           /*相当于汇编中的 RRC */
        T_CLK = 1;
        T_CLK = 0;
        ACC = ACC >> 1; 
    } 

函 数 名:RTOutputByte()
功 能:实时时钟读取一字节
说 明:从DS1302读取1Byte数据 (内部函数)
入口参数:无
返 回 值:ACC

uchar RTOutputByte(void) 
{ 
    uchar i;
    for(i=8; i>0; i--)
    {
        ACC = ACC >>1;         /*相当于汇编中的 RRC */
        ACC7 = T_IO;
        T_CLK = 1;
        T_CLK = 0;
    } 
    return(ACC); 
}

函 数 名:W1302()
功 能:往DS1302写入数据
说 明:先写地址,后写命令/数据 (内部函数)
调 用:RTInputByte() , RTOutputByte()
入口参数:ucAddr: DS1302地址, ucData: 要写的数据
返 回 值:无

void W1302(uchar ucAddr, uchar ucDa)
{
    T_RST = 0;
    T_CLK = 0;
    T_RST = 1;
    RTInputByte(ucAddr);       /* 地址,命令 */
    RTInputByte(ucDa);       /* 写1Byte数据*/
    T_CLK = 1;
    T_RST = 0;
}

函 数 名:R1302()
功 能:读取DS1302某地址的数据
说 明:先写地址,后读命令/数据 (内部函数)
调 用:RTInputByte() , RTOutputByte()
入口参数:ucAddr: DS1302地址

返 回 值:ucData :读取的数据

uchar R1302(uchar ucAddr)
{
    uchar ucData;
    T_RST = 0;
    T_CLK = 0;
    T_RST = 1;
    RTInputByte(ucAddr);             /* 地址,命令 */
    ucData = RTOutputByte();         /* 读1Byte数据 */
    T_CLK = 1;
    T_RST = 0;
    return(ucData);
}

DS1302与微处理器进行数据交换时,首先由微处理器向电路发送命令字节,命令字节最高位MSB(D7)必须为逻辑 1,如果D7=0,则禁止写DS1302,即写保护;D6=0,指定时钟数据,D6=1,指定RAM数据;D5~D1指定输入或输出的特定寄存器;最低位LSB(D0)为逻辑0,指定写操作(输入),D0=1,指定读操作(输出) 。

3.3 完整代码

#include
#include 
#define uint unsigned int
#define uchar unsigned char
char a,miao,shi,fen,ri,yue,nian,keynum;
int temp;//,year1,month1,day1;

#define h1 0x80 //LCD第一行的初始化位置
#define h2 0x80+0x40 //LCD第二行初始化位置

//定义1602相关管脚
sbit rs=P1^2;
sbit en=P1^0;
sbit rw=P1^1;

//DS1302芯片的管脚定义
sbit DSIO=P1^5;
sbit SCLK=P1^4;
sbit RST=P1^6;

sbit ACC0=ACC^0;//设置累加器
sbit ACC7=ACC^7;

//按键
sbit key1=P3^2;
sbit key2=P3^3;
sbit key3=P3^4;

void delay2(uint s)//延时,用于温度程序部分
{
	while(s--);//区分i,用s表示
}

void delay(uint z)//延时函数
{
	uint x,y;
	for(x=z;x>0;x--)
	for(y=110;y>0;y--);
}
void writecom(uchar com)//写入指令函数
{	
	rs=0;
	rw=0;
	P0=com;
	delay2(1);
	en=1;
	delay2(1);
	en=0;
}
void writedata(uchar dat)//写入数据函数
{
	rs=1;
	rw=0; 
	P0=dat;
	delay2(1);
	en=1;
	delay2(1);
	en=0;
}
void print(uchar a3,uchar *str)//写字符串函数
{
	writecom(a3|0x80);
	while(*str!='\0')
	{
		//delay(100);
		writedata(*str++);
	}
	*str=0;
}

void lcdinit()
{
	writecom(0x38);//设置为两行显示,8位显示
	writecom(0x0c);//开显示,不显示光标
	writecom(0x06);//光标右移
	writecom(0x01);//清屏		
}

void write_1302(uchar addr, uchar dat)
{
    uchar n;
	RST = 0;
	_nop_();

	SCLK = 0;//先将SCLK置低电平。
	_nop_();
	RST = 1; //然后将RST(CE)置高电平。
	_nop_();

	for (n=0; n<8; n++)//开始传送八位地址命令
	{
		DSIO = addr & 0x01;//数据从低位开始传送
		addr >>= 1;
		SCLK = 1;//数据在上升沿时,DS1302读取数据
		_nop_();
		SCLK = 0;
		_nop_();
	}
	for (n=0; n<8; n++)//写入8位数据
	{
		DSIO = dat & 0x01;
		dat >>= 1;
		SCLK = 1;//数据在上升沿时,DS1302读取数据
		_nop_();
		SCLK = 0;
		_nop_();	
	}	
		 
	RST = 0;//传送数据结束
	_nop_();
}
uchar read_1302(uchar addr )//从1302读数据函数,指定读取数据来源地址
{
	uchar n,dat,dat1;
	RST = 0;
	_nop_();

	SCLK = 0;//先将SCLK置低电平。
	_nop_();
	RST = 1;
	_nop_();

	for(n=0; n<8; n++)//开始传送八位地址命令
	{
		DSIO = addr & 0x01;
		addr >>= 1;
		SCLK = 1;
		_nop_();
		SCLK = 0;
		_nop_();
	}
	_nop_();
	for(n=0; n<8; n++)//读取8位数据
	{
		dat1 = DSIO;
		dat = (dat>>1) | (dat1<<7);
		SCLK = 1;
		_nop_();
		SCLK = 0;
		_nop_();
	}

	RST = 0;
	_nop_();	
	SCLK = 1;
	_nop_();
	DSIO = 0;
	_nop_();
	DSIO = 1;
	_nop_();
	return dat; 
}

uchar turnBCD(uchar bcd)//BCD码转换为十进制函数
{
	return((bcd>>4)*10+(bcd&0x0F));
}
void ds1302_init()//1302时钟芯片初始化函数
{
	RST=0;
	SCLK=0;
	write_1302(0x8e,0x00);//允许写
	write_1302(0x8e,0x80);//打开保护
}

//时分秒显示函数
void writetime(uchar add,uchar dat)//写入时分秒
{
	uchar gw,sw;
	gw=dat%10;//取得个位数
	sw=dat/10;//取得十位数
	writecom(h2+add);//第二行显示
	writedata(0x30+sw);//显示该数字
	writedata(0x30+gw);
}
//年月日显示函数
void writeday(uchar add,uchar dat)//写入年月日函数
{
	uchar gw,sw;
	gw=dat%10;//取得个位数字
	sw=dat/10;//取得十位数字
	writecom(h1+add);//在第一行显示
	writedata(0x30+sw);
	writedata(0x30+gw);//显示
}
//按键扫描函数
void keyscan()
{
	if(key1==0)
	{
		delay(5);
		if(key1==0)
		{
			while(!key1);
			keynum++;
			if(keynum>=8)
			keynum=1;
			switch(keynum)
			{
			case 1:TR0=0;
				   writecom(h2+0x0b);//秒的位置
				   writecom(0x0f);//设置为光标闪烁
				   temp=(miao)/10*16+(miao)%10;//秒化为bcd码
				   write_1302(0x8e,0x00);
				   write_1302(0x80,0x80|temp);//秒数据写入
				   write_1302(0x8e,0x80);
				   break;
			case 2:writecom(h2+8);
				   break;
			case 3:writecom(h2+5);
				   break;
			case 4:writecom(h1+0x0c);
				   break;
			case 5:writecom(h1+0x09);
				  break;
			case 6:writecom(h1+0x06);
				  break;
			case 7:writecom(0x0c);
				  TR0=1;//重新打开定时器
				  temp=(miao)/10*16+(miao)%10;
				  write_1302(0x8e,0x00);
				  write_1302(0x80,0x00|temp);//写入秒
				  write_1302(0x8e,0x80);
				  break;
			}
		}
	}
	if(keynum!=0)//当设置键按下时才能操作
	{
		if(key2==0)
		{
			delay(5);
			if(key2==0)
			{
				while(!key2);
				switch(keynum)
				{
					case 1:miao++;//
						   if(miao>=60)	miao=0;
						   writetime(0x0a,miao);
						   temp=(miao)/10*16+(miao)%10;//转换为bcd码
						   write_1302(0x8e,0x00);//允许写
						   write_1302(0x80,temp);// 写入秒
						   write_1302(0x8e,0x80);//打开保护
						   writecom(h2+0x0b);//液晶模式为写入后自动右移,在此返回原来位置
						   break;
					case 2:fen++;
						   if(fen>=60) fen=0;
						   writetime(0x07,fen);
						   temp=(fen)/10*16+(fen)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x82,temp);
						   write_1302(0x8e,0x80);
						   writecom(h2+0x08);
						   break;
					case 3:shi++;
						   if(shi>=24) shi=0;
						   writetime(0x04,shi);
						   temp=(shi)/10*16+(shi)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x84,temp);
						   write_1302(0x8e,0x80);
						   writecom(h2+0x05);
						   break;
					case 4:ri++;
						   if(ri>=32) ri=1;
						   writeday(0x0b,ri);
						   temp=(ri)/10*16+(ri)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x86,temp);
						   write_1302(0x8e,0x80);
						   writecom(h1+0x0c);
						   break;
					case 5:yue++;
						   if(yue>=13) yue=1;
						   writeday(0x08,yue);
						   temp=(yue)/10*16+(yue)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x88,temp);
						   write_1302(0x8e,0x80);
						   writecom(h1+0x09);
						   break;
					case 6:nian++;
						   if(nian>=100) nian=0;
						   writeday(0x05,nian);
						   temp=(int)((nian)/10*16+(nian)%10);
						   write_1302(0x8e,0x00);
						   write_1302(0x8c,temp);
						   write_1302(0x8e,0x80);
						   writecom(h1+0x06);
						   break;
				}		   

			}
		}
		//以下是减的函数
		if(key3==0)
		{
			delay(5);//消除抖动
			if(key3==0)
			{
				while(!key3);
				switch(keynum)
				{
					case 1:miao--;
						   if(miao<0) miao=59;//减到-1返回59
						   writetime(0x0a,miao);//在十位数写入 
						   temp=(miao)/10*16+(miao)%10;//转换为bcd码
						   write_1302(0x8e,0x00);//允许写
						   write_1302(0x80,temp);//写入秒
						   write_1302(0x8e,0x80);//打开保护
						   writecom(h2+0x0b);//返回个位位置
						   break;
					case 2:fen--;
						   if(fen<0) fen=59;
						   writetime(0x07,fen);
						   temp=(fen)/10*16+(fen)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x82,temp);
						   write_1302(0x8e,0x80);
						   writecom(h2+8);
						   break;
				    case 3:shi--;
						   if(shi<0) shi=23;
						   writetime(0x04,shi);
						   temp=(shi)/10*16+(shi)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x84,temp);
						   write_1302(0x8e,0x80);
						   writecom(h2+0x05);
						   break;
					case 4:ri--;
						   if(ri<1) ri=31;
						   writeday(0x0b,ri);
						   temp=(ri)/10*16+(ri)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x86,temp);
						   write_1302(0x8e,0x80);
						   writecom(h1+0x0c);
						   break;
					case 5:yue--;
						   if(yue<1) yue=12;
						   writeday(0x08,yue);
						   temp=(yue)/10*16+(yue)%10;
						   write_1302(0x8e,0x00);
						   write_1302(0x88,temp);
						   write_1302(0x8e,0x80);
						   writecom(h1+0x09);
						   break;
					case 6:nian--;
						   if(nian<0) nian=99;
						   writeday(0x05,nian);
						   temp=(int)((nian)/10*16+(nian)%10);
						   write_1302(0x8e,0x00);
						   write_1302(0x8c,temp);
						   write_1302(0x8e,0x80);
						   writecom(h1+0x06);
						   break;
				}
			}
		}
	}
}

void init()
{
	TMOD=0x01;
	TH0=(65536-60000)/256;//10毫秒
	TL0=(65536-60000)%256;
	EA=1;
	ET0=1;//允许T0中断
	TR0=1;//启动中断
}


void main()
{
	lcdinit();
	ds1302_init();
	init();//定时器初始化函数
	while(1)
	{
		keyscan();
	}
}
void timer0() interrupt 1
{
	TH0=(65536-60000)/256;
	TL0=(65536-60000)%256;
//	TR0=0;
	//读取数据
	miao=turnBCD(read_1302(0x81));
	fen=turnBCD(read_1302(0x83));
	shi=turnBCD(read_1302(0x85));
	ri=turnBCD(read_1302(0x87));
	yue=turnBCD(read_1302(0x89));
	nian=turnBCD(read_1302(0x8d));
	//显示数据
									
		print(0x80+3,"20");
		print(0x80+7,"/");
		print(0x80+10,"/");

		writeday(0x0b,ri);//显示日
		writeday(0x08,yue);//显示月
		writeday(0x05,nian);//显示年
		print(0x40+6,":");
		print(0x40+9,":");
		writetime(0x0a,miao);//显示出秒
		writetime(0x07,fen);//显示出分
		writetime(0x04,shi);//显示出时,第二行第一个开始
}

4、 运行结果或者测试结果

①次电路主要是检测格其引脚电压是否正常,晶振和电源是否接好,检测硬件电路是否有短路、断路、虚焊等,以确保设计的可靠性和电器元件的性能。而电路中的电源电路、晶体振荡电路、按键接口电路及复位电路、闹钟电路等都是采用基础的电路设计,除了基础电路硬件调试外我们还可以通过软件来测试硬件,如通过下载口写入其它一个比较简单的程序,以便测试。
②首先由USB电源插口接入5V的直流电压供给系统使用。在这里接上一个发光二级管作为指示,单输入电压正常时,二极管亮,LCD同时显示正常。系统在正常工作时,LCD液晶上第一行显示时分秒和温度,第二行显示年月日和星期,如果想要对时间进行调整,可以通过调整设置模块来实现。当按下设置键P3.0键时可调节主页面的时分秒、年月日的调节,P3.1为调整加按键,P3.2为调整减按键,P3.3按下时可进入另一种模式。第二种模式可显示闰年,第三种模式可设置闹钟时间。如果想要退出该模式就在按一下P3.3即可。

③在硬件调试过程中,当接通电源的时候,我们发现液晶显示器没有工作,背光灯有亮但没有数据出来。但电源指示灯已亮,说明电源输入正常,待我们用万用表电路中各电压时发现,单片机各引脚电压也正常,显示器的各引脚也正常。经过同学与老师的帮助,发现程序出错,改后再接电源,电路一切正常。
基于AT89S52单片机的多功能电子万年历_第4张图片

基于AT89S52单片机的多功能电子万年历_第5张图片

5、 遇到的问题及解决的方法

出现电子数码万年历死机的现象:
此故障多为电压不稳和其他干扰造成的,首先更换记忆电池,可排除多数情况下的故障;仍不工作,拔下电源与主板连线,再次插上,部分死机故障可以恢复正常;若还不工作,可以在不通电的情况下,用镊子短路主板上的所有滤波电容,通电看是否正常工作,最后检查晶振。

出现走时不准现象:
5V电压低,记忆电池欠压,晶振性能不良,修理电源使其恢复正常,更换电池,更换晶振。
  
再次通电时间和日期出错:
这是很长见得故障,很可能是线路板自带的圆形电子没电了,长时间不用很可能会耗尽其自带的电量,那样的话就失去了其记忆功能。

6、总 结

在整个设计过程中,硬件方面主要设计了AT89S52单片机的最小系统、DS1302接口电路、DS18B20接口电路、闹钟及LCD显示;软件方面借助各个渠道的资料,主要设计了阳历数据读取程序、阳历转阴历程序、温度采集程序、闹铃程序以及LCD显示程序;系统的调试主要是通过一块AT89S52开发板,再借助于Keil、STC以及少许自己搭建的外围电路实现的;再此过程中,分步调试时显示出了阳历的日期及时间,还有实时温度,集中调试时没有达到预期效果。此万年历具有读显示直观、功能多样、电路简洁、成本低廉等诸多优点,符合电子仪器仪表的发展趋势,具有广阔的市场前景。
在整个设计过程中学到了许多没学到的知识,对电路的设计、布局要先有一个好的构思,才显得电路板美观、大方。程序编写中,由于思路不清晰,开始时遇到了很多的问题,经过静下心来思考,理清了思路,反而得心应手。在此次设计中,知道了做事要有一颗平常的心,不要想着走捷径,一步一脚印。也练就了我们的耐心,做什么事都要有耐心。在本次设计中学到了很多很多东西,这是最重要的

7、源码获取

万年历

你可能感兴趣的:(微机原理,单片机,嵌入式硬件)