单片机学习(三)开发板动态数码管的控制

目录

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

单片机学习(三)开发板动态数码管的控制

可以看出来,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; 进行测试。):
单片机学习(三)开发板动态数码管的控制

上一篇:Codeforce 835B - The number on the board (贪心)


下一篇:Python 的一些高级特性