1.3 I/O引脚驱动数码管

在单片机系统中,有时需要显示一些简单的数字或者字符,此时可以使用数码管。数码管是一种由多个发光二极管组成的半导体发光器件,是51单片机系统常用的一种外围显示器件。常见的数码管可按显示的段数分为7段数码管、8段数码管和异型数码管;按能显示多少个字符/数字分为一位、两位等“X”位数码管;按照数码管中各个发光二极管的连接方式分为共阴极数码管和共阳极数码管。常见的数码管实物如图1.5所示。

图1.5 常见数码管实物

1.3.1 单位数码管的工作原理

常见的单位(一位)8段数码管的内部结构如图1.6所示,其由8个发光二极管组成,通过点亮不同的发光二极管组合可显示数字0~9,字符A、F、H、L、P、R、U、Y,符号“-”及小数点“.”。

图1.6 一位8段数码管内部结构

如图1.6所示,8段共阳极数码管中8个发光二极管的阳极(正极)连接在一起,其他引脚接各段驱动电路输出端。当这些发光二极管的正极输入高电平,对应发光二极管的输出端为低电平时,对应发光二极管导通,对应的段点亮,根据发光字段的不同组合显示出各种数字或字符。八段共阴极数码管的结构正好相反,8个发光二极管的阴极(负极)连接在一起,其他引脚接各段驱动电路输出端,当这些发光二极管的负极输入低电平,对应发光二极管的输出端为高电平时,对应的发光二极管导通,对应的段点亮,根据发光字段的不同组合可显示出各种数字或字符。与LED类似,当通过数码管的电流较大时显示段的亮度较高,反之较低,通常使用限流电阻来决定数码管的亮度。

51单片机一般采用软件译码和硬件译码两种方式来扩展数码管,前者是指通过软件控制51单片机的I/O输出从而达到控制数码管显示的方式,后者则是指使用专门的译码驱动硬件来控制数码管显示的方式;前者的硬件成本较低,但是占用单片机更多的I/O引脚,软件较为复杂,后者的硬件成本较高,但是程序设计简单,只占用较少的I/O引脚。

根据8段数码管的显示原理,要使数码管显示出相应的字符必须使单片机I/O口输出的数据即输入到数码管每个字段发光二极管的电平符合想要显示的字符要求。这个从目标输出字符反推出数码管各段应该输入数据的过程称为字形编码,8段数码管的字形编码如表1.3所示。

表1.3 8段数码管的字形编码

也就是说,对于I/O口驱动的数码管而言,只需在I/O上输出对应的字形编码,8段数码管即可显示所需要的字符或者数字。应该注意,在扩展数码管时也要考虑单片机的I/O驱动能力,与LED的驱动方式类似,数码管也有“拉电流”和“灌电流”两种驱动方式。

1.3.2 应用实例——流水数字

流水数字是一个51单片机使用I/O引脚驱动单位8段数码管的实例,数码管轮流地循环显示“0”~“F”数字或者字符,通常用于在单片机系统中显示一些数字或者字符。

流水数字实例的电路图如图1.7所示,单位8段共阳极数码管的公共端连接到VCC上,数码管的8位数据引脚则连接到P1的八位引脚上,使用1 kΩ电阻作为限流电阻,表1.4是实例使用的典型器件说明。

表1.4 数字流水实例器件列表

图1.7 流水数字实例电路图

【例1.3】 与LED闪烁实例类似,51单片机使用两个嵌套的for循环来进行软件延时,然后通过P1引脚将对应字符的字形编码送出供数码管显示,代码中使用一个存放在code存储器的数组SEGtable来存放字形编码,用一个conter计数器来指示当前应该输出的数字或者字符的字形编码在表格中的位置。

        #include <AT89X51.h>
        unsigned char code SEGtable[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,
        0xC6,0xA1,0x86,0x8E};
        //“0”~“F”对应的字形编码,共阳极8段数码管
        main()
        {
          unsigned char i,j;
          unsigned char counter=0;      //指示当前显示的数字或字符在表格中的存放位置
          while(1)
          {
            for(i=0;i<250;i++)      //软件延时
            {
              for(j=0;j<250;j++);
              for(j=0;j<250;j++);
            }
            if(counter>=15)          //已经显示到F,回到0继续显示
            {
              counter=0;
            }
            else
            {
              counter++;               //数字字符增加
            }
            P1 =SEGtable[counter];           //输出字符
          }
        }

1.3.3 多位数码管的工作原理

在实际的单片机系统应用中,常常是使用多个数码管来显示多个字符或者数字,此时需要多位数码管。可以使用多个独立的8段数码管来自己拼接,其优点是位数不限,布局灵活;也可以直接使用集成好的多位数码管,优点是引线简单(只有一套8段驱动引脚),价格相对来说便宜一些。

图1.8是6位共阳极集成数码管的结构图,可以看到,6位数码管的a、b、c、d、e、f、dp引脚都集成到了一起,而位选择1、2、3、4、5、6引脚则对应位数码管的阳极端点,用于选择点亮的位。

图1.8 6位共阳极集成数码管的结构图

多位数码管可以使用多个I/O端口驱动,如P0~P3分别驱动4个数码管,但是这样极大地浪费了I/O资源,所以通常在实际使用中用动态扫描的方法来实现多位数码管的显示。动态扫描是针对静态显示而言的,所谓静态显示是指数码管显示某一字符时,相应的发光二极管恒定导通或恒定截止,这种显示方式的每个数码管相互独立,公共端恒定接地(共阴极)或接电源(共阳极),每个数码管的每个字段分别与一个I/O口地址相连或与硬件译码电路相连,这时只要I/O口或硬件译码器有所需电平输出,相应字符即显示出来,并保持不变,直到需要更新所显示字符。采用静态显示方式占用单片机时间少,编程简单,但其占用的口线多,硬件电路复杂,成本高,只适合于显示位数较少的场合。而动态扫描则是一个一个地轮流点亮每个数码管,方法是多位数码管的a~dp数据段都用相同的I/O引脚来驱动,而使用不同的I/O引脚来控制位选择引脚。在动态扫描显示时,先选中第一个数码管,把数据送给它显示,一定时间后再选中第二个数码管,把数据送给它显示,一直到最后一个。这样虽然在某一时刻只有一个数码管在显示字符,但是只要扫描的速度足够高(超过人眼的视觉暂留时间),动态显示的效果在人看来就是几个数码管同时显示。采用动态扫描的方式比较节省I/O口,硬件电路也较静态显示方式简单,但其亮度不如静态显示方式,而且在显示的数码管较多时,51单片机要依次扫描,占用了单片机较多的时间。

在动态扫描的电路中,使用不同的I/O引脚来进行位选择,此时该I/O引脚必须能完成“点亮”→“熄灭”数码管的控制功能。该功能一般是通过一个通断电路控制共阳/共阴极端(位选择端)来实现的,当I/O引脚控制该电路接通时共阳/共阴极端被连接到VCC/地,对应的位数码管被选中显示。通常这个通断电路使用三极管来实现,图1.9是使用三极管控制4位8段数码管的电路结构图。

图1.9 用三极管控制4位8段数码管电路结构图

图1.9中,使用4个PNP三极管来控制4位数码管,当对应的控制引脚NUM1~NUM4输出高电平时,三极管导通,VCC被加在对应数码管的公共端(选择端),对应的数码管被选中,按照该数码管的数据输入显示对应的字符或者数字。

1.3.4 应用实例——多位数字显示和流水数码管显示

1. 多位数字显示实例

本实例使用51单片机驱动6位数码管显示“123456”6位数字,该应用常常用于需要显示多位数字或者字符的应用系统。

图1.10是实例的应用电路,51单片机用P1给NUM0~NUM5共6个独立的8段数码管提供字形编码,而用P2.0~P2.5共6个引脚通过PNP三极管来选通对应的数码管显示。表1.5是实例使用的典型器件说明。

图1.10 多位数码管显示数字实例电路图

表1.5 多位数字显示实例器件列表

【例1.4】 为了精确控制延时的时间以便于造成“扫描”效果,使用Delayus和DleayMs两个函数来控制精确延时。整个扫描过程如下:先通过P1端口输出要显示数字或者字符对应的字形编码,然后用P2口控制对应的位选通引脚输出高电平,PNP三极管导通,数码管显示;延时一段时间,关闭P2口对应的引脚,切换到下一位数码管,重复上述操作。

        #include <AT89X52.h>
        unsigned char code SEGtable[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,
        0xC6,0xA1,0x86,0x8E};
        //字形编码表
        void Delayus(unsigned int US)                //延时US函数
        {
            unsigned int i;
            US=US*5/4;
            for(i=0;i<US;i++);
        }
        void Delayms(unsigned int MS)                //延时MS函数
        {
            unsigned int i,j;
            for(i=0;i<MS;i++)
                for(j=0;j<1141;j++);
        }
        main()
        {
          P2=0x00;                                    //全部数码管未选中
          while(1)
          {
            P1 =SEGtable[0];                          //送数字1
            P2=0x01;                                  //选中数码管0
            Delayms(1);                             //延时保证显示完成
            P2=0x00;                                  //停止选中数码管0
            Delayus(100);                           //延时,切换到下一位数码管
            P1 =SEGtable[1];
            P2=0x02;                                  //选中数码管1
            Delayms(1);
            P2=0x00;
            Delayus(100);
            P1 =SEGtable[2];
            P2=0x04;                                  //选中数码管2
            Delayms(1);
            P2=0x00;
            Delayus(100);
            P1 =SEGtable[3];
            P2=0x08;                                  //选中数码管3
            Delayms(1);
            P2=0x00;
            Delayus(100);
            P1 =SEGtable[4];
            P2=0x10;                                  //选中数码管4
            Delayms(1);
            P2=0x00;
            Delayus(100);
            P1 =SEGtable[5];
            P2=0x20;                                  //选中数码管5
            Delayms(1);
            P2=0x00;
          }
        }

2. 流水数码管显示实例

流水数码管显示实例中,使用6位数码管从左到右轮流显示“0”~“F”字符。在类似于广告牌等的实际应用场合中,常常需要从左到右或者从右到左流动显示一些数字或字符。

该实例的电路图和多位数码管显示数字实例相同,如图1.10所示,【例1.5】是流水数码管显示实例的代码。

【例1.5】 与 【例1.4】类似,使用51单片机的P1口输出字形编码,使用P2口选通对应的位数码管,区别在于使用两个for循环嵌套,构成较长时间的延时,没有“视觉残留”效果。在P2输出选择的代码段,用switch-case语句来判断应该使P2的哪一位引脚输出高电平。

        #include <AT89X52.h>
        unsigned char code SEGtable[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,
        0xC6,0xA1,0x86,0x8E};
        main()
        {
          unsigned char i,j;
          unsigned char counter=0;             //用于确定显示的数字或者字符
          unsigned char Lcounter=0;            //用于确定选中的数码管
          P2=0x00;
          while(1)
          {
            for(i=0;i<250;i++)            //延时
            {
              for(j=0;j<250;j++);
              for(j=0;j<250;j++);
            }
            P1 =SEGtable[counter];            //输出用于显示数字或者字符的字形编码
            if(counter>=15)                 //如果到了“F”则回到“0”
            {
              counter=0;
            }
            else
            {
              counter++;
            }
            switch(Lcounter)                      //根据选中的位输出对应的控制信号
            {
              case 0x00:P2=0x01;break;
              case 0x01:P2=0x02;break;
              case 0x02:P2=0x04;break;
              case 0x03:P2=0x08;break;
              case 0x04:P2=0x10;break;
              case 0x05:P2=0x20;break;
              default:P2=0x01;break;
            }
            if(Lcounter>=5)                      //如果到了最边上的第5 位,回到0位
            {
              Lcounter=0;
            }
            else
            {
              Lcounter++;
            }
          }
        }