我们最常用的是七段式和八段式LED数码管,八段比七段多了一个小数点,其他的基本相同。所谓的八段就是指数码管里有八个小LED发光二极管,通过控制不同的LED的亮灭来显示出不同的字形。数码管又分为共阴极和共阳极两种类型,其实共阴极就是将八个LED的阴极连在一起,让其接地,这样给任何一个LED的另一端高电平,它便能点亮。而共阳极就是将八个LED的阳极连在一起。其原理图如下。
其中引脚图的两个COM端连在一起,是公共端,共阴数码管要将其接地,共阳数码管将其接正5伏电源。一个八段数码管称为一位,多个数码管并列在一起可构成多位数码管,它们的段选线(即a,b,c,d,e,f,g,dp)连在一起,而各自的公共端称为位选线。显示时,都从段选线送入字符编码,而选中哪个位选线,那个数码管便会被点亮。
数码管的8段,对应一个字节的8位,a对应最低位,dp对应最高位。所以如果想让数码管显示数字0,那么共阴数码管的字符编码为00111111,即0x3f;共阳数码管的字符编码为11000000,即0xc0。可以看出两个编码的各位正好相反。如下图。
下面以七段共阴数码管为例讲述如何点亮一个数码管。
l 51系列单片机的P0口没有上拉电阻(其他端口有),所以如果直接接数码管的段选线,那么不能将其点亮。我们需要为其加上220欧姆的上拉电阻,注意,上拉电阻阻值不能过大。实验原理图如下。
其中,7SEG-COM-CAT-GRN为七段共阴数码管,显示为绿色。RES为电阻。查找电阻时,需要选中下面的Resistors,如下图。
右击选中图中的电阻再左击,弹出的窗口中可改变它的阻值。如下图。
那七个电阻看上去很乱,其实他们可以用一个排阻(RESPACK-7)代替。如下图。
到这里原理图就画完了,我们开始写源程序。让数码管显示字符“0”。
#include
void main()
{
P0 = 0x3f; //P0口送字符‘0’的编码
}
显示效果如下。
因为这个程序就一句话,很简单,所以我们不再进行分析。
下面的程序让一个数码管轮流显示不同的字符。
#include
void delay();
void main()
{
P0 = 0x3f; //显示字符‘0’
delay(); //延时一会
P0 = 0x06; //显示字符‘1’
delay();
P0 = 0x5b; //显示字符‘2’
delay();
}
void delay()
{
int i,j;
for(i=1000;i>0;i--)
for(j=100;j>0;j--);
}
这个程序实现字符‘0’,‘1’,‘2’的循环显示。但如果要循环显示更多的数字,每次都写出他们的编码很麻烦,这里我们可以将所有的编码都写到一个数组里,以后只需调用数组就可以了。程序如下。
#include
unsigned char code table[]={0x3f,0x06,0x5b}; //定义编码数组,注意最后的分号
void delay();
void main()
{
P0 = table[0]; //调用数组的第一个元素
delay();
P0 = table[1];
delay();
P0 = table[2];
delay();
}
void delay()
{
int i,j;
for(i=1000;i>0;i--)
for(j=100;j>0;j--);
}
这里要说明的是,unsigned char表明数组中的元素是无符号字符型数据,code表明这是编码数组,其编译后不占内存空间而是占程序存储空间,我们知道单片机的内存空间很小,所以这个很重要。table是数组名字,自己可以随便更换。因为数组中的元素是从0开始排的,所以table[0]就是第一个元素0x3f。
原理图如下:
其中,7SEG-MPX8-CC-BLUE是8位八段共阴数码管,显示为蓝色。其段选线接在P0口,位选线接在P2口。
让所有数码管显示同一个字符。源程序如下:
#include
void main()
{
P2 = 0; //P2口各位全为低电平,选中数码管所有位
P0 = 0x3f; //显示字符‘0’
}
这个程序只比第一个程序多了一条“P2 = 0;”,这样来实现位选。最终效果如下:
让任意位显示字符。源程序如下:
#include
void main()
{
P2 = 0xaa; //选中从左数的第1,3,5,7位数码管
P0 = 0x3f;
}
效果如下:
以上的显示均为静态显示,下面讲述动态显示。而到底什么是静态显示什么是动态显示,等看完下面的内容就会很清楚了。
因为上面多个数码管显示时只能显示同一个字符,怎么才能让不同的数码管显示不同的字符呢?我们先完成这样的一个程序,让第一位数码管显示1,然后第二位数码管显示2,然后第三位数码管显示3。为了使程序短些,我们只控制前三位,要想让其他五位也显示,道理是一样的。
源程序如下:
#include
unsigned char code table[]={0x3f,0x06,0x5b,0x4f};
void delay();
void main()
{
P2 = 0xfe; //选中第一位数码管
P0 = table[1]; //让其显示字符‘1’
delay(); //延时一会
P2 = 0xfd; //选中第二位数码管
P0 = table[2]; //让其显示字符‘2’
delay();
P2 = 0xfb; //选中第三位数码管
P0 = table[3]; //让其显示字符‘3’
delay();
}
void delay()
{
int i,j;
for(i=1000;i>0;i--)
for(j=100;j>0;j--);
}
这个程序就是分别选中一位数码管,让它显示一个字符,同单位数码管显示的原理是一样的。这里你会发现每显示完一个字符之后都有一个延时,这个延时有什么作用呢?我们可以先试着改变这个延时,看会有什么效果。我们先将delay()函数中的第一个for循环中的i的初值由1000,改为100,再运行一下程序,有什么效果?然后再将其改为10呢?这时是不是我们想要的不同数码管同时显示不同的字符的效果已经出来了。效果如下:
这就是上面所说的动态显示效果。那静态显示与动态显示到底有什么不同呢?很明显,通俗的说,我们把向数码管各位轮流送入字符编码和位选信号,利用人眼的视觉暂留,让人感觉好像几位数码管被同时点亮,这样便可以在不同的数码管上同时显示不同的字符的效果称为动态显示。打个比方,你晚上拿根点着的烟,在空中快速划过,你就会看到一条亮线,但其实它只是一个亮点划过而已。如果你对它还不了解,可以到别的资料上查看一下视觉暂留的相关知识。而静态显示就是真实的同时选中几位。这就是它们的根本区别。
到这里我们必须先说明一个问题了。前面我们写程序都是全部直接写到main()函数里的。那么你有没有想过,main()函数里的语句从头执行到尾,那么语句全部执行完了会怎么样呢?你会想到它会从头再开始执行,对吧!因为由前面的程序可以看出,指令是在无限循环执行的。但依靠这种默认的循环并不可靠,一般地,我们都是在程序中用一个死循环语句来实现无限循环的。上面的源程序的主函数可改为:
void main()
{
while(1) //死循环
{
P2 = 0xfe;
P0 = table[1];
delay();
P2 = 0xfd;
P0 = table[2];
delay();
P2 = 0xfb;
P0 = table[3];
delay();
}
}
可以看到,我们是把所有要循环的语句都放到了一个while(1){}循环中执行的。在以后的程序中,程序的主体部分都会放到这个语句中。
程序写成这样以后,你再将延时函数的延时缩减,比如:
void delay()
{
int i,j;
for(i=5;i>0;i--)
for(j=1;j>0;j--);
}
这时运行程序,是不是发现很乱了!效果可能如下:
这就是我们所说的“拖影”。
其实在真实的板子上,就算延时很长,也可以看见“拖影”现象。出现这样的现象的原因是CPU的执行速度很快,当送入位选和段选数据后,接着又送入位选数据,但该位的段选数据还没有送入,所以该位还保持着上次的段选数据,接着该位的段选数据送入,由于视觉残留,两个段选数据的显示效果重合,形成了混乱。简单的说,就是一位数码管显示了它前一位要显示的字符和它本身要显示的字符的重叠效果。要想避免“拖影”就必须在每位数码管显示完后将其关闭,我们可以加入“P2 = 0xff;”,这样各位数码管都不会选中,然后下一位再显示时就不会有影响了,这就是所谓的“消影”。我们把程序改为如下:
void main()
{
while(1)
{
P2 = 0xfe;
P0 = table[1];
delay();
P2 = 0xff; //消影
P2 = 0xfd;
P0 = table[2];
delay();
P2 = 0xff;
P2 = 0xfb;
P0 = table[3];
delay();
P2 = 0xff;
}
}
但是当运行后,你会发现效果并没有变化。为什么呢?为了研究原因,我们进行联机调试,然后单步运行程序,看看程序到底是怎么执行的。关于怎么联机调试,我们以前已经专门讲过,这里不再叙述。
如下图,先在keil中按下调试按键,会发现Proteus仿真图已经开始运行。然后在keil中选择源程序one显示界面,并按下单步调试按键,它表示进入子函数内部,例如下面的调试过程中会进入delay()函数的内部。按下该按键后,会在第一条语句前出现黄色箭头,表明这条语句还没有执行,下一次将会执行该语句。
再次点击单步按键,第一条语句执行完毕,会发现第一位数码管被点亮,因为还没有赋值,所以七段都被点亮了。如下图。
再点击单步按键,可以看到虽然段选已经赋值了,但数码管并没有显示。如下图。
再点击单步,便进入了delay()函数的内部,此时数码管也显示出‘1’了,如下图。
连续点击单步,直到跳出delay()函数,以后我们就点击另一个单步按键,它不会进入子函数内部。如下图。
点击单步后,执行完P2=0xff;语句,数码管不再显示,如下图。
再点击单步,执行完P2=0xfd;语句,我们发现第二个数码管居然显示的是‘1’,其实也对,因为段选的数据还没有改变呢。这正是产生“拖影”的原因。如下图。
再点击单步,准备执行延时函数。如下图。
点击单步,执行完延时函数后,显示出了正确的字符,如下图。
因为已经找到了原因,所以我们联机调试就到这里。可以看到,在进行联机调试单步运行时可以发现很多程序执行的细节,所以对一些不好想的问题,我们都可以通过这种方法去寻找答案。
我们已经看到程序出错是因为消影语句“P2 = 0xff;”并没有起到应有的作用。那怎样才能起到作用呢?刚才在联机调试时我们已经发现只要给了数码管位选数据,它就会被点亮,所以我们可以先给其送入段选数据,然后给其送入位选数据,这样它应该会显示正确字符了,然后延时让它亮一会,再加上消影语句,它就会被熄灭。再给第二位送入段选数据,但这时数码管还是灭这的,它不会产生拖影,此时给其送入位选数据,它就能显示正确的字符了。程序可更改如下:
void main()
{
while(1)
{
P0 = table[1];
P2 = 0xfe;
delay();
P2 = 0xff;
P0 = table[2];
P2 = 0xfd;
delay();
P2 = 0xff;
P0 = table[3];
P2 = 0xfb;
delay();
P2 = 0xff;
}
}
这样编译后运行就能完美的显示了。如果你还是不太明白,可以再次进行联机调试,看一下程序的运行过程。
说明:从上面可以看出,就算是两条语句的顺序错误,也会很大地影响整个程序的运行效果。调试程序是个很繁杂的工作,为了少出错,我们就要在写源程序时规范我们的语句,从最简单的程序开始,将它研究透了,这样再写大程序时,才不会在这些细节问题上浪费大量的时间。这里我们只是做一个示例,还有更多的细节知识需要自己在写大量的程序的过程中积累。知识可以传授,但经验是不能传授的。