推荐-单片机C51的简单万年历设计 精品
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第十届学生科技节
-----单片机设计大赛
作品名称:万年历
专业:物联网
班级:1
指导老师:郑旭丹
组长:邓清兰学号:3112113111
组员:黄孟秋学号:3112113101
目录
一、绪论
众所周知单片机是一种集成在电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计时器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的计算机系统。
本设计要制作的就是单片机于生活中最为常见的几种应用——万年历。
本设计以AT89S52单片机作为核心,可以显示当前的日历和时间,时间也可以人为设定,显示格式为年(四位),月(两位),日(两位),时(两位),分(两位),秒(两位)。
设置时间的位切换、设定数值、启动定时器、切换日历通过外部中断来实现。
万年历显示电路由LCD1602组成,制作该装置的材料需要有软硬件的支持,硬件方面AT89C51单片机,晶振,电源,液晶屏LCD1602。
二、系统功能
本次设计的是万年历,采用LCD1602显示,实现的功能如下:
1.显示当前时间,显示格式为年(四位),月(两位),日(两位),时(两
位),分(两位),秒(两位);
2.时钟计时,自动进行时间的位切换;
3.键盘设置,能通过键盘设定当前时间和计时,并通过键盘人为设定时间。
三、硬件电路
3.1.电路设计框图
3.2.系统硬件概述
本电路是由AT89S52单片机为控制核心,具有在线编程功能,低功耗,能在3V超低压工作,本系统选用12MHZ的晶振,使得单片机有合理的运行速度;利用液晶屏LCD1602显示时间信息,通过对AT89S52单片机的编程控制液晶屏LCD1602的显示。
显示时间在LCD1602,通过按键切换选择。
外部按键可及时设定或调整时间信息。
四、各模块功能及工作原理
4.1.单片机主控制模块的设计
AT89S52是一种低功耗、高性能CMOS8位微控制器,具有8K 在系统可编程Flash 存储器。
AT89S52单片机为40引脚双列直插芯片,有四个I/O口P0,P1,P2,P3, MCS-51单片机共有4个8位的I/O口(P0、P1、P2、P3),每一条I/O线都能独立地作输出或输入。
18引脚和19引脚接时钟电路,XTAL1接外部晶振和微调电容的一端,在片内它是振荡器倒相放大器的输入,XTAL2接外部晶振和微调电容的另一端,在片内它是振荡器倒相放大器的输出。
第9引脚为复位输入端,接上电容,电阻及开关后够上电复位电路,20引脚为接地端,40引脚为电源端。
引脚图如图图4.1:
图4.1 AT89S52 引脚结构图4.2.1 LCD1602 引脚结构
4.2.LCD1602显示模块
4.2.1LCD1602引脚介绍
LCD1602采用标准的14脚(无背光)或16脚(带背光)接口,引脚图
第1脚:VSS为地电源。
第2脚:VDD接5V正电源。
第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。
第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。
第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。
当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平R/W 为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据。
第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命
令。
第7~14脚:D0~D7为8位双向数据线。
第15脚:背光源正极。
第16脚:背光源负极。
4.2.2LCD1602指令
1602液晶模块内部的控制器共有11条控制指令,如表4.2.3
1602液晶模块的读写操作、屏幕和光标的操作都是通过指令编程来实现的。
(说明:1为高电平、0为低电平)
指令1:清显示,指令码01H,光标复位到地址00H位置。
指令2:光标复位,光标返回到地址00H。
指令3:光标和显示模式设置I/D:光标移动方向,高电平右移,低电平左
移S:屏幕上所有文字是否左移或者右移。
高电平表示有效,低电平则无效。
指令4:显示开关控制。
D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示C:控制光标的开与关,高电平表示有光标,低电平表示无光标B:控制光标是否闪烁,高电平闪烁,低电平不闪烁。
指令5:光标或显示移位S/C:高电平时移动显示的文字,低电平时移动光标。
指令6:功能设置命令DL:高电平时为4位总线,低电平时为8位总线N:低电平时为单行显示,高电平时双行显示F: 低电平时显示5x7的点阵字符,高电平时显示5x10的点阵字符。
指令7:字符发生器RAM地址设置。
指令8:DDRAM地址设置。
指令9:读忙信号和光标地址BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
指令10:写数据。
指令11:读数据。
4.2.3 LCD1602读/写操作
LCD1602基本操作时序说明,如表4.2.4
4.2.4 LCD1602显示模块流程图
开始
LCD 1602初始界面
显示欢迎界面
N
按下按键?
Y
显示初始时间
结束
4.3.键盘模块
本设计采用4乘4键盘的反转法控制万年历的计时和时间设定,4乘4键盘电路图如图图4.3.1
图4.3.2
4.3.1键盘功能图
4.4计时模块
4.4.1计数器工作原理
计数器:是单片机应用中的重要部件,工作方式灵活,可以对外部事件计数和检测等;实现计数功能。
其核心是特殊功能寄存器中能进行加1计数的寄存器。
实现原理:对T0或T1引脚输入的外部脉冲计数,如下图:
4.4.2
五、实现结果
六、系统的实用性及创新性
基本功能:能显示当前时间和计时,实现基本时钟功能。
创新性:界面友好,能实现计时和键盘数字的时间设定和自增、自减的时间设定。
七、调节中遇到的问题及心得体会
问题一
LCD1602的初始化不规范,导致无法正确在LCD1602上显示相应的字符,时间等。
解决
在网上及课本上查找有关LCD1602初始化的程序,找出错误的地方并及时改正,最终解决了LCD1602的初始化问题。
问题二
在对秒、分、时、日、月、年等的大小判断时,采用逐位判断,导致程序冗长,不利于程序的优化。
解决
通过老师的指导及其他相关程序的借鉴,对时间大小的判断进行优化,将秒的十位和各位合为一个整体进行判断,分、时、日、月、年类似。
问题三
在计时模块中,因为细节问题,找不到导致不能正常计时的原因。
解决
通过老师的精心指导,找到不能正常计时的原因,将中断次数的判断if(count>100)改为if(count>=100)即可。
问题四
在键盘设计模块中,对空数组的定义无效。
解决
通过查找相关资料,了解到空数组的定义要放在data中,不能放在code 中。
将空数组定义为uchar a[14]使其默认放在data中就解决了该问题。
八、程序设计
8.1.流程图
8.2.源代码
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
uchar code Buffer[4]={0xfe,0xfd,0xfb,0xf7};
uchar code table[]={'0','1','2','3','4','5','6','7','8','9','<','>','+','-','!','@'};
uchar code w[14]={0x83,0x84,0x85,0x86,0x88,0x89,0x8b,0x8c,0xc4,0xc5,0xc7,0xc8,0xca,0xcb};
uchar a[14];
sbit E=P2^7; //1602使能引脚
sbit RW=P2^6; //1602读写引脚
sbit RS=P2^5; //1602数据/命令选择引脚
void Delay(uint xms); //延时
void Time0_Init(); //中断请求
void timer1_int(void); //中断
void charge(); //判断秒分时日月等是否大于额外值,若大于向上一位加一
void ftime(); //初始时间
void timeon(); //计时
void add(); //自加
void sub(); //自减
void wcmd(uchar del); //1602写指令
void write(uchar del); //1602写数据
void L1602_init(void); //1602初始化
void second(); //秒显示
void minute(); //分显示
void hours(); //时显示
void days(); //日显示
void month(); //月显示
void years(); //年显示
void other(); //符号显示
void number(); //键盘输入数字
uint count,t; //中断执行次数,光标位置参数
uchar sec=0; //初始秒
uchar min=0; //初始分
uchar hour=0; //初始时
uchar day=0; //初始日
uchar mon=0; //初始月
uchar yearh=0; //初始年前两位uchar yearl=0; //初始年后两位
//1602命令函数
void wcmd(uchar del)
{
P0 = del;
RS = 0;
RW = 0;
E = 1;
Delay(1);
E = 0;
Delay(1);
}
// 1602写数据函数
void write(uchar del)
{
P0 = del;
RS = 1;
RW = 0;
E = 1;
Delay(1);
E = 0;
Delay(1);
}
// 1602初始化
void L1602_init(void)
{
wcmd(0x38);
wcmd(0x38);
wcmd(0x38);
wcmd(0x0e);
wcmd(0x06);
wcmd(0x01);
}
//显示秒
void second()
uchar i,j;
i=sec/10;
j=sec%10;
wcmd(0xca);
write(table[i]);
write(table[j]); }
//显示分
void minute()
{
uchar i,j;
i=min/10;
j=min%10;
wcmd(0xc7);
write(table[i]);
write(table[j]); }
//显示时
void hours()
{
uchar i,j;
i=hour/10;
j=hour%10;
wcmd(0xc4);
write(table[i]);
write(table[j]); }
//显示日
void days()
{
uchar i,j;
i=day/10;
j=day%10;
wcmd(0x8b);
write(table[i]);
write(table[j]); }
//显示月
void month()
{
uchar i,j;
i=mon/10;
j=mon%10;
wcmd(0x88);
write(table[i]);
write(table[j]); }
//显示年
void years()
{
uchar i,j,m,n;
i=yearh/10;
j=yearh%10;
m=yearl/10;
n=yearl%10;
wcmd(0x83);
write(table[i]);
write(table[j]);
write(table[m]);
write(table[n]); }
//符号显示
void other()
{
wcmd(0xc9);
write(':');
wcmd(0xc6);
write(':');
wcmd(0x87);
write('-');
wcmd(0x8a);
write('-');
}
//延时
void Delay(uint xms)
{
uint i,j;
for(i=0;i<xms;i++)
for(j=0;j<110;j++);
}
//计时
void timeon()
{
if(count>=100)
{ count=0;
charge();
}
ftime(); //显示初始时间}
//时间初值
void ftime()
{
second();Delay(5);
minute();Delay(5);
hours();Delay(5);
days();Delay(5);
month();Delay(5);
years();Delay(5);
other();Delay(5);
}
//各个按键返回值
uint Keyvalue()
{ uint i,j;
uchar temp;
uint Key_value;
while(1)
{
P1=0xf0;
if(P1!=0xf0)
{Delay(100);
if(P1!=0xf0)
for(j=0;j<4;j++)
{P1=Buffer[j];
temp=0x10;
for(i=0;i<4;i++)
{
if(!(P1&temp))
{
Key_value=i+j*4;
}
temp<<=1;
}
}
return Key_value;
}
}
}
//字符串显示
void L1602_string(uchar hang,uchar lie,uchar *p)
{
uchar a,b=0;
if(hang == 1) a = 0x80;
if(hang == 2) a = 0xc0;
a = a + lie - 1;
while(1)
{
wcmd(a++);
b++;
if((*p == '\0')||(b==16)) break;
write(*p);
Delay(500);
p++;
}
}
//自增
void add()
{
wcmd(w[t]); //光标置位
if(t==1) //光标在第二个位置时
{
if(yearh<99) //判断年前两位是否小于99,是,年前两位加1,并显示
{
yearh++;
years();
}
}
else if(t==3) //光标在第四个位置时
{
if(yearl<99) //判断年后两位是否小于99,是,年后两位加1,并显示{
yearl++;
years();
}
}
else if(t==5) //光标在第六个位置时
{
if(mon<12) //判断月是否小于12,是,月加1,并显示
{
mon++;
month();
}
}
else if(t==7) //光标在第八个位置时
{
if(day<30) //判断日是否小于30,是,日加1,并显示
{
day++;
days();
}
}
else if(t==9) //当光标在第十个位置时
{
if(hour<12) //判断时是否小于12,是,时加1,并显示
{
hour++;
hours();
}
}
else if(t==11) //光标在第十二个位置时
{
if(min<59) //判断分是否小于59,是,分加1,并显示
{
min++;
minute();
}
}
else if(t==13) //光标在第十四个位置时
if(sec<59) //判断秒是否小于59,是,秒加1,并显示
{
sec++;
second();
}
}
}
//自减
void sub()
{
wcmd(w[t]); //光标置位
if(t==1) //光标在第二个位置时,判断年前两位是否大于0,是,年前两位减1,并显示
{
if(yearh>0)
{
yearh--;
years();
}
}
else if(t==3) //光标在第四个位置时,判断年后两位是否大于0,是,年后两位减1,并显示
{
if(yearl>1)
{
yearl--;
years();
}
}
else if(t==5) //光标在第六个位置时,判断月是否大于1,是,月减1,并显示
{
if(mon>1)
{
mon--;
month();
}
}
else if(t==7) //光标在第八个位置时,判断日是否大于1,是,日减1,并显示
if(day>1)
{
day--;
days();
}
}
else if(t==9) //光标在第十个位置时,判断时是否大于0,是,时减1,并显示
{
if(hour>0)
{
hour--;
hours();
}
}
else if(t==11) //光标在第十二个位置时,判断分是否大于0,是,分减1,并显示
{
if(min>0)
{
min--;
minute();
}
}
else if(t==13) //光标在第十四个位置时,判断秒是否大于0,是,秒减1,并显示
{
if(sec>0)
{
sec--;
second();
}
}
}
//主函数
void main()
{ uchar keys;
uint i,key; //数组a的参数
i=0;
t=0; //位置参数
L1602_init(); //1602初始化
L1602_string(1,5,"Showtime"); //初始界面显示
L1602_string(2,5,"Wele!"); //初始界面显示
while(1)
{
key=Keyvalue(); //将键值赋值给key
keys=table[key];
if(keys=='@') //判断键值是否为@,是,执行计时
{
Time0_Init();
while(1)
timeon();
}
else if(keys=='!') //判断键值是否为!,是,显示初始时间,光标在第一个位置{
ftime();
wcmd(0x83);}
else if(keys=='-'||keys=='+') //判断键值是否为+或-
{
if(keys=='+') //键值为+,执行自加函数
{
add();
}
else //键值为-,执行自减函数
{
sub();
}
}
//判断键值是否为<,是,光标不在最左边时,光标左移一位
else if(keys=='<')
{if(t>=1)
{
i--;
t=t-1;
wcmd(w[t]);
}
}
else if(keys=='>') //判断键值是否为>,是,光标不在最右边时,光标右移一位
{if(t<13)
{
i++;
t=t+1;
wcmd(w[t]);
}
}
else //键值为数字键时,将键值放入a数组中,在调用函数,显示当前设置的时间
{ number();
i=i%14;
a[i]=key;
i++;
t++;
number();
}
}
}
//输入数字
void number()
{
yearh=a[0]*10+a[1];
yearl=a[2]*10+a[3];
mon=a[4]*10+a[5];
day=a[6]*10+a[7];
hour=a[8]*10+a[9];
min=a[10]*10+a[11];
sec=a[12]*10+a[13];
ftime();
}
//中断请求
void Time0_Init()
{
TMOD=0X10;
TH1=(65536-10000)/256;
TL1=(65536-10000)%256;
EA=1;
ET1=1;
TR1=1;
}
//中断
void timer1_int(void) interrupt 3
{
count++;
TH1=(65536-10000)/256;
TL1=(65536-10000)%256;
}
//判断年月日是否超过额外值
void charge()
{
sec++; //秒加1
if(sec==60) //秒等于60时,分加1,秒置0
{ sec=0;
min++; }
if(min==60) //分等于60时,时加1 ,分置0
{ m in=0;
hour++; }
if(hour==24) //时等于24时,日加1,时置0
{ hour=0;
day++; }
if(day==31) //日等于30时,月加1,日置1
{ day=1;
mon++; }
if(mon==13) //月等于613时,年后两位加1,月置1
{ mon=1;
yearl++; }
if(yearl==99) //年后两位等于99时,年前两位加1,年后两位置0
{yearh=0;
yearh++;
if(yearh==99) //年前两位等于99时,年前两位置0
yearh=0;
}
}
九、总结及感谢
在用单片机实现万年历的设计中使用到了AT89S52、LCD1602及其4*4键盘的使用。
在设计过程中我通过在网上和图书馆查阅资料,收集了关于单片机和液晶屏LCD1602显示方面的资料,通过对这些资料的学习,了解了单片机的基本结构和单片机在生活和生产中所发挥的作用以及液晶屏LCD1602的原理和使用。
在整个设计过程中,发挥团队精神,分工合作,充分发挥人的主观能动性,自主学习,学到了许多没学到的知识,较好的完成了作品。
虽然这次顺利地完成的设计,但这之中还有很多需要去改进的地方还有很多进步的空间,我们会继续努力去改进,不会因为科技节的结束而停止学习。
这次能顺利完成设计,首先我们要感谢我们的指导老师郑旭丹老师的热情关怀和悉心指导,在设计过程中,老师总是耐心的为答疑。
最后,我们要感谢彼此,感谢彼此的对于这次比赛的热忱,感谢彼此的包容,感谢彼此的谅解。