电子万年历是一种非常广泛日常计时工具,给人们的带来了很大的方便,在社会上越来越流行。 它可以对年、月、日、时、分、秒进行计时,采用直观的数字显示,可以同时显示年月日时分秒和温度等信息,还有时间校准、闹钟等功能。该电子万年历主要采用STC89C52单片机作为主控核心,用DS1302时钟芯片作为时钟、液晶12864显示屏显示。STC89C52单片机是由宏晶公司推出的,功耗小,电压可选用4~6V电压供电;DS1302时钟芯片是美国DALLAS公司推出的具有细电流充电功能的低功耗实时时钟芯片,它可以对年、月、日、星期、时、分、秒进行计时,还具有闰年补偿等多种功能,而且DS1302的使用寿命长,误差小;数字显示是采用的12864液晶显示屏来显示,可以同时显示年、月、日、星期、时、分、秒和温度等信息。此外,该电子万年历还具有时间校准等功能。
2.1 功能要求
本万年历能动态显示年、月、日、星期、小时、分钟、秒、温度。
2.2 方案论证
2.2.1技术可行性
方案一:采用传统的51单片机作为整个电路的控制核心。单片机算术运算功能强,软件编程灵活、自由度大,可用软件编程实现各种算法和逻辑控制,并且由于其功耗低、体积小、技术成熟和成本低等优点,使其在各个领域应用广泛。
方案二:采用FTC10F04单片机,还带有非易失性Flash程序存储器。它是一种高性能、低功耗的8位CMOS微处理芯片,市场应用最多。其主要特点如下:8KB Flash ROM,可以擦除1000次以上,数据保存10年。
由于方案一已能满足设计要求,故选用方案一。
2.2.3显示模块的选择
方案一:使用液晶显示屏显示时间数字。液晶显示屏(LCD)具有轻薄短小、低耗电量、无辐射危险,平面直角显示以及影象稳定不闪烁等优势,可视面积大,画面效果好,分辨率高,抗干扰能力强等特点。液晶显示屏分两种,一种是以点阵的模式显示各种符号,需要利用控制芯片创建字符库,编程工作量大,控制器的资源占用较多,其成本也偏高。在使用时,不能有静电干扰,否则易烧坏液晶的显示芯片,不易维护,另外一种是带字库版本,省去了编程字符库的过程操作简单便捷便于使用。
方案二:使用传统的LED数码管显示。 数码管具有:低能耗、低损耗、低压、寿命长、耐老化、防晒、防潮、防火、防高(低)温,对外界环境要求低,易于维护,同时其精度比较高,称重轻,精确可靠,操作简单。数码管采用BCD编码显示数字,程序编译容易,资源占用较少。
根据以上的论述,虽然LED的各方面性能比较好,但是目前在已有的原件中有一块带字库的LCD,省去了创建字库符的编程且控制相对简单,故采用方案一。
2.2.4键盘模块的选择
在对日期和时间进行切换,对日期和时间进行调节校准过程中,系统需要,产生激励电流,因此需要用按键。
方案一:使用独立式键盘。独立式键盘是指直接用I/O口线构成的单个按键电路。独立式按键电路配置灵活,软件结构简单。
方案二:使用矩阵式键盘。矩阵式键盘是由行线和列线组成,按键位于行、列的交叉点上,行线、列线分别连接到按键开关的两端。其特点是简单且不增加成本,这种键盘适合按键数量较多的场合。
根据以上的论述,因本系统需要的按键不多,上下左右调节四个按键、功能控制两个按键,要求简单。所以采用方案一独立式键盘。
2.2.5 总体方案论证与选择
3 硬件电路设计及概述
3.1 系统的结构设计
本设计采用的是宏晶公司的STC89C52单片机为核心,通过单片机与时钟芯片DS1302通信来对时间的读写,保证时钟时间的稳定性,并带有内部电源模块,使系统断电时时钟数据不会丢失,以DS18B20温度感应芯片作为温度电路的核心,向单片机发出获取到的温度数据,并通过LCD12864将时间、显示出来,并可以通过按键调整时间日期和闹钟时间。
图3.1
3.2 单片机的系统结构
3.2.1 单片机的内部结构
一个基本的MCS-51单片机通常包括:中央处理器、ROM、
RAM、定时/计数器和I/O口等各功能部件,各个功能由内部的总线连接起来,从而实现数据通信。其内部框图如图3.2所示
图3.2
3.2.2 单片机的引脚
常见的51单片机中一般采用双列直插(DIP)封装,共40个引脚。图3.3为引脚排列图。其中的40个引脚大致可以分为4类:电源、时钟、控制和I/O引脚。
3.2.3 最小系统的介绍
单片机最小系统主要由电源、复位、振荡电路以及扩展部分等部分组成。最小系统原理图如图3.4所示。
图3.4
3.3 按键开关去抖动
键盘在单片机系统中是一个很重要的部件。为了输入数据、查询和控制系统的工作状态,都要用到键盘,键盘是人工干预计算机的主要手段。
键盘可分为编码和非编码键盘两种。编码键盘采用硬件线线路来实现键盘编码,每按下一个键,键盘能自动生成按键代码,键数较多,而且还具有去抖动功能。这种键盘使用方便,但硬件较复杂,PC机所用的键盘就属于这种。非编码键盘仅提供按键开关工作状态,其他工作由软件完成,这种键盘键数较少,硬件简单,一般在单片机应用系统中广泛使用。此处主要介绍该类非编码键盘及其与单片机的接口。
按键开关在电路中的连接如图3.5(a)所示。按键未按下时,A点电位为高电平5V;按键按下时,A点电位为低电平。A点电位就用于向CPU传递按键的开关状态。但是由于按键的结构为机械弹性开关,在按键按下和断开时,触点在闭合和断开瞬间还会接触不稳定,引起A点电平不稳定,如图3.5(b)所示,键盘的抖动时间一般为5~10ms,抖动现象会引起CPU对一次键操作进行多次处理,从而可能产生错误。因此必须设法消除抖动的不良后果。
消除抖动的不良后果的方法有硬、软件两种。
为了节省硬件,通常在单片机系统中,一般不采用硬件方法消除键的抖动,而是用软件消除抖动的方法。根据抖动特性,在第一次检测到按键按下后,执行一段延时5~10ms让前延抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认真正有键按下。当检测到按键释放后,也要给5~10ms的延时,待后延抖动消失后才转入该键处理程序。
3.4 键盘扫描控制方式
在单片机应用系统中,对键盘的处理工作仅是CPU工作内容的一部分,CPU还要进行数据处理、显示和其他输入输出操作,因此键盘处理工作既不能占用CPU太多时间,又需要CPU对键盘操作及时作出响应。CPU对键盘处理控制的工作方式有以下几种:
程序控制扫描方式是在CPU工作空余,调用键盘扫描子程序,响应键输入信号要求。
(2) 定时控制扫描方式
定时控制扫描方式是利用定时/计数器每隔一段时间产生定时中断,CPU响应中断后对键盘进行扫描,并在有键闭合时转入该键的功能子程序。
(3)中断控制扫描方式
中断控制扫描方式是利用外部中断源,响应输入信号。当无按键按下时,CPU执行正常工作程序。当有按键按下时,CPU立即产生中断。在中断服务子程序中扫描键盘,判断是哪一个键被按下,然后执行该键的功能子程序。这种控制方式克服了前两种控制方式可能产生的空扫描和不能及时响应键输入的缺点,既能及时处理键输入,又能提高CPU运行效率,但要占用一个宝贵的中断资源。
因为时间的走时需要不断的进行扫描,并且在任何时刻都可能进行时间调整,所以需要按键在任何时候都能控制整个系统,故采用中断扫描方式。
3.5 DS1302工作方式简介及数据操作原理
DS1302可以对年、月、日、星期、时、分、秒进行计时, 且具有闰年补偿功能, 工作电压宽达2.5~5.5V。采用三线接口与CPU进行同步通信, 并可采用突发方式一次传送多个字节的时钟信号或RAM数据。DS1302内部有一个33x8的用于临时性存放数据的RAM寄存器。DS1302是的DS1202升级产品, 与DS1202兼容, 但增加了主电源/后背电源双电源引脚, 同时提供了对后背电源进行涓细电流充电的能力。
file:///C:/Users/13909/AppData/Local/Temp/msohtmlclip1/01/clip_image014.jpg
图3.6
DS1302在任何数据传送时必须先初始化,把RST脚置为高电平,然后把8位地址和命令字装入移位寄存器,数据在SCLK的上升沿被访问到。在开始8个时钟周期,把命令字节装入移位寄存器后,另外的时钟周期在读操作时输出数据,在写操作时写入数据。时钟脉冲的个数在单字节方式下为8+8,在多字节方式下为8+字节数,最大可达248字节数。如果在传送过程中置RST脚为低电平,则会终止本次数据传送,并且I/O引脚变为高阻态。上电运行时,在Vcc≥2.5V之前,RST脚必须保持低电平。只有在SCLK为低电平时,才能将RST置为高电平。
DS1302的控制字如图所示。控制字节的最高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。位6如果为0,则表示存取日历时钟数据;为1则表示存取RAM数据。位5~1(A4~A0)指示操作单元的地址。最低有效位(位0)如果为0,则表示药进行写操作;为1表示进行读操作。控制字节总是从最低位开始输入/输出。
为了提高对32个地址寻址能力(地址/命令位1~5=逻辑1),可以把时钟/日历或RAM寄存器规定为多字节(burst)方式。位6规定时钟或RAM,而位0规定读或写。在时钟/日历寄存器中的地址9~31或RAM寄存器中的地址31不能存储数据。在多字节方式下,读或写从地址0的位0开始。必须按数据传送的次序写最先的8个寄存器。但是,当以多字节方式写RAM时,为了传送数据不必写所有的31字节,不管是否写了全部31字节,所写的每一字节都将传送至RAM。
DS1302共有12个寄存器,其中有7个寄存器与日历、时钟相关,存放的数据位为BCD码形式。其日历、时间寄存器及其控制字如下表所示,其中奇数为读操作,偶数为写操作。
图3.7
时钟暂停:秒寄存器的位7定义位时钟暂停位。当它为1时,DS1302停止振荡,进入低功耗的备份方式,通常在对DS1302进行写操作时(如进入时钟调整程序),停止振荡。当它为0时,时钟将开始启动。
AM-PM/12-24小时方式:小时寄存器的位7定义为12或24小时方式选择位。它为高电平时,选择12小时方式。在此方式下,位5为第二个10小时位(20~23h)。
DS1302的晶振选用32768Hz,电容推荐值为6pF。因为振荡频率较低,也可以不接电容,对计时精度影响不大。
3.6 DS18B20数据原理
图3.8
DS18B20测温原理如图3.8所示。图中低温度系数晶振的振荡频率受温度影响很小,用于产生固定频率的脉冲信号送给计数器1。高温度系数晶振随温度变化其振荡率明显改变,所产生的信号作为计数器2的脉冲输入。计数器1和温度寄存器被预置在-55℃所对应的一个基数值。计数器1对低温度系数晶振产生的脉冲信号进行减法计数,当计数器1的预置值减到0时,温度寄存器的值将加1,计数器1的预置将重新被装入,计数器1重新开始对低温度系数晶振产生的脉冲信号进行计数,如此循环直到计数器2计数到0时,停止温度寄存器值的累加,此时温度寄存器中的数值即为所测温度。斜率累加器用于补偿和修正测温过程中的非线性,其输出用于修正计数器1的预置值。
图3.9
3.7 系统模块介绍
4.1.1 主程序流程
4.1.2 DS1302与按键控制流程
图4.2
4.2 程序编写
采用Keil uVision4编译软件,具体程序编写说明参照附录1。
附录1:程序代码
#include<reg51.h>
#include<intrins.h>
#include<LCD1602.h>
#include<DS18b20.h>
#include<DS1302.h>
#ifndef __DS1302_H__
#define __DS1302_H__
#include<reg51.h>
#include<intrins.h> //包含_nop_()函数定义的头文件
#define uchar unsignedchar
#define uint unsignedint
#ifndef __LCD1602_H__
#define __LCD1602_H__
#include<reg51.H>
#include<intrins.h> //包含_nop_()函数定义的头文件
#define uint unsignedint
#define uchar unsignedchar
#define OUT P0
#define yh 0x80
#define er0x80+0x40
//液晶屏的与C51之间的引脚连接定义
#ifndef __DS18b20_H__
#define __DS18b20_H__
#include<reg51.h>
#include<intrins.h>
#define uchar unsignedchar
#define uint unsignedint
uint tvalue;
uchar tflag;
sbit DQ=P3^3;
sbit rs=P2^0;
sbit en=P2^2;
sbit rw=P2^1; //如果硬件上rw接地,就不用写这句和后面的rw=0了
sbit BF=P0^7;
void delay_18B20(unsigned int i)
{
while(i--);
}
void ds1820rst()
{ unsigned char x=0;
DQ = 1;
delay_18B20(4);
DQ = 0;
delay_18B20(100);
DQ = 1;
delay_18B20(40);
}
uchar ds1820rd()
{ unsigned char i=0;
unsigned char dat = 0;
for (i=8;i>0;i--)
{ DQ = 0;
dat>>=1;
可调用**************/
uchar BusyTest(void)
{
bit result;
rs=0; //根据规定,RS为低电平,RW为高电平时,可以读状态
rw=1;
en=1; //E=1,才允许读写
_nop_(); //空操作
_nop_();
_nop_();
_nop_(); //空操作四个机器周期,给硬件反应时间
result=BF; //将忙碌标志电平赋给result
en=0;
return result;
}
/********液晶写入指令函数与写入数据函数,以后可调用**************/
write_1602com(uchar com) //****液晶写入指令函数****
{
while(BusyTest()==1);
rs=0; //数据/指令选择置为指令
rw=0; //读写选择置为写
OUT=com; //送入数据
delay(1);
en=1; //拉高使能端,为制造有效的下降沿做准备
delay(1);
en=0; //en由高变低,产生下降沿,液晶执行命令
}
voidwrite_1602dat(uchar dat) //***液晶写入数据函数****
{
while(BusyTest()==1);
rs=1; //数据/指令选择置为数据
rw=0; //读写选择置为写
OUT=dat; //送入数据
delay(1);
en=1; //en置高电平,为制造下降沿做准备
delay(1);
en=0; //en由高变低,产生下降沿,液晶执行命令
}
void lcd_init() //***液晶初始化函数****
{
write_1602com(0x38); //设置液晶工作模式,意思:16*2行显示,5*7点阵,8位数据
write_1602com(0x0c); //开显示不显示光标
write_1602com(0x06); //整屏不移动,光标自动右移
write_1602com(0x01); //清显示
}
#endif
//DS1302时钟芯片与C51之间的引脚连接定义
sbit IO=P1^7;
sbit SCLK=P1^6;
sbit RST=P1^5;
sbit ACC0=ACC^0;
sbit ACC7=ACC^7;
/***************DS1302有关子函数********************/
void write_byte(uchardat)//写一个字节
{ uchar a;
ACC=dat;
RST=1;
for(a=8;a>0;a--)
{
IO=ACC0;
SCLK=0;
SCLK=1;
ACC=ACC>>1;
}
}
uchar read_byte() //读一个字节
{ uchar a;
RST=1;
for(a=8;a>0;a--)
{
ACC7=IO;
SCLK=1;
SCLK=0;
ACC=ACC>>1;
}
return (ACC);
}
//----------------------------------------
void write_1302(ucharadd,uchar dat) //向1302芯片写函数,指定写入地址,数据
{
RST=0;
SCLK=0;
RST=1;
write_byte(add);
write_byte(dat);
SCLK=1;
RST=0;
}
uchar read_1302(ucharadd) //从1302读数据函数,指定读取数据来源地址
{
uchar temp;
RST=0;
SCLK=0;
RST=1;
write_byte(add);
temp=read_byte();
SCLK=1;
RST=0;
return(temp);
}
uchar BCD_Decimal(ucharbcd) //BCD码转十进制函数,输入BCD,返回十进制
{
uchar Decimal;
Decimal=bcd>>4;
return(Decimal=Decimal*10+(bcd&=0x0F));
}
//--------------------------------------
void ds1302_init() //1302芯片初始化子函数(2010-01-07,12:00:00,week4)
{
RST=0;
SCLK=0;
write_1302(0x8e,0x00); //允许写,禁止写保护
write_1302(0x80,0x00); //向DS1302内写秒寄存器80H写入初始秒数据00
write_1302(0x82,0x00); //向DS1302内写分寄存器82H写入初始分数据00
write_1302(0x84,0x12); //向DS1302内写小时寄存器84H写入初始小时数据12
write_1302(0x8a,0x05); //向DS1302内写周寄存器8aH写入初始周数据4
write_1302(0x86,0x18); //向DS1302内写日期寄存器86H写入初始日期数据07
write_1302(0x88,0x03); //向DS1302内写月份寄存器88H写入初始月份数据01
write_1302(0x8c,0x16); //向DS1302内写年份寄存器8cH写入初始年份数据10
write_1302(0x8e,0x80); //打开写保护
}
#endif
uchar a,miao,shi,fen,ri,yue,nian,week,flag,key1n,temp;
void write_sfm(uchar add,uchar dat);
void write_week(uchar week);
void write_nyr(uchar add,uchar dat);
sbit key1=P1^1;
sbit key2=P1^2;
sbit key3=P1^3;
sbit buzzer=P1^3;
uchar code tab1[]={"20 - - "};
uchar code tab2[]={" : : "};
void write_temp(uchar add,uchar dat)
{
uchar gw,sw;
gw=dat%10;
sw=dat/10;
write_1602com(er+add);
if(tflag==0)
write_1602dat(' ');
else
write_1602dat('-');
if(sw==0)
{write_1602dat(' ');write_1602dat(0x30+gw);}
else
{write_1602dat(0x30+sw);
write_1602dat(0x30+gw);}
write_1602dat(0xdf);
write_1602dat(0x43);
}
void write_sfm(uchar add,uchar dat)
{
uchar gw,sw;
gw=dat%10;
sw=dat/10;
write_1602com(er+add);
write_1602dat(0x30+sw);
write_1602dat(0x30+gw);
}
void write_nyr(uchar add,uchar dat)
{
uchar gw,sw;
gw=dat%10;
sw=dat/10;
write_1602com(yh+add);
write_1602dat(0x30+sw);
write_1602dat(0x30+gw);
}
void write_week(uchar week)
{
write_1602com(yh+0x0c);
switch(week)
{
case 1:write_1602dat('M');
write_1602dat('O');
write_1602dat('N');
break;
case 2:write_1602dat('T');
write_1602dat('U');
write_1602dat('E');
break;
case 3:write_1602dat('W');
write_1602dat('E');
write_1602dat('D');
break;
case 4:write_1602dat('T');
write_1602dat('H');
write_1602dat('U');
break;
case 5:write_1602dat('F');
write_1602dat('R');
write_1602dat('I');
break;
case 6:write_1602dat('S');
write_1602dat('T');
write_1602dat('R');
break;
case 7:write_1602dat('S');
write_1602dat('U');
write_1602dat('N');
break;
}
}
void keyscan()
{
if(key1==0)
{
delay(9);
if(key1==0)
{
buzzer=0;
delay(20);
buzzer=1;
while(!key1);
key1n++;
if(key1n==9)
key1n=1;
switch(key1n)
{
case 1: TR0=0;
write_1602com(er+0x09);
write_1602com(0x0f);
temp=(miao)/10*16+(miao)%10;
write_1302(0x8e,0x00);
write_1302(0x80,0x80|temp);
write_1302(0x8e,0x80);
break;
case 2: write_1602com(er+6);
break;
case 3:write_1602com(er+3);
break;
case 4:write_1602com(yh+0x0e);
break;
case 5:write_1602com(yh+0x0a);
break;
case 6:write_1602com(yh+0x07);
break;
case 7:write_1602com(yh+0x04);
break;
case 8:
write_1602com(0x0c);
TR0=1;
temp=(miao)/10*16+(miao)%10;
write_1302(0x8e,0x00);
write_1302(0x80,0x00|temp);
write_1302(0x8e,0x80);
break;
}
}
}
if(key1n!=0)
{
if(key2==0)
{
delay(10);
if(key2==0)
{
buzzer=0;
delay(20);
buzzer=1;
while(!key2);
switch(key1n)
{
case 1:miao++;
if(miao==60)
miao=0;
write_sfm(0x08,miao);
temp=(miao)/10*16+(miao)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(er+0x09);
break;
case 2:fen++;
if(fen==60)
fen=0;
write_sfm(0x05,fen);
temp=(fen)/10*16+(fen)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(er+6);
break;
case 3:shi++;
if(shi==24)
shi=0;
write_sfm(2,shi);
temp=(shi)/10*16+(shi)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+0x0e);
break;
case 4:week++;
if(week==8)
week=1;
write_1602com(yh+0x0C);
write_week(week);
temp=(week)/10*16+(week)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+0x0e);
break;
case 5:ri++;
if(ri==32)
ri=1;
write_nyr(9,ri);
temp=(ri)/10*16+(ri)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+10);
break;
case 6:yue++;
if(yue==13)
yue=1;
write_nyr(6,yue);
temp=(yue)/10*16+(yue)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+7);
break;
case 7:nian++;
if(nian==100)
nian=0;
write_nyr(3,nian);
temp=(nian)/10*16+(nian)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+4);
break;
}
}
}
}
if(key3==0)
{
delay(10);
if(key3==0)
{
buzzer=0;
delay(20);
buzzer=1;
while(!key3);
switch(key1n)
{
case 1:miao--;
if(miao==-1)
miao=59;
write_sfm(0x08,miao);
temp=(miao)/10*16+(miao)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(er+0x09);
break;
case 2:fen--;
if(fen==-1)
fen=59;
write_sfm(5,fen);
temp=(fen)/10*16+(fen)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(er+6);
break;
case 3:shi--;
if(shi==-1)
fen=23;
write_sfm(2,shi);
temp=(shi)/10*16+(shi)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(er+3);
break;
case 4:week--;
if(week==0)
week=7;
write_1602com(yh+0x0C);
write_week(week);
temp=(week)/10*16+(week)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+0x0e);
break;
case 5:ri--;
if(ri==-1)
ri=31;
write_nyr(9,ri);
temp=(ri)/10*16+(ri)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+10);
break;
case 6:ri--;
if(yue==0)
yue=12;
write_nyr(6,yue);
temp=(yue)/10*16+(yue)%10;
write_1302(0x8e,0x00);
write_1302(0x80,temp);
write_1302(0x8e,0x80);
write_1602com(yh+7);
break;
write_nyr(9,ri);
write_nyr(6,yue);
write_nyr(3,nian);
write_week(week);
}
|