北京交通大学单片机实验报告 电子时钟

单片机课程设计

实验报告

北京交通大学单片机实验报告电子时钟

电子时钟

一、 实验目的

学习8051定时器时间计时处理、按键扫描及LED数码管显示的设计方法。

二、 设计任务及要求

利用实验平台上4个LED数码管, 设计带有闹铃功能的数字时钟,要求:

1. 在4位数码管上显示当前时间。显示格式“时时分分”

2. 由LED闪动做秒显示。

3. 利用按键可对时间及闹玲进行设置,并可显示闹玲时间。当闹玲时间到蜂鸣器发出声响,按停止键使可使闹玲声停止。

4. 拓展:使数字时钟的闹铃为音乐。(电子音调发生器)

三、 硬件设计

1. 显示模块

为了将时间在LED数码管上显示,可采用静态显示法和动态显示法,由于静态显示法需要数据锁存器等较多硬件,可采用动态显示法实现LED显示。方法是将所有位的段选线相应并联,由一个8位I/O口控制,从而形成段选线的多路复用,同时各位的公共端分别由相应的I/O线控制,实现分时选通。

硬件电路图如下,图中10k电阻起到限流作用;三极管起到驱动数码管的作用。若不使用三极管,数码管发光微弱。

北京交通大学单片机实验报告电子时钟

2. 闹铃模块

闹铃声由交流蜂鸣器产生,电路图如下。当P1.7输出不同频率的方波,.蜂鸣器便会发出不同的声音。

3. 整体硬件电路图

四、 软件设计

1. 计时模块

利用单片机定时器0完成计时功能。定时器0计时中断程序每隔1ms中断一次并当作一个计数,每中断一次计数加1,当计数1000次时,则表示1s到了,秒变量加1。当秒变量达到60时,秒变量清零同时分变量加1。分变量达到60时,分变量清零同时时变量加1。当时变量达到24时,时变量清零。

由于实验要求由LED闪动做秒显示,因此每隔0.5s即计数500次时,P1^1(驱动LED灯)取反一次,从而实现LED灯闪动一次为1s,秒变量加1。

该模块流程图如下:

北京交通大学单片机实验报告电子时钟

北京交通大学单片机实验报告电子时钟

2. 显示模块

为在各位LED上分别显示不同的字符,需要采用循环扫描显示的方法,即在某一时刻只选通一条位选线,并输出该位的字段码,其余位则处于关闭状态。可见,各位LED显示的字符并不是同时出现的,但由于人眼的视觉暂留及LED的余辉,可以达到同时显示的效果。程序流程图如下:

北京交通大学单片机实验报告电子时钟

北京交通大学单片机实验报告电子时钟

采用动态显示时,需要确定LED各位显示的保持时间。由于LED从导通到发光有延时,时间太短会造成发光微弱,显示不清晰;如果显示时间太长,则会占用较多的CPU时间。

3. 按键判断及处理程序

按键的闭合与否,反映在电压上就是呈现出高电平或低电平。由于机械触点的弹性作用,在闭合及断开的瞬间,电压信号伴随有一定时间的抖动,抖动时间与按键的机械特性有关,一般是5~10ms。为了保证CPU确认一次按键动作,既不重复也不遗漏,必须消除抖动的影响。

通过软件消除抖动的方法为:在程序执行过程中检测到有按键按下时,调用一段延时(约10ms)子程序,然后判断该按键的电平是否仍然保持在闭合状态,如果是,则确认有键按下。

按键判断流程图如下:

按键处理流程图如下:

北京交通大学单片机实验报告电子时钟

北京交通大学单片机实验报告电子时钟

4. 音乐响铃模块

音乐闹铃程序:单片机演奏一个音符,是通过引脚,周期性的输出一个特定频率的方波。这就需要单片机,在半个周期内输出低电平、另外半个周期输出高电平,周而复始。众所周知,周期为频率的倒数,可以通过音符的频率计算出周期;演奏时,要根据音符的不同,把对应的半个周期的定时时间初始值,送入定时器,再由定时器按时输出高低电平。另外,音乐的节拍是由延时实现的。

我所使用的单片机音乐演奏程序中,包括了两个数据表,其中存放了事先算好的各种音符频率所对应的半周期的定时时间初始值。有了这些数据,单片机就可以演奏低音、中音、高音,三个八度共21个音符。演奏乐曲时,就根据音符的不同数值,从表中找到定时时间初始值,送入定时器即可控制音调。通过调用延迟来实现节拍数。

乐曲的数据,也要写个数据表:表中每三个数字,说明了一个音符,它们分别代表:第一个数字是音符的数值;第二个数字是123之一,代表低音、中音、高音;第三个数字是时间长度,以半拍为单位。乐曲数据表的结尾是三个0。

音节与频率的关系如下表所示

北京交通大学单片机实验报告电子时钟

北京交通大学单片机实验报告电子时钟

程序流程图如下:

5. 主函数流程图

五、 程序清单

#include <reg52.h>

#define uchar unsigned char

#define uint unsigned int

uchar code segcode[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};//共阳数码管0~9

uchar code select[]={0x0e,0x0d,0x0b,0x07};//正常显示时数码管位选

uchar code select1[]={0x0f,0x0d,0x0b,0x07};//高两位屏蔽时的位选(第2位只显示dp)

uchar code select2[]={0x0e,0x0d,0x0f,0x0f};//低两位屏蔽时的位选

uchar buffer[]={0,0,0,0};//用来存放时间

uint hour,min,sec;

uint alarmhour,alarmmin;

uint status=0;//模式值

sbit music=P1^0;//闹铃

sbit led=P1^1;//秒驱动LED闪烁

北京交通大学单片机实验报告电子时钟

bit ringoff=1;//闹铃停止

uint count=0;//定时器计数

uchar keyinput;

uchar buf=0xff;//用来存放按键值

uchar timer1h,timer1l,time;//time为节拍(延迟时间),timer1l、timer1h为计数器1初值

uchar code freqh[]={0xF9,0xF9,0xFA,0xFA,0xFB,0xFB,0xFC, //低音1~7 第一个八度 0xFC,0xFC,0xFD,0xFD,0xFD,0xFD,0xFE,//中音 1~7 第二个八度 0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF};//高音 1~7 第三个八度 uchar code freql[] = {0x21,0xE1,0x8C,0xD8,0x68,0xE9,0x5B, //低音1234567 0x8F,0xEE,0x44,0x6B,0xB4,0xF4,0x2D, //中音 1234567 0x47,0x77,0xA2,0xB6,0xDA,0xFA,0x16}; //高音 1234567 /*uchar code song[]={3,2,2,3,2,2,4,2,2,5,2,2,5,2,2,4,2,2,3,2,2,2,2,2,1,2,2,1,2,2,2,2,2,3,2,2,3,2,3,2,2,1,2,2,4,

3,2,2,3,2,2,4,2,2,5,2,2,5,2,2,4,2,2,3,2,2,2,2,2,1,2,2,1,2,2,2,2,2,3,2,2,2,2,3,1,2,1,1,2,4, 2,2,2,2,2,2,3,2,2,1,2,2,2,2,2,3,2,1,4,2,1,3,2,2,1,2,2,2,2,2,3,2,1,4,2,1,3,2,2,2,2,2,

1,2,2,2,2,2,5,1,2,3,2,2,3,2,2,3,2,2,4,2,2,5,2,2,5,2,2,4,2,2,3,2,2,4,2,1,2,2,1,1,2,2,1,2,2, 2,2,2,3,2,2,2,2,3,1,2,1,1,2,4,2,2,2,2,2,2,3,2,2,1,2,2,2,2,2,3,2,1,4,2,1,3,2,2,1,2,2,2,2,2, 3,2,1,4,2,1,3,2,2,2,2,2,1,2,2,2,2,2,5,1,2,3,2,2,3,2,2,3,2,2,4,2,2,5,2,2,5,2,2,4,2,2,3,2,2, 4,2,1,2,2,1,1,2,2,1,2,2,2,2,2,3,2,2,2,2,3,1,2,1,1,2,4,0,0,0};

//欢乐颂 */

uchar code song[]={5,3,2,3,3,1,4,3,1,5,3,2,3,3,1,4,3,1,5,3,1,5,2,1,6,2,1,7,2,1,1,3,1,2,3,1,3,3,1,4,3,1,3,3,2,1,3,1,2,3,1,3,3,2,

3,2,1,4,2,1,5,2,1,6,2,1,5,2,1,4,2,1,5,2,1,3,2,1,4,2,1,5,2,1,4,2,2,6,2,1,5,2,1,4,2,2,3,2,1,2,2,1,3,2,1,2,2,1,1,2,1,2,2,1,

3,2,1,4,2,1,5,2,1,6,2,1,4,2,2,6,2,1,5,2,1,6,2,2,7,2,1,1,3,1,5,2,1,6,2,1,7,2,1,1,3,1,2,3,1,3,3,1,4,3,1,5,3,1,3,3,2,1,3,1,2,3,1,

3,3,2,2,3,1,1,3,1,2,3,1,7,2,1,1,3,1,2,3,1,3,3,1,2,3,1,1,3,1,7,2,1,1,3,2,6,2,1,7,2,1,1,3,2,1,2,1,2,2,1,

3,2,1,4,2,1,3,2,1,2,2,1,3,2,1,1,3,1,7,2,1,1,3,1,6,2,2,1,3,2,7,2,1,6,2,2,5,2,1,4,2,1,5,2,1,4,2,1,3,2,1,4,2,1,5,2,1,6,2,1,7,2,1,1,2,1,

6,2,2,1,3,1,7,2,1,1,3,2,7,2,1,6,2,1,7,2,1,1,3,1,2,3,1,1,3,1,7,2,1,1,3,1,6,2,1,7,2,1,0,0,0}; //卡农

//一个音符有三个数字。前为音节、中为第几个八度、后为时长(以半拍为单位)。 /******************延迟tms程序**************************/

void delay1ms(uint t)

{

uint i;

while(t--)

for(i=0;i<123;i++) ;

}

/******************按键处理程序************************/

void keyprocess(uchar key)

{

switch(key)

{

case 0xe0:status++; if(status>=5) status=0;break;//第一个键被按下,模式值+1 case 0xd0: //第二个键被按下 switch(status)

{

case 0x01:if(hour<23) hour++; else hour=0;break;//模式1小时+1,到24变成0

case 0x02:if(min<59) min++; else min=0;break; //模式2分钟+1

case 0x03:if(alarmhour<23) alarmhour++; else alarmhour=0;break;//模式3闹铃小时+1

case 0x04:if(alarmmin<59) alarmmin++; else alarmmin=0;break;//模式4闹铃分钟+1

}

break;

case 0xb0: //第三个键被按下 switch(status)

{

case 0x01:if(hour>0) hour--; else hour=23;break;

case 0x02:if(min>0) min--; else min=59;break;

case 0x03:if(alarmhour>0) alarmhour--; else alarmhour=23;break;

case 0x04:if(alarmmin>0) alarmmin--; else alarmmin=59;break;

}

break;

case 0x70:ringoff=~ringoff; break; //第四个键按下,闹铃开关。

default:break;

}

}

/******************正常走时时数码管显示程序*************/

void display()

{

uint i;

if(status==3||status==4) //闹铃时间

{

buffer[0]=alarmhour/10;

buffer[1]=alarmhour%10;

buffer[2]=alarmmin/10;

buffer[3]=alarmmin%10;

}

else //正常时间

{

buffer[0]=hour/10;

buffer[1]=hour%10;

buffer[2]=min/10;

buffer[3]=min%10;

}

for(i=0;i<4;i++)

{

if(i==1)

{

P0=segcode[buffer[i]]-0x80;//第二个数码管显示dp P2=select[i];

delay1ms(1); //否则数码管上无显示

P2=0xff;//否则乱码

}

else

{

P0=segcode[buffer[i]]; //查找段码值

P2=select[i]; //查找位选

delay1ms(1); //否则数码管上无显示

P2=0xff;//否则乱码

}

}

}

/********************高两位屏蔽时的显示程序*************/ void display1()

{

uint i;

if(status==1)

{

buffer[0]=hour/10;

buffer[1]=hour%10;

buffer[2]=min/10;

buffer[3]=min%10;

}

else //status==3时

{

buffer[0]=alarmhour/10;

buffer[1]=alarmhour%10;

buffer[2]=alarmmin/10;

buffer[3]=alarmmin%10;

}

for(i=0;i<4;i++)

{

if(i==1)

{

P0=0x7f; //只显示小数点

P2=select1[i];

delay1ms(1); //否则数码管上无显示

P2=0xff;//否则乱码

}

else

{

P0=segcode[buffer[i]];

P2=select1[i];

delay1ms(1); //否则数码管上无显示

P2=0xff;//否则乱码

}

}

}

/*****************低两位屏蔽时的显示程序************/ void display2()

{

uint i;

if(status==2)

{

buffer[0]=hour/10;

buffer[1]=hour%10;

buffer[2]=min/10;

buffer[3]=min%10;

}

else //status==4时

{

buffer[0]=alarmhour/10;

buffer[1]=alarmhour%10;

buffer[2]=alarmmin/10;

buffer[3]=alarmmin%10;

}

for(i=0;i<4;i++)

{

if(i==1)

{

P0=segcode[buffer[i]]-0x80; //第2个数码管显示dp P2=select2[i];

delay1ms(1); //否则数码管上无显示

P2=0xff;//否则乱码

}

else

{

P0=segcode[buffer[i]];

P2=select2[i];

delay1ms(1); //否则数码管上无显示

P2=0xff;//否则乱码

}

}

}

/******************数码管抖动延迟********************/

void delaydd(uint p)

{

uint k;

for(k=p;k!=0;k--)

display();//延迟时也显示,避免数码管闪烁

}

/**********************判断按键程序******************/

void press()

{

keyinput=P1&0xf0; //取按键状态

if(keyinput!=0xf0) //有按键按下?

{

delaydd(20); //利用数码管显示来达到延迟的效果。防止有键按下是数码管闪

if(keyinput!=0xf0) //开关仍在处于闭合状态

{

buf=keyinput;//将按键值赋给buf

}

else

{

keyprocess(buf);

buf=0xff;

}

}

else

{

keyprocess(buf); //当松开按键时(无键按下)才进行按键处理

buf=0xff;

}

}

/*******************定时器T0中断*****************/

void t0() interrupt 1

{

TH0=(65536-1000)/256;

TL0=(65536-1000)%256;//重新装入计数初值

count++; //正常计时

if(count==550) // 定时 0.5S 到,以下为时钟的正常走钟逻辑。理论上应为

500

{

led=~led; //LED灯亮0.5s,灭0.5s。达到秒显示的效果 count=0;

if(led==0) //达到1s

sec++;

}

if(sec>=60)

{

sec=0;min++;

}

if(min>=60)

{

min=0;hour++;

}

if(hour>=24)

hour=0;

}

/**********定时器T1中断。用来产生不同频率方波********/ void t1() interrupt 3

{

TR1=0; //先关中断

music=~music; //产生方波

TH1=timer1h;

TL1=timer1l; //重新装入初值

TR1=1; //在开始计数

}

/*********************延迟半节拍数*******************/ void delay(uchar t)

{

uchar t1;

unsigned long t2;

for(t1=0;t1<10*t;t1++)

{

display(); //防止响音乐时数码管无显示

press();

for(t2=0;t2<200;t2++) ;

}

TR1=0; //一个音符发送完后关计数

}

/********************产生音乐程序***************/ void send()

{

TH1=timer1h;

TL1=timer1l;

TR1=1;

delay(time); //根据节拍数调用延迟

}

/********************主函数*********************/

void main()

{

uint h=0;

uint i,k;

music=1;

TMOD=0x11;

TH0=(65536-1000)/256; //1ms计数初值

TL0=(65536-1000)%256;

EA=1;

ET0=1;//开中断

ET1=1;

P3=0xff;

while(1)

{

if(status==1||status==2) TR0=0;//当调整时间时,秒不走。 else TR0=1; //开始计数

if(status==0) P3=0xff;

if(status==1||status==2) P3=0xfe; //调整时间指示灯 if(status==3||status==4) P3=0xfd; //调整闹铃指示灯

press(); //判断是否有键按下并处理

if(hour==alarmhour&&min==alarmmin)//闹铃时间到 {

i=0;

time=1;

while(!ringoff)

{

k=song[i]+7*song[i+1]-8;//找到相应音符的计数初值 timer1h=freqh[k];

timer1l=freql[k]; //装入计数初值

time=song[i+2];

i=i+3;

if(time!=0) send(); //播放音符

else i=0; //循环播放

}

}

music=1;

}

} if(status==0) //给显示加上一些效果 display(); else if(status==1||status==3) //高两位闪烁 { if(h<75) {display();h++;} else { display1(); h++; if(h==150) h=0; } } else //低两位闪烁 { if(h<75) {display(); h++;} else { display2(); h++; if(h==150) h=0; } }

六、 实验遇到的问题及解决方法

1. 问题:数码管上无显示

解决方法:用三极管来驱动数码管的位选端,否则数码管发光微弱。

2. 问题:数码管显示出现乱码

解决方法:动态显示时,需要确定LED各位显示的保持时间。在某一位显示结束后,应将P2口置为0xff。

3. 问题:按下按键时,数码管上的数字跳跃式变化。

解决方法:按键处理程序调用的位置错误。当有键按下时,将按键值赋给buf,松开按键时,才对buf中的数据进行按键处理,并重新将buf置为0xff。若在键按下时进行处理,会导致数字跳跃式变化。

if(keyinput!=0xf0)

{

buf=keyinput;//有键按下时赋给buf

}

else

{

keyprocess(buf);//当松开按键时(无键按下)才进行按键处理 buf=0xff;

}

4. 问题:按下按键时,数码管的显示闪烁一次

解决方法:在程序执行过程中检测到有按键按下时,会调用一段延时(约10ms)子程序来消抖。若通过执行空操作来实现延时10ms,则会使动态扫描的时间间隔变长,数码管亮度变暗。因此我通过调用数码管显示程序来实现延时10ms,即如下:

void delaydd(uint p)

{

uint k;

for(k=p;k!=0;k--)

display();//延迟时也显示,避免数码管闪烁

}

5. 问题:在放音乐过程中,数码管上无显示

解决方法:音乐的节拍是由延时实现的。当放音乐时,一直在调用延时程序,因此在延时程序中调用数码管显示程序,问题即可得到解决。如下:

void delay(uchar t)

{

uchar t1;

unsigned long t2;

for(t1=0;t1<10*t;t1++)

{

display(); //防止响音乐时数码管无显示

press();

for(t2=0;t2<200;t2++) ;

}

TR1=0; //一个音符发送完后关计数

}

若将display()放在第二重for循环内,会对音乐有较大的影响。

6. 问题:时钟计时的准确性不是很好

解决方法:定时器0计时中断程序每隔1ms中断一次并当作一个计数,当达到0.5s时,count应为500。实际中,可以通过调整count的值来调整其准确性。

if(count==500) // 定时 0.5S 到。根据实际情况调整count的值

{

led=~led;

count=0;

if(led==0) //达到1s

sec++;

}

七、 实验感想

这次课程设计是我第一次真正的接触单片机。从选题到定稿,从理论到实践,在这十多天里,我学到了很多的东西。

一开始我从参考书上找来了课题,但是毕竟是参考书,做到后来发现很多程序都是不完善的,比如说当有按键按下时,数码管的显示会有所闪烁等等。在解决问题的

过程中,我利用了proteus和keil的联调,通过软件仿真来检验程序的正确性。不仅巩固了以前所学的内容,而且还学到了很多在书本上没有学到的知识。其中收获最大的就是学会了中断的使用,利用定时器的中断计数来进行计时。为了使操作更加人性化,我还添加了一些特殊效果:当对时钟进行调整时,相应数码管会进行闪烁。

同时,在这次设计中也遇到了很多实际性的问题。在实际设计中才发现,书本上理论性的东西与在实际运用中的还是有一定出入的,所以有些问题不但要深入地理解,而且要不断地更正以前的错误思维。一切问题必须要靠自己一点一滴的解决,而在解决的过程当中你会发现自己在飞速的提升。对于单片机设计,其硬件电路是比较简单的,主要是解决程序设计中的问题,而程序设计是一个很灵活的东西,它反映了你解决问题的逻辑思维和创新能力,它才是一个设计的灵魂所在。因此在整个设计过程中大部分时间是用在程序上面的。很多子程序是可以借鉴书本上的,但怎样衔接各个子程序才是关键问题所在。比如在音乐响铃时,会导致数码管没有显示。这就要求我们仔细分析程序的结构,在响铃程序的相应位置调用数码管显示子程序。

通过这次课程设计我也发现了自身存在的不足之处,虽然感觉理论上已经掌握,但在运用到实践的过程中仍有意想不到的困惑,经过一番努力才得以解决。不过因此也培养了我独立工作的能力,树立了对自己工作能力的信心,相信会对今后的学习工作生活有非常重要的影响。而且大大提高了我的动手的能力,使我充分体会到了在创造过程中探索的艰难和成功时的喜悦。

八、 参考文献

1. 戴胜华,蒋大明,杨世武等. 单片机原理与应用[M]. 北京:北京交通大学出版社,

2008.

2. 张毅刚. 单片机原理与应用[M]. 北京: 高等教育出版社, 2004.

3. 周航慈. 单片机程序设计基础[M]. 北京: 北京航空航天大学出版社,1997. 附录(作品实物)

北京交通大学单片机实验报告电子时钟

相关推荐