第2讲(二)流水灯的实现

前面我们已经完成了流水灯的电路设计,现在我们开始根据电路图实现流水灯的程序设计。在程序的开头我们将根据第1讲的点亮一个LED灯的程序进行扩展,用最容易想到的方法实现流水灯效果。然后我们将利用其他方法改写这个程序,一步一步实现最优化的程序。

步骤一:让一个LED灯闪烁起来。

clip_image002

如上图,我们知道如果让P0口的第0位(下面我们用led1表示)赋值0即低电平,那么LED小灯点亮,而给它赋值1即高电平,那么它便会熄灭。所以我们的程序改成这样:

#include

sbit led1 = P0^0;

void main()

{

led1 = 0; //小灯亮

led1 = 1; //小灯灭

}

但是当我们将该程序编译并运行后,发现结果并不是我们想要的那样LED灯有规律的一亮一灭,而是很无规律的乱闪。这是为什么呢?原因是单片机CPU执行程序语句的速度非常快,而LED灯的反应却没那么灵敏,所以当执行“led1 = 0;”语句LED灯亮后,又执行“led1 = 1;”时可能LED灯还没有来得及熄灭,又开始执行“led1 = 0;”语句了,所以LED灯长时间亮着。相反的,LED灯可能长时间熄灭。这就造成了程序的混乱。

为了解决上面的问题,我们可以让CPU执行完“led1 = 0;”语句后,不直接执行“led1 = 1;”而是去执行一些其他无关的操作,那么LED灯就会亮一段时间。此后再执行“led1 = 1;”让LED灯熄灭,再去执行一些其他无关的操作,那么LED灯就会熄灭一段时间,然后再循环到程序开始,这样LED灯便可以稳定的一亮一灭闪烁起来了。

我们只更改主函数:

void main()

{

int i,j; //加入两个临时变量

led1 = 0;

for(i=1000;i>0;i--) //双重循环,循环体为空,就是说这个循环什么也不做,只是

for(j=100;j>0;j--); //进行空循环,来消耗CPU周期,达到延时的效果

led1 = 1;

for(i=1000;i>0;i--)

for(j=100;j>0;j--);

}

可以看出,是我们加入了循环函数来让CPU进行其他操作的,这样便实现了一个LED灯的闪烁效果。但是同样的双重for循环语句我们却写了两遍,显得很臃肿,我们可以将for循环写到一个函数里,然后用到延时的地方只需要调用这个函数名即可。程序修改如下:

#include

sbit led1 = P0^0;

void delay(); //加入子函数的声明

void main() //主函数

{

led1 = 0;

delay(); //子函数的调用

led1 = 1;

delay(); //子函数的调用

}

void delay() //子函数的定义

{

int i,j;

for(i=1000;i>0;i--)

for(j=100;j>0;j--);

}

在程序中除了main()函数外其他函数都称为子函数,而子函数一般定义在主函数的后面。这样在程序的开头必须先对子函数进行声明,说明我下面要用到这样一个函数。声明与定义的区别就是定义需将函数的具体实现语句写出来,而声明不用。

到此,一个LED灯闪烁的程序便完成了。

步骤二:让多个LED灯同时点亮。

学会了点亮一个LED灯,那么点亮多了LED灯也就很容易了。下面是第2讲(一)中画好的电路图,可以知道只需同时让需要亮的LED灯的P0口的相应位赋值0即可。下面的程序我们将让第1,3,5,7个LED灯点亮。

liushui

#include

sbit led1 = P0^0;

sbit led2 = P0^1;

sbit led3 = P0^2;

sbit led4 = P0^3;

sbit led5 = P0^4;

sbit led6 = P0^5;

sbit led7 = P0^6;

sbit led8 = P0^7;

void main()

{

led1 = 0; //点亮第1个LED灯

led3 = 0; //点亮第3个LED灯

led5 = 0; //点亮第5个LED灯

led7 = 0; //点亮第7个LED灯

}

运行效果如下:

clip_image006

可以看出,P0口没有赋值的其他位默认为高电平,其实单片机初始化后所有端口均为高电平。

到这里你是不是觉得上面那么多位定义很麻烦,其实还有一种对P0口整体赋值的方法,被称为总线方法。例如上面的程序可以改写为:

#include

void main()

{

P0 = 0xaa; //利用总线方法给端口赋值

}

其中,语句“P0 = 0xaa;”中的0xaa是十六进制数,C语言中十六进制数都以0x开头。十六进制中用0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f来表示十进制中的0到15,

其中a表示10,即二进制的1010,那么0xaa就表示1010 1010,这八位数从右到左分别对应P0口的0到7位,如下图:

clip_image008

所以执行完语句“P0 = 0xaa;”后,便能点亮第1,3,5,7个小灯。可以看出利用总线的方法能大大的简化程序。

步骤三:实现流水灯。

会让一个小灯亮灭了,会点亮多个小灯了,那么也许你已经想到了怎样去实现流水灯效果。还用上面的电路图,程序如下:

#include

void delay();

void main()

{

P0 = 0xfe; //点亮第1个小灯

delay();

P0 = 0xff; //熄灭小灯

P0 = 0xfd; //点亮第2个小灯

delay();

P0 = 0xff;

P0 = 0xfb; //点亮第3个小灯

delay();

P0 = 0xff;

P0 = 0xf7; //点亮第4个小灯

delay();

P0 = 0xff;

P0 = 0xef; //点亮第5个小灯

delay();

P0 = 0xff;

P0 = 0xdf; //点亮第6个小灯

delay();

P0 = 0xff;

P0 = 0xbf; //点亮第7个小灯

delay();

P0 = 0xff;

P0 = 0x7f; //点亮第8个小灯

delay();

P0 = 0xff;

}

void delay() //延时函数的定义

{

int i,j;

for(i=1000;i>0;i--)

for(j=100;j>0;j--);

}

有了上面的十六进制的知识,第一条语句“P0 = 0xfe;”应该知道是什么意思了,就是P0口的第0为赋值0,其他位赋值1,即1111 1110,也就是0xfe了。它实现了点亮第一个LED灯。

上面的方法虽然容易想到,但是程序也太长了吧,有没有好一点的方法呢?有的,下面我们用C51即单片机C语言提供的一个函数来实现流水灯。

步骤四:其他方法实现流水灯。

我们先直接改写程序,然后再进行解释。

#include

#include //循环移位函数定义在该头文件中,要想使用该函数必须先包含此头文件

void delay();

void main()

{

unsigned char temp; //定义无符号字符型变量temp

temp = 0xfe; //temp赋初值0xfe

while(1) //循环执行下面的语句

{

P0 = temp; //将temp的值赋给P0口

delay();

temp = _crol_(temp,1); //temp的值每次向左循环移动1位

}

}

void delay()

{

int i,j;

for(i=1000;i>0;i--)

for(j=100;j>0;j--);

}

其中,unsigned char类型的变量是8位的,给temp赋值0xfe,即1111 1110。而_crol_函数是循环左移函数,它有两个参数,第一个参数是要进行循环的变量,第二个参数是每次循环的位数。“temp = _crol_(temp,1)”的意思是让temp的值向左环移一位,即temp由1111 1110变为了1111 1101,最高位移动到了最末位,其他位依次向左移动一位。示意图如下:

clip_image010

这样小灯便挨着被点亮,“wehile(1)”一直循环执行那三条语句,所以就出现了流水灯效果。

与其对应的还有循环右移函数_cror_(),其用法与_crol_()一样。其实我们也可以不用这个环移函数,直接利用位运算符也能实现流水灯效果。程序如下:

#include

void delay();

void main()

{

int i;

while(1)

{

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

{

P0 = ~(1<

delay();

}

}

}

void delay()

{

int i,j;

for(i=1000;i>0;i--)

for(j=100;j>0;j--);

}

下面先介绍一下“<<”左移符号的用法,每执行一次该操作,它便将原来的数据的最高位移动到CY位中,其他位依次左移,最低位补0,而CY位中的原来的数据将丢失。当i=1时,1<<1的示意图如下:

移位前:

clip_image012

移位后:

clip_image014

所以,当i=0时,1<<0,还是1,即0000 0001,~(1<

与“<<”对应的还有“>>”右移符号,它将数值的最低位移到CY中,最高位补0。

至此,流水灯的程序编写便全部介绍完了。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值