分析动态数码管控制的原理

可以看出来,P0寄存器的每一个bit分别与输入a~dp联系起来,当对应的引脚为高电平时,对应的LED则点亮。

但是我们也可以发现每个8位数码管都是由输入a~dp进行控制的,那岂不是我们输入一个值,每个LED都呈现相同的内容了?但其实除了P0寄存器之外,P2的第2-4位作为了动态数码管的使能位,例如当这三位为1,1,1时,此时编号为0的寄存器处于使能状态,它可以输出发光的内容,而其他寄存器处于禁用状态。

由此可见,我们在同一个时刻最多只能有1个八位数码管可以发光,其他的7个都是熄灭的,那如何使我们能看到它们同时发光呢?其实很简单,只要使能位切换得够快,我们肉眼就会发现变化的几个数码管都是发光的了。

编码实现效果

数码管计数器

在这里插入图片描述
首先我们需要先将0~F这16个字符使用数码管表示出来,例如0这个字符,输入a,b,c,d,e,f应该为1(即高电平),则我们输入给P0的值应该是0b0011 1111,即0x3f,这样再设置P2[2..4]=0b111(设置使能位,使第0个数码管可以工作),这样我们就可以在第0个数码管看到字符0了,其他以此类推,就可以得到这16个字符对应的二进制编码。

为了方便使用,我们使用一个数组将这16个字符的编码存储起来:

u8 code smgduan[16]={
    0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
    0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71            
};

然后我们可以实现计数器,即使一个数码管从0到F不断地变化,核心代码如下:

int main() {
    u8 i;

    P0 = smgduan[2];
    while (1)
    {
        for(i=0;i<16;i++) {
            P0 = smgduan[i];
            defaultDeley();
        }
    }
}

因为P2[2..4]默认为0b111,即默认是第一个数码管是可以显示的,所以我们会看到第一个数码管由0变化到F。

运行效果:

数码管流水灯

之前已经实现了LED流水灯,这次希望可以实现数码管的流水灯。要实现它,我们需要完成数码管索引到使能位的映射,即当我指定一个编号的数码管使能时,它就能工作。

我们先看一下映射关系:
这里0号数码管使能时,P2[2..4]=0b111;当1号数码管使能时,P2[2..4]=0b110...

可以发现这里有个明显的对应关系,设数码管编号为index,则只需令P2[2..4]=0b111-index,即可使对应的数码管工作了,但是如何使结果赋值到P2[2..4]这三位中呢?

此时我们可以使用“与置零,或置一”的方法进行部分赋值,例如下面的一个例子:

有变量toSet == 0b1010 0011此时我们希望使它的低4位变成1010,我们可以

  1. toSet = toSet & 0b1111 0000,此时toSet的值为0b1010 0000,即我们将它的低4位设置为了0
  2. toSet = toSet | 0b0000 1010,此时toSet的值为0b1010 1010,完成了要求

即我们对某几位赋值时可以先用与运算符清零,再使用或运算符赋值完成操作,而这里需要修改P[2...4]三位,我们先使用0x1110 0011(即~(0x7<<2))使用与运算符进行清零,再将(0b111-index)<<2使用或运算符进行赋值即可,代码如下:

void enableIndexLED(u8 index) {
    P2 = P2 & ~(0x07 << 2);
    P2 = P2 | (0x7 - index)<<2;
}

如此一来,我们的流水灯就可以轻松实现,核心代码如下(这里流水灯的数字为‘2’):

int main() {
    u8 i = 0;

    P0 = smgduan[2];
    while (1)
    {
        for(i = 0;i<8;i++) {
            enableIndexLED(i);
            defaultDeley();
        }
    }
}

运行效果:

显示0~65535的数字

首先我们将各个数码管需要显示的数字使用一个数组进行存储:u8 array[8];,这个数组的元素的值被初始化为notDisplay:

#define notDisplay 255
int main() {
	u8 array[8];
	u8 n= 8;
	for(i=0;i<n;i++) {
	    array[i] = notDisplay;
	}
}

然后我们编写一个函数,接收一个变量num,然后将它分解后填入到array数组中的位置中:

void updateArray(u16 num, u8* array, u8 n) {
    u8 i = n-1;
    if (num == 0) {
        array[i] = 0;
        return;
    }
    while (num)
    {
        array[i] = num %10;
        num /=10;
        i--;
    }
}

例如数字65535会使数组变为:

    array=[notDisplay,notDisplay,notDisplay,6,5,5,3,5]
    

    然后我们将数组的每个非notDisplay的元素映射到数码管即可。

    显示数字函数:

    // 第index个数码管显示num数字
    void displayOneNum(u8 index, u8 num) {
        enableIndexLED(index);
        if (num == notDisplay) {
            P0 = 0x00;
        }else {
            P0 = smgduan[num];
        }
    }
    
    void display(u16 num, u8* array, u8 n) {
        u8 i;
        updateArray(num, array, n);
        for(i=0;i<n;i++) {
            displayOneNum(i, array[i]);
            deley(100);
            P0=0x00;//消隐
        }
    }
    

    主函数:

    int main() {
        u8 i = 0;
        u8 array[8];
        u8 n = 8;
        u16 cnt = 1;
    
        for(i=0;i<n;i++) {
            array[i] = notDisplay;
        }
        while (1) {
            display(12345,array,n);
        }
    }
    

    这样我们就可以在数码管中看到“65535”的数字字符串了,运行结果:

    计数器升级版

    这次的计数器是可以从0计数到65535的

    displayForAWhile()函数如下所示:

    void displayForAWhile(u16 num, u8* array, u8 n) {
        u8 times = 125;
        while (times--)
        {
            display(num,array,n);
        }
    }
    

    当输入一个数字时,这个函数中会重复显示这个数字125次,因为1s大概是deley(100000)消耗的时间,这里渲染一次一个数字的时间大概是deley(100*8)=deley(800),然后渲染125次的时间约为deley(100000),即这个函数会显示num约一秒钟,所以我们可以用来做个简单的计数器,主函数代码如下:

    int main() {
        u8 i = 0;
        u8 array[8];
        u8 n = 8;
        u16 cnt = 1;
        
        while (1)
        {
            for(i=0;i<n;i++) {
                array[i] = notDisplay;
            }
            for(cnt = 0; cnt <= 65535; cnt++) {
                displayForAWhile(cnt, array, n);
            }
        }
    }
    

    这样它就会从0一直变化到65535了。

    运行结果(这是加快后的运行结果):

    模拟时钟

    tips: 在设计的过程中其实使用到了面向对象的思想,那些Time_xxx的函数其实在观念上是Time类的成员函数或静态函数,这样设计可以更加有条理。

    首先先设计存储时间的结构体:

    typedef struct {
        u8 hour, minute, second;
    } Time;
    

    时间递增函数,每调用一次秒数加一,然后再处理进位:

    void Time_increace(Time *time) {
        time->second++;
        if (time->second==60)
        {
            time->second = 0;
            time->minute++;
            if (time->minute==60)
            {
                time->minute = 0;
                time->hour++;
                if (time->hour == 24)
                {
                    time->hour = 0;
                }
            }
        }
    }
    

    数字分解函数:

    void Time_split(u8 num, u8* first, u8* second) {
        *second = num%10;
        num/=10;
        *first = num%10;
    }
    

    即功能是输入一个数字,这里是时或秒或分,把它分解成两个数并存储到first和second指针指向的空间。

    数据展示函数:

    static void Time_displayData(u8 num, u8 startIndex) {
        u8 first, second;
        Time_split(num, &first, &second);
        displayOneNum(startIndex, first);
        deley(100);
        P0=0x00;//消隐
    
        displayOneNum(startIndex+1,second);
        deley(100);
        P0=0x00;//消隐
    }
    

    输入一个数字,为时或秒或分,先将这个数字分解成两个数字,然后分别展示到第startIndex个数码管和第startIndex+1个数码管上。

    中间分隔符展示函数:

    void Time_displaySplitor() {
        displayOneChar(2,0x40);
        deley(100);
        P0=0x00;//消隐
        displayOneChar(5,0x40);
        deley(100);
        P0=0x00;//消隐
    }
    

    总的时间展示函数:

      void Time_show(Time* time) {
          Time_displayData(time->hour, 0);
          Time_displayData(time->minute, 3);
          Time_displayData(time->second, 6);
      
          Time_displaySplitor();
      }
      

      展示一个时间一秒钟的函数:(即展示一个时间100次,大概1s的时间)

      Time_showForASecond(Time* time) {
          u8 times = 100;
          while (times--) {
              Time_show(time);
          }
      }
      

      主函数:

      int main() {
          Time time;
          time.hour = time.minute = time.second = 0;
          
          while (1)
          {
              Time_showForASecond(&time);
              Time_increace(&time);
          }
      }
      

      这样我们就实现了计时的功能。

      运行效果(这里使用time.hour = 23; time.minute = 59; time.second = 50; 进行测试。):
      在这里插入图片描述