硬知识
选自《普中51单片机开发攻略_V1.2》

数码管简介
数码管是一种半导体发光器件,也称 LED 数码管,其基本单元是发光二极管。
数码管按段数可分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元,也就是多一个小数点(DP),这个小数点可以更精确的表示数码管想要显示的内容;
按能显示多少个【8】可分为 1 位、 2 位、 3 位、 4 位、 5 位、 6 位、7 位等数码管。
按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。
共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。
共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极 COM 接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮。
不同位数的数码管实物图如下所示:


由图可知,一位数码管的引脚是 10 个,显示一个 8 字需要 7 个小段, 另外还有一个小数点,所以其内部一共有 8 个小的发光二极管,最后还有一个 公共端,多数生产商为了封装统一,单位数码管都封装 10 个引脚,其中第 3 和 第 8 引脚是连接在一起的。
公共端可分为共阳极和共阴极,图中间为共阳极内部原理图,右图为共阴极内部原理图。
对共阴极数码管来说,其 8 个发光二极管的阴极在数码管内部全部连接在一起,所以称“共阴”,而它们的阳极是独立的,通常在设计电路时一般把阴极接地。 当我们给数码管的任意一个阳极加一个高电平时,对应的这个发光二极管就点亮 了。如果想要显示出一个 8 字,并且把右下角的小数点也点亮的话,可以给 8 个阳极全部送高电平,如果想让它显示出一个 0 字,那么我们可以除了给第“g, dp” 这两位送低电平外,其余引脚全部都送高电平,这样它就显示出 0 字了。
如果使用共阴数码管,需要注意增加单片机 IO 口驱动电流,因为共阴数码管是要靠单片机 IO 口输出电流来点亮的,但单片机 I/O 口难以输出稳定的、如此大的电流,所以数码管与单片机连接时需要加驱动电路,可以用上拉电阻的方法或使用专门的数码管驱动芯片,比如 74HC573、74HC245 等,其输出电流较大, 电路接口简单。
共阳极数码管其内部 8 个发光二极管的所有阳极全部连接在一起,电路连接时,公共端接高电平,因此我们要点亮哪个发光管二极管就需要给阴极送低电平, 此时显示数字的编码与共阴极编码是相反的关系,数码管内部发光二极管点亮时,也需要 5mA 以上的电流,而且电流不可过大,否则会烧坏发光二极管。因此不仅要防止数码管电流过大,同时要防止流经数码管的电流集中到单片机时电流不能过大,否则会损坏主芯片。
一般共阳极数码管更为常用,这是因为数码管的非公共端往往接在 IC 芯片的 I/O 上,而 IC 芯片的驱动能力往往是比较小的,如果采用共阴极数码管,它的驱动端在非公共端, 就有可能受限于 IC 芯片输出电流不够而显示昏暗,要外加上拉电阻或者是增加三极管加大驱动能力。但是 IC 芯片的灌电流, 即输入电流范围比较大。所以使用共阳极数码管的好处是:将驱动数码管的工作交到公共端(一般接驱动电源),加大驱动电源的功率自然要比加大 IC 芯片 I/O 口的驱动电流简单许多。另一方面,这样也能减轻主芯片的负担。

多位数码管简介
多位数码管,即两个或两个以上单个数码管并列集中在一起形成一体的数码管。当多位一体时,它们内部的公共端是独立的,而负责显示什么数字的段线 (a-dp)全部是连接在一起的,独立的公共端可以控制多位一体中的哪一位数码管点亮,而连接在一起的段线可以控制这个能点亮数码管亮什么数字,通常把公共端叫做“位选线”,连接在一起的段线叫做“段选线”,有了这两个线后, 通过单片机及外部驱动电路就可以控制任意的数码管显示任意的数字了。

数码管动态显示原理
当多位数码管应用于某一系统时,它们的“位选”是可独立控制的,而“段选”是连接在一起的,我们可以通过位选信号控制哪几个数码管亮,而在同一时刻,位选选通的所有数码管上显示的数字始终都是一样的,因为它们的段选是连接在一起的,送入所有数码管的段选信号都是相同的,所以它们显示的数字必定一样,数码管的这种显示方法叫做静态显示。
而动态显示,就是利用减少段选线,分开位选线,利用位选线不同时选择通断,改变段选数据来实现的。比如在第一次选中第一位数码管时,给段选数据 0, 下一次位选中第二位数码管时显示 1。为了在显示 1 的时候,0 不会消失(当然实际上是消失了),必须在人肉眼观察不到的时间里再次点亮第一次点亮的 0。 而这时就需要记住,人的肉眼正常情况下只能分辨变化超过 24ms 间隔的运动。 也就是说,在下一次点亮 0 这个数字的时间差不得大于 24ms。这时就会发现, 数码管点亮是在向右或者向左一位一位点亮,形成了动态效果。如果把间隔时间改长就能直接展现这一现象。
74HC245 芯片简介

74HC245 是一种三态输出、八路信号收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动能力。

给 OE 使能管脚低电平,DIR 管脚为高电平传输方向是 A->B 输出,DIR 为低电平传输方向是 B->A,至于输出高电平还是输出低电平取决于输入端的状态,如果输入为低电平,输出即为低;输入为高电平,输出即为高。如果 OE 使能管脚为高电平, 不论 DIR 管脚是高还是低,输出是高阻态。
开发板上OE管脚已接GND,DIR管脚已接VCC,传输方向是 A->B。

74HC138 芯片简介


给 E1、E2 使能管脚低电平,E3 管脚为高电平,至于哪个管脚输出有效电平(低电平),要看 A0,A1,A2 输入管脚的电平状态。如果 A0,A1,A2 都为低电平,则 Y0 输出有效电平(低电平),其他管脚均输出高电平。如果 A0 为高电平,A1, A2 都为低电平,则 Y1 输出有效电平(低电平),其他管脚均输出高电平。
A0、A1、A2 输入就相当于 3 位 2 进制数,A0 是低位,A1 是次高位,A2 是高位。而 Y0~Y7 具体哪一个输出有效电平,就看输入二进制对应的十进制数值。比如输入是 101(A2,A1,A0),其对应的十进制数是 5,所以 Y5 输出有效电平(低电平)。

上机实战
stdint.h见【51单片机快速入门指南】1:基础知识和工程创建

源码
Tube.c

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"
#include "Tube.h"

#define Tube_Port P0

sbit P74138_A0 = P2^2;
sbit P74138_A1 = P2^3;
sbit P74138_A2 = P2^4;

//显示的延时 400us @11.0592MHz
void LED_Tube_Delay()
{
    unsigned char i;

    _nop_();
    i = 181;
    while (--i);
}

//位选
void F74138(uint8_t num)
{
    num &= 7;
    P74138_A0 = (num & 1);
    P74138_A1 = (num & 2) >> 1;
    P74138_A2 = (num & 4) >> 2;
}

code uint8_t Tube_Codes_0ToF[] =
{
    Tube_Code_0,
    Tube_Code_1,
    Tube_Code_2,
    Tube_Code_3,
    Tube_Code_4,
    Tube_Code_5,
    Tube_Code_6,
    Tube_Code_7,
    Tube_Code_8,
    Tube_Code_9,
    Tube_Code_A,
    Tube_Code_b,
    Tube_Code_C,
    Tube_Code_d,
    Tube_Code_E,
    Tube_Code_F
};

//以科学计数法显示小数
void Display_Double(double Num)
{
    uint8_t i = 0;
    int8_t pow = 0;
    uint8_t DisplayNum = 0;
    double Num_clone;
    char Tube_Double_Buffer[8] = {0};

    if (Num < 0)
    {
        Tube_Double_Buffer[7] = Tube_Code_Negative_Sign;                
        Num = -Num;
    }
    if (Num < 1e-8)                                                    //视为0
    {
        for(i = 0; i < 7; ++i)
            Tube_Double_Buffer[i + 1] = Tube_Code_NULL;
        Tube_Double_Buffer[0] = Tube_Codes_0ToF[0];
    }
    else if (Num > 1e8)                                                //视为溢出,Error
    {
        Tube_Double_Buffer[6] = Tube_Code_NULL;
        Tube_Double_Buffer[5] = Tube_Code_E;    
        Tube_Double_Buffer[4] = Tube_Code_r;                        
        Tube_Double_Buffer[3] = Tube_Code_r;                        
        Tube_Double_Buffer[2] = Tube_Code_o;                        
        Tube_Double_Buffer[1] = Tube_Code_r;                        
        Tube_Double_Buffer[0] = Tube_Code_NULL;
    }
    else 
    {
        Num_clone = Num;
        if(Num_clone < 1000)
        {
            while(Num_clone < 1000)
            {
                Num_clone *= 10;
                ++pow;
            }
            pow = 3-pow;                                            //求指数
        }
        else
        {
            pow = 3;
            while(Num_clone > 9999)
            {
                Num_clone /= 10;
                ++pow;                                                //求指数
            }
        }
        Num_clone /= 10;
        for(i = 0; i < 3; ++i)                                        //三位有效数字
        {
            DisplayNum = (int)(Num_clone + (i == 0) * 0.5) % 10;    //末位四舍五入
            Tube_Double_Buffer[i + 4] = Tube_Codes_0ToF[DisplayNum];
            Num_clone /= 10;
        }
        Tube_Double_Buffer[6] |= Tube_Code_Dot;                        //补小数点
        Tube_Double_Buffer[3] = Tube_Code_E;    

        if(pow < 0)
        {
            Tube_Double_Buffer[2] = Tube_Code_Negative_Sign;        //指数符号为负
            pow = -pow;
        }
        for(i = 0; i < 2; ++i)
        {
            DisplayNum = pow % 10;
            Tube_Double_Buffer[i] = Tube_Codes_0ToF[DisplayNum];    //指数
            pow /= 10;
        }
    }
    for(i = 0; i < 8; ++i)
    {
        Tube_Port = Tube_Code_NULL;                                    //关闭位选
        F74138(i);                                                    
        Tube_Port = Tube_Double_Buffer[i];                            //显示
        LED_Tube_Delay();
    }
    return;
}

//显示整数
void Display_Int(int32_t Num)
{
    uint8_t i = 0;
    uint8_t DisplayNum = 0;
    char Tube_Double_Buffer[8] = {0};
    if(Num >= 10000000 || Num <= -10000000)                                    
    {
        Display_Double((double)Num);                            //以指数形式输出
        return;
    }
    else
    {
        if(Num < 0)
        {
            Tube_Double_Buffer[7] = Tube_Code_Negative_Sign;    //负号
            Num = -Num;
        }
        for(i = 0; i < 7; ++i)
        {
            DisplayNum = Num % 10;
            if(Num == 0 && i != 0)
                Tube_Double_Buffer[i] = Tube_Code_NULL;
            else
                Tube_Double_Buffer[i] = Tube_Codes_0ToF[DisplayNum];
            Num /= 10;
        }
        for(i = 0; i < 8; ++i)
        {
            Tube_Port = Tube_Code_NULL;                    //关闭位选
            F74138(i);
            Tube_Port = Tube_Double_Buffer[i];            //显示
            LED_Tube_Delay();
        }
        return;
    }
}

//显示数字
void Display_Num(double Num)
{
    if(Num == (int32_t)Num)
        Display_Int((int32_t)Num);
    else
        Display_Double(Num);
    return;
}

Tube.h

#ifndef TUBE_H_
#define TUBE_H_

#define Tube_Code_NULL 0x00

#define Tube_Code_0 0x3f
#define Tube_Code_1 0x06
#define Tube_Code_2 0x5b
#define Tube_Code_3 0x4f
#define Tube_Code_4 0x66
#define Tube_Code_5 0x6d
#define Tube_Code_6 0x7d
#define Tube_Code_7 0x07
#define Tube_Code_8 0x7f
#define Tube_Code_9 0x6f

#define Tube_Code_A 0x77
#define Tube_Code_b 0x7c
#define Tube_Code_C 0x39
#define Tube_Code_c 0x58
#define Tube_Code_d 0x5e
#define Tube_Code_E 0x79
#define Tube_Code_F 0x71
#define Tube_Code_G 0x3d
#define Tube_Code_H 0x76
#define Tube_Code_I 0x30
#define Tube_Code_i 0x10
#define Tube_Code_J 0x0e
#define Tube_Code_K    0x7a
#define Tube_Code_L 0x38
#define Tube_Code_M 0x55
#define Tube_Code_n    0x54
#define Tube_Code_o 0x5c
#define Tube_Code_P 0x73
#define Tube_Code_q 0x67
#define Tube_Code_r 0x50
#define Tube_Code_S 0x64
#define Tube_Code_t 0x78
#define Tube_Code_U    0x3e
#define Tube_Code_u 0x1c
#define Tube_Code_v 0x62
#define Tube_Code_W 0x6a
#define Tube_Code_X 0x36
#define Tube_Code_y 0x6e
#define Tube_Code_Z 0x49

#define Tube_Code_Dot 0x80
#define Tube_Code_Negative_Sign 0x40

void Display_Int(int32_t Num);
void Display_Double(double Num);
void Display_Num(double Num);

#endif

main.c

#include <REGX52.H>
#include "intrins.h"
#include "stdint.h"
#include "Tube.h"

void main(void)
{    
    while(1)
    {
        Display_Num(114514);
    }        
}

效果
显示整数
正数



较大的数


负数


显示小数(保留三位有效数字)
正数



负数