资源描述
成绩
课程设计报告
题 目 单片机八位口扩展成十六位
课 程 名 称 单片机原理及应用课程设计
院 部 名 称 龙蟠学院
专 业 电气工程及其自动化
班 级 M11电气工程及其自动化
学 生 姓 名 王东
学 号 1121408013
课程设计地点 工科楼 B403
课程设计学时
指 导 教 师 高 峰
金陵科技学院教务处制
摘 要
单片机电子闹钟是集电子技术、数字显示技术为一体的产品,具有按时闹铃,使用方便等优点。本论文从电子闹钟系统的功能、软件设计、软件调试等方面论述这一系统。
本设计以AT89C51芯片为核心,采用动态扫描方式显示,通过使用该单片机,加之在显示电路部分使用的驱动电路,实现在4个LED数码管上显示时间、定时、闹铃的功能,并通过4个按键实现设置日期、进行调时、设定闹铃等功能。在实现各功能时数码管进行相应显示,闹铃或定时时间到时蜂鸣器响,整点报时等功能。
关键词:定时闹钟、C52单片机、软件分析、软件设计
目 录
一 、设计任务与要求………………………………………………………………………1
二 、总体设计方案与说明……………………………………………………………1
三 、系统硬件部分设计………………………………………………………………………2
3.1 8086微处理器的工作原理………………………………………………………2
3.2 8259A芯片的工作原理……………………………………………………3
3.3 8253芯片的工作原理……………………………………………………………4
3.4 LED显示器………………………………………………………………………5
3.5 系统原理图 ………………………………………………………………………6
四 、系统软件部分设计…………………………………………………………………… 7
4.1 软件流程图……………………………………………………………………7
4.2 软件程序……………………………………………………………………………9
五 、系统调试………………………………………………………………………… 17
六 、课程设计体会………………………………………………………………………… 17
七 、参考文献…………………………………………………………………………… 17
25
一、设计任务与要求
设计一个可调时电子钟,要求:
1)正确显示时钟时分
2)可以利用按钮调整时间和设定闹钟时间
3)当时间到达设定的闹钟时间时,蜂鸣器发出滴、滴、滴的报警声
二、总体设计方案与说明
· 单片机通过T0产生的脉冲计时,并通过键盘或者串口来调整闹钟及时钟时间。
· 单片机发送的信号通过程序控制最终在数码管上显示出来。
· 单片机通过输出各种电脉冲信号来驱动控制各部分正常工作。
系统工作框图如下:
三、系统硬件部分设计
3.1、AT89C51单片机:
该单片机功能强大,不仅能满足设计的需要,也可以在设计要求的基础上进行一些扩展。
单片机的结构如下:
图2 AT89C51
在使用时VCC接电源电压,GND接地。P0,P1,P2,P3可作为输入或输出端口,RST是复位输入,接复位电路。XTAL1和XTAL2接复位电路。这些可以在硬件设计部分体现出来。
单片机为内含8K FLASH程序存储器的STC98C52RC,EA接高电平;各并行口都加了10K的上拉电阻;晶振为11.0592M。设置了上电复位和手动复位电路。
3.2、 ULN2003是大电流驱动阵列,多用于单片机、智能仪表、PLC、数字量输出卡等控制电路中。可直接驱动继电器等负载。 输入5VTTL电平,输出可达500mA/50V。 ULN2003是高耐压、大电流达林顿陈列,由七个硅NPN达林顿管组成。 该电路的特点如下: ULN2003的每一对达林顿都串联一个2.7K的基极电阻,在5V的工作电压下它能与TTL和CMOS电路 直接相连,可以直接处理原先需要标准逻辑缓冲器来处理的数据。 ULN2003 是高压大电流达林顿晶体管阵列系列产品,具有电流增益高、工作电压高、温度范围宽、带负载能力强等特点,适应于各类要求高速大功率驱动的系统。
ULN2003芯片引脚图
ULN2003芯片引脚介绍
引脚1:CPU脉冲输入端,端口对应一个信号输出端。
引脚2:CPU脉冲输入端。 引脚3:CPU脉冲输入端。 引脚4:CPU脉冲输入端。 引脚5:CPU脉冲输入端。 引脚6:CPU脉冲输入端。 引脚7:CPU脉冲输入端。 引脚8:接地。
引脚9:该脚是内部7个续流二极管负极的公共端,各二极管的正极分别接各达林顿管的集电极。用于感性负载时,该脚接负载电源正极,实现续流作用。如果该脚接地,实际上就是达林顿管的集电极对地接通。
引脚10:脉冲信号输出端,对应7脚信号输入端。
引脚11:脉冲信号输出端,对应6脚信号输入端。
引脚12:脉冲信号输出端,对应5脚信号输入端。
引脚13:脉冲信号输出端,对应4脚信号输入端。
引脚14:脉冲信号输出端,对应3脚信号输入端。
引脚15:脉冲信号输出端,对应2脚信号输入端。
引脚16:脉冲信号输出端,对应1脚信号输入端。
控制原理
软件流程图说明
系统功能及使用方法:
步进电机,顾名思义,就是一步步走的电动机,所谓“步”指的是转动角度,一般每步为1.8°,若转一圈360°,需要200步才能完成。有的每步为7.5°,还有的每步为18°,转一圈只需20步。
步进电机每走一步,就要加一个脉冲信号,也称激磁信号。无脉冲信号输入时,转子保持一定的位置,维持静止状态。
软件程序
#include<reg52.h> //头文件
#include<intrins.h>
#define uchar unsigned char //宏定义
#define uint unsigned int //宏定义
#define fmq RD
sbit key1=P2^0; //位声明 //MODIFIED
sbit key2=P2^1;
sbit key3=P2^2;
sbit key4=P2^3;
uchar code table[]={0x3f,0x06,0x5b,//数码管显示的数值
0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,
0xbf,0x86,0xdb,//带小数点的数值
0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};
void plus(); //函数声明
void minus();
void sint();
uchar table_1[6]; //定义数组,数组内含有6个数值
uchar table_2[6];
uchar shi=1,fen=1,miao=30; //显示初始值
uchar shi1,fen1,miao1,shi2,fen2,miao2,shi3,fen3,miao3;//定义全局变量
uchar flag,flag1,flag2,cnt,cnt1,count;//定义全局变量
void delay(uchar i) //延时函数,用于动态扫描数码管
{
uchar x,y;
for(x=i;x>0;x--)
for(y=110;y>0;y--);
}
void init() //初始化函数
{
TMOD=0X21; //使用T0和T1
TH0=(65536-50000)/256; //定时时间为:50ms
TL0=(65536-50000)%256;
ET0=1; //打开定时器
EA=1; //开总中断
TR0=1; //启动定时器
SCON = 0x50; //0101 0000 SM1SM2=10,方式二 REN=1允许接受 (串口初始化)
TH1 = 0xFD;
TR1 = 1; //启动T/C1
ES = 1;
}
void display() //显示子函数,用于显示时间数值
{
uchar i,j;
table_1[0]=miao%10; //分离秒的各位与十位
table_1[1]=miao/10;
table_1[2]=fen%10+11; //分离分的各位与十位
table_1[3]=fen/10;
table_1[4]=shi%10+11; //分离时的各位与十位
table_1[5]=shi/10;
j=0x7f; //从秒到时的扫描
for(i=2;i<6;i++)
{
P2=j;
P0=table[table_1[i]];//显示数值
delay(10);
j=_cror_(j,1);//循环右移
}
}
void display_1() //显示子函数,用于显示定时时间
{
uchar i,j;
table_2[0]=miao2%10; //以下含义同上
table_2[1]=miao2/10;
table_2[2]=fen2%10+11;
table_2[3]=fen2/10;
table_2[4]=shi2%10+11;
table_2[5]=shi2/10;
j=0x7f;
for(i=2;i<6;i++)
{
P2=j;
P0=table[table_2[i]];
delay(10);
j=_cror_(j,1);
}
}
void shijian() //时间子函数
{
if(flag>=20) //判断是否到一秒
{
flag=0; //到了,则标志位清零
miao++; //秒加1
if(miao>=60) //判断秒是否到60s
{
miao=0;//到了,则清零
fen++; //分加1
if(fen>=60) //以下含义同上
{
fen=0;
shi++;
if(shi>23)
shi=0;
}
}
}
}
void key_scan() //键盘扫描子函数
{
uchar i; //定义局部变量
if(key1==0) //判断key1是否按下
{
while(!key1) //防止掉显
{
if(cnt==0)
{
display();
}
if(cnt==3||cnt==4)
{
display_1();
}
if(cnt==1&&(cnt1==0||cnt1==2))
display_1();
if(cnt==2&&(cnt1==0||cnt1==2))
{ display();
cnt=0;
}
if(cnt1==1&(cnt==1||cnt==2))
display();
}
cnt++; //记下按键key1按下的次数
cnt=cnt%5;
if(cnt==1&&cnt1==1) //以下含义同上
{
fen1=fen;
fen=99;
for(i=0;i<100;i++)
display();
fen=fen1;
}
if(cnt==2&&cnt1==1)
{
shi1=shi;
shi=99;
for(i=0;i<100;i++)
display();
shi=shi1;
}
if(cnt==3&&cnt1==1)
{
fen1=fen2;
fen2=88;
for(i=0;i<100;i++)
display_1();
fen2=fen1;
}
if(cnt==4&&cnt1==1)
{
shi1=shi2;
shi2=88;
for(i=0;i<100;i++)
display_1();
shi2=shi1;
}
}
if(key2==0)
{
while(!key2) //防止掉显
{
if(cnt==0)
{
display();
}
if(cnt==3||cnt==4)
{
display_1();
}
if(cnt==1&&(cnt1==0||cnt1==2))
display_1();
if(cnt==2&&(cnt1==0||cnt1==2))
{ display();
cnt=0;
}
if(cnt1==1&(cnt==1||cnt==2))
display();
}
cnt1++;
cnt1=cnt1%3;
if(cnt!=0&&cnt1==1) //第一次按下,停止计数
TR0=0;
if(cnt1==2)
{
TR0=1; //开始计数
cnt=0; //按下次数清零
}
}
if(key3==0) //判断key3是否按下
{
while(!key3) //防止掉显
{
if(cnt==0)
{
display();
}
if(cnt==3||cnt==4)
{
display_1();
}
if(cnt==1&&(cnt1==0||cnt1==2))
display_1();
if(cnt==2&&(cnt1==0||cnt1==2))
{ display();
cnt=0;
}
if(cnt1==1&(cnt==1||cnt==2))
display();
}
plus();//调用加1的子函数
}
if(key4==0) //判断key3是否按下
{
while(!key4) //防止掉显
{
if(cnt==0)
{
display();
}
if(cnt==3||cnt==4)
{
display_1();
}
if(cnt==1&&(cnt1==0||cnt1==2))
display_1();
if(cnt==2&&(cnt1==0||cnt1==2))
{ display();
cnt=0;
}
if(cnt1==1&(cnt==1||cnt==2))
display();
}
minus(); //调用减1子函数
}
}
void plus() //加1子函数
{
if(cnt==1) //以下含义同上
{
fen++;
if(fen>59)
fen=0;
}
if(cnt==2)
{
shi++;
if(shi>23)
shi=0;
}
if(cnt==3)
{
fen2++;
if(fen2>59)
fen2=0;
}
if(cnt==4)
{
shi2++;
if(shi2>23)
shi2=0;
}
}
void minus() //减1子函数
{
if(cnt==1)
{
fen--;
if(fen==255)
fen=59;
}
if(cnt==2)
{
shi--;
if(shi==255)
shi=23;
}
if(cnt==3)
{
fen2--;
if(fen2==255)
fen2=59;
}
if(cnt==4)
{
shi2--;
if(shi2==255)
shi2=23;
}
}
void clock() //闹铃子函数
{
if(cnt1!=1&&miao>=0&&miao<3)
{
if(fen2==fen) //是,在判断分是否相等
if(shi2==shi) //是,再判断时是否相等
{
flag1=0; //是,则标志位,flag1清零
while(!(flag1==10)) //判断flag1是否到20
{
fmq=0; //没有,则,继续驱动蜂鸣器响,时间约为:1s
shijian(); //调用时间子函数
display(); //调用显示子函数
}
fmq=1;//关闭蜂鸣器
flag1=0;
while(!(flag1==10)) //判断flag1是否到20
{
fmq=1; //继续驱动蜂鸣器停,时间约为:1s
shijian(); //调用时间子函数
display(); //调用显示子函数
}
fmq=1;
}
}
if(cnt1!=1&&fen==0&&table_1[1]==0) //整点报时
{
if(table_1[0]==0)
fmq=0;
if(table_1[0]==1)
fmq=1;
if(table_1[0]==2)
fmq=0;
if(table_1[0]==3)
fmq=1;
}
}
void main()
{
init();//调用初始化子函数
while(1)
{
key_scan(); //调用键盘扫描子函数
shijian(); //时间子函数
clock(); //闹钟子函数
//显示子函数
if(cnt==0)
{
display();
}
if(cnt==3||cnt==4)
{
display_1();
}
if(cnt==1&&(cnt1==0||cnt1==2))
display_1();
if(cnt==2&&(cnt1==0||cnt1==2))
{ display();
cnt=0;
}
if(cnt1==1&(cnt==1||cnt==2))
display();
}
}
void time0() interrupt 1 //定时器0
{
TH0=(65536-50000)/256; //初值50ms
TL0=(65536-50000)%256;
flag++; //标志位
flag1++;
}
void sint(void) interrupt 4 //串口中断
{
unsigned char temp;
flag2++;
flag2=flag2%4;
if(RI&&flag2==1)
{
RI=0;
temp=SBUF;
shi=temp;
}
if(RI&&flag2==2)
{
RI=0;
temp=SBUF;
fen=temp;
}
if(RI&&flag2==3)
{
RI=0;
temp=SBUF;
shi2=temp;
}
if(RI&&flag2==0)
{
RI=0;
temp=SBUF;
fen2=temp;
}
}
· 实际电路设计及程序设计
图3.1.1 protues 7.7仿真示意图,D1为蜂鸣器,此处用发光二极管代替
系统硬件电路的设计:
电路是由控制部分和显示部分两大部分组成。利用单片机程序进行控制,单片机以晶体振荡器的振荡周期(或外部引入的时钟周期)为最小的时序单位,片内的各种微操作都以此周期为时序基准。振荡频率二分频后形成状态周期或称s周期,所以,1个状态周期包含有2个振荡周期。振荡频率foscl2分频后形成机器周期MC。所以,1个机器周期包含有6个状态周期或12个振荡周期。1个到4个机器周期确定一条指令的执行时间,这个时间就是指令周期。AT89C52单片机指令系统中,各条指令的执行时间都在1个到4个机器周期之间。锁相环允许用户在片外使用频率较低的晶振,可以很大地减小板级噪声;而且,由于时钟频率可由程序控制,系统时钟可以在一个很宽的范围内调整,总线频率往往能升得很高。但是,使用锁相环也会带来额外的功率消耗。 单就时钟方案来讲,使用外部晶振且不使用锁相环是功率消耗最小的一种。AT89C52单片机的时钟信号通常用两种电路形式得到:内部振荡方式和外部振荡方式。
在引脚XTAL1和XTAL2外接晶体振荡器(简称晶振)或陶瓷谐振器,就构成了内部振荡方式。由于单片机内部有一个高增益反相放大器,当外接晶振后,就构成了自激振荡器并产生振荡时钟脉冲。内部振荡方式的外部电路如下图所示。
图中,电容器C01,C02起稳定振荡频率、快速起振的作用,其电容值一般在5-30pF。晶振频率的典型值为12MH2,采用6MHz的情况也比较多。内部振荡方式所得的时钟情号比较稳定,实用电路中使用较多。
外部振荡方式是把外部已有的时钟信号引入单片机内。这种方式适宜用来使单片机的时钟与外部信号保持同步。外部振荡方式的外部电路如下图所示。
各模块分析:
· 显示模块:
电路先通过电源电路送出+5V电压,单片机AT89C52通过74LS47和CD4515(4—16译码器)驱动数码管显示数值, 显示部分采用普通共阳极数码管显示,采用动态扫描,以减少硬件电路。考虑到一次扫描12位数码管显示时会出现闪烁情况,设计时分两排显示,一排显示时间和年月日,一排显示星期和温度, 共阳极数码管中8个发光二极管的阳极(二极管正端)连在一起。通常,公共阳极接高电平(一般接电源),其它管脚接段驱动电路输出端。当某段驱动电路的输入端为低电平时,该端所连接的字段导通并点亮。根据发光字段的不同组合可显示出各种数字或字符。此时,要求段驱动电路能吸收额定的段导通电流,还需根据外接电源及额定段导通电流来确定相应的限流电阻。采用动态显示方式,比较节省I/O口,硬件电路也较静态显示简单,但其亮度不如静态显示方式,而且在显示位数较多时,CPU要依次扫描,占用CPU较多时间。
为了提供共阳LED数码管的驱动电压,用三极管9012作电源驱动输出。采用12MHz晶振,有利于提高秒计时的精确性。三极管采用9012。数码管采用红色的共阳型LED数码管,亮度高些,因为是扫描的显示方式,所以各个数码管的abcdefg各脚采用了总线并联,改动510欧姆的电阻可以改变显示亮度;
· 时钟模块
利用芯片内部的振荡器,然后在引脚XTAL1和引脚 XTAL2两端接晶体谐振器,就构成了稳定的自激振荡器,其发出的脉冲直接送入内部的时钟电路,如图外接晶振时,C1和C2的值通常选择30pF; C1、C2对频率有微调作用,晶体谐振器的频率12MHz。为了减少寄生电容,更好地保证振荡器稳定、可靠地工作,振荡器和电容应尽可能安装得与单片机芯片靠近。设置了12—24两种显示状态,调整计时的按键、设置定时的按键且定时设置了3次定时、还另加载了星期、年、月、日的调整及闰年的自动调整;
时间显示程序部分代码为:
void display() //显示子函数,用于显示时间数值
{
uchar i,j;
table_1[0]=miao%10; //分离秒的各位与十位
table_1[1]=miao/10;
table_1[2]=fen%10+11; //分离分的各位与十位
table_1[3]=fen/10;
table_1[4]=shi%10+11; //分离时的各位与十位
table_1[5]=shi/10;
j=0x7f; //从秒到时的扫描
for(i=2;i<6;i++)
{
P2=j;
P0=table[table_1[i]];//显示数值
delay(10);
j=_cror_(j,1);//循环右移
}
}
· 复位模块
单片机复位电路是使CPU和系统中的其他功能部件都处在一个确定的初始状态,并从该状态开始工作,例如复位后PC=0000H,使单片机从第一个单元取指令。无论是在单片机刚接上电源时,还是断电后或者发生故障后都要复位;单片机的复位操作使单片机进入初始化状态,其中包括使程序计数器PC=0000H,这表明程序从0000H地址单元开始执行。单片机冷启动后,片内RAM为随机值,运行中的复位操作不改变片内RAM区中的内容,21个特殊功能寄存器复位后的状态为确定值。
· 按键电路的设计
作为一个按键从没有按下到按下以及释放是一个完整的过程,也就是说当我们按下一个按键时,总希望某个命令只执行一次。而在按下的过程中不要有干扰进来,因为在按下的过程中,一旦有干扰过来可能造成误触发过程,因此我们在设计按键电路的时候应注意不要有干扰进来以用在焊接时应注意:
独立式按键。如果设置过多按键,将会占用较多I/O口,而且会给布线带来不便,因此,此方案适用于按键较少的情况。如果选择此方案,由于按键较少,在修改时间或设置闹铃时间时就不能直接输入,只能通过加或减完成,稍为麻烦一些,但其程序简单。
考虑到电路不要复杂性,因而设计成4个按键,一个为选择,一个为更改确认,其它2个为按数字时间加减。按键功能在前文中已有描述。
KEY1代码:
if(key1==0) //判断key1是否按下
{
“…” //防掉显程序
cnt++; //记下按键key1按下的次数
cnt=cnt%5;
if(cnt==1&&cnt1==1) //以下含义同上
{
fen1=fen;
fen=99;
for(i=0;i<100;i++)
display();
fen=fen1;
}
if(cnt==2&&cnt1==1)
{
shi1=shi;
shi=99;
for(i=0;i<100;i++)
display();
shi=shi1;
}
if(cnt==3&&cnt1==1)
{
fen1=fen2;
fen2=88;
for(i=0;i<100;i++)
display_1();
fen2=fen1;
}
if(cnt==4&&cnt1==1)
{
shi1=shi2;
shi2=88;
for(i=0;i<100
展开阅读全文