2024年6月5日发(作者:)

题目:

基于AT89C52单片机的多模式流水灯设计——程序设计

一、 概述

AT89C52是一个低电压,高性能CMOS 8位单片机,片内含8k bytes的可反复擦写的

Flash只读程序存储器和256 bytes的随机存取数据存储器(RAM),器件采用ATMEL公司

的高密度、非易失性存储技术生产,兼容标准MCS-51指令系统,片内置通用8位中央处理

器和Flash存储单元。AT89C52有40个引脚,32个外部双向输入/输出(I/O)端口,同时内

含2个外中断口,3个16位可编程定时计数器,2个全双工串行通信口,2个读写口线,AT89C52

可以按照常规方法进行编程。

P为40 脚双列直插封装的8 位通用微处理器,采用工业标准的C51内核,在内部功能

及管脚排布上与通用的8xc52 相同,其主要用于会聚调整时的功能控制。功能包括对会聚主

IC 内部寄存器、数据RAM及外部接口等功能部件的初始化,会聚调整控制,会聚测试图控

制,红外遥控信号IR的接收解码及与主板CPU通信等。主要管脚有:XTAL1(19 脚)和X

TAL2(18 脚)为振荡器输入输出端口,外接12MHz 晶振。RST/Vpd(9 脚)为复位输入

端口,外接电阻电容组成的复位电路。VCC(40 脚)和VSS(20 脚)为供电端口,分别接

+5V电源的正负端。P0~P3 为可编程通用I/O 脚,其功能用途由软件定义。

二、 实验原理与设计方案

设计平台:

AT89C51单片机;PC机,含Keil软件平台,Proteus软件平台;单片机开发板。

第1页 共17页

如下图2为开发板:

设计思路:

本系统主要通过P1口来控制LED的闪烁,有10种模式(0~9)可供选择。通过模式按键来选择

需要的闪烁方式,这里主要通过几个计数器来控制,按下一次按键,计数器加1,模式切换到下一

种模式。同时利用数码管将模式显示出来。另外还有一个加速和减速的按键,系统设定有30种速度

可供选择。按下一次加速键,速度计数器加1,按下一次减速键,速度计数器减1。其中加速减速主

要是通过设定定时器2的定时时间来改变的,加速,减速主要是将定时器的时间加减。特别一提的

是,按键的按下读取要消除抖动。

设计方案与电路模块:

要实现流水灯功能,我们只要将发光二极管LED1~LED16依次点亮、熄灭,16只LED灯便会

一亮一暗的做流水灯了。在这个设计中,我们不只是单一的做向左运动或向右运动,而是设计了十

个模式,而每一种模式也有30种速度可供选择,因此我们在普通的流水灯基础上增加了模式的选择

功能、速度的选择功能和一个数码显示管来显示模式编号。在此我们还应注意一点,由于人眼的视

觉暂留效应以及单片机执行每条指令的时间很短,我们在控制二极管亮灭的时候应该延时一段时间,

否则我们就看不到“流水”效果了。

该流水灯实验一共可分为六个模块:晶振电路模块、复位电路模块、数码管显示模块、AT89C

52单片机模块、功能模式选择模块、LED流水灯模块。

其中五个模块连接在AT89C52单片机上构成一个完整的系统。此系统的原理框图如图1所示:

第2页 共17页

图1

1、晶振电路模块

晶振是晶体振荡器的简称。在电气上它可以等效成一个电容和一个电阻并联再串联一个电容的

二端网络,电工学上这个网络有两个谐振点,以频率的高低分其中较低的频率是串联谐振,较高的

频率是并联谐振。由于晶体自身的特性致使这两个频率的距离相当的接近,在这个极窄的频率范围

内,晶振等效为一个电感,所以只要晶振的两端并联上合适的电容它就会组成并联谐振电路。这个

并联谐振电路加到一个负反馈电路中就可以构成正弦波振荡电路,由于晶振等效为电感的频率范围

很窄,所以即使其他元件的参数变化很大,这个振荡器的频率也不会有很大的变化。

晶振有一个重要的参数,那就是负载电容值,选择与负载电容值相等的并联电容,就可以得到

晶振标称的谐振频率。一般的晶振的负载电容为15p或12.5p,如果再考虑元件引脚的等效输入电容,

则两个22p的电容构成晶振的振荡电路就是比较好的选择。

2、复位电路模块

这个模块主要进行复位操作。

3、LRD流水灯模块

要实现流水灯功能,我们只要将发光二极管LED1~LED16依次点亮、熄灭,16只LED灯便会

一亮一暗的做流水灯了。在此我们还应注意一点,由于人眼的视觉暂留效应以及单片机执行每条指

令的时间很短,我们在控制二极管亮灭的时候应该延时一段时间,否则我们就看不到“流水”效果了。

且每一个LED灯于一个电阻串联。

4、功能模式选择模块

在该模块中,我们要运用3个键,分别是模式键、加速键和减速键。因为该设计一共设有10

种模式,因此模式键的功能就是选择运行哪一个模式。加速键顾名思义,是调节增加流水灯的运行

速度。减速键就是减少流水灯的运行速度。

5、数码管显示模块

此模块主要是用来显示按下模式键后选择是哪个模式,其选择范围为0—9,既10个模式,因

此只用一个数码管就可以了。

第3页 共17页

三、硬件电路图与流程图

1、电路图:

硬件电路图

2、消抖模块流程图

因为在按键产生高电平的时候,数码管会产生抖动。在上升沿处抖动不强烈,可以忽略。但在下降

沿处抖动会很强烈,而使我们很难观察到数码管的变化,只是会看到数码管在一直闪动,因此我们

需要增加延迟的时间来消除抖动。如减慢键的程序:

if(!(CheckValue&0x20)) //判断是否被按下

{

delay(5); //判断上升沿

if(!(CheckValue&0x20))

{

第4页 共17页

Key|=0x01;

delay(500); // 下降沿,消除抖动

}

else

return 0x00;

}

上升沿的抖动可以忽略,故延迟时间只是5ms,而下降沿抖动剧烈,故延迟时间给了500ms,用以

消除抖动。

3、定时器中断和速度改变模块

定时器中断程序:

unsigned int TimerCount,SystemSpeed,SystemSpeedIndex;

void InitialTimer2(void)

{

T2CON = 0x00; //8位自动重装模式,设定时间,控制中断,控制流水灯速度

TH2 = RCAP2H = 0xFC; //重装值,初始值 TL2 = RCAP2L = 0x18;

ET2=1; //定时器 2 中断允许

TR2 = 1; //定时器 2 启动

EA=1;

}

中断处理函数:

void Timer2(void) interrupt 5 using 3

{

TF2 = 0; //中断端口,中断标志清除( Timer2 必须软件清除标志)

if(++TimerCount>=SystemSpeed)

{

TimerCount = 0;

TimerEventRun();

}

}

速度改变程序:

unsigned int code SpeedCode[]={ 1, 2, 3, 5, 8, 10, 14, 17, 20, 30,

40, 50, 60, 70, 80, 90, 100, 120, 140, 160,

180, 200, 300, 400, 500, 600, 700, 800, 900,1000}; //30种速度

第5页 共17页

void SetSpeed(unsigned char Speed)

{

SystemSpeed =SpeedCode[Speed];

}

这里采用数组法,并且定义一开始的速度是第十个,既30ms。

4、主程序流程图

由调用模式的模块实现:

void TimerEventRun(void)

{

if(RunMode==0x00)

{

Mode_0();

}

else if(RunMode ==0x01)

{

Mode_1();

}

else if(RunMode ==0x02)

{

Mode_2();

}

else if(RunMode ==0x03)

{

Mode_3();

}

else if(RunMode ==0x04)

{

Mode_4();

}

else if(RunMode ==0x05)

{

Mode_5();

}

else if(RunMode ==0x06)

{

Mode_6();

}

else if(RunMode ==0x07)

{

Mode_7();

}

else if(RunMode ==0x08)

{

Mode_8();

}

else if(RunMode ==0x09)

第6页 共17页

{

Mode_9();

}

}

四、实验结果举例

1、模式0:

一个亮灯循环左移 ,按下列步骤运行,初始状态○○○○○○○○

1 ○○○○○○○●

2 ○○○○○○●○

3 ○○○○○●○○

4 ○○○○●○○○

5 ○○○●○○○○

6 ○○●○○○○○

7 ○●○○○○○○

8 ●○○○○○○○

回初始状态重复循环

2、 模式5:

四个亮灯一起循环左移,按下列步骤运行,初始状态○○○○○○○○

1 ○○○○○○○●

第7页 共17页

2 ○○○○○○●●

3 ○○○○○●●●

4 ○○○○●●●●

5 ○○○●●●●○

6 ○○●●●●○○

7 ○●●●●○○○

8 ●●●●○○○○

9 ●●●○○○○○

10●●○○○○○○

11●○○○○○○○

12○○○○○○○○

3、 模式8:

五、实验总结

在这次的作业中,我们选择做流水灯的实验,虽然难度不大,但我们用C语言编写了10种流水

灯的模式,并从中较好的理解和运用AT89C52单片机的功能。我们利用空余时间焊了一个实物

电路板,但由于粗心等原因,焊板质量比较粗糙。最终该焊板也没能正常工作。以下是我们

的电路板:

第8页 共17页

由于电路板不能正常工作,我们只好把原程序稍作改动,移植到开发板上,如图:

另外上面所提到的电平抖动(电平毛刺)问题,按键抖动的问题对所有的处理器都存在,单

片机、CPLD/FPGA以及ARM等等,都会存在抖动,所以在编写程序的时候,需要适当的绕开抖动

的边沿再去采样,也就是说,要避免直接去采样按键瞬间产生的不稳定的信号。通常采用的方法有:

延时采样和多次采样等,前者应用较多,如单片机中,按键之后延时一段时间再读取信号即可。本

实验就是采用这种方法来消除抖动的。 关于原程序的改动,就是去除了rst电路,其它原理和

原程序完全一致。

在编写程序时用的是C语言,其主要原因是因为相对之下,我比较掌握C语言,对于接

近硬件方面的汇编不是很熟悉。编写最开始是要调用库文件、单片机端口、变量等。程序主

要分5个部分:部分一是关于模式键的判断;部分二是流水灯的功能实现;部分三是调用模

式;部分四是中断处理;部分五是执行循环。上面已经提过,消除抖动非常重要,主要办法

是在下降沿地方给一个较大的延迟时间,以覆盖抖动的区域,不然实际操作中我们是看不到

数码管的显示。通过这次作业,我对单片机的学习有了更深刻的掌握,并复习了C语言,对

以后的设计提供基础。

第9页 共17页

六、附录:完整C代码及注释

#include

unsigned char RunMode;

sbit dula = P2^6;

sbit wela = P2^7;

sbit leden = P2^5;

void delay(unsigned int z) //无返回值,调用无符号变量z

{

unsigned int x,y;

for(x=z;x>0;x--) //x减1,循环语句

for(y=110;y>0;y--);

}

//**********************************System

Fuction*************************************************第一部分

void Delay1ms(unsigned int count)

{

unsigned int i,j;

for(i=0;i

for(j=0;j<120;j++);

}

unsigned char code LEDDisplayCode[] =

{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //0—9,定义一个无符号数组,显示一

共有10种模式

void Display(unsigned char Value) //上面10个取一个放进value

{

P0 = LEDDisplayCode[Value]; //P0端口接数码管

}

void LEDFlash(unsigned char Count) //设定LED的闪烁次数

{

unsigned char i;

bit Flag; //定义flag是一个位数

for(i = 0; i

{

Flag = !Flag;

if(Flag)

Display(RunMode);

else

Display(0x10);

Delay1ms(100); //延时100ms

}

Display(RunMode);

} //整个功能为开电源后数码管闪了0.1s后熄灭

第10页 共17页

unsigned char GetKey(void) //判断是模式,加快还是减慢按键被按下

{ //部分二,判断键是否被按下

unsigned char KeyTemp,CheckValue,Key = 0x00; //初始三个键为0

CheckValue = P3&0xe0;

if(CheckValue==0xe0) //什么都没有按下

return 0x00;

Delay1ms(10);

KeyTemp = P3&0xe;

if(KeyTemp==CheckValue)

return 0x00;

if(!(CheckValue&0x20)) //减慢键,判断是否按下

{

delay(5); //判断上升沿

if(!(CheckValue&0x20))

{

Key|=0x01;

delay(500); //判断下降沿,消除抖动

}

else

return 0x00;

}

if(!(CheckValue&0x40)) //加速键

{

delay(5);

if(!(CheckValue&0x40))

{

Key|=0x02;

delay(500);

}

else

return 0x00;

}

if(!(CheckValue&0x80)) //模式键

{

delay(5);

if(!(CheckValue&0x80))

{

Key|=0x04;

delay(500);

}

else

return 0x00;

}

return Key;

第11页 共17页

}

unsigned int TimerCount,SystemSpeed,SystemSpeedIndex; //定义计数器、速度、速度数值

void InitialTimer2(void) //调用硬件

{

T2CON = 0x00; //8位自动重装模式,设定时间,控制中断,控制流灯速度

TH2 = RCAP2H = 0xFC; //重装值,初始值 TL2 = RCAP2L = 0x18;

ET2=1; //定时器 2 中断允许

TR2 = 1; //定时器 2 启动

EA=1;

}

//部分三,实现功能的主程序

unsigned int code SpeedCode[]={ 1, 2, 3, 5, 8, 10, 14, 17, 20, 30,

40, 50, 60, 70, 80, 90, 100, 120, 140, 160,

180, 200, 300, 400, 500, 600, 700, 800, 900,1000};//30

种速度,当速度加到最大值时数码管会闪动

void SetSpeed(unsigned char Speed)

{

SystemSpeed =SpeedCode[Speed];

}

void LEDShow(unsigned int LEDStatus) //初始全亮

{

P1 = ~(LEDStatus&0x00FF);

}

void InitialCPU(void) //初始化CPU

{

RunMode = 0x00; //数码管

TimerCount = 0;

SystemSpeedIndex = 10; //定义初始速度为第十个:30ms

leden = 1; //使用上

wela = 1; //使用上

P0 = 0x00; //全亮

wela = 0; //没使用

P1 = 0x00;

Delay1ms(500); //闪烁

P1 = 0xFF;

SetSpeed(SystemSpeedIndex); //取速度值

Display(RunMode);

}

//Mode 0,一个灯由上至下依次点亮

unsigned int LEDIndex = 0;

bit LEDDirection = 1,LEDFlag = 1;

void Mode_0(void)

第12页 共17页

{

LEDShow(0x0001<

LEDIndex = (LEDIndex+1)%16;

}

//Mode 1,一个灯由下至上依次点亮

void Mode_1(void)

{

LEDShow(0x8000>>LEDIndex);

LEDIndex = (LEDIndex+1)%16;

}

//Mode 2,一个灯先由下至上依次点亮,再由上至下点亮

void Mode_2(void)

{

if(LEDDirection)

LEDShow(0x0001<

else

LEDShow(0x8000>>LEDIndex);

if(LEDIndex==15)

LEDDirection = !LEDDirection;

LEDIndex = (LEDIndex+1)%16;

}

//Mode 3,灯首先全亮,一个灭灯先由下至上依次熄灭,再由上至下熄灭

void Mode_3(void)

{

if(LEDDirection)

LEDShow(~(0x0001<

else

LEDShow(~(0x8000>>LEDIndex));

if(LEDIndex==15)

LEDDirection = !LEDDirection;

LEDIndex = (LEDIndex+1)%16;

}

//Mode 4,灯首先全亮,由下至上依次熄灭,再由下至上依次点亮,再由上至下点亮,最后由上至

下熄灭

void Mode_4(void)

{

if(LEDDirection)

{

if(LEDFlag)

LEDShow(0xFFFE<

else

LEDShow(~(0x7FFF>>LEDIndex));

}

else

{

if(LEDFlag)

第13页 共17页

LEDShow(0x7FFF>>LEDIndex);

else

LEDShow(~(0xFFFE<

}

if(LEDIndex==15)

{

LEDDirection = !LEDDirection;

if(LEDDirection) LEDFlag = !LEDFlag;

}

LEDIndex = (LEDIndex+1)%16;

}

//Mode 5,四个亮灯一起先由下至上点亮,再由上至下熄灭,和mode2对比

void Mode_5(void)

{

if(LEDDirection)

LEDShow(0x000F<

else

LEDShow(0xF000>>LEDIndex);

if(LEDIndex==15)

LEDDirection = !LEDDirection;

LEDIndex = (LEDIndex+1)%16;

}

//Mode 6,四个灭灯一起先由下至上熄灭,再由上至下点亮,与mode5相反

void Mode_6(void)

{

if(LEDDirection)

LEDShow(~(0x000F<

else

LEDShow(~(0xF000>>LEDIndex));

if(LEDIndex==15)

LEDDirection = !LEDDirection;

LEDIndex = (LEDIndex+1)%16;

}

//Mode 7,六个灯一起先由下至上熄灭,再由上至下点亮

void Mode_7(void)

{

if(LEDDirection)

LEDShow(0x003F<

else

LEDShow(0xFC00>>LEDIndex);

if(LEDIndex==9)

LEDDirection = !LEDDirection;

LEDIndex = (LEDIndex+1)%10;

第14页 共17页

}

//Mode 8,由上至下亮灯往下走,当第一个亮灯走到第二个灯位时,第二个亮灯开始走,如此积累,

知道8个灯全亮

void Mode_8(void)

{

LEDShow(++LEDIndex);

}

//Mode 9,与mode8相反

void Mode_9(void)

{

LEDShow(--LEDIndex);

}

//调用模式

void TimerEventRun(void)

{

if(RunMode==0x00)

{

Mode_0();

}

else if(RunMode ==0x01)

{

Mode_1();

}

else if(RunMode ==0x02)

{

Mode_2();

}

else if(RunMode ==0x03)

{

Mode_3();

}

else if(RunMode ==0x04)

{

Mode_4();

}

else if(RunMode ==0x05)

{

Mode_5();

}

else if(RunMode ==0x06)

{

Mode_6();

}

else if(RunMode ==0x07)

{

第15页 共17页

Mode_7();

}

else if(RunMode ==0x08)

{

Mode_8();

}

else if(RunMode ==0x09)

{

Mode_9();

}

}

//中断处理函数

void Timer2(void) interrupt 5 using 3

{

TF2 = 0; //中断端口,中断标志清除( Timer2 必须软件清除标志)

if(++TimerCount>=SystemSpeed)

{

TimerCount = 0;

TimerEventRun();

}

}

unsigned char MusicIndex = 0;

void KeyDispose(unsigned char Key)

{

if(Key&0x01) // 按键设定8个模式,判断key值

{

LEDDirection = 1;

LEDIndex = 0;

LEDFlag = 1;

RunMode = (RunMode+1)%10; //循环计数

Display(RunMode);

}

if(Key&0x02) //流水灯流动的速度减慢

{

if(SystemSpeedIndex>0) //第几个速度

{

--SystemSpeedIndex;

SetSpeed(SystemSpeedIndex);

}

else

{

LEDFlash(6); //数码管闪烁时间

}

}

if(Key&0x04) //流水灯流动的速度加快

{

if(SystemSpeedIndex<28)

第16页 共17页

{

++SystemSpeedIndex;

SetSpeed(SystemSpeedIndex);

}

else

{

LEDFlash(6);

}

}

}

//***********************************************************************************

main()

{

unsigned char Key;

InitialCPU();

InitialTimer2();

while(1) //条件语句,死循环,永远执行

{

Key = GetKey();//获取按键

if(Key!=0x00)

{

KeyDispose(Key);//按照设定的模式和速度亮灯

}

}

}

第17页 共17页