资源描述
万年历课程设计(完整版)
精品资料
基于51单片机的万年历的设计
学生姓名
学 号
所在专业
电子信息工程
所在班级
电子1111
小组成员
指导教师
完成日期
基于51单片机的万年历的设计
1 需求分析
1.1 “需“的分析
万年历是一个可以显示当前时间、日历、温度信息以及设置闹钟报警的应用系统,其具有以下功能:
(1)可以显示当前的时间信息;
(2)可以显示当前的温度;
(3)可以手动修改时间;
(4)可以设置闹钟,并且达到设置的时间点发出音响信号。
1.2 “求“的分析
设计万年历,需要考虑以下几方面的内容:
(1)如何获取当前的时间信息,这些时间信息包括时、分、秒、年、月、日、和星期;
(2)如何获得当前的温度信息,精确到1°C即可;
(3)提供必要的用户输入设置通道;
(4)提供相应的显示和报警部件;
(5)需要写出合适的软件应用代码。
2 方案设计与论证
2.1 时间获取方法的方案设计与论证
方案一:
使用单片机的内部定时器进行定时,使用软件算法来计算当前的时间信息。此种方案虽然可以不使用时钟芯片,节约成本,但是时间的精准度一般,软件代码复杂。
方案二:
采用外部扩展实时时钟芯片DS12C887的方式来获取相应的时间信息。
DS12C887是一种高性能的芯片,能自动产生年、月、日、时、分、秒等信息,并有闰年修正功能。采用双电源供电方式,可设置备用电源充电方式,提供了对后备电源进行涓细电流的充电的能力,在能满足精度要求的同时价格也比较便宜,性价比较高。
所以采用方案二。
2.2 显示模块的方案设计与论证
方案一:采用LED数码管动态扫描。LED数码管价格适中,但只能显示数字和简单的字符,且其的接口线少。
方案二:
采用点阵式数码管显示。点阵式数码管是由八行八列的发光二极管组成,并不太适合于对数字跟字母进行显示,其主要用于汉字的显示。
方案三:
采用LED液晶显示屏。液晶显示屏的显示功能强大,可显示大量文字,图形,显示多样,清晰可见,虽然价格稍微贵了点,但其接口线多。
所以采用方案三。
2.3 温度采集的方案设计与论证
方案一:
使用PT铂电阻采集。PT铂电阻根据温度变化的只是其电阻值,在实际使用过程,需要额外的辅助器件将其转化为电压信号,并且通过调整后送到模/数字转换器件才能让51单片机进行处理。
方案二:使用数字温度传感器采集。温度传感器虽然、精度采样速度等性能比不上PT铂电阻,但是其没有额外的附加器件,且能满足系统对采集精度的要求。
所以采用数字温度传感器。
3 硬件设计
3.1 万年历的硬件模块
显示模块
51
单
片
机
声音报警模块
用户输入模块
时钟日历模块
温度传感器模块
图3-1 万年历的硬件模块
3.2 万年历的电路
图3-2 万年历的电路
3.3硬件基础
DS1302的硬件基础:
(1) 时钟芯片DS1302的工作原理:
DS1302在每次进行读、写程序前都必须初始化,先把SCLK端置 “0”,接着把RST端置“1”,最后才给予SCLK脉冲。图5为DS1302的控制字的位7必须置1,若为0则不能把对DS1302进行读写数据。对于位6,若对程序进行读/写时RAM=1,对时间进行读/写时,CK=0。位1至位5指操作单元的地址。位0是读/写操作位,进行读操作时,该位为1;该位为0则表示进行的是写操作。控制字节总是从最低位开始输入/输出的。DS1302的日历、时间寄存器内容:“CH”是时钟暂停标志位,当该位为1时,时钟振荡器停止,DS1302处于低功耗状态;当该位为0时,时钟开始运行。“WP”是写保护位,在任何的对时钟和RAM的写操作之前,WP必须为0。当“WP”为1时,写保护位防止对任一寄存器的写操作。
(2) DS1302的控制字节
DS1302的控制字如表-1所示。控制字节的高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入DS1302中,位6如果0,则表示存取日历时钟数据,为1表示存取RAM数据;位5至位1指示操作单元的地址;最低有效位(位0)如为0表示要进行写操作,为1表示进行读操作,控制字节总是从最低位开始输出。
表3-1 DS1302的控制字格式
1
RAM
/CK
A4
A3
A2
A1
A0
RD
/WR
(3) 数据输入输出(I/O)
在控制指令字输入后的下一个SCLK时钟的上升沿时,数据被写入DS1302,数据输入从低位即位0开始。同样,在紧跟8位的控制指令字后的下一个SCLK脉冲的下降沿读出DS1302的数据,读出数据时从低位0位到高位7。如下图3-3所示:
图3-3 DS1302读/写时序图
(4) DS1302的寄存器
DS1302有12个寄存器,其中有7个寄存器与日历、时钟相关,存放的数据位为BCD码形式,其日历、时间寄存器及其控制字见表3-2。
表3-2 DS1302的日历、时间寄存器
此外,DS1302 还有年份寄存器、控制寄存器、充电寄存器、时钟突发寄存器及与RAM相关的寄存器等。时钟突发寄存器可一次性顺序读写除充电寄存器外的所有寄存器内容。 DS1302与RAM相关的寄存器分为两类:一类是单个RAM单元,共31个,每个单元组态为一个8位的字节,其命令控制字为C0H~FDH,其中奇数为读操作,偶数为写操作;另一类为突发方式下的RAM寄存器,此方式下可一次性读写所有的RAM的31个字节,命令控制字为FEH(写)、FFH(读)。
1602液晶的硬件基础 :
1602通常有14条引脚线或16条引脚线的LCD,多出来的2条线是背光电源线VCC(15脚)和地线GND(16脚),其控制原理与14脚的LCD完全一样,引脚定义如表3-3所示:
表3-3 1602的引脚及其对应的功能
1602支持的一系列指令:
(1)清屏指令:
表3-4 清屏指令
(2)归零指令:
表3-5 归零指令
(3)输入方式选择指令:
表3-6 输入方式选择指令
(4)显示开关控制指令:
表3-7显示开关控制指令
(5) 光标和画面移动指令:
表3-8光标和画面移动指令
(6)功能设定指令:
表3-9功能设定指令
(7) CGRAM设置指令:
表3-10 CGRAM设置指令
(8)DDRAM设置指令:
表3-11 DDRAM设置指令
(9)读BF和AC指令:
表3-12读BF和AC指令
(10)写数据指令:
表3-13写数据指令
(11)读数据指令:
表3-14读数据指令
4 软件设计系统初始化
设置闹钟
按键被按下
驱动蜂鸣器报警
时钟数据和闹钟信息等
初始化时钟芯片和液晶显示
显示当前的数据和温度数据
读取DS18B20数据并且进行相应的处理
读取DS1302的时钟数据
是
否
是
图4-1 万年历的工作流程
5 总结
5.1 仿真结果及分析
经过多次调试、修改程序,万年历终于在pruteus正常运行起来了。前面由于初次写的代码有较多不完善的地方,所以调试的时候出了很多大的问题,第一次发现大的问题后,团队认真地讨论并修改了程序,后面调试遇到的都是些小问题,无非是打漏或者打错这类问题。
5.2 心得
工程上的问题很多不是一个人的能力可以解决的,所以不能老是想着单干、蛮干,一定要发挥团队的力量,多讨论,讨论往往可以碰撞出思想的火花来。做事的时候一定要静下心来,保持专注,不能老想着快,不然会弄出很多本可以避免的问题,敲代码的时候,为了求快,结果调试的遇到小问题花了可不少时间才解决。本次课程设计,让我知道了要想解决工程上的问题,必须现将问题具体化、模块化,然后再逐个击破,这样才能更有效率地解决整个工程设计再到实物的问题。
附录一:仿真图
附录二:程序
#include<AT89X52.H>
#include<INTRINS.H>
#define TIME (0X10000-50000)
#define FLAG 0XEF //闹钟标志
//引脚连接图
sbit rst=P3^5;
sbit clk=P3^4;
sbit dat=P3^3;
sbit rs=P1^5;
sbit rw=P1^6;
sbit e=P1^7;
sbit DQ=P1^4; //温度输入口
sbit ACC_7=ACC^7;
//全局变量及常量定义
unsigned char i=20,j,time1[16];
unsigned char alarm[2],time2[15],time[3];
unsigned char code Day[]={31,28,31,30,31,30,31,31,30,31,30,31};//12个月的最大日期(非闰年)
//音律表
unsigned int code table1[]={64260,64400,64524,64580,64684,64777,
64820,64898,64968,65030,65058,65110,65157,65178,65217};
//发声部分的延时时间
unsigned char code table2[]={0x82,1,0x81,0xf4,0xd4,0xb4,0xa4,
0x94,0xe2,1,0xe1,0xd4,0xb4,0xc4,0xb4,4,0};
//LCD自建字
unsigned char code tab[]={0x18,0x1b,5,4,4,5,3,0,
0x08,0x0f,0x12,0x0f,0x0a,0x1f,0x02,0x02,//年
0x0f,0x09,0x0f,0x09,0x0f,0x09,0x11,0x00,//月
0x0f,0x09,0x09,0x0f,0x09,0x09,0x0f,0x00};//日
//*******温度小数部分用查表法**********//
unsigned char code ditab[16]={0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x04,0x05,0x06,0x06,0x07,0x08,0x08,0x09,0x09};
//闹钟中用的全局变量
unsigned char th1,tl1;
unsigned char temp_data[2]={0x00,0x00}; // 读出温度暂放
bit flag; //18b20存在标志位
/***********11微秒延时函数**********/
delay(unsigned int t)
{
for(;t>0;t--);
}
/***********18B20复位函数**********/
RST18B20(void)
{
unsigned char i;
DQ=1;_nop_();_nop_();
DQ=0; //
delay(50); // 550us
DQ=1; //
delay(6); // 66us
for(i=0;i<0x30;i++)
{
if(!DQ)
goto d1;
}
flag=0; //清标志位,表示ds1820不存在
DQ=1;
return;
d1: delay(45); //延时500us
flag=1;
DQ=1; //置标志位,表示ds1820存在
}
/**********18B20写命令函数*********/
//向 1-WIRE 总线上写一个字节
void Write1Byte18B20(unsigned char val)
{
unsigned char i;
for (i=8; i>0; i--) //
{
DQ=1;_nop_();_nop_();
DQ=0;_nop_();_nop_();_nop_();_nop_();_nop_();//5us
DQ=val&0x01; //最低位移出
delay(6); //66us
val=val/2; //右移一位
}
DQ=1;
delay(1);
}
/*********18B20读1个字节函数********/
//从总线上读取一个字节
unsigned char Read1Byte18B20(void)
{
unsigned char i;
unsigned char value=0;
for (i=8;i>0;i--)
{
DQ=1;_nop_();_nop_();
value>>=1;
DQ=0; //
_nop_();_nop_();_nop_();_nop_(); //4us
DQ=1;_nop_();_nop_();_nop_();_nop_(); //4us
if(DQ)
value|=0x80;
delay(6); //66us
}
DQ=1;
return(value);
}
/***********读出温度函数**********/
Read18B20()
{
RST18B20(); //总线复位
if(!flag) //判断ds1820是否存在?若ds18b20不存在则返回
return;
//Write1Byte18B20(0xCC); // Skip ROM
//Write1Byte18B20(0x44); // 发转换命令
//delay(70);
Write1Byte18B20(0xCC); //发Skip ROM命令
Write1Byte18B20(0xBE); //发读命令
temp_data[0]=Read1Byte18B20(); //温度低8位
temp_data[1]=Read1Byte18B20(); //温度高8位
RST18B20();
Write1Byte18B20(0xCC); // Skip ROM
Write1Byte18B20(0x44); // 发转换命令
}
/***********温度数据处理函数**********/
DealTempData()
{
unsigned char n=0,m;
if(temp_data[1]>127)//负温度求补码
{
temp_data[1]=(256-temp_data[1]);
temp_data[0]=(256-temp_data[0]);
n=1;
}
time2[13]=ditab[temp_data[0]&0x0f]+'0';
time2[12]='.';
m=((temp_data[0]&0xf0)>>4)|((temp_data[1]&0x0f)<<4);//
if(n)
{
m-=16;
}
time2[9]=m/100+'0';
time2[11]=m%100;
time2[10]=time2[11]/10+'0';
time2[11]=time2[11]%10+'0';
if(time2[9]=='0')//最高位为0时都不显示
{
time2[9]=0x20;
if(n)//负温度时最高位显示"-"
{
time2[9]='-';
}
if(time2[10]=='0')
{
if(n)
{
time2[10]='-';
time2[9]=0x20;
}
else
time2[10]=0x20;
if(time2[11]=='0'&&time2[13]=='0')
time2[11]=time2[12]=0x20;
}
}
}
delay1ms(unsigned char time) //延时1ms
{
unsigned char i,j;
for(i=0;i<time;i++)
{
for(j=0;j<250;j++);
}
}
//使能1602
EnableLCD()
{
rs=0;
rw=0;
e=0;
delay1ms(3);
e=1;
}
//写LCD函数
WriteLCD(unsigned char i)
{
P0=i;
rs=1;
rw=0;
e=0;
delay1ms(2);
e=1;
}
//写LCD内部地址函数
WriteLCDRAM(unsigned char data *address,m)
{
unsigned char i,j;
for(i=0;i<m;i++,address++)
{
j=*address;
WriteLCD(j);
}
}
//LCD显示
LCDSHOW()
{
P0=0XC; //显示器开、光标关
EnableLCD();
P0=0x80; //写入显示起始地址
EnableLCD();
WriteLCDRAM(time1,16);
P0=0xc1; //写入显示起始地址
EnableLCD();
WriteLCDRAM(time2,15);
}
//自建字函数库
DesignHZ()
{
unsigned char i;
P0=0x40;
EnableLCD();
for(i=0;i<32;i++)
{
WriteLCD(tab[i]);
delay1ms(2);
}
}
//写DS1302子程序
WriteDS1302(unsigned char address)
{
unsigned char i;
clk=0;
_nop_();
rst=1;
_nop_();
for(i=0;i<8;i++)
{
dat=address&1;
_nop_();
clk=1;
address>>=1;
clk=0;
}
}
//读DS1302子程序
unsigned char ReadDS1302()
{
unsigned char i,j=0;
for(i=0;i<8;i++)
{
j>>=1;
_nop_();
clk=0;
_nop_();
if(dat)
j|=0x80;
_nop_();
clk=1;
}
return(j);
}
//部分显示数据初始化
TimeInit()
{
time1[1]=time1[13]=time2[8]=time2[9]=time2[10]=0x20,time2[14]=0;
time1[6]=1,time1[9]=2,time1[12]=3,time1[2]='2',time1[3]='0';
time1[14]='W',time2[2]=time2[5]=':';
WriteDS1302(0xc1);
alarm[0]=ReadDS1302();
rst=0;
WriteDS1302(0xc3);
alarm[1]=ReadDS1302();
rst=0;
WriteDS1302(0xc5);
time1[0]=ReadDS1302();
rst=0;
}
//读取时间
ReadTime()
{
unsigned char i,m,n;
WriteDS1302(0x8d); //读取年份
m=ReadDS1302();
rst=0;
time1[4]=m/16+0x30;
time1[5]=m%16+0x30;
WriteDS1302(0x8b); //读取星期
m=ReadDS1302();
rst=0;
time1[15]=m+0x30;
for(i=7,n=0x89;i<11;i+=3,n-=2) //读取月份和日期
{
WriteDS1302(n);
m=ReadDS1302();
rst=0;
time1[i]=m/16+0x30;
time1[i+1]=m%16+0x30;
}
for(m=0,i=0,n=0x85;i<7;i+=3,n-=2,m++) //读取时,分,秒
{
WriteDS1302(n);
time[m]=ReadDS1302();
rst=0;
time2[i]=time[m]/16+0x30;
time2[i+1]=time[m]%16+0x30;
}
}
time0() interrupt 1 using 1
{
i--;
if(i==0)
{
if(j!=0)
j--;
i=20;
}
TH0=TIME/256,TL0=TIME%256;
}
//闹钟部分
intime1() interrupt 3
{
TH1=th1,TL1=tl1;
P3_2=!P3_2;
}
//显示闹钟设置数据
showalarm()
{
unsigned char i,j,a,b,n;
ET1=1;
for(j=0;j<6;j++)
{
i=0;
while(1)
{
a=table2[i];
if(a==0)
break;
b=a&0xf;
a>>=4;
if(a==0)
{
TR1=0;
goto D1;
}
a=((--a)<<1)/2;
TH1=th1=table1[a]/256,TL1=tl1=table1[a]%256;
TR1=1;
D1: do
{
b--;
for(n=0;n<3;n++)
{
ReadTime();
LCDSHOW();
P2=0xf7;
if(P2==0xe7)
{
delay1ms(100);
if(P2==0xe7)
{
TR1=0;
ET1=0;
return;
}
}
}
}while(b!=0);
i++;
}
TR1=0;
}
ET1=0;
}
//根据日期的变动自动调整星期
unsigned char setweek()
{
unsigned char i=5,j,n;
j=(time1[4]&0xf)*10+(time1[5]&0xf);
n=j/4;
i=i+5*n;
n=j%4;
if(n==1)
i+=2;
else if(n==2)
i+=3;
else if(n==3)
i+=4;
j=(time1[7]&0xf)*10+(time1[8]&0xf);
if(j==2)
i+=3;
else if(j==3)
i+=3;
else if(j==4)
i+=6;
else if(j==5)
i+=1;
else if(j==6)
i+=4;
else if(j==7)
i+=6;
else if(j==8)
i+=2;
else if(j==9)
i+=5;
else if(j==11)
i+=3;
else if(j==12)
i+=5;
if(n==0)
if(j>2)
i++;
j=(time1[10]&0xf)*10+(time1[11]&0xf);
i+=j;
i%=7;
if(i==0)
i=7;
return(i);
}
//设置时间
settime()
{
unsigned char i=0x85,year,month,day,n;
time2[6]=time2[7]=0x30,time1[14]=time1[15]=0x20;
LCDSHOW();
while(1)
{
P0=0xe; //显示器开、光标开
EnableLCD();
P0=i; //定光标
EnableLCD();
P2=0xf7;
if(P2!=0XF7)
{
delay1ms(100); //延时0.1s去抖动
if(P2!=0XF7)
{
j=7;
if(P2==0X77)
{
i+=3;
if(i==0x8e)
i=0xc2;
else if(i>0xc5)
i=0x85;
}
else if(P2==0xb7)
{
year=(time1[4]&0xf)*10+(time1[5]&0xf);
month=(time1[7]&0xf)*10+(time1[8]&0xf);
day=(time1[10]&0xf)*10+(time1[11]&0xf);
if(i==0x85)
{
year++;
if(year>99)
year=0;
if((year%4)!=0)
if(month==2&&day==29)
day=28;
}
else if(i==0x88)
{
month++;
if(month>12)
month=1;
if(day>Day[month-1])
{
day=Day[month-1];
if(month==2&&(year%4)==0)
day=29;
}
}
else if(i==0x8b)
{
day++;
if(day>Day[month-1])
{
if(month==2&&(year%4)==0)
{
if(day>29)
day=1;
}
if(month!=2)
day=1;
}
}
else if(i==0xc2)
{
n=(time2[0]&0xf)*10+(time2[1]&0xf);
n++;
if(n>23)
n=0;
time2[0]=n/10+0x30;
time2[1]=n%10+0x30;
}
else
{
n=(time2[3]&0xf)*10+(time2[4]&0xf);
n++;
if(n>59)
n=0;
time2[3]=n/10+0x30;
time2[4]=n%10+0x30;
}
time1[4]=year/10+0x30;
time1[5]=year%10+0x30;
time1[7]=month/10+0x30;
time1[8]=month%10+0x30;
time1[10]=day/10+0x30;
time1[11]=day%10+0x30;
LCDSHOW();
}
else if(P2==0xd7)
{
WriteDS1302(0x8c);
WriteDS1302((time1[4]&0xf)*16+(time1[5]&0xf));
rst=0;
WriteDS1302(0x8a);
WriteDS1302(setweek());
rst=0;
for(i=7,n=0x88;i<11;i+=3,n-=2)
{
WriteDS1302(n);
WriteDS1302((time1[i]&0xf)*16+(time1[i+1]&0xf));
rst=0;
}
for(i=0;i<7;i+=3,n-=2)
{
WriteDS1302(n);
WriteDS1302((time2[i]&0xf)*16+(time2[i+1]&0xf));
rst=0;
}
TR0=0;
time1[14]='W';
return;
}
else
{
TR0=0;
time1[14]='W';
return;
}
}
}
if(j==0)
{
TR0=0;
time1[14]='W';
return;
}
}
}
//设置闹钟
setalarm()
{
unsigned char i,n;
for(i=1;i<16;i++)
{
time1[i]=0x20;
}
time2[0]=alarm[0]/16+0x30;
time2[1]=(alarm[0]&0xf)+0x30;
time2[3]=alarm[1]/16+0x30;
time2[4]=(alarm[1]&0xf)+0x30;
time2[6]=time2[7]=0x30;
LCDSHOW();
i=0xc2;
while(1)
{
P0=0xe; //显示器开、光标开
EnableLCD();
P0=i; //定光标
EnableLCD();
P2=0xf7;
if(P2!=0XF7)
{
delay1ms(100); //延时0.1s去抖动
if(P2!=0XF7)
{
j=7;
if(P2==0X77)
{
i+=3;
if(i>0xc5)
i=0xc2;
}
else if(P2==0xb7)
{
if(i==0xc2)
{
n=(time2[0]&0xf)*10+(time2[1]&0xf);
n++;
if(n>23)
n=0;
time2[0]=n/10+0x30;
time2[1]=n%10+0x30;
}
else
{
n=(time2[3]&0xf)*10+(time2[4]&0xf);
n++;
if(n>59)
n=0;
time2[3]=n/10+0x30;
time2[4]=n%10+0x30;
}
LCDSHOW();
}
else if(P2==0xd7)
{
WriteDS1302(0xc0);
WriteDS1302((time2[0]&0xf)*16+(time2[1]&0xf));
rst=0;
WriteDS1302(0xc2);
WriteDS1302((time2[3]&0xf)*16+(time2[4]&0xf));
rst=0;
time1[0]=FLAG;
WriteDS1302(0xc4);
WriteDS1302(time1[0]);
rst=0;
TR0=0;
TimeInit();
return;
}
else
{
TR0=0;
TimeInit();
return;
}
}
}
if(j==0)
{
TR0=0;
TimeInit();
return;
}
}
}
main()
{
IE=0X82;
TMOD=0x11;
WriteDS1302(0x8E); //禁止写保护
WriteDS1302(0);
rst=0;
P0=1; //清屏并光标复位
EnableLCD();
P0=0X38; //设置显示模式:8位2行5x7点阵
EnableLCD();
P0=6; //文字不动,光标自动右移
EnableLCD();
DesignHZ(); //自建字
TimeInit();
while(1)
{
ReadTime(); //读取时间
Read18B20(); //读出18B20温度数据
DealTempData(); //处理温度数据
LCDSHOW(); //显示时间
if(time1[0]!=0x20)
if(time[0]==alarm[0])
if(time[1]==alarm[1])
if(time[2]==0)
showalarm(
展开阅读全文