第3讲 数码管显示
一、 数码管显示原理
我们最经常使用的是七段式和八段式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口没有上拉电阻(其它port有),所以假设直接接数码管的段选线,那么不能将其点亮。我们须要为其加上220欧姆的上拉电阻,注意,上拉电阻阻值不能过大。实验原理图例如以下。
当中,7SEG-COM-CAT-GRN为七段共阴数码管,显示为绿色。RES为电阻。查找电阻时,须要选中以下的Resistors,例如以下图。
右击选中图中的电阻再左击,弹出的窗体中可改变它的阻值。例如以下图。
那七个电阻看上去非常乱,事实上他们能够用一个排阻(RESPACK-7)取代。例如以下图。
到这里原理图就画完了,我们開始写源程序。让数码管显示字符“0”。
#include
void main()
{
P0 = 0x3f; //P0口送字符‘0’的编码
}
显示效果例如以下。
由于这个程序就一句话,非常easy,所以我们不再进行分析。
三、 一个数码管显示不同字符
以下的程序让一个数码管轮流显示不同的字符。
#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;
}
}
这样编译后运行就能完美的显示了。假设你还是不太明确,能够再次进行联机调试,看一下程序的运行过程。
说明:从上面能够看出,就算是两条语句的顺序错误,也会非常大地影响整个程序的运行效果。调试程序是个非常繁杂的工作,为了少出错,我们就要在写源程序时规范我们的语句,从最简单的程序開始,将它研究透了,这样再写大程序时,才不会在这些细节问题上浪费大量的时间。这里我们仅仅是做一个演示样例,还有很多其它的细节知识须要自己在写大量的程序的过程中积累。知识能够传授,但经验是不能传授的。