赞
踩
以八段管为例介绍其字段,八段数码管其实是由八个LED灯组成的8字型,如图8.2所示,这八个灯按逆时针方向分别为:a,b,c,d,e,f,g,h。如果要显示“7”字,只需要将a,b,c三个灯点亮即可,如图8.3所示:
这样来理解的话,要点亮一个数码管很容易,其实就是点亮相应的LED灯,就能显示不同的数字。我们在前面的实验中已经知道怎么点亮开发板上的LED灯,就是用单片机的I/O脚向LED灯输出低电平,灯就能点亮,输出高电平,灯就熄灭。那我们点亮数码管上的LED灯是不是也是这样呢?这就涉及到数码管是共阳极的还是共阴极的。
3、数码管工作原理详解
我们先以一位数码管为例。数码管的共阳极工作方式和共阴极工作方式对初学者而言可能不太好理解,为了加强理解下面把数码管简化,如图8.4所示:
上图中的DP就是前边图中的h,也就是小数点,图中的数字是数码管的引脚,一位数码管管脚如图8.5所示,大家
可以对照看,找到数码管各个字段对应哪个引脚。这里主要看字母。从图中可以看出数码管其实就是多个发光二极管的集合体。
共阴数码管:将发光二极管的阴极共同连接在一起;
共阳数码管:将发光二极管的阳极共同连接在一起;
对共阳数码管而言,如果要显示“7”字,公共端给高电平,A,B,C给低电平,D,E,F,G,DP给高电平就可以了。如图8.6所示。
回到我们本次课要完成的实验中来,本次实验我们要实现用程序将开发板上最左边的数码管显示任意的数字。我们还是从两方面考虑:硬件设计和软件设计。
1、硬件设计
本实验板上的数码管使用的是共阴极数码管,硬件上将最左边数码管的共阴极引脚也就是实验板上的LED_CH1插针接到GND上,A到DP段的阳极引脚也就是实验板上的LED_DAT1到LED_DAT8插针按顺序接到P20到P27上。
2、软件设计
1)数据结构
因为显示码在程序运行中不会改变,可以在程序存储器中定义一个显示码数组,数组里的值按从0到9的显示码的顺序存放,定义的格式为:
unsigned char code display_code[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
2)软件代码
下面是让第一个数码管显示5的程序代码。
#include"REG51.h"
#include"intrins.h"
unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf};
void main(void)
{
unsigned int i;
P0=0xfe;
P2=display_code[5]; //将5的显示码赋给P2口,数码管显示5
while(1);
}
上次课我们了解了一位数码管显示数据的原理,但在实际应用中往往需要多位数码管,例如我们要做电子表,我们就需要8位数码管,来显示时、分、秒。当我们需要多位数码管时,我们不需要买多个一位的数码管,可以买多位的数码管,例如两位的或四位的,如图10.2.1所示。本开发板上使用的是两个4位的数码管,我们先来认识一下4位数码管的结构及引脚,图10.2.2为4位数码管引脚图,数码管正面的左下角为第1引脚,按逆时针顺序,左上方第12引脚。
#include"REG51.h" unsigned char code display_code[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; sbit P00=P0^0; sbit P01=P0^1; void main(void) { while(1) { P00=1; P01=0; P2=display_code[1]; //将1的显示码赋给P2口,第一个数码管显示1 P00=0; P01=1; P2=display_code[1]; //将2的显示码赋给P2口,第二个数码管显示2 } }
将上面的代码编译下载到实验板上运行,发现两个数码管显示不清楚并且有重影,大家思考一下这是什么原因呢?这是因为上面的代码让一个数码管显示后,显示还没稳定,就立马就关闭,显示另一个数码管。要解决这个问题,需要每个数码管显示后加一定的延时,等显示稳定后,再显示另一个数码管,将上面的代码加上延时代码如下:
将上面的代码编译下载到实验板上,发现两个数码管是很清楚的显示“1”和“2”,但会跳动,那是因为延时时间太长,人的眼睛存在“视觉驻留效应”,如果,人眼就会感觉到跳动,只有让关闭某位数码管到下次再次点亮的时间间隔小于眼睛的驻留时间,也就是眼睛还没觉察到数码管熄灭,就再次的点亮,就可以给人一种稳定的显示效果。把上面代码中的延时时间缩短,修改代码如下:
#include"REG51.h" unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf}; sbit P00=P0^0; sbit P01=P0^1; void main(void) { unsigned int i; while(1) { P2=0xff; P00=1; P01=0; P2=display_code[1]; //将1的显示码赋给P2口,第一个数码管显示1 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=0; P01=1; P2=display_code[2]; //将2的显示码赋给P2口,第二个数码管显示2 for(i=0;i<500;i++); //延时一段时间 } }
将上面的代码编译下载到实验板上,发现两个数码管是很清楚、稳定的显示“1”和“2”。我们把上面数码管的显示方法称为数码管动态显示。用数码管显示信息时,由于每个数码管至少需要8 个I/O 口,如果需要多个数码管,则需要太多I/O 口,而单片机的I/O 口是有限的。在实际应用中,一般采用动态显示的方式解决此问题。动态显示的特点是将所有位数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出显示码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。动态显示的亮度比静态显示要差一些,所以在选择限流电阻时应略小于静态显示电路中的。在动态显示程序中,各个数码管显示的延时时间长短是非常重要的,如果延时时间长,则会出现闪烁现象;如果延时时间太短,则会出现显示暗且有重影。
上次课讲解了数码管动态显示原理,并利用数码管动态显示原理,实现了在两个数码管上同时显示“12”。下面我们讲解上次课布置的作业,在八个数码管上同时显示“12345678”。硬件上,我们顺着两位数码管的显示的硬件连线,将另外六个数码管的选择线LED_CH3到LED_CH8按顺序分别接到P02到P07引脚上。软件上,只要在两位显示的基础上,用同样的方法再增加另外6个数码管的显示,代码如下:
#include"REG51.h" unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf}; sbit P00=P0^0; sbit P01=P0^1; sbit P02=P0^2; sbit P03=P0^3; sbit P04=P0^4; sbit P05=P0^5; sbit P06=P0^6; sbit P07=P0^7; void main(void) { unsigned int i; while(1) { P2=0xff;//如果不加P2=0xff;的话只会显示1234567,而8是显示不出来的 P00=0;//选择第一个数码管 P01=1; P02=1; P03=1; P04=1; P05=1; P06=1; P07=1; P2=display_code[1]; //将1的显示码赋给P2口,第一个数码管显示1 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=1; P01=0;//选择第二个数码管 P02=1; P03=1; P04=1; P05=1; P06=1; P07=1; P2=display_code[2]; //将2的显示码赋给P2口,第二个数码管显示2 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=1; P01=1; P02=0;//选择第三个数码管 P03=1; P04=1; P05=1; P06=1; P07=1; P2=display_code[3]; //将3的显示码赋给P2口,第三个数码管显示3 for(i=0;i<500;i++); //延时一段时间 P00=1; P01=1; P02=1; P03=0;//选择第四个数码管 P04=1; P05=1; P06=1; P07=1; P2=display_code[4]; //将4的显示码赋给P2口,第四个数码管显示4 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=1; P01=1; P02=1; P03=1; P04=0;//选择第五个数码管 P05=1; P06=1; P07=1; P2=display_code[5]; //将5的显示码赋给P2口,第五个数码管显示5 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=1; P01=1; P02=1; P03=1; P04=1; P05=0;//选择第六个数码管 P06=1; P07=1; P2=display_code[6]; //将6的显示码赋给P2口,第六个数码管显示6 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=1; P01=1; P02=1; P03=1; P04=1; P05=1; P06=0;//选择第七个数码管 P07=1; P2=display_code[7]; //将7的显示码赋给P2口,第七个数码管显示7 for(i=0;i<500;i++); //延时一段时间 P2=0xff; P00=1; P01=1; P02=1; P03=1; P04=1; P05=1; P06=1; P07=0;//选择第八个数码管 P2=display_code[8]; //将8的显示码赋给P2口,第八个数码管显示8 for(i=0;i<500;i++); //延时一段时间 } }
//显示87654321 #include <reg51.h> unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; unsigned char code display_code2[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; void main(){ char x; unsigned int i,j; while(1){ P2=0xff; P0=display_code2[0]; P2=P2=display_code[8]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[1]; P2=display_code[7]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[2]; P2=display_code[6]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[3]; P2=display_code[5]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[4]; P2=display_code[4]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[5]; P2=display_code[3]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[6]; P2=display_code[2]; for(i=0;i<500;i++); P2=0xff; P0=display_code2[7]; P2=display_code[1]; for(i=0;i<500;i++); } }
现在大家编写上面的代码。将上面的代码编译、下载,开发板的八个数码管能清晰、稳定的显示“12345678”。但我们发现上面的代码太冗余,每个数码管显示的代码是有规律的,选择线上的0是循环左移,显示的数据按顺序是1到8,于是我们可以将上面的代码优化为如下的代码:
#include"REG51.h" #include"intrins.h" unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; void delay(unsigned int i) { unsigned int k; for(k=0;k<i;k++); } void main(void) { unsigned char x,i; x=0xfe; // while(1) { for(i=1;i<=8;i++) { P2=0xff; P0=x; P2=display_code[i]; //将数字i的显示码赋给P2口 delay(100); x=_crol_(x,1); } } }
现在大家实现上面的代码。
大家想想,上面的代码还有没有什么需要推敲的地方?上面的代码是只能显示“12345678”这组数据,但我们实际要显示的数字是变化的、没规律的,为了使程序通用并简洁,可以给八个数码管要显示的数字定义个数组display_data,如下:unsigned char display_data[8]={1,2,3,4,5,6,7,8};
这个数组称为显示缓冲区,程序设计时,可以预先将要显示的八个数字按顺序存放到显示缓冲区中,数码管动态显示时,到该缓冲区中取要显示的数字,将上面的代码加上显示缓冲区,优化后的代码如下:
//显示12345678 #include"REG51.h" #include"intrins.h" unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; unsigned char display_data[8]={1,2,3,4,5,6,7,8}; //定义显示缓冲区,赋上要显示的值 void delay(unsigned int i) { unsigned int k; for(k=0;k<i;k++); } void main(void) { unsigned char x,i; x=0xfe; // while(1) { for(i=0;i<8;i++) { P2=0xff; P0=x; //选择一个数码管显示 P2=display_code[display_data[i]]; //到显示缓冲区中取出第i+1个数码管的 //显示数字,再到显示码数组中取出对应的显示码,将该显示码送到数据线上。 delay(100); //延时一段时间 x=_crol_(x,1); //将x循环左移一位,准备选择下一个数码管 } } }
现在大家实现上面的代码。
我们知道,主函数一般不实现具体的功能,可以把动态显示数码管的功能放到子函数中实现,代码如下:
#include"REG51.h" #include"intrins.h" unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; unsigned char display_data[8]={1,2,3,4,5,6,7,8}; //定义显示缓冲区,赋上要显示的值 void display_LED(); void delay(unsigned int i) { unsigned int k; for(k=0;k<i;k++); } void main(void) { while(1) { display_LED(); //调用数码管显示 } } /* 子函数名:display_LED() 函数功能:动态刷新显示八个数码管 */ void display_LED() { unsigned char x=0xfe,i; // for(i=0;i<8;i++) { P2=0xff; P0=x; //选择一个数码管显示 P2=display_code[display_data[i]]; delay(100); //延时一段时间 x=_crol_(x,1); //将x循环左移一位,准备选择下一个数码管 } }
现在大家实现上面的代码。
上面显示函数是实现八个数码管的刷新,每个数码管的刷新需要延时一段时间,八个数码管加起来的延时时间就比价长了,也就是CPU在该函数中等待的时间比较长,为了提高CPU效率,可以设想让CPU每次刷新显示一个数码管,不在子函数里延时,直接回到主函数处理其他程序,如果主函数没有其他程序处理,就在主函数中延时。要实现这个设想,关键点是要解决两个问题,一是每次调用显示函数,该函数都是刷新的上次显示的数码管的下一个数码管,也就是上面函数中的x要一直存在,而不能回收;二是缓冲区中的数字也要是下一个数码管显示的数字。要解决这两个问题,只需要将x、i定义为静态局部变量就可以了,将上面的代码修改后的代码如下:
#include"REG51.h" #include"intrins.h" unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; unsigned char display_data[8]={1,2,3,4,5,6,7,8}; //定义显示缓冲区,赋上要显示的值 void display_LED(); void delay(unsigned int i) { unsigned int k; for(k=0;k<i;k++); } void main(void) { while(1) { display_LED(); //调用数码管显示 delay(100); //延时一段时间 } } /* 子函数名:display_LED() 函数功能:动态刷新显示八个数码管 */ void display_LED() { static unsigned char x=0xfe,i=0; P2=0xff; P0=x; //选择一个数码管显示 P2=display_code[display_data[i]]; i=(++i)%8; //i向后加1,若加到8,从新赋为0 x=_crol_(x,1); //将x循环左移一位,准备选择下一个数码管 }
上次课讲解并整理了数码管刷新显示函数。下面我们讲解上次课布置的作业,在八位的数码管上显示模拟的时间,显示要求是:第一、二个数码管显示小时,第三个数码管不显示,第四、五个数码管显示分钟,第六个数码管不显示,第七、八个数码管显示秒。硬件上和上次实验的连线一样。软件上要定义三个时间变量:hour(小时)、minute(分钟)、second(秒),并分别赋上模拟时间。要在第一、二个数码管上显示小时,要把小时的十位和个位分别解析到显示缓冲区的第0、第1个元素中;第三个数码管不显示,首先在原来显示码数组最后里加一个元素,赋上不显示的显示码0xff,该元素的下标为10;要在第四、第五个数码管上显示分钟,先要把分钟的十位和个位分别解析到显示缓冲区的第3、第4个空间中;第六个数码管不显示,将10赋给显示缓冲区的第5个元素中;要在第七、第八个数码管上显示秒,先要把秒的十位和个位分别解析到显示缓冲区的第6、第7个空间中。代码如下:
//显示10-35-29 #include"REG51.h" #include"intrins.h" void display_LED();//申明数码管显示子函数 unsigned char code display_code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xbf}; unsigned char display_data[8]; //定义显示缓冲区 unsigned char hour=10; //定义小时 unsigned char minute=35; //定义分钟 unsigned char second=29; //定义秒 void delay(unsigned int i) { unsigned int k; for(k=0;k<i;k++); } void main(void) { display_data[0]=hour/10;//将小时的十位数送到显示缓冲区第0个元素中 display_data[1]=hour%10;//将小时的个位数送到显示缓冲区第1个元素中 display_data[2]=10; //将不显示10送到显示缓冲区第2个元素中 display_data[3]=minute/10;//将分钟的十位数送到显示缓冲区第3个元素中 display_data[4]=minute%10;//将分钟的个位数送到显示缓冲区第4个元素中 display_data[5]=10; //将不显示10送到显示缓冲区第5个元素中 display_data[6]=second/10;//将秒的十位数送到显示缓冲区第6个元素中 display_data[7]=second%10;//将秒的个位数送到显示缓冲区第7个元素中 while(1) { display_LED(); //调用数码管显示,见10-3 delay(100); //延时一段时间 } } /* 子函数名:display_LED() 函数功能:动态刷新显示八个数码管 */ void display_LED() { static unsigned char x=0xfe,i=0; P2=0xff; P0=x; //选择一个数码管显示 P2=display_code[display_data[i]]; i=(++i)%8; //i向后加1,若加到8,从新赋为0 x=_crol_(x,1); //将x循环左移一位,准备选择下一个数码管 }
现在大家实现上面的代码。
上面的实验是在数码管上显示了一个模拟的时间,但时间一直没改变,下面我们模仿时间的计算,做一个模拟电子表。设计思路:秒钟间隔一秒累加1,若加到60,秒清0,分钟累加1,若分钟累加到60,分钟清0,小时累加1,若小时累加到24,小时清0。这里关键要解决的秒钟间隔1秒,这一秒时间怎么来?本次课我们先让CPU做一定的延时来达到,那延时能不能直接安排在主循环里让CPU执行延时1秒的函数呢?因为这个延时时间比较长,不能这样安排,否则会影响数码管的刷新,造成数码管闪烁现象。为了解决这个问题,我们可以不要刻意的让CPU执行延时函数,可以让CPU每执行很多次的主循环后,处理一下时间,这样既能更新时间,又不影响数码显示。于是,定义一个计数变量times,每次主循环执行一次,就累加1,直到累加到一个值,时间刚好是1秒,就处理更新时间函数,这个值是多少,需要我们计算一次循环CPU执行的时间,再根据1秒的时间计算总共执行多少次循环得出,这里我们就不细细计算了,我们假设算出来是500,下面的代码就可以实现一个模拟的电子表了。
//显示C 238.5 #include"REG51.h" #include"intrins.h" #include "stdio.h" void SEG_refresh(unsigned char *p); void char_to_code(); unsigned char code display_code[]={'C',0xc6,' ',0xff,'0',0xc0,'1',0xf9,'2',0xa4,'3',0xb0,'4',0x99,'5',0x92,'6',0x82,'7',0xf8,'8',0x80,'9',0x90,'-',0xbf}; float t=234.5; char hour=11,min=13,sec=56; char display_dat[8]; char display_char[10]; void main() { unsigned int j; sprintf(display_char,"C %.1f",t); //将C 234.5输入到display_char数组里面 char_to_code(); while(1) { SEG_refresh(display_dat); for(j=0;j<500;j++); } } void SEG_refresh(unsigned char *p) { static char x=0xfe; // 1111 1110 static unsigned int i=0; P2=0xff; P0=x; P2=p[i]; x=_crol_(x,1); i++; if(i==8) i=0; } void char_to_code() { unsigned int i,j,k; for(k=0,j=0;k<8;j++,k++) { for(i=0;i<13;i++) { if(display_char[j]==display_code[2*i]) { display_dat[k]=display_code[2*i+1]; break; } if(display_char[j+1]=='.') { display_dat[k]=display_dat[k] & 0x7f;//将最高位置为0,即显示小数点 j++; } } } }
#include"REG51.h" #include"intrins.h" #include "stdio.h" void SEG_refresh(unsigned char *p); void char_to_code(unsigned char *p,unsigned char *q); unsigned char code display_code[]={'C',0xc6,' ',0xff,'0',0xc0,'1',0xf9,'2',0xa4,'3',0xb0,'4',0x99,'5',0x92,'6',0x82,'7',0xf8,'8',0x80,'9',0x90,'-',0xbf}; float t=234.5; char hour=11,min=13,sec=56; char display_dat[8]; char display_char[10]; void main() { unsigned int j; sprintf(display_char,"C %.1f",t); //将C 234.5输入到display_char数组里面 char_to_code(display_char,display_dat); while(1) { SEG_refresh(display_dat); for(j=0;j<500;j++); } } void SEG_refresh(unsigned char *p) { static char x=0xfe; // 1111 1110 static unsigned int i=0; P2=0xff; P0=x; P2=p[i]; x=_crol_(x,1); i++; if(i==8) i=0; } void char_to_code(unsigned char *p,unsigned char *q) // display_char是p,display_dat是q { unsigned int i,j,k; for(k=0,j=0;k<8;j++,k++) { for(i=0;i<13;i++) { if(p[j]==display_code[2*i]) { q[k]=display_code[2*i+1]; break; } if(p[j+1]=='.') { q[k]=q[k] & 0x7f;//将最高位置为0,即显示小数点 j++; } } } }
拆分成3个文件写
//1.c #include"REG51.h" #include"intrins.h" #include "stdio.h" #include "seg.h" //unsigned char code display_code[]={'C',0xc6,' ',0xff,'0',0xc0,'1',0xf9,'2',0xa4,'3',0xb0,'4',0x99,'5',0x92,'6',0x82,'7',0xf8,'8',0x80,'9',0x90,'-',0xbf}; float t=234.5; char hour=11,min=13,sec=56; char display_dat[8]; char display_char[10]; void main() { unsigned int j; sprintf(display_char,"C %.1f",t); //将C 234.5输入到display_char数组里面 char_to_code(display_char,display_dat); while(1) { SEG_refresh(display_dat); for(j=0;j<500;j++); } }
//seg.h
void SEG_refresh(unsigned char *p);
void char_to_code(unsigned char *p,unsigned char *q);
//seg.c #include"REG51.h" #include"intrins.h" #include "stdio.h" #include "seg.h" unsigned char code display_code[]={'C',0xc6,' ',0xff,'0',0xc0,'1',0xf9,'2',0xa4,'3',0xb0,'4',0x99,'5',0x92,'6',0x82,'7',0xf8,'8',0x80,'9',0x90,'-',0xbf}; void SEG_refresh(unsigned char *p) { static char x=0xfe; // 1111 1110 static unsigned int i=0; P2=0xff; P0=x; P2=p[i]; x=_crol_(x,1); i++; if(i==8) i=0; } void char_to_code(unsigned char *p,unsigned char *q) // display_char是p,display_dat是q { unsigned int i,j,k; for(k=0,j=0;k<8;j++,k++) { for(i=0;i<13;i++) { if(p[j]==display_code[2*i]) { q[k]=display_code[2*i+1]; break; } if(p[j+1]=='.') { q[k]=q[k] & 0x7f;//将最高位置为0,即显示小数点 j++; } } } }
//显示11-13-56 #include<REG51.h> #include<intrins.h> #include <stdio.h> #include "seg.h" char hour=11,min=13,sec=56; char display_dat[8]; char display_char[10]; void main() { unsigned int j; sprintf(display_char,"%02d-%02d-%02d",(int)hour,(int)min,(int)sec); //将C 234.5输入到display_char数组里面 char_to_code(display_char,display_dat); while(1) { SEG_refresh(display_dat); for(j=0;j<500;j++); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。