51单片机万年历课程设计报告.docx
《51单片机万年历课程设计报告.docx》由会员分享,可在线阅读,更多相关《51单片机万年历课程设计报告.docx(14页珍藏版)》请在冰豆网上搜索。
51单片机万年历课程设计报告
一、设计任务:
1、设计任务:
设计并制作一个数字钟。
2、设计要求:
●显示年月日时分秒及星期信息
●具有可调整日期和时间功能
●增加闰年计算功能
●显示部分由LCD1602完成
二、方案论证:
1.显示部分:
显示部分是本次设计的重要部分,一般有以下两种方案:
方案一:
采用LED显示,分静态显示和动态显示。
对于静态显示方式,所需的译码驱动装置很多,引线多而复杂,且可靠性也较低。
而对于动态显示方式,虽可以避免静态显示的问题,但设计上如果处理不当,易造成亮度低,有闪烁等问题。
方案二:
采用LCD显示。
LCD液晶显示具有丰富多样性、灵活性、电路简单、易于控制而且功耗小等优点,对于信息量多的系统,是比较适合的。
鉴于上述原因,我们采用方案二。
2.数字时钟:
数字时钟是本设计的核心的部分。
根据需要可采用以下两种方案实现:
方案一:
方案完全用软件实现数字时钟。
原理为:
在单片机内部存储器设三个字节分别存放时钟的时、分、秒信息。
利用定时器与软件结合实现1秒定时中断,每产生一次中断,存储器内相应的秒值加1;若秒值达到60,则将其清零,并将相应的分字节值加1;若分值达到60,则清零分字节,并将时字节值加1;若时值达到24,则将时字节清零。
该方案具有硬件电路简单的特点,但当单片机不上电,程序将不执行。
而且由于每次执行程序时,定时器都要重新赋初值,所以该时钟精度不高。
方案二:
方案采用Dallas公司的专用时钟芯片DS1302。
该芯片内部采用石英晶体振荡器,其芯片精度不大于10ms/年,且具有完备的时钟闹钟功能,因此,可直接对其以用于显示或设置,使得软件编程相对简单。
为保证时钟在电网电压不足或突然掉电等突发情况下仍能正常工作,芯片内部包含锂电池。
当电网电压不足或突然掉电时,可使系统自动转换到内部锂电池供电系统。
而且即使系统不上电,程序不执行时,锂电池也能保证芯片的正常运行,以备随时提供正确的时间。
基于时钟芯片的上述优点,本设计采用方案二完成数字时钟的功能。
三、总体方案:
本设计采用STC89C52RC单片机作为本系统的控制模块。
单片机可把由DS1302、LCD液晶显示模块中的数据利用软件来进行处理,从而把数据传输到显示模块,实现日历和修改的显示。
以LCD液晶显示器为显示模块,把单片机传来的数据显示出来,并且显示多样化。
在显示电路中,主要靠按键来实现日期的修改和选择。
四、系统硬件设计:
1.STC89C52RC单片机最小系统:
最小系统包括晶体振荡电路、复位开关和电源部分。
图1为STC89C52RC单片机的最小系统。
图1单片机最小系统
2.时钟模块:
时钟模块采用DS1302芯片,DS1302是DALLAS公司推出的涓流充电时钟芯片内含有一个实时时钟/日历和31字节静态RAM通过简单的串行接口与单片机进行通信实时时钟/日历电路提供秒分时日日期月年的信息每月的天数和闰年的天数可自动调整时钟操作可通过AM/PM指示决定采用24或12小时格式DS1302与单片机之间能简单地采用同步串行的方式进行通信仅需用到三个口线:
RST复位、I/O数据线、SCLK串行时钟。
时钟/RAM的读/写数据以一个字节或多达31个字节的字符组方式通信。
DS1302工作时功耗很低,保持数据和时钟信息时功率小于1mW,其接线电路如图2所示:
图2时钟模块
3.LCD液晶显示模块:
LCD液晶显示模块采用LCD1602型号,具有很低的功耗,正常工作时电流仅2.0mA/5.0V。
通过编程实现自动关闭屏幕能够更有效的降低功耗。
LCD1602分两行显示,每行可显示多达16个字符。
LCD1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,通过内部指令可实现对其显示多样的控制,并且还能利用空余的空间自定义字符。
其接线如图3所示:
图3LCD液晶显示
五、整体电路:
1.电路如下图:
图4整体电路
2.AltiumDesigner布线图
图5AltiumDesigner布线图
3.手动布线PCB图
图6PCB图
六、数字时钟使用说明:
1)调整时间设定:
在时间显示界面中按K1键后,进入时间调整,选择需要修改的或设定的时间。
按K2键对应内容加1,K3键减1,K4键确定时间设定并退出设置。
2)调整屏幕显示/背光亮度:
调整左上方的蓝色变阻器可调整LCD显示灰度LCD背光亮度。
3)复位键(K5):
按下该键系统复位,系统从头开始执行程序。
如遇故障可按下该键进行系统复位。
复位不会造成时间、生日和闹铃等信息的丢失。
七、心得体会:
1.将理论教学与实践相结合,使我对于单片机的实际应用有了较深刻的认识。
2.让我熟悉电子系统设计的全过程,提高了我对设计课题的分析能力、编程能力及解决实际问题的综合能力。
3.对于Protues软件有了更加深入的了解,仿真这方面有很大的缺点,程序时序不对软件直接忽略了,因此在这方面花了好多时间进行调试。
同时在与老师和同学的交流过程中,互动学习,将知识融会贯通。
通过自己的努力,做出了一个万年历,对以后的学习是一个莫大的鼓舞,激起了我的学习兴趣和开发创新思维。
八、程序及解释:
#include
#include
#include
#defineuintunsignedint
#defineucharunsignedchar
unsignedcharcodedisplaywelcome[]={"WelcomeToMyLcdTimer"};//欢迎界面
unsignedcharcodedisplaywish[]={"通信102闭光俭"};//欢迎界面
sbitIO=P2^3;//DS1302数据线
sbitSCLK=P2^4;//DS130时钟线
sbitRST=P2^5;//DS1302复位线
sbitRS=P2^0;//LCD数据/命令选择端
sbitRW=P2^1;//LCD读/写控制
sbitEN=P2^2;//LCD使能端
sbitK1=P3^4;//选择
sbitK2=P3^5;//加
sbitK3=P3^6;//减
sbitK4=P3^7;//确定
uchartCount=0;
ucharMonthsDays[]={0,31,0,31,30,31,30,31,31,30,31,30,31};
uchar*WEEK[]={"SUN","MON","TUS","WEN","THU","FRI","SAT"};
ucharLCD_DSY_BUFFER1[]={"DATE00-00-00"};//显示格式
ucharLCD_DSY_BUFFER2[]={"TIME00:
00:
00"};
ucharDateTime[7];//所读取的日期时间
charAdjust_Index=-1;//当前调节的时间对象:
,,分,是,日,月,年(1,2,3,4,6)
ucharChange_Flag[]="-MHDM-Y";//(分,时,日,月,年)(不调节秒与周)
voidDelayMS(uintms)//延时程序
{uchari;
while(ms--){for(i=0;i<120;i++);}}
voidWrite_A_Byte_TO_DS1302(ucharx)//向DS1302写入一字节
{uchari;SCLK=0;
for(i=0;i<8;i++){
IO=x&0x01;//每一位与1与存入IO中
SCLK=1;SCLK=0;//一个高脉冲将数据送入液晶控制器
x>>=1;//右移}}
ucharGet_A_Byte_FROM_DS1302()//从DS1302读取一字节
{uchari,b=0x00;
for(i=0;i<8;i++){
b|=_crol_((uchar)IO,i);
SCLK=1;SCLK=0;//每一个高脉冲读取一位数据}
returnb/16*10+b%16;//返回BCD码}
ucharRead_Data(ucharaddr)//从DS1302指定位置读数据
{uchardat;
RST=0;SCLK=0;RST=1;//RST高电平时读/写
Write_A_Byte_TO_DS1302(addr);//先写入地址
dat=Get_A_Byte_FROM_DS1302();
SCLK=1;RST=0;returndat;}
voidWrite_DS1302(ucharaddr,uchardat)//向DS1302某地址写入数据
{RST=0;SCLK=0;RST=1;
Write_A_Byte_TO_DS1302(addr);
Write_A_Byte_TO_DS1302(dat);
SCLK=1;RST=0;//高脉冲写入数据}
voidSET_DS1302()//设置时间
{uchari;//写控制字,取消写保护
Write_DS1302(0x8E,0x00);//分时日月年依次写入
for(i=1;i<7;i++){Write_DS1302(0x80+2*i,(DateTime[i]/10<<4)|(DateTime[i]%10));//分的起始地址(0x82),后面依次是时,日,月,周,年,写入地址每次递增2}
Write_DS1302(0x8E,0x80);//加保护}
voidGetTime()//读取当前日期时间
{uchari;
for(i=0;i<7;i++){DateTime[i]=Read_Data(0X81+2*i);}}
ucharRead_LCD_State()//读LCD状态
{ucharstate;RS=0;RW=1;EN=1;//输出:
D0~D7=状态字
DelayMS
(1);state=P0;//从P0口读LCD状态
EN=0;DelayMS
(1);returnstate;}
voidLCD_Busy_Wait()//忙等待
{while((Read_LCD_State()&0x80)==0x80);DelayMS(5);}
voidWrite_LCD_Data(uchardat)//向LCD写数据
{LCD_Busy_Wait();RS=1;EN=0;RW=0;//写数据,EN为高脉冲,
P0=dat;EN=1;DelayMS
(1);EN=0;}
voidWrite_LCD_Command(ucharcmd)//写LCD指令
{LCD_Busy_Wait();RS=0;EN=0;RW=0;//写指令,EN高脉冲,输出:
D0~D7=数据
P0=cmd;EN=1;DelayMS
(1);EN=0;}
voidInit_LCD()//LCD初始化
{Write_LCD_Command(0x38);//设置16*2显示,5*7点阵,8位数据接口
DelayMS
(1);Write_LCD_Command(0x01);//显示清零,数据指针清零
DelayMS
(1);Write_LCD_Command(0x06);//写一个字符后地址指针自动加1
DelayMS
(1);Write_LCD_Command(0x0c);//设置开显示,不显示光标
DelayMS
(1);}
voidSet_LCD_POS(ucharp)//设置液晶显示位置
{Write_LCD_Command(p|0x80);//相当于在0x80基础上加入位置量}
voiddisplaystar(void)//显示欢迎界面
{unsignedchari,j;Set_LCD_POS(0x0f);
while(displaywelcome[i]!
='\0')
{Write_LCD_Data(displaywelcome[i]);i++;DelayMS
(1);}
i=0;Set_LCD_POS(0x4f);
while(displaywish[i]!
='\0')
{Write_LCD_Data(displaywish[i]);i++;DelayMS
(1);}
j=40;while(j--)
{Write_LCD_Command(0x18);//循环左移DelayMS(200);}
Write_LCD_Command(0x01);DelayMS
(1);}
voidDisplay_LCD_String(ucharp,uchar*s)//----在LCD上显示字符串---------//
{uchari;Set_LCD_POS(p);
for(i=0;i<16;i++)
{Write_LCD_Data(s[i]);//在固定位置显示时间日期DelayMS
(1);}}
voidFormat_DateTime(uchard,uchar*a)//日期与时间值转换为数字字符
{a[0]=d/10+'0';a[1]=d%10+'0';}
ucharisLeapYear(uinty)//判断是否为闰年
{return(y%4==0&&y%100!
=0)||(y%400==0);}
//求自2000.1.1开始的任何一天是星期几
//函数没有通过,求出总天数后再求星期几
//因为求总天数可能会超出uint的范围
voidRefreshWeekDay()
{uinti,d,w=5;//已知1999.12.31是周五
for(i=2000;i<2000+DateTime[6];i++)
{d=isLeapYear(i)?
366:
365;w=(w+d)%7;}d=0;
for(i=1;i//保存星期,0~6表示星期日,星期一,二,...,六,为了与DS1302的星期格式匹配,返回值需要加1
DateTime[5]=(w+d)%7+1;}
voidDateTime_Adjust(charx)//年月日时分++/--
{switch(Adjust_Index)
{case6:
//年00-99
if(x==1&&DateTime[6]<99)DateTime[6]++;
if(x==-1&&DateTime[6]>0)DateTime[6]--;
MonthsDays[2]=isLeapYear(2000+DateTime[6])?
29:
28;//获取2月天数
//如果年份变化后当前月份的天数大于上限则设为上限//
if(DateTime[3]>MonthsDays[DateTime[4]])
{DateTime[3]=MonthsDays[DateTime[4]];}
RefreshWeekDay();//刷新星期break;
case4:
//月01-12if(x==1&&DateTime[4]<12)DateTime[4]++;{if(DateTime[4]>12)DateTime[4]=0;}
if(x==-1&&DateTime[4]>1)DateTime[4]--;if(DateTime[4]<0)DateTime[4]=12;
MonthsDays[2]=isLeapYear(2000+DateTime[6])?
29:
28;
if(DateTime[3]>MonthsDays[DateTime[4]])
{DateTime[3]=MonthsDays[DateTime[4]];}RefreshWeekDay();break;
case3:
//日00-28、29、30、31,调节之前首先根据年份得出该年中断二月天数
MonthsDays[2]=isLeapYear(2000+DateTime[6])?
29:
28;
//根据当前月份决定调节日期的上限
if(x==1&&DateTime[3]if(x==-1&&DateTime[3]>0)DateTime[3]--;RefreshWeekDay();break;
case2:
//时
if(x==1&&DateTime[2]<23)DateTime[2]++;
if(x==-1&&DateTime[2]>0)DateTime[2]--;break;
case1:
//分
if(x==1&&DateTime[1]<59)DateTime[1]++;
if(x==-1&&DateTime[1]>0)DateTime[1]--;break;}}
voidT0_INT()interrupt1//定时器0每秒刷新LCD显示
{TH0=-50000/256;TL0=-50000%256;if(++tCount!
=2)return;tCount=0;
//按指定格式生成待显示的日期时间串//
Format_DateTime(DateTime[6],LCD_DSY_BUFFER1+5);
Format_DateTime(DateTime[4],LCD_DSY_BUFFER1+8);
Format_DateTime(DateTime[3],LCD_DSY_BUFFER1+11);
strcpy(LCD_DSY_BUFFER1+13,WEEK[DateTime[5]-1]);//星期
Format_DateTime(DateTime[2],LCD_DSY_BUFFER2+5);//时分秒
Format_DateTime(DateTime[1],LCD_DSY_BUFFER2+8);
Format_DateTime(DateTime[0],LCD_DSY_BUFFER2+11);
Display_LCD_String(0x00,LCD_DSY_BUFFER1);//显示年月日,星期,时分秒
Display_LCD_String(0x40,LCD_DSY_BUFFER2);}
voidEX_INT0()interrupt0//键盘中断(INT0)
{if(K1==0)//选择调整对象(YMDHM)
{DelayMS(10);if(K1==0){
while(K1==0);if(Adjust_Index==-1||Adjust_Index==1)
{Adjust_Index=7;}
Adjust_Index--;if(Adjust_Index==5)Adjust_Index=4;
LCD_DSY_BUFFER2[13]='[';
LCD_DSY_BUFFER2[14]=Change_Flag[Adjust_Index];//显示调节对象
LCD_DSY_BUFFER2[15]=']';}}
elseif(K2==0)//加
{while(K2==0);DelayMS(10);if(K2==0)DateTime_Adjust
(1);}
elseif(K3==0)//减
{DelayMS(10);//while(K3==0);if(K3==0)DateTime_Adjust(-1);}
elseif(K4==0)//确定
{while(K4==0);DelayMS(10);if(K4==0){
SET_DS1302();//将调整后的时间写入DS1302
LCD_DSY_BUFFER2[13]='';LCD_DSY_BUFFER2[14]='';
LCD_DSY_BUFFER2[15]='';Adjust_Index=-1;}}}
voidmain()
{Init_LCD();//液晶初始化displaystar();
IE=0x83;//允许INT0,T0中断,EA=1,,ET0=1,EX0=1
IP=0x01;//设置外部中断0为高级中断
IT0=0x01;//外部中断0为电平触发,低电平有效
TMOD=0x01;//设置定时器T0工作方式为方式1,
TH0=-50000/256;//装入初始值,定时1秒TL0=-50000%256;
TR0=1;//启动定时器GetTime();
while
(1)
{if(Adjust_Index==-1)GetTime();//如果未执行调整操作则正常读取当前时间}}