51单片机万年历开发

2022-01-05 23:13:22 浏览数 (2)

设计内容

万年历是采用数字电路实现对时、分、秒等信息进行数字显示的计时装置。广泛用于个人、家庭,车站,码头办公室等公共场所,成为人们日常生活中不可少的必需品,由于数字集成电路的发展和石英晶体振荡器的广泛应用,使得数字钟的精度,远远超过老式钟表,钟表的数字化给人们生产生活带来了极大的方便,而且大大地扩展了钟表原先的报时功能。诸如定时自动报警、按时自动打铃、时间程序自动控制、定时广播、自动起闭路灯、定时开关烘箱、通断动力设备、甚至各种定时电气的自动启用等,但是所有这些,都是以钟表数字化为基础的。因此,研究万年历及扩大其应用,有着非常现实的意义。

本设计是电子万年历能显示年月日时分秒及星期,并具有可调整日期和时间功能。本设计以数字集成电路技术为基础,单片机(8051)技术为核心,来实现电子万年历的功能。

设计思路

设计一台电子万年历,主控芯片采用STC89C516单片机,日历时钟芯片采用美国DALLAS公司推出的高性能、低功耗、带RAM的实时时钟DS1302,显示器采用点阵字符型液晶显示模块,分2行显示,第1行显示日、月、年,第2行显示时、分、秒。

使用DS1302时钟芯片与单片机8051相连接,通过软件编程的方法实现了以24小时为一个周期同时显示小时,分钟和秒采集并显示的要求;利用单片机定时器及计数器产生定时效果通过编程形成数字钟效果,再利用点阵字符型液晶显示模块动态扫描显示单片机内部处理的数据。同时通过端口读入当前外部控制状态来改变程序的不同状态,实现不同功能。本电子万年历采用单片机利用C语言来设计制作完成,由于其功能的实现主要通过软件编程来完成,这就降低了硬件电路的复杂性,也降低了成本。

设计与制作中之所以选用单片机8051,是因为它是低功耗、高性能的CMOS型8位单片机。片内带有4KB的存储器,且允许在系统内改写或用编程器编程。因此,采用8051原理制作的电子万年历,不仅仅在原理上能够成功实现计时等功能,也更经济,更适用,更符合我们实际生活的需要。

设计解释

设计环境介绍

对此次作品的方案选定:采用8051作为主控制系统;DS1302提供时钟;LCD液晶显示屏作为显示。利用C语言进行软件开发。

51单片机芯片

单片微型计算机简称为单片机,又称为微型控制器,是微型计算机的一个重要分支。单片机是70年代中期发展起来的一种大规模集成电路芯片,是CPU、RAM、ROM、I/O接口和中断系统于同一硅片的器件。80年代以来,单片机发展迅速,各类新产品不断涌现,出现了许多高性能新型机种,现已逐渐成为工厂自动化和各控制领域的支柱产业之一。

MCS-51是标准的40引脚双列直插式集成电路芯片,引脚分布如图。

P0.0 ~ P0.7 P0口8位双向口线(在引脚的39 ~ 32号端子)。

P1.0 ~ P1.7 P1口8位双向口线(在引脚的1 ~ 8号端子)。

P2.0 ~ P2.7 P2口8位双向口线(在引脚的21 ~ 28号端子)。

P3.0 ~ P3.7 P3口8位双向口线(在引脚的10 ~ 17号端子)。

P0口的三个功能

  1. 外部扩展存储器时,当做数据总线(如图1中的D0~D7为数据总线接口)
  2. 外部扩展存储器时,当作地址总线(如图1中的A0~A7为地址总线接口)
  3. 不扩展时,可做一般的I/O使用,但内部无上拉电阻,作为输入或输出时应外部接上拉电阻。  

P1及P2口功能

  1. P1口只做I/O口使用:其内部有上拉电阻。
  2. P2口有两个功能:
    • 扩展外部存储器时,当作地址总线使用
    • 做一般I/O口使用,其内部有上拉电阻;
    • P3口有两个功能:

除了作为I/O使用外(其内部有上拉电阻),还有一些特殊功能,寄存器来设置,具体功能请参考前面的引脚说明。

DS1302芯片

DS1302 是DALLAS 公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和31 字节静态RAM,通过简单的串行接口与单片机进行通信实时时钟/日历电路,提供秒分时日日期/月年的信息,每月的天数和闰年的天数可自动调整时钟操作可通过AM/PM 指示决定采用24或12 小时格式。DS1302 与单片机之间能简单地采用同步串行的方式进行通信,仅需用到三个口线:1、RES 复位,2、I/O 数据线,3 、SCLK串行时钟。时钟/RAM 的读/写数据以一个字节或多达31个字节的字符组方式通信。DS1302 工作时功耗很低,保持数据和时钟信息时功率小于1mW。DS1302 是由DS1202 改进而来,增加了以下的特性。双电源管脚用于主电源和备份电源供应Vcc1,为可编程涓流充电电源附加七个字节存储器。它广泛应用于电话传真便携式仪器以及电池供电的仪器仪表等产品领域。

DS1302 内部寄存器

CH: 时钟停止位 存器2 的第7 位12/24 小时标志

CH=0 振荡器工作允许 bit7=1,12 小时模式

CH=1 振荡器停止 bit7=0,24 小时模式

WP: 写保护位 寄存器2 的第5 位:AM/PM 定义

WP=0 寄存器数据能够写入 AP=1 下午模式

WP=1 寄存器数据不能写入 AP=0 上午模式

TCS: 涓流充电选择 DS: 二极管选择位

TCS=1010 使能涓流充电 DS=01 选择一个二极管

TCS=其它 禁止涓流充电 DS=10 选择两个二极管

DS1302的应用

实时时钟芯片DS1302采用串行数据传输,可为掉电保护电源提供可编程的充电功能,也可以关闭充电功能,芯片采用32768Hz晶振。要特别说明的是,备用电源BT1可以用电池或超级电容(10万μF以上)。虽然DS1302在主电源掉电后耗电很小,但如果要长时间保证时钟正常,最好选用小型充电电池。如果断电时间较短(几小时或几天),可以用漏电较小的普通电解电容代替(100μF就可以保证1小时的正常走时)9。DS1302在第一次加电后,需进行初始化操作。初始化后就可以按正常方法调整时间及闹铃。

程序解释

硬件部分

系统的硬件部分主要由主控制器单片机,显示电路,时钟电路构成。系统电路框图如图所示。

8051单片机与DS1302之间采用3线串行通信方式。复位/通信允许信号RST接到单片机的P1.5引脚,RST=1允许通信,RST=0禁止通信;串行时钟信号SCLK接到单片机的P1.6引脚;数据输入/输出信号I/O接到单片机的P1.7引脚。8051作为主机通过控制RST、SCLK和I/O信号实现两芯片间的数据传送。

DS1302芯片的X1和X2端外接32.768KHz的石英晶振,Vcc1和Vcc2是电源引脚,单电源供电时接Vcc1脚,双电源供电时主电源接Vcc2,备份电池接Vcc1,如果采用可充电镍镉电池,可启用内部涓流充电器在主电压正常时向电池充电,以延长电池使用时间。备份电池也可用1µF以上的超容量电容代替,需要注意备份电池电压应略低于主电源工作电压。

数据传送是以8051单片机为主控芯片进行的,每次传送时由8051向DS1302写入一个命令字节开始。命令字节的格式如下

跟着再接收来命令字节的最高位必须为1。RAM/CK位为DS1302片内RAM/时钟选择位,RAM/CK=1选择RAM操作,RAM/CK=0选择时钟操作。RD/W位为读写控制位,RD/W=1为读操作,表示DS1302接受完命令字节后,按指定的选择对象及寄存器(或RAM)地址,读取数据并通过I/O线传送给单片机8051.RD/W=0为写操作,表示DS1302接受完命令字节后,紧自单片机8051的数据字节,并写入到DS1302相应的寄存器或RAM单元中。A4~A0为片内日历时钟寄存器或RAM的地址选择位。

DS1302与8051之间通过I/O线进行同步串行数据传送,SCLK为串行通信时的位同步时钟,一个SCLK脉冲传送一位数据。每次数据传送时都以字节为单位,低位在前,高位在后,传送一个字节需要8个脉冲。数据传送可以单字节方式或多字节突发方式进行。

数据单字节方式传送时序如图3所示,在RST=1期间,8051单片机先向DS1302发送一个命令字节,紧接发送一个字节的数据,DS1302在接收的命令字节后自动将数据写入指定的片内地址或从该地址读取数据。

DS1302共有12个寄存器,其中7个寄存器与日历时钟有关,存放的数据为BCD码格式,日历、时钟寄存器地址及其内容如图5所示。秒寄存器的第7位为时钟暂停控制位,该位为1时暂停时钟振荡器,DS1302进入低功耗状态,该位为0时启动时钟。时寄存器的第7位为12或24小时方式选择,该位为1时选择12小时方式,该位为0时选择24小时方式。在12小时方式下,时寄存器的第5位为AM/PM选择,该位为1时选择PM,该位为0时选择AM,在24小时方式下,时寄存器的第5位为第2个小时位(20~23)。

电子万年历的显示部分采用点阵字符型液晶显示模块,以直接方式与8051单片机进行接口。将单片机的P2.7通过适当逻辑门电路组合接到液晶显示模块的E端,P0口通过外部锁存器得到的最低2位地址线A0和A1分别接到液晶显示模块的RW和RS端,从而可得该接口电路的命令写入地址为7FF0H,命令读取地址为7FF1H,数据操作地址为7FF2H,分别对这3个地址进行操作即可将DS1302中的日历时钟信息显示在LCD屏幕上。

软件部分

Keil软件是目前最流行开发MCS-51系列单片机的软件,Keil C51生成的目标代码效率非常之高,多数语句生成的汇编代码很紧凑,容易理解。在开发大型软件时更能体现高级语言的优势。Keil提供了包括C编译器、宏汇编、连接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(uVision)将这些部分组合在一起。运行Keil软件需要Pentium或以上的CPU,16MB或更多RAM、20M以上空闲的硬盘空间、WIN98、NT、WIN2000、WINXP等操作系统。

由前述可知,从P2 口输出位选码,从P0 口输出段选码,LED 就会显示出数字来。但P0口的输出的数据是要BCD 码,各存储单元存储的是二进制数,也就是和要显示出的字符表达的含义是不一致的。可见,将要显示的存储单元的数据直接送到P0 口去驱动LED 数码管显示是不能正确表达的,必须在系统内部将要显示的数据经过BCD 码行转换后,将各个单元数据的段选代码送入P0 口,给CD4511 译码后去驱动数码管显示。

我们先将要显示的数据装入累加器A 中,再将A 中的数据转换成高低两位的BCD 码,再放回A 中,然后将A 中的值输出。如:有一个单元存储了45 这样一位数,则需转换成四位的BCD 码:(0100)(0101)然后放入A 中。 A 中BCD 码,高位四位代表4低四位代表5同时送给两个译码器中,译码后45字就在两个LED 中显示出来。

时间的运行依靠定时中断子程序对时钟单元数值进位调整来实现的。计数器T0 打开后,进入计时,满100 毫秒后,重装定时。中断一次,满一秒后秒进位,满60 秒后即为1 分钟,分钟单元进位,60 分到了后,时单元进位,24 小时满后,天单元进位。这样然后根据进率,得到年、月、日、时、分、秒存储单元的值,并经译码后,通过扫描程序送LED 中显示出来,实现时钟计时功能。累加是用指令INC 来实现的。进入中断服务程序以后,执行PUSH PSWPUSH A 将程序状态寄存器PSW 的内容和累加器A 中的数据保存起来,这便是所谓的保护现场 ,以保护现场和恢复现场时存取关键

数据的存储区叫做堆栈。在软件的控制之下,堆栈可在片内RAM 中的任一区间设定,而堆栈的数据存取与一般的RAM 存取又有区别,对它的操作,要遵循后进先出的原则。

系统软件设计主要包括主程序设计、日期数据采集子模块程序设计、按键处理子模块程序设计和显示子模块程序设计等。主程序主要完成器件的初始化,并判断有无按键按下,并根据判断的结果调用相应的子模块程序:日期数据采集子模块程序完成相应的数据采集、处理和保存,按键处理子模块程序完成日期的设置,而显示子模块程序只要把上述子模块储存的数据送去显示即可。

设计体会与建议

当电子万年历可以成功实现时,那种激动和喜悦只有自己可以体会。在整个设计过程中,自主学习,学到了许多没学到的知识。较好的完成了设计,达到了预期的目的,完了最初的设想。对电路的设计、布局要先有一个好的构思,才显得电路板美观、大方。程序编写中,由于思路不清晰,开始时遇到了很多的问题,经过静下心来思考,和同学讨论,理清了思路,反而得心应手。在此次设计中,知道了做事要有一颗平常的心,不要想着走捷径,一步一个脚印。此次课程设计中学到了很多很多东西,这是最重要的。总之,此次课程设计使我的能力得到了全方位的提高,使得我的操作能力和专业技能都有了很大的提高。

LCD显示万年历部分程序

代码语言:txt复制
/*******************************************************************************
注意事项:

单片机与独立按键连接说明:
K1-->P31
K2-->P30
K3-->P32

单片机与DS1302模块接线说明:
P34-->DIO
P35-->CE
P36-->CLK

实验操作:
LCD1602显示时钟,按K3键进入时钟设置,此时秒钟停止走动,按K2键选择设置的秒、分、时、
日、月、星期、年,按K1键进行加1,设置完成后,再次按下K3键继续走时
*******************************************************************************/

void main()
{
	unsigned char i;
	Int0Configuration();
	LcdInit();  // 显示屏初始化
	Ds1302Init();  // 时钟初始化
	while(1)
	{	
		if(SetState == 0)
		{
			Ds1302ReadTime(); // 读取时间
		}
		else
		{
			if(K1==0) // 检测按键K1是否按下
			{
				Delay10ms(); // 消除抖动
				if(K1 == 0)
				{
					SetPlace  ;
					if(SetPlace >= 7)
						SetPlace=0;					
				}

				while((i < 50) && ( K1==0 )) //检测按键是否松开
				{
					Delay10ms();  // 消除抖动
					i  ;
				}
				i = 0;
			}
			
			if(K2 == 0) //检测按键K2是否按下
			{
				Delay10ms(); //消除抖动
				if(K2 == 0)
				{
					TIME[SetPlace]  ;
					if((TIME[SetPlace]&0x0f) > 9)	 //换成BCD码。
					{
						TIME[SetPlace] = TIME[SetPlace]   6;
					}

					if((SetPlace < 2) && (TIME[SetPlace] >= 0x60))  // 分秒只能到59
					{
						TIME[SetPlace] = 0;
					}
					if((SetPlace == 2) && (TIME[2] >= 0x24))	//小时只能到23
					{
						TIME[SetPlace] = 0;
					}
					if (SetPlace == 3)  // 日期限制
					{	
						int isYear = 0;
						int i;
						for ( i = 0; i < 25; i  )
						{
							if (year[i] == TIME[6])
							{
								isYear=1;
								break;
							}
						}
						if (TIME[4]==0x02)  // 闰年2月最多29天
						{
							if (TIME[3]==0x29 && isYear==1)
							{
								TIME[3]=0x01;   
							}
							if (TIME[3]>=0x30)
							{
								TIME[3]=0x01;
							}							
						}
					    else if (TIME[4] == 0x01 || TIME[4] == 0x03 || TIME[4] == 0x05 || 
					    TIME[4] == 0x07 || TIME[4] == 0x08 || TIME[4] == 0x10 || TIME[4] == 0x12)  // 1、3、5、7、8、10、12最多31天
						{
							if (TIME[3] >= 0x32)
							{
								TIME[3] = 0x01;
							}	
						}
						else{
							if (TIME[3] >= 0x31)
							{
								TIME[3] = 0x01;
							}
						}
					}
							
					if((SetPlace == 4) && TIME[4] >= 0x13 ){  // 月只能到12	
						TIME[SetPlace]=0x01;  
					}
					if((SetPlace == 5) && (TIME[5] >= 0x8))  // 校正星期数值
					{
						TIME[SetPlace] = 1;
					}	
					if ((SetPlace == 6) && TIME[6] > 0x99 )
					{
						TIME[6] = 0x00;
					}
				}
				
				while((i<50)&&(K2==0))	 //检测按键是否松开
				{
					Delay10ms();
					i  ;
				}
				i=0;
				
			}

		}
		LcdDisplay();	
	}
	
}

// 显示函数
void LcdDisplay()
{
	LcdWriteCom(0x80   0X40);
	LcdWriteData('0'   TIME[2] / 16);  // 时
	LcdWriteData('0'   (TIME[2] & 0x0f));				 
	LcdWriteData('-');
	LcdWriteData('0'   TIME[1] / 16);  // 分
	LcdWriteData('0' (TIME[1] & 0x0f));	
	LcdWriteData('-');
	LcdWriteData('0'   TIME[0] / 16);  // 秒
	LcdWriteData('0'   (TIME[0] & 0x0f));

	LcdWriteCom(0x80);
	LcdWriteData('2');
	LcdWriteData('0');
	LcdWriteData('0'   TIME[6] / 16); // 年
	LcdWriteData('0'   (TIME[6] & 0x0f));
	LcdWriteData('-');
	LcdWriteData('0'   TIME[4] / 16); // 月
	LcdWriteData('0'   (TIME[4] & 0x0f));
	LcdWriteData('-');
	LcdWriteData('0'   TIME[3] / 16); // 日
	LcdWriteData('0'   (TIME[3] & 0x0f));
	LcdWriteCom(0x8F);
	LcdWriteData('0'   (TIME[5] & 0x07)); // 星期			 
}

0 人点赞