大家经常可以看到外面大街小巷各种各样漂亮的闪烁的流水或者说广告灯,如图1,这是怎么实现的呢,这节课我们就来讲下如让发光二极管闪烁和流水灯程序的设计。
图1漂亮的广告灯
1如何实现发光二极管闪烁和简单延时
学过汇编的同学都清楚,单片机机在执行指令时,是一条一条指令顺序执行的,对于C语言也一样,也是一条一条语句执行,而每执行一条指令或一条语句,都要占用一定的时间,利用单片机执行程序的这个特点就可能用来实现发光二极管闪烁。
具体方法就如图2所示,是先点亮发光二极管,然后写一条延时语句,在这段延时时间内,单片机什么事也不做,而此时发光二极管还是一直保持亮的状态,然后延时时间完后,再让发光二极管熄灭,熄灭后也写一条延时程序,而在这段时间内,发光二极管一直保持熄灭,直到延时时间结束再复上而的亮的过程,这样一直不断的循环下去就实现在发光二极不断闪烁。
图2发光二极管闪烁
延时可以由while语句来实现,也可由for语句实现,由于前面我们已经介绍过while语句,所以这里先介绍如何利用while语句实现延时。
(1) 利用while语句进行延时
由于while语句在前我们已经讲解过,这里直接给出程序如例1。
例1利用whiel语句实现发光二极管闪烁程序
#include<reg52.h>
sbit D1=P1^0;
unsigned int a;
void main()
{
while(1)
{
a=50000;
D1=0;
while(a--);
a=50000;
D1=1;
while(a--);
}
}
程序分析:上面程序中出现了新的语句,unsigned int a;由于程序的需要,这里我们先定义了一个变量a,所谓变量其实就是代表单片机内存中具有特定属性的一个存储单元,它用来存放数据,也就是变量的值,如下面的a=50000,在程序中这些值是可以改变的。其中a表示变量的名字,unsigned int是无符号整形的意思,它的取值范围为0-65535,也就是定义成此类型后,在对程序编译时,编译系统会给这个变量分配一个存储空间用来存放数据,且无符号整形的空间存数的范围为0-65535。 后面的程序对a赋值时,其范围只能是0-65535,如程序后面的a=50000,如果此时我们对a赋值a=70000,此时编译时就是报错。我们也可以把变量a定义为其它类型的,如unsigned char a表示为把变量a定义为无符号字符型,其赋值范围为0-255
当然我们也可以定义其它的类型 short int、float等,但作为单片机初学者,一般只用到无符号字符型unsigned char(赋值范围为0-255)和无符号整形unsigned int(赋值范围为0-65535)两种,其它类型可以暂时不管,具体请参考C语言相关书籍。
unsigned int a明白后,下面就是一个大循环while(1),在大循环里,我们先给a赋值50000,然后灯亮D1=0,再进行延时while(a--)(a--,表示自减1的意思,while(a--),表示a从50000开始自减50000次,此时每自减1有一定的时间,所以自减50000次相当于延时的意思),再后的程序就是延时后再灯灭,再延时,再重复前面的过程,最终就可以实现灯一亮一灭闪烁的现象。下载到实验室的现象如图3所示。
图3 发光管闪烁
通过上面程序我们实现 了发光管闪烁的功能,但这里闪烁的间隔时间我们并不清楚具体是多少,我们只能根据具体的多次实验确定我们所需的时间间隔,如果实在要知道的话,可以通过51单片机的软件仿真得出,这里我们不作讲解,以后等大家单片机学得差不多再去学习。
下面我们再给出另一种程序延时的for语句的写法,这种写法在具体的项目用得更多。
(2) for语句及其延时
for语句是C语言中重要的语句,其格式如下:
格式:
利用for语句同样可以写出延时程序,例如:
unsigned int i;
for(i=1;i<=3;i++) ;
这里首先定义了一个无符号变量i,在for语句中,i=1为表达式1;i<=100为表达式2;i++为表达式3,for语句内程序执行如下
1、给变量i赋值为1;
2、判断i是否小于等于3;此时值为真,即执行for中的语句,此处for后面的语句为空语句,省略不写,
相当于什么都不执行,然后跳到语句3;
3、执行i++,i++为自加1的意思,此时i的值变为2,
4、跳到第2 步判断i是否小于等于3,此时仍然小于等于3,又执行空语句;
5、执行i++,此时i的值变为3;
6、跳到第2 步判断i是否小于等于3,此时等于3,直接跳出;
通过上面6步,for语句执行完毕,注意,单片机在执行每一条语句的时候都需要一定的时间,此时我们只需要改变表达式2的值就
可以得到我们想要的延时时间了。
此处需要注意的时,我们把i定义为无符号整形后,i的值最大值只能为65535,也就是利用上面的格式,我们可以写出最大的延时程序如下:
unsigned int i;
for(i=1;i<=65536;i++)
但我们此时如还需要更长的时间时,如果再让i值变大的话,编译时就会出错,此时我们可以写成如下的写法:
unsigned int i;
unsigned int j;
for(i=1;i<=65536;i++)
{
for(j=1;j<=100;j++);
}
上面的语句称为c语句的嵌套,是指一个for语句中又包含一个完整的for语句,内嵌的语句还可以嵌套for语句,这称为多层嵌套,我们这里只有两层。
注意第一个for语句后面没有“;”,这里第一个for语句执行了65535次,第二个for语句执行了100次,相当于总共执行了655535x100次,这样的写法就可以写出较长的延时时间。
这里的for语句和上面的while语句都可以用来进行延时,这种方法称为软件延时,和上面for语句一样,其准确的延时时间在C语言程序中不容易计算出来。如果想要得到精确的延时时间,我们则可以用到单片机内部的硬件资源定时器,它可以精确的微秒级,这个后面的课程中会讲到。虽然得不到精确的延时时间,但大概的时间我们刚可以通过软件仿真的方法得出,关于仿真具体的调试方法我们这里不作讲解,这此我们给出一个51单片机经常用到延时时间:
for语句中两个变量的类型都为unsigned int 时,内层的for语句变量的值恒为110时,外层for语句变量的值为多少,这个for嵌套语句延时时间就约为多少毫秒。如下面的程序:
unsigned int i;
unsigned int j;
for(i=1;i<=1000;i++)
{
for(j=1;j<=110;j++);
}
这个程序中外层for语句中变的值为1000,其延时的时间就约为1000毫秒,即1秒,若要想得到其它的延时时间,只需要改变i变量的值就可以了。
下面我们利用 for语句写一个程序,其功能为让L1灯以间隔1秒的时间闪烁。程序如例2所示。
例2 L2灯以1秒的时间时隔闪烁
#include<reg52.h>
#define unsigned int;
sbit D1=P1^0;
uint i,j;
void main()
{
while(1)
{
D1=0;
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
D1=1;
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
}
}
下载到实验板上现象如图4所示:
图4 L1以1秒的间隔闪烁。
例2程序中,在写延时程序时,分别用到了两个延时程序,并且这两个程序内容格式完全相同,此时从简化程序的角度出发,我们可以采取另外一种写法-子函数调用法.下面对其进行介绍.
3、不带参数的子函数调用
我们把上式中的延时函数单独提取出来,然后写成一个函数—此函数称为一个程序中的子函数,然后在主函数中直接调用即可。上式中延时函数的子函数写法如下。
void delay1s()
{
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
}
上式中,void意为无返回值,也就是此函数执行完毕后不返回任何值。delay1s表示函数名,这里用户可以随便取,只需要满意C语言的命名规则即,因为这个子程序在上面已经写过,是延时1钞的函数,所以我们此时命名延时1秒(delay1ms)样可以便于大家阅读和记忆。函数名delay1s后面紧跟一小括号,括号里面什么内容也没有,这表示此子函数不带任何参数(后面会介绍到带参数的子函数)。
需要注意的是,程序中中的子函数,如果是写在主函数之前,此不需要声明,但如果是写在主函数之后,则需要声明,声明的方法如下:将返回值特性、函数名及后面的小括号完全复制,若是无参函数,则小括号内为空,若是有参函数,则需要在小括号内依次写上参数类型,参数类型之间用逗号隔开。最后在小括号后面加上分号即可。
下面对例2的程序改为不带参数子函数的写法,程序如例3所示。
例2 L2灯以1秒的时间时隔闪烁(不带参数子函数的写法)
#include<reg52.h>
#define unsigned int;
sbit D1=P1^0;
void delay1s()
void main()
{
while(1)
{
D1=0;
delay1s();
D1=1;
delay1s();
}
}
void delay1s()
{
uint i,j;
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
}
在例3中,我们注意到“uint i,j”语句,i和j两个变量的定义放到了子函数里,而没有写在主函数的最外面。在主函数外面定义的变量叫做全局变量,像这种定义在了函数内部的称为局部变量,这里i和j就是局部变量;局部变量只在当前函数中起作用,程序一但执行完当前子函数,它内部的所有变量将自动销毁,当下次再调用此子函数时,编译器重新为其分配内存空间。我们要知道,在一个程序中,每个变量都占据着单片机的固定的RAM, 局部变量是用时随时分配,不用时立即销毁。一个单片机的RAM是很有限的,像STC89C52内部有256字节的RAM,所以用时我们必须节约,显然,放在子程序中更能节省RAM空间。将来程序下载到实验板,我们此时依然可以看到如例2中的现象,L1灯以1秒的时间间隔闪烁。
4、带参数子函数的写法
上面我们讲到过,C语言中子函数分为两种,一种是不带参数子函数,另一种是带参数的子函数。下面我们讲一下带参数子函数。在例3中,我们延时 1000ms,i=1000.如果我们要延时500ms,刚i=500.如果要延时300ms,i=300,这样程序改起来就很麻烦,特别是在一些大型的程序中,这里我们如果写成带参数的子函数就方便很多。程序写法如下:
void delayxms(unsigned int z)
{
for(i=z;i>0;i--)
{
j=1;j<=110;j++;
}
}
上面程序中小括号内多了“unsigned int z”,其中“z”这个就是这个子函数带的参数,unsigned int 是定义参数z的类型为无符号整型。子函数中z这个参数我们称为形参,在调用子函数时我们用一个真实的数据代替此形参,这个真实的数据我们称为实参。在调用子函数时,只需要的延时时间改变小括号内的数据就可以了,如要延时1000s,则delayxms(1000),800ms时,delayxms(800)。
下面我们写一个程序让8个发光二极管以间隔800ms的时间闪烁。
例58个发光二极管以间隔500ms的时间间隔闪烁。
#include<reg52.h>
#define unsigned int;
sbit D1=P1^0;
void delayxms(unsigned int z)
{
for(i=z;i>0;i--)
{
j=1;j<=110;j++;
}
}
void main()
{
while(1)
{
D1=0;
delay1s();
D1=1;
delay1s();
}
}