基于51单片机的多功能电子万年历设计(LCD12864+DS1302+DS18B20)

随着电子技术的发展,人类不断研究,不断创新纪录。万年历目前已经不再局限于以书本形式出现。以电脑软件或者电子产品形式出现的万年历被称为电子万年历。与传统书本形式的万年历相比,电子万年历得到了越来越广泛的应用,采用电子时钟作为时间显示已经成为一种时尚。目前市场上各式各样的电子时钟数不胜数,但多数是只针对时间显示,功能单一不能满足人们日常生活需求。

本设计将制作一种基于单片机控制的带实时温度显示、具有定时功能的电子万年历。传统的电子日历大都体积大,功耗大,显示不准确等特点。为了缩小体积,减小功耗,使其变得小巧灵敏,本设计加入了时钟芯片DS1302,可对时间进行准确记时,同时可设置定时时间,实现定时功能。另外本设计具有显示实时温度的功能。传统的温度传感器系统大都采用放大、调理、A/D转换,转换后的数字信号送入计算机处理,处理电路复杂、可靠性相对较差,占用计算机的资源比较多。本设计将采用DS18B20一线制数字温度传感器,可将温度信号直接转换成数字信号送给微处理器,电路简单,成本低,实现了时间温度同时显示的效果。最后,温度和时间都将通过12864液晶显示器进行显示。测试表明系统达到了设计要求的各项功能,各部分工作正常。


# 摘 要

本设计将制作一种基于单片机控制的带实时温度显示、具有定时功能的电子万年历。传统的电子日历大都体积大,功耗大,显示不准确等特点。为了缩小体积,减小功耗,使其变得小巧灵敏,本设计加入了时钟芯片DS1302,可对时间进行准确记时,同时可设置定时时间,实现定时功能。另外本设计具有显示实时温度的功能。传统的温度传感器系统大都采用放大、调理、A/D转换,转换后的数字信号送入计算机处理,处理电路复杂、可靠性相对较差,占用计算机的资源比较多。本设计将采用DS18B20一线制数字温度传感器,可将温度信号直接转换成数字信号送给微处理器,电路简单,成本低,实现了时间温度同时显示的效果。最后,温度和时间都将通过12864液晶显示器进行显示。测试表明系统达到了设计要求的各项功能,各部分工作正常。
关键词 : 时钟 温度检测 单片机 温度 LCD12864 DS1302

1 概论

1.1 万年历发展背景

随着电子技术的发展,人类不断研究,不断创新纪录。万年历目前已经不再局限于以书本形式出现。以电脑软件或者电子产品形式出现的万年历被称为电子万年历。与传统书本形式的万年历相比,电子万年历得到了越来越广泛的应用,采用电子时钟作为时间显示已经成为一种时尚。目前市场上各式各样的电子时钟数不胜数,但多数是只针对时间显示,功能单一不能满足人们日常生活需求。

1.2 国内外现状、发展

随着电子技术的迅速发展,特别是随大规模集成电路出现,给人类生活带来了根本性的改变。尤其是单片机技术的应用产品已经走进了千家万户。电子万年历的出现给人们的生活带来的诸多方便。
万年历中使用的LCD的应用很广泛,如手表上的液晶显示屏,仪表仪器上的液晶显示器或者是电脑笔记本上的液晶显示器,都使用了LCD。在一般的办公设备上也很常见,如传真机,复印机,以及一些娱乐器材玩具等也常常见到LCD的足迹。字符型液晶显示模块是一种专门用于显示字母,数字,符号等的点阵式液晶显示模块。在显示器件上的设计,它是由若干个5×7或5×11等点阵符位组成。每一个点阵字符位都可以显示一个字符。点阵字符位之间有一空点距的间隔起到了字符间距和行距的作用。目前市面上常用的有16字×1行,16字×2行,20字×2行和40字×2行等的字符模块组。这些LCD虽然显示字数各不相同,但是都具有相同的输入输出界面。
随着单片机的发展,电子万年历呈现了微型化 ,功能丰富化的趋势,而且价格在不断下降,考虑到资源问题,现在的设计设计的万年历都采用了节能设计方案,万年历对人们的生活有着十分重要的作用,所以电子万年历还是有很大的发展前景的。

2 系统基本方案选择和论证

2.1 单片机芯片的选择方案和论证

方案一:
采用89C51芯片作为硬件核心,89C51是一种带4K字节闪烁可编程可擦除只读存储器,采用Flash ROM,内部具有4KB ROM 存储空间,能于3V的超低压工作,而且与MCS-51系列单片机完全兼容,与工业标准的MCS-51指令集和输出管脚相兼容。由于将多功能8位CPU和闪烁存储器组合在单个芯片中,89C51是一种高效微控制器,51单片机为很多嵌入式控制系统提供了一种灵活性高且价廉的方案但是运用于电路设计中时由于不具备在线编程(ISP)技术,当在对电路进行调试时,由于程序的错误修改或对程序的新增功能需要烧入程序时,对芯片的多次拔插可能对芯片造成一定的损坏,目前该型号芯片已经停产。
方案二:
采用AT89S52单片机,AT89S52单片机是ATMEL生产的单片机,是新一代8051单片机,指令代码完全兼容传统8051。内部集成看门狗电路。AT89S52单片机内部有8KB的程序Flash存储器。由于我们设计的万年历烧写文件大概在7KB左右 ,而AT89S52单片机的程序Flash为8KB,不用再外接程序存储器了。

经过综合比较最终选择方案二,即选择AT89S52作为主控制器。

2.2 显示模块的选择方案和论证

方案一:
LCD12864液晶是一种具有8位并行接口方式的点阵图形液晶显示模块;其显示分辨率为128×64。利用该模块灵活的接口方式和简单、方便的操作指令,可构成全中文人机交互图形界面。可以显示16×16点阵的汉字,也可完成图形显示,低电压低功耗是其又一显著特点。由该模块构成的液晶显示方案与同类型的图形点阵液晶显示模块相比,不论硬件电路结构或显示程序都要简洁得多。万年历要求显示年月日、时分秒、星期、和农历。LCD12864液晶可以完成设计的要求 。
方案二:
系统采用LED显示。LED应用可分为两大类:一是LED单管应用,包括背光源LED,红外线LED等;另外就是LED显示屏,目前,中国在LED基础材料制造方面与国际还存在着一定的差距,但就LED显示屏而言,中国的设计和生产技术水平基本与国际同步。LED显示屏是由发光二极管排列组成的显示器件。它采用低电压扫描驱动,具有:耗电少、使用寿命长、成本低、亮度高、故障少、视角大、可视距离远等特点。采用LED数码管动态扫描.价格上比较经济实惠,但不能显示文字,性价比不是很高,操作起来比较液晶显示来说略显繁琐,所以也不用此种作为显示。
经过综合比较最终选择方案一,即选择LCD12864液晶显示屏。

2.3 时钟芯片的选择方案和论证

方案一:
采用单片机定时。单片机集成度高、功能强、可靠性高、体积小、功耗低、使用方便、价格低廉等一系列优点,单片机的应用领域已从面向工业控制、通讯、交通、智能仪表等迅速发展到家用消费产品、办公自动化、汽车电子、PC机外围以及网络通讯等广大领域。
直接采用单片机定时计数器提供秒信号,计数的脉冲由外部提供,定时的脉冲由外部晶振提供,定时加1的周期为一个机器周期;定时时间与初值和晶振频率有关。使用程序实现年、月、日、星期、时、分、秒计数。采用此种方案减少芯片的使用,节约成本,但程序复杂度较高。
方案二:
采用DS1302时钟芯片。DS1302 是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、星期、时、分、秒进行计时,具有闰年补偿功能,工作电压为2.5V~5.5V。采用双电源供电(主电源和备用电源),可设置备用电源充电方式,提供了对后背电源进行涓细电流充电的能力。DS1302用于数据记录,特别是对某些具有特殊意义的数据点的记录上,能实现数据与出现该数据的时间同时记录,因此广泛应用于测量系统中。采用三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号或RAM数据。DS1302内部有一个31×8的用于临时性存放数据的RAM寄存器。采用DS1302只需要写出驱动程序,调用程序读出寄存器内数据经过简单的变换就可以输出万年历的数据。
经过综合比较最终选择方案二,即采用DS1302时钟芯片。

2.4 温度传感器的选择方案和论证

方案一:
采用热敏电阻作为温度传感器。热敏电阻是开发早、种类多、发展较成熟的敏感元器件。热敏电阻由半导体陶瓷材料组成,利用的原理是温度引起电阻变化。热敏电阻的主要特点是:灵敏度较高,其电阻温度系数要比金属大10~100倍以上;工作温度范围宽,常温器件适用于-55℃~315℃,高温器件适用温度高于315℃(目前最高可达到2000℃)低温器件适用于-273℃~55℃;体积小,能够测量其他温度计无法测量的空隙、腔体及生物体内血管的温度;使用方便,电阻值可在0.1~100kΩ间任意选择;易加工成复杂的形状,可大批量生产;稳定性好、过载能力强。由于半导体热敏电阻有独特的性能,所以在应用方面它不仅可以作为测量元件(如测量温度、流量、液位等),还可以作为控制元件(如热敏开关、限流器)和电路补偿元件。热敏电阻广泛用于家用电器、电力工业、通讯、军事科学、宇航等各个领域,发展前景极其广阔。
使用热敏电阻作为传感器,用热敏电阻与一个相应阻值电阻相串联分压,利用热敏电阻阻值随温度变化而变化的特性,采集这两个电阻变化的分压值,并进行A/D转换。此设计方案需用A/D转换电路,增加硬件成本而且热敏电阻的感温特性曲线并不是严格线性的,会产生较大的测量误差。
方案二:
采用DS18B20温度传感器。在应用与高精度、高可靠性的场合时DALLAS(达拉斯)公司生产的DS18B20温度传感器当仁不让。超小的体积,超低的硬件开消,抗干扰能力强,精度高,附加功能强,使得DS18B20更受欢迎。对于我们普通的电子爱好者来说,DS18B20的优势更是我们学习单片机技术和开发温度相关的小产品的不二选择。这是世界上第一片支持“一线总线”接口的温度传感器。DS18B20数字温度计提供9位(二进制)温度读数,指示器件的温度。信息经过单线接口送入DS18B20或从DS18B20送出,因此从单片机到DS18B20仅需一条线连接即可。它可在1秒钟(典型值)内把温度变换成数字
经过综合比较最终选择方案二,即采用采用DS18B20温度传感器。

2.5 电路设计最终方案确定

最终选择单片机AT89S52作为主控制器;选择LCD12864型液晶作为显示模块,此模块可以显示字母、数字符号、中文字型及图形,具有绘图及文字画面混合显示功能,可实现设计对“年”、“月”、“日”、“室内温度”、“度”五个词语的显示要求;选择采用DS1302时钟芯片,使程序实现年、月、日、星期、时、分、秒的显示。采用DS18B20温度传感器,可以对温度做出比较精确的测量,而且和单片机通讯只要一个IO,连接方便。

3 系统硬件电路设计

3.1 系统功能模块划分及整体电路图

根据系统功能要求,可大致画出系统所需硬件结构框图如图3-1-1所示:
在这里插入图片描述

图3-1-1 系统功能模块图

主控模块采用性价比较高的AT89S52单片机芯片,在其内部烧写好程序,可通过程序的运行控制测温模块进行测温;测温模块主要是由DS18B20构成,将其与所测对象进行接触即可获取被测对象的温度数据,而所测得的温度和时钟芯片测得的实时日历将通过显示模块的液晶显示器以数字形式显示;单片机调用程序,读取DS1302内寄存器,可以得到万年历的时间数据,经过程序处理就可以输出在LCD上;键盘电路可对实时日历进行调整;蜂鸣器可以在闹钟定时中,作为声音提醒。
作品整体电路图如图3-1-2所示
在这里插入图片描述

图3-1-2 整体电路图

3.2 各单元模块功能分析及模块电路设计

3.2.1 时钟模块

DS1302的工作原理和单片机的接口:
DS1302为美国DALLAS公司的一种实时时钟芯片,主要特点是采用串行数据传输,可为掉电保护电源提供可编程的充电功能,并且可以关闭充电功能。采用32.768Hz晶振。它可以对年、月、日、星期、时、分、秒进行计时,且具有闰年补偿等多种功能。DS1302 用于数据记录,特别是对某些具有特殊意义的数据点的记录上,能实现数据与出现该数据的时间同时记录。这种记录对长时间的连续测控系统结果的分析以及对异常数据出现的原因的查找有重要意义。在本设计中,它的实际电路图如图3-2所示:
在这里插入图片描述

图3-2 DS1302与单片机的连接
DS1302需要外接32.768K的晶振,1号引脚接主电源VCC(5V)电源,8号引脚接备用电池(3V),当主电源掉电后,备用电源为DS1302提供电源,维持DS1302内数据不丢失,这正是时钟芯片所必须的特性。

3.2.2 温度模块

传统的温度传感器系统大都采用放大、调理、A/D转换,转换后的数字信号送入计算机处理,处理电路复杂、可靠性相对较差,占用计算机的资源比较多,本设计测温模块采用一线制总线数字温度传感器DS18B20,可将温度信号直接转换成数字信号送给微处理器,电路简单,成本低,其电路原理图如图3-3所示:
在这里插入图片描述

图3-3 DS18B20温度模块
从图中可看出,将温度传感器的一线制总线通过端口2与本设计主控芯片STC12C5A6S2的端口标号为DS18B20的相连即可实现相互之间的通信。设计中的测温元件采用的是DS18B20测温元件,DS18B20是由DALLAS(达拉斯)公司生产的一种温度传感器。超小的体积,超低的硬件开消,抗干扰能力强,精度高,附加功能强,使得DS18B20很受欢迎。这是世界上第一片支持“一线总线”接口的温度传感器。DS18B20数字温度计提供9位(二进制)温度读数,指示器件的温度。信息经过单线接口送入DS18B20或从DS18B20送出,因此从单片机到DS18B20仅需一条线连接即可。它可在1秒钟(典型值)内把温度变换成数字。

3.2.3 显示模块

本设计显示模块主要采用LCD12864液晶显示器,其电路原理图如下:
在这里插入图片描述

图3-5 LCD12864模块
LCD12864液晶显示器通过数据端口也即端口7~14与主控芯片AT89S52的I/O端口P3相连接实现数据与指令的传输,再通过控制端口RS、RW、EN也即端口4~6与主控芯片P1.5,P1.6,P1.7端口相接实现对数据和指令传输的控制 。显示模块采用12864液晶显示器可实现对温度和时间的直接显示,清晰明了。

3.2.4 独立键盘模块

键盘是人与万年历实现信息交互的接口,本设计中,我们采用3个独立键盘,电路原理如下图3-9:
在这里插入图片描述

图3-9 独立键盘
当按键按下,与主控芯片连接的端口被降为低电平,按键松开则也升为高电平。按键采用的是Tack Switch按钮开关,它具有自动恢复(弹回)的功能。当我们按下按钮时,其中的接点接通(或切断),放开按钮后,接点恢复为切断(或接通)。按照尺寸区分,电子电路或微型计算机所使用的Tack Swith可分为8mm、10mm、12mm等。虽然Tack Switch有4个引脚,但实际上,其内部只有一对a接点,即其中两个引脚是内部相连通的,而另外两个引脚内部也是相连通的。3个按键实现了开机模式选择,日期调节等功能,独立按键的引入使得体现了本设计的人性化,智能化,功能的强大。

3.2.5 蜂鸣器模块

蜂鸣器模块是本设计中体现人机交互的又一大设计亮点,其电路原理图如下
在这里插入图片描述

图3-10 蜂鸣器模块
本设计里,我们采用有源蜂鸣器,由于蜂鸣器的工作电流一般比较大,以至于单片机的I/O 口是无法直接驱动的,所以要利用放大电路来驱动,我们使用三极管来放大电流,驱动蜂鸣器,此模块只要通过BELL(连接到到单片机P2.7)输入的PWM波既可以使蜂鸣器分出声音,我们设计的这款万年历可以在闹钟定时中作为声音提醒信号。

4 系统软件设计

4.1 万年历软件系统的流程图

在这里插入图片描述

图4-1 系统软件流程图
当接通电源开始工作后,单片机中的程序开始运行,将对DS18B20进行初始化,以便和单片机芯片达成通信协议。完成初始化后,由于本系统只有一个测温元件,单片机会向其发出跳过RAM指令,接下来便可向其发送操作指令,启动测温程序,测温过程完成后,发出温度转换指令,从而便可将温度转化成数字模式进行显示读取;同时DS1302将读取时分秒星期以及年月日寄存器然后通过液晶显示实时时间、星期及日期;键盘电路中按键可对实时日历时钟进行调整。

4.2 万年历软件系统的程序部分

DS1302程序代码

/*
* DS1302 时钟芯片
*/
#include "DS1302.h"		//包含头文件
/*
 * 写一个字节
*/
void write_ds1302_byte(uint8 dat) 
{
	uint8 i;	
	for (i=0;i<8;i++) //循环8次
	{ 	SDA = dat & 0x01;   
		SCK = 1;     // SCK端口置1
		dat >>= 1;
		SCK = 0;	// SCK端口置0
	}
}

/*
 * 读一个字节
*/
uint8 read_ds1302_byte(void) 
{
	uint8 i, dat=0;
	
	for (i=0;i<8;i++)//循环8次
	{	
		dat >>= 1;
		if (SDA)
			dat |= 0x80;
		SCK = 1;// SCK端口置1
		SCK = 0;// SCK端口置0
	} 

	return dat;
}

void reset_ds1302(void)
{
	RST = 0;// RST端口置0
	SCK = 0;// SCK端口置0
	RST = 1;// RST端口置1
}

/*
 * 清除写保护
*/
void clear_ds1302_WP(void) 
{
	reset_ds1302();
	RST = 1;// RST端口置1
	write_ds1302_byte(0x8E); 
	write_ds1302_byte(0);
	SDA = 0;// SDA端口置0
	RST = 0;// RST端口置0
}

/*
 * 设置写保护
*/
void set_ds1302_WP(void) 
{
	reset_ds1302();//复位1302
	RST = 1;// RST端口置1
	write_ds1302_byte(0x8E);
	write_ds1302_byte(0x80);
	SDA = 0;// SDA端口置0
	RST = 0;// RST端口置0
}


/*
 * 设定时钟数据 (秒分时日月周年)
*/
void set_time(uint8 *timedata)
{
	uint8 i, tmp, tmps[7];

	for (i=0; i<7; i++)  // 转化为BCD格式
	{
		tmp = timedata[i] / 10;
		tmps[i] = timedata[i] % 10;
		tmps[i] = tmps[i] + tmp*16;
	}

	clear_ds1302_WP();//取消写保护

	reset_ds1302();//复位芯片
	RST = 1;// RST端口置1
	write_ds1302_byte(DS1302_W_ADDR);
	for (i=0; i<7; i++)//循环7次
	{
		write_ds1302_byte(tmps[i]);
		delay(10);//延时
	}
	write_ds1302_byte(0);
	SDA = 0;// SDA端口置0
	RST = 0;// RST端口置0

	set_ds1302_WP();//设置写保护
}

/*
 * 读时钟数据(秒分时日月周年)
*/
void read_time(uint8 *timedata)
{
	uint8 i, tmp;

	clear_ds1302_WP();//取消写保护

	reset_ds1302();	//复位芯片
	RST = 1;		//ST端口置1
	write_ds1302_byte(DS1302_R_ADDR);
	for (i=0; i<7; i++)//循环7次
	{
		timedata[i] = read_ds1302_byte();
		delay(10);	//延时
	}
	SDA = 0;// SDA端口置0
	RST = 0;// RST端口置0

	set_ds1302_WP();//设置写保护

	for (i=0; i<7; i++)//循环7次
	{
		tmp = timedata[i];
		timedata[i] = (tmp/16%10)*10;
		timedata[i] += (tmp%16);
	}
}

LCD12864程序代码

/*
 * LCD128*64
*/

#include "LCD.h"
#include "word.h"
#define Page_Add   0xb8
#define Col_Add    0x40
#define Disp_On    0x3f
#define Disp_Off   0x3e
#define Start_Line 0xc0
/*
 * 12864判忙
*/
void chekbusy12864(void)
{
	uint8 dat;

	RS = 0;       	//指令模式 
	RW = 1;       	//读数据
	do{
		P3 = 0;
		E = 1;		//E端口置1
	    _nop_();	//延时1us
		dat = P3 & 0x80;
		E = 0;		//E端口置0
	}while (dat != 0);
}


/*
 * 12864片选
 * i:0是左屏,1是右屏,2是双屏
*/
void choose12864(uint8 i)
{
	switch (i)
	{
		case 0: CS1 = 0; CS2 = 1; break;//片选左屏
		case 1: CS1 = 1; CS2 = 0; break;//片选右屏
//		case 2: CS1 = 0; CS2 = 0; break;
		default: break;//退出
	}
}

/*
 * 写命令
*/
void cmd_w12864(uint8 cmd)
{
	chekbusy12864();
	
	RS = 0;//RS端口置0
	RW = 0;//RW端口置0
	_nop_();//延时1us
	E = 1;//E端口置1
	_nop_();//延时1us
	P3 = cmd;
	_nop_();//延时1us
	E = 0;//E端口置0
}

/*
 * 写数据
*/
void dat_w12864(uint8 dat)
{
	chekbusy12864();
	
	RS = 1;//RS端口置1
	RW = 0;//RW端口置0
   _nop_();//延时1us
	E = 1;//E端口置1
   _nop_();//延时1us
	P3 = dat;
   _nop_();//延时1us
	E = 0;//E端口置0
}

/*
 * 清屏
*/
void clear12864(void)
{
	uint8 page,row;
	
	choose12864(0);

	for(page=0; page<8; page++)
	{
		cmd_w12864(0xb8+page);
		cmd_w12864(0x40);
		for(row=0; row<64; row++)
		{  
		    
			dat_w12864(0x00);//写数据0
		}
	}
		choose12864(1);

	for(page=0; page<8; page++)
	{
		cmd_w12864(0xb8+page);
		cmd_w12864(0x40);
		for(row=0; row<64; row++)
		{  		    
			dat_w12864(0x00);//写数据0
		}
	}
}


/*
 * LCD初始化
*/
void LCD_init(void)
{
	chekbusy12864();
	cmd_w12864(0xc0);	  //从第0行开始显示
	cmd_w12864(0x3f);	  //LCD显示RAM中的内容
	clear12864();
}

/*
 * 8x16字符的显示
*/
void play8(uint8 x, uint8 y, uint8 *addr)
{
	uint8 i;

	if (x > 63)
	{
		choose12864(1);
		x = x-64;
	}
	else
	{
		choose12864(0);
	}
	cmd_w12864(0x40|x);
	cmd_w12864(0xb8|(y++));
	if ((y & 0x80) == 0)
		for (i=0;i<8;i++)
			dat_w12864(*addr++);
	else
		for (i=0;i<8;i++)
			dat_w12864(0xFF - *addr++);

	cmd_w12864(0x40|x);
	cmd_w12864(0xb8|y);
	if ((y & 0x80) == 0)
		for (i=0;i<8;i++)
			dat_w12864(*addr++);
	else
		for (i=0;i<8;i++)
			dat_w12864(0xFF - *addr++);
}

/*
 * 16x16显示
*/
void play16(uint8 x, uint8 y, uint8 *addr)
{
	uint8 i;

	if (x > 63)
	{
		choose12864(1);
		x = x-64;
	}
	else
	{
		choose12864(0);
	}
	cmd_w12864(0x40|x);
	cmd_w12864(0xb8|(y++));
	if ((y & 0x80) == 0)
		for (i=0; i<16; i++)
			dat_w12864(*addr++);
	else
		for (i=0; i<16; i++)
			dat_w12864(0xFF - *addr++);

	cmd_w12864(0x40|x);
	cmd_w12864(0xb8|y);
	if ((y & 0x80) == 0)
		for (i=0; i<16; i++)
			dat_w12864(*addr++);
	else
		for (i=0; i<16; i++)
			dat_w12864(0xFF - *addr++);
}

/*
 * 16*32 字符显示
*/
void play32(uint8 x, uint8 y, uint8 num)
{
	uint8 i, j, *addr;

	addr = Num + num*64;
	if (x > 63)
	{
		choose12864(1);
		x = x-64;
	}
	else
	{
		choose12864(0);
	}

	
	for (j=0; j<4; j++)
	{
		cmd_w12864(0x40|x);
		cmd_w12864(0xb8|(y++));
		if ((y & 0x80) == 0)
			for (i=0; i<16; i++)
				dat_w12864(*addr++);
		else
			for (i=0; i<16; i++)
				dat_w12864(0xFF - *addr++);
	}
}

/*
 * 8x16数字的显示
*/
void play8_num(uint8 x, uint8 y, uint8 num)
{
	play8(x, y, &S_num[16*(num/10%10)]);
	play8(x+8, y, &S_num[16*(num%10)]);
}

/*
 * 16x32BCD数字的显示
*/
void play32_num(uint8 x, uint8 y, uint8 num)
{
	play32(x, y, num/10%10);
	play32(x+16, y, num%10);
}

void play_week(uint8 x, uint8 y, uint8 num)
{
	switch (num)
	{
		case 1: play16(x+32, y, zhou_yi); break;
		case 2: play16(x+32, y, zhou_er); break;
		case 3: play16(x+32, y, zhou_san); break;
		case 4: play16(x+32, y, zhou_si); break;
		case 5: play16(x+32, y, zhou_wu); break;
		case 6: play16(x+32, y, zhou_liu); break;
		case 7: play16(x+32, y, zhou_qi); break;
		default : break;
	}
}
//************************************************************************/
// 函数: LCD_Delay()
// 描述: 延时t ms函数
// 参数: t 
// 返回: 无
// 备注: 11.0592MHZ       t=0延时时间约13us
// 版本:  2011/01/01      First version
//************************************************************************/
void LCD_Delay_us(unsigned int t)
{
	while(t--); 
}
//************************************************************************/
// 函数: LCD_Delay()
// 描述: 延时t ms函数
// 参数: t 
// 返回: 无
// 备注: 11.0592MHZ       t=1延时时间约1ms
// 版本:  2011/01/01      First version
//************************************************************************/
void LCD_Delay_ms(unsigned int t)
{
	unsigned int i,j;
	for(i=0;i<t;i++)
	for(j=0;j<113;j++)
	;
}

主界面框架显示及设置页面

/*
 * 主界面框架
*/
void main_frame(void)
{
	play32(80, 2, 10); //显示数
	play32(32, 2, 10); //显示数
	play16(17, 0, hanzi_nian);//显示斜线
	play16(49, 0, hanzi_yue);//显示斜线
	play16(80, 0, hanzi_ri);//显示斜线
  
	play16(0, 6 ,hanzi_si);
	play16(16, 6 ,hanzi_nei);
	play16(32, 6 ,hanzi_wen);
	play16(48, 6 ,hanzi_du);
	play16(112, 6,hanzi_du);//显示度
}

/*
 * 主界面
*/
void main_show(bit refresh)
{
//    uint8  lunar[2];
	if (refresh)
		read_time((uint8 *)&time);// 读时间函数// 时间											   
	if (refresh || (time.sec != tmp_time.sec)) // 秒更新
	{
		tmp_time.sec = time.sec;			  	//读取秒数据
 	    play8_num(96, 6,zhengshu);   			//温度显示
		play32_num(96, 2, time.sec);			//显示秒
	}
	if (refresh)
		main_frame();//刷新界面
	if (refresh || (time.min != tmp_time.min)) // 分更新
	{
		if (!refresh)
			flag = 0;
		tmp_time.min = time.min;//读取分
		play32_num(48, 2, time.min); //显示分
	}
	if (refresh || (time.hour != tmp_time.hour)) // 时更新
	{
		if ((!refresh)&&(Clock_flag))
			alarm_sound();
		tmp_time.hour = time.hour; 				//读取时
		play32_num(0, 2, time.hour);			//显示时
	}
	if (refresh || (time.day != tmp_time.day))  //日更新
	{
		tmp_time.day = time.day;				//读取日
		play8_num(64, 0, time.day);				//显示日
		// 农历	
		turn_lunar_calendar(&time, lunar);
		play_lunar_calendar(0, 6, lunar[0], lunar[1]);
	}
	if (refresh || (time.week != tmp_time.week)) // 周更新
	{
		tmp_time.week = time.week;				//读取周
		play_week(140, 0, time.week);			//显示周
	}
	if (refresh || (time.mon != tmp_time.mon))  // 月更新
	{
		tmp_time.mon = time.mon;				//读取月
		play8_num(32, 0, time.mon);				//显示月
		// 农历	
		turn_lunar_calendar(&time, lunar);				//转换农历年
		play_lunar_calendar(0, 6, lunar[0], lunar[1]);	//显示农历年
	}

	if (refresh || (time.year != tmp_time.year)) // 年更新
	{
		tmp_time.year = time.year;					//读取年数据
		play8_num(0, 0, time.year);					//显示年
		// 农历	
		turn_lunar_calendar(&time, lunar);				//转换农历年
		play_lunar_calendar(0, 6, lunar[0], lunar[1]);	//显示农历年
	}	
}
/*
 * 主机界面设置 
*/
void main_set(void)
{	
	int8 key_val, state=1;			 	//变量
	play32_num(96, 2|0x80, time.sec);	//显示秒			
	while (1)
	{	key_val = scan_key();//键盘扫描
		if (key_val == 1) // 设置
		{
			if (state >= 7)
				state = 0;
			else
				state++;				//位置状态加1
			set_time((uint8 *)&time);	//设置时间
			main_show(1);				//显示主界面
			switch (state)
			{	case 0:	set_time((uint8 *)&time); break;//设置时间
				case 1:	play32_num(96, 2|0x80, time.sec); break;//显示秒
				case 2:	play32_num(48, 2|0x80, time.min); break;//显示分
				case 3:	play32_num(0, 2|0x80, time.hour); break;//显示时
				case 4:	play_week(140, 0|0x80, time.week); break;//显示周
				case 5:	play8_num(64, 0|0x80, time.day); break; //显示日
				case 6:	play8_num(32, 0|0x80, time.mon); break; //显示月
				case 7:	play8_num(0, 0|0x80, time.year); break; //显示年
				default: break;		//退出循环						  
			}
		}
		else if (key_val > 1)//按键值大于1
		{		
			if (key_val == 4)
			{
				state = 0;
				clear12864();         //清屏幕
				main_show(1);		  //主界面
			}
			if (state == 1)//位置1设置秒
			{
				if (key_val == 3)//加按下?
					time.sec++;//秒加1
				else if(key_val == 2)
					time.sec--; //秒减1
				if (time.sec >= 60)
					time.sec = 0;
				else if (time.sec < 0)
					time.sec = 59;       
				play32_num(96, 2|0x80, time.sec);//显示秒	
			}
			else if (state == 2)				//位置2设置分
			{
				if (key_val == 3)				//加按下?
					time.min++;					//加1
				else if(key_val == 2)
					time.min--;					//减1
				if (time.min >= 60)
					time.min = 0;
				else if (time.min < 0)
					time.min = 59;
				play32_num(48, 2|0x80, time.min);//显示分
			}
			else if (state == 3)				//位置3设置时
			{		
				if (key_val == 3)				//加按下?
					time.hour++;				//加1
				else if(key_val == 2)
					time.hour--;				//减1
				if (time.hour >= 24)
					time.hour = 0;
				else if (time.hour < 0)
					time.hour = 23;
				play32_num(0, 2|0x80, time.hour);//显示时
			}
			else if (state == 4)				//位置4设置周
			{		
				if (key_val == 3)			//加按下?
					time.week++;				//加1
				else if(key_val == 2)
					time.week--;				//减1
				if (time.week >= 8)
					time.week = 1;
				else if (time.week < 1)
					time.week = 7;
				play_week(140, 0|0x80, time.week);//显示周
			}
			else if (state == 5)//位置5设置日
			{
				if (key_val == 3)				//加按下?
					time.day++;					//加1
				else if(key_val == 2)
					time.day--;					//减1
				if (time.day >= 32)
					time.day = 1;
				else if (time.day < 1)
					time.day = 31;
				play8_num(64, 0|0x80, time.day);//显示日
			}
			else if (state == 6)				//位置6设置月
			{
				if (key_val == 3)				//加按下?
					time.mon++;					//加1
				else if(key_val == 2)
					time.mon--;					//减1
				if (time.mon >= 13)
					time.mon = 1;
				else if (time.mon < 1)
					time.mon = 12;
				play8_num(32, 0|0x80, time.mon);//显示月
			}
			else if (state == 7)				//位置7设置年
			{
				if (key_val == 3)				//加按下?
					time.year++;				//加1
				else if(key_val == 2)
					time.year--;				//减1
				if (time.year >= 100)
					time.year = 0;				//0年
				else if (time.year < 0)
					time.year = 99;				//99年
				play8_num(0, 0|0x80, time.year);//显示年
			}
			else
			{
				break;		//退出循环
			}
		}
		if (state == 0)
			break;		//退出循环
	}
}

闹钟界面显示

/*
 * 闹钟界面显示
*/
void alarm_show(void)
{
	int8 key_val, state=1;
	uint32 t=0;

	play16(16, 0, nao);			//显示 闹
	play16(32, 0, zhong);		//钟
	play16(48, 0, hanzi_she);
	play16(64, 0, hanzi_ding);
	play16(80, 0, hanzi_ye);
	play16(96, 0, hanzi_mian);
	
	play16(0, 2, nao);			//显示 闹
	play16(16, 2, zhong);		//钟
	play16(32, 2, maohao);		//冒号:
	if (Alarm_flag)
		play16(48, 2, kai);		//开
	else
		play16(48, 2, guan);//关
	play8_num(80, 2, alarm.hour);	//时
	play8(96, 2, maohao1);				//冒号
	play8_num(104, 2, alarm.min);	//分	
	play16(0, 4, zheng);			//显示 整
	play16(16, 4, dian);		//显示 点
	play16(32, 4, bao);			//显示 报
	play16(48, 4, shi);			//显示 时
	play16(64, 4, maohao);			  //显示 冒号
	play16(0, 6, hanzi_she);			//显示 整
	play16(16, 6, hanzi_ding);		//显示 点
	play16(32, 6, hanzi_wen);			//显示 报
	play16(48, 6, hanzi_du);			//显示 时
	play16(64, 6, maohao);			  //显示 冒号
	play8_num(80, 6,shedingwendu);
	play16(96, 6, hanzi_du);      //显示 度
	if (Clock_flag)
		play16(80, 4, kai);			//显示 开
	else
		play16(80, 4, guan);		//显示 关	
	for (t=0; t<30000; t++)
	{	
		key_val = scan_key();		//键盘扫描 获取键值
		if (key_val > 1)			//判断数据
			break;
		if (key_val ==4)			//判断数据
		{
			clear12864();         //清屏幕
			main_show(1);		  //主界面
		}
		else if (key_val == 1)		//判断数据
		{
			if (Alarm_flag)
				play16(48, 2|0x80, kai);//显示 开
			else
				play16(48, 2|0x80, guan);//关			
			while (1)
			{				
				key_val = scan_key();//键盘扫描 获取键值
				if (key_val == 1) 				// 完成设置
				{
					if (state >= 5)				//判断数据
						state = 0;
					else
						state++;
					if (Alarm_flag)
						play16(48, 2, kai);			//显示 开
					else
						play16(48, 2, guan);		//显示 关
					play8_num(80, 2, alarm.hour);  //闹钟 时 显示 
					play8_num(104, 2, alarm.min);	//闹钟 分 显示 
					if (Clock_flag)
						play16(80, 4, kai);			//显示 开
					else
						play16(80, 4, guan);		//显示 关

					switch (state) //判断数据
					{
						case 1: 	
							if (Alarm_flag)//判断数据
								play16(48, 2|0x80, kai);	//显示 开
							else
								play16(48, 2|0x80, guan); 	//显示 关
							break;
						case 2:
							play8_num(104, 2|0x80, alarm.min);//闹钟 分 显示 
							break;
						case 3: 
							play8_num(80, 2|0x80, alarm.hour);//闹钟 时 显示 
							break;
						case 4: 
							if (Clock_flag)//判断数据
								play16(80, 4|0x80, kai);//显示 开
							else
								play16(80, 4|0x80, guan);//显示 关
							break;
						case 5: 
							play8_num(80, 6|0x80, shedingwendu);
						default: break;
					}
				}
				else if (key_val > 1)//判断数据
				{
					if (state == 1)//判断数据
					{
						Alarm_flag = ~Alarm_flag;
						if (Alarm_flag)
							play16(48, 2|0x80, kai);//显示 开
						else 
							play16(48, 2|0x80, guan);//显示 关
					}
					else if (state == 2)//判断数据
					{
						if (key_val == 3)//判断数据
							alarm.min++;//加1
						else if(key_val == 2)
							alarm.min--;//减1
						if (alarm.min >= 60)//判断数据
							alarm.min = 0;
						else if (alarm.min < 0)//判断数据
							alarm.min = 59;
						play8_num(104, 2|0x80, alarm.min);//闹钟 分 显示 
					}
					else if (state == 3)//判断数据
					{
						if (key_val == 3)//判断数据
							alarm.hour++;//加1
						else if(key_val == 2)
							alarm.hour--;//减1
						if (alarm.hour >= 24)//判断数据
							alarm.hour = 0;
						else if (alarm.hour < 0)//判断数据
							alarm.hour = 23;
						play8_num(80, 2|0x80, alarm.hour);//闹钟 时 显示 
					}
					else if (state == 4)			//判断数据
					{
						Clock_flag = ~Clock_flag;
						if (Clock_flag)				//判断数据
							play16(80, 4|0x80, kai);//显示 开
						else
							play16(80, 4|0x80, guan);//显示 关
					}
					else if (state == 5)			//判断数据
					{
						if (key_val == 3)//判断数据
							shedingwendu++;//加1
						else if(key_val == 2)
							shedingwendu--;//减1
						if (shedingwendu >= 99)//判断数据
							shedingwendu = 99;
						else if (shedingwendu < 0)//判断数据
							shedingwendu = 99;
						play8_num(80, 6|0x80, shedingwendu);
					}
					else
					{
						break;	//退出
					}
				}
				if (state == 0)	//状态为0退出
				break;			//状态为0退出
			}
			if (state == 0)		//状态为0退出
				break;			//状态为0退出
		}
	}
}

主函数

void main()
{
	uint8 key_val;
	read_18B20();    //初始DS18B20
	Delay_nms(1000);//延时1S,等待18B20工作正常
	
	LCD_init();           //初始化液晶
	clear12864();         //清屏幕
	information();
	delayms(3000);
	LCD_init();           //初始化液晶
	clear12864();         //清屏幕
	main_frame();         //显示主界面框架
	main_show(1);         //刷新1次
    read_18B20();    				//读温度
	play8_num(96, 6,zhengshu);   //显示温度

	while(1)
	{ 
		key_val = scan_key();
		if (key_val == 1)         //K1?
		{
			main_set();           //设置主界面
		}

		else if (key_val == 3)    //K3?
		{
			clear12864();         //清屏幕
			alarm_show();		  //闹钟画面
			clear12864();         //清屏幕
			main_show(1);		  //主界面
		}
		else
		{
			read_time((uint8 *)&time);			//读取时间
			main_show(0);						//显示主界面
			if((time.sec%2)==0){read_18B20();}  //每隔2S采集一次
		}
		
		/*********************闹钟*********************/
		if (Alarm_flag)//如果闹钟标志有 执行下面的
		{
			if ((flag == 0) && (alarm.hour == time.hour) && (alarm.min == time.min))//判断条件是否满足
			{
				flag = 1;
				clear12864();     //清屏幕
				alarm_show();     //闹钟
		    	PlayMusic();    //播放音乐
				PlayMusic();      //播放音乐
				clear12864();     //清屏幕  
				main_show(1);     //显示主界面
			}
		}
	  if(zhengshu>shedingwendu)
		{
			BEEP = ~BEEP;			
		}
	}
}

5 测试结果

在硬件电路焊接和软件程序设计分别完成的基础之上,进行软硬件的结合与调试。通过下载将在电脑上已完成的程序下载到单片机芯片中。在调试中发现软件中存在的问题,及时解决问题,确保系统能正常工作并达到设计要求。通过反复的调试与实验,可以证明该系统能够较好地完成设计所需的基本要求。即能够正确的显示万年历。
在完成软件系统时,刚开始我是用的是12M的晶振,所有器件正常,后来我换了11.0592M的晶振,结果温度就不正常了,经过认真排查才发现是由于DS18B20在数据读取时,对时间要求很精确,由于晶振的不同造成了读数据的错误,经过这次调试,让我更清晰的认识到了时序对元器件的重要性。在设计中,因为考虑到闹钟定时功能,我们希望我们设置的闹钟时刻不会因为系统的掉电而丢失,考虑到DS1302是有锂电池作为电源的,不会因为主系统掉电丢失内部数据,所以我们将闹钟的定时时刻放到了DS1302内的空余寄存器里面,像这些灵活的技巧就需要我们认真的阅读元件的数据手册,从中索取对自己有用的信息。
经测试,本作品完成设计所有要求。经过万年历的设计,让我学到了很多,让我认识到了学习基础知识的重要性,当设计完整的系统时,要考虑到硬件和软件两者的结合,有时硬件的不足,我们可以用软件程序来弥补,从而节约硬件成本,在设计软件程序时要模块化,可以提高程序的可读性。

致 谢

在论文即将完成之际,我要特别感谢我的指导老师对我的热情关怀和细心指导。在我做毕业设计的整个过程中,老师都以她最大的可能来帮助我,教导我,跟着老师做毕业设计,我学会了好多东西,这些都对我未来的工作和生活产生重大的影响。她不仅仅是我们学术上的良师,更是生活中的益友。她以一个教育工作者热忱的心胸不厌其烦地指导着我们,教育者我们,使我们不仅学到了扎实的专业知识,更学到了做人的道理。她孜孜不倦悉心细致的教诲和严谨治学一丝不苟的工作作风使我永远都不能忘记。在此,特向她表示真诚的感谢。祝老师身体健康,桃李满天下。
同时,在我三年的大学生活中,也得到了很多老师、同学、朋友的支持和帮助,在此一并表示感谢,正是由于你们,才使我的大学生活更加丰富多彩,感谢你们。

参考文献

[1] 李群芳,肖看.单片机原理、接口及应用.北京:清华大学出版社,2007
[2] 谭浩强.C语言程序设计.北京:清华大学出版社,2006
[3] 张义和,王敏男,许宏昌等.例说51单片机.北京:人民邮电出版社,2008
[4] 刘坤,宋戈,赵红波等.51单片机C语言应用技术开发技术大全.北京:人民邮电出版社,2008
[5] 白延敏.51单片机典型系统开发实例精讲.北京:电子工业出版社,2009
[6] 周丽娜.Protel99SE电路设计技术.北京:中国铁道出版社,2009
[7] 王为青,程国钢.单片机Keil C×51应用开发技术.北京:人民邮电出版社,2007
[8] 江志红.51单片机技术与应用系统开发案例精选.北京:清华大学出版社,2009
[9] Muhammad Ali Mazidi,Janice Gillispie,Rolin Mckinlay.The 8051 Microcontroller and Embedded Systems:Using Assembly and C,Second Edition.Pearson Education,2006
[10] U. Tietze Ch. Schenk. Electronic Circuits. Handbook for Design and Application, Berlin, New York: Springer-Verlag,2005

创作不易,分享更需勇气! 喜欢的可以给点个 ❤赞❤ 吗?欢迎评论区留言交流~

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页