正文

【006】多位数码管动态显示 [51]2006-04-16 16:04:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/wentao/12535.html

分享到:

实验目的:数码管动态显示多位数字。
实验参考:笨笨工作室 实验五、多位数码动态显示。(查看原文
实验板: FB51A(查看


该实验用到实验板的资源电路图如下:

其中P0口是段码,低电平有效。P2口是位码,高电平有效。P2.0口控制第1个数码管,一直到P2.7口控制第8个。该板的段码表如下:

┌─────┬────────────────┬────┐
ㄧ          ㄧ P0.*:  7  6  5  4  3  2  1  0  ㄧ P0口值 ㄧ
ㄧ 十进制数 ㄧ  段 :  d dp  e  c  g  b  f  a  ㄧ 即段码 ㄧ
├─────┼────────────────┼────┤
ㄧ    0     ㄧ        0  1  0  0  1  0  0  0  ㄧ  48H   ㄧ
ㄧ    1     ㄧ        1  1  1  0  1  0  1  1  ㄧ  ebH   ㄧ
ㄧ    2     ㄧ        0  1  0  1  0  0  1  0  ㄧ  52H   ㄧ
ㄧ    3     ㄧ        0  1  1  0  0  0  1  0  ㄧ  62H   ㄧ
ㄧ    4     ㄧ        1  1  1  0  0  0  0  1  ㄧ  e1H   ㄧ
ㄧ    5     ㄧ        0  1  1  0  0  1  0  0  ㄧ  64H   ㄧ
ㄧ    6     ㄧ        0  1  0  0  0  1  0  0  ㄧ  44H   ㄧ
ㄧ    7     ㄧ        1  1  1  0  1  0  1  0  ㄧ  eaH   ㄧ
ㄧ    8     ㄧ        0  1  0  0  0  0  0  0  ㄧ  40H   ㄧ
ㄧ    9     ㄧ        0  1  1  0  0  0  0  0  ㄧ  60H   ㄧ
├─────┼────────────────┼────┤
ㄧ    0.    ㄧ        0  0  0  0  1  0  0  0  ㄧ  08H   ㄧ
ㄧ    1.    ㄧ        1  0  1  0  1  0  1  1  ㄧ  abH   ㄧ
ㄧ    2.    ㄧ        0  0  0  1  0  0  1  0  ㄧ  12H   ㄧ
ㄧ    3.    ㄧ        0  0  1  0  0  0  1  0  ㄧ  22H   ㄧ
ㄧ    4.    ㄧ        1  0  1  0  0  0  0  1  ㄧ  a1H   ㄧ
ㄧ    5.    ㄧ        0  0  1  0  0  1  0  0  ㄧ  24H   ㄧ
ㄧ    6.    ㄧ        0  0  0  0  0  1  0  0  ㄧ  04H   ㄧ
ㄧ    7.    ㄧ        1  0  1  0  1  0  1  0  ㄧ  aaH   ㄧ
ㄧ    8.    ㄧ        0  0  0  0  0  0  0  0  ㄧ  00H   ㄧ
ㄧ    9.    ㄧ        0  0  1  0  0  0  0  0  ㄧ  20H   ㄧ
└─────┴────────────────┴────┘

各个数码管的段码都是p0口的输出,即各个数码管输入的段码都是一样的, 为了使其分别显示不同的数字, 可采用动态显示的方式,即先只让最低位显示0(含点),经过一段延时,再只让次低位显示1,如此类推。由视觉暂留,只要我们的延时时间足够短,就能够使得数码的显示看起来非常的稳定清楚。过程如下图。

┌─────┬────┬─────────┐
ㄧ   段码   ㄧ  位码  ㄧ    显示器状态    ㄧ
├─────┼────┼─────────┤
ㄧ   08H    ㄧ  01H   ㄧ □□□□□□□0. ㄧ
ㄧ   abH    ㄧ  02H   ㄧ □□□□□□1.□ ㄧ
ㄧ   12H    ㄧ  04H   ㄧ □□□□□2.□□ ㄧ
ㄧ   22H    ㄧ  08H   ㄧ □□□□3.□□□ ㄧ
ㄧ   a1H    ㄧ  10H   ㄧ □□□4.□□□□ ㄧ
ㄧ   24H    ㄧ  20H   ㄧ □□5.□□□□□ ㄧ
ㄧ   04H    ㄧ  40H   ㄧ □6.□□□□□□ ㄧ
ㄧ   aaH    ㄧ  80H   ㄧ 7.□□□□□□□ ㄧ
└─────┴────┴─────────┘

采用上述方法思路编写如下:

       org   0000h

start: mov   a,#08h       ;0    ;段码
       mov   p0,a
       mov   p2,#01h            ;位码
       lcall delay_1ms

       mov   a,#0abh      ;1    
       mov   p0,a
       mov   p2,#02h
       lcall delay_1ms

       mov   a,#12h       ;2
       mov   p0,a
       mov   p2,#04h
       lcall delay_1ms

       mov   a,#22h       ;3
       mov   p0,a
       mov   p2,#08h
       lcall delay_1ms

       mov   a,#0a1h      ;4
       mov   p0,a
       mov   p2,#10h
       lcall delay_1ms

       mov   a,#24h       ;5
       mov   p0,a
       mov   p2,#20h
       lcall delay_1ms

       mov   a,#04h       ;6
       mov   p0,a
       mov   p2,#40h
       lcall delay_1ms

;      mov   a,#0aah      ;7
;      mov   p0,a

       mov   p0,#0aah     ;感觉用这句和上面两句实现一样,可能这种习惯以后会有用吧
       mov   p2,#80h
       lcall delay_1ms

       ljmp  start

delay_1ms: mov  r6,#2
temp:      mov  r5,#0ffh
           djnz r5,$
           djnz r6,temp
           ret
end

下载到板上得到测结果为从低到高八位分别显示0到7(含点)。

上述方法逐次给P0或者P2赋值,一方面程序的复杂程度增加,另外一方面会使得程序的灵活性降低。如果要改变显示的数字,程序改动起来很麻烦。 所以要用51单片机中常用的一种方法:查表法。例如P0口输出段码时,我们可以把要显示的段码放在一个表格中,然后每次从这个表格里面取数,送到P0口即可。P2口输出位码时,可以把要用的位码放在另一个表格里,每次从此表中取数,送入P2口。这样,如果要改变显示的数字,只需要改变表格里面的数。

       org   0000h

start: mov   r7,#0ffh     ;r7,r6查表时送入变址寄存器a (因自加1后为0,所以预置ffh)
       mov   r6,#0ffh
loop:  lcall play1        ;调用显示段码子程序
       lcall play2        ;调用显示位码子程序
       lcall delay_1ms
       cjne  a,#80h,loop  ;判断是否到了最左边的数,即第8个位码
       ajmp  start

play1:                    ;查表求段码子程序             
;      mov   a,r7          
;      inc   a
;      mov   r7,a

       inc   r7           ;这2句和上面三条语句实现功能相同
       mov   a,r7         ;a在这里做变址寄存器

       mov   dptr,#table1 ;表首址送dptr,dptr做基址寄存器
       movc  a,@a+dptr    ;基址寄存器加变址寄存器寻址
       mov   p0,a
       ret

play2:                    ;查表求位码子程序(原理同play1)
       mov   a,r6             
       inc   a
       mov   r6,a
       mov   dptr,#table2
       movc  a,@a+dptr
       mov   p2,a
       ret

table1: db 08h,0abh,12h,22h,0a1h,24h,04h,0aah  ;段码表
table2: db 01h,02h,04h,08h,10h,20h,40h,80h     ;位码表

delay_1ms:  mov   r5,#02h                      ;延时1ms子程序
temp:       mov   r4,#0ffh
            djnz  r4,$
            djnz  r5,temp
            ret
end

下载到板上验证得到预想结果。


C51实现如下(参考了AS的例程):

#include <reg51.h>
#include <intrins.h>             // 包含了左移函数_crol_()

void delayms(unsigned char ms); // 延时子程序

unsigned char data dis_digit;   // 位选通值, 传送到P2口用于选通当前数码管的数值,
                                // 如等于0x01时,选通P2.0口数码管

unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1,        // 0,1,2,3, 4
                                 0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9, off

unsigned char data dis_buf[8];  // dis_buf 显于缓冲区基地址

unsigned char data dis_index;   // 显示索引, 用于标识当前显示的数码管和缓冲区的偏移量

void main()
{
    P0 = 0xff;  // 关闭所有数码管
    P2 = 0x00;

    dis_buf[0] = dis_code[0];
    dis_buf[1] = dis_code[1];
    dis_buf[2] = dis_code[2];
    dis_buf[3] = dis_code[3];
    dis_buf[4] = dis_code[4];
    dis_buf[5] = dis_code[5];
    dis_buf[6] = dis_code[6];
    dis_buf[7] = dis_code[7];
   
    dis_digit = 0x01;  // 首先选通P2.0
    dis_index = 0;     // 当前偏移量为0
   
    while(1)
    {
         P0 = dis_buf[dis_index];          // 段码送P0口
         P2 = dis_digit;                   // 选能位(即位码)
         delayms(1);                       // 延时
         dis_digit = _crol_(dis_digit, 1); // 位选通左移, 下次选通下一位
         dis_index++;                      // 下一个段码
        
         dis_index &= 0x07;                // 见注释
    }

}
void delayms(unsigned char ms)             // 延时子程序(晶振12M)
{                       
    unsigned char i;
    while(ms--)
    {
        for(i = 0; i < 120; i++);
    }
}


注释: 此句作用是8个数码管全部扫描完一遍之后,再回到第一个开始下一次扫描。写回一般形式:dis_index = dis_index & 0x07 。这种方法挺新,第一次见到,十六进制的07就是二进制的00000111,这样通过与操作可以控制循环了。比如dis_index 经第一次循环后值为00000001,和0x07与操作后值不变仍为0x01,第二次循环时,其值为0为0x02,与0x07后仍为0x02,一直到其值增为0x07时还是不变的,但再次循环后其值为0x80,再与0x07后就变成0x00了,这样又从初始循环了。此句可用 if (dis_index == 8) dis_index = 0 代替,效果一样。

通过C51用上述方法实现时,其段码放在了数组dis_code[11]中,再通过缓冲区数组dis_buf[]将程序中要调用的值装入,这样就可以用下标(偏移量)访问了。这样看上去有些繁锁,但其思路比较清楚,结构上也很明了,具有通用性,便于扩展。

另外只要把程序中的延时加长,如delayms(250),下载到板上就可以看到实际上数码管是由低位到高位逐位显示的。


    若单单就实现这个功能而言,可以直接调入段码数组dis_code[11]中下标从0到7的值,而不必再设置缓冲数组dis_buf[],实现如下:

#include <reg51.h>
#include <intrins.h>            //_crol_()用

void delayms(unsigned char ms); //延时子程序

unsigned char data dis_digit;   //位选通值, 传送到P2口用于选通当前数码管的数值,
                                //如等于0x01时,选通P2.0口数码管

unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1,        // 0,1,2,3,4
                                 0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9,off

unsigned char data dis_index;  //显示索引, 用于标识当前显示的数码管和缓冲区的偏移量

void main()
{
    P0 = 0xff;        // 关闭所有数码管
    P2 = 0x00;

    dis_index = 0;    // 当前偏移量为0
    dis_digit = 0x01; // 选通P2.0

    while(1)
    {
        P0 = dis_code[dis_index]; // 段码送P0口
        P2 = dis_digit;           // 位码送P2口
        delayms(1);

        dis_digit = _crol_(dis_digit, 1); // 位选通左移, 下次选通下一位

        dis_index++;
        dis_index &= 0x07;
    }
}
void delayms(unsigned char ms)  // 延时子程序(晶振12M)
{                       
    unsigned char i;
    while(ms--)
    {
        for(i = 0; i < 120; i++);
    }
}

本来是想通过以下方式实现一次循环的:

        for (dis_index = 0; dis_index < 8; dis_index++)
        {
            P0 = dis_code[dis_index]; // 段码送P0口
            P2 = dis_index+1;         // 位码送P2口
            delayms(1);
        }

可得到的总是错误的结果:第0位到第2位这三位显示的是三个8,第3位显示的是7,高四位没有显示。加长延时逐位观察也没有发现错误的规律,对Keil的调试也不熟悉,先把问题留到这,待找出原因后再补上。

[2006.5.2] 找出原因啦,补上:

今天又看了一下,找到上面的错误出在哪了。当时是想用dis_index的值做为位码的,即第一位显示0时,段码为dis_code[0], 即dis_index值为0, 此时位码值为1。第二位显示1时,段码为dis_code[1],即dis_index值为1,此时位码值为2。所以就简单用了个加1运算,将P0口的偏移值与P2口的位码联系起来。但仔细想一下位码的原理,上述方法显然是错的,只要再验证一步就明白了,即当第3位显示2时,段码为dis_code[2], dis_index值为2,加1后为3,按上述方法时就将这个3作为了位码,而正确的位码应该是4 (00000100B)。所以出错。实际上这个对应关系是有的,但不是简简单单的加1,位码应该是2的dis_index次幂。即:
0--1
1--2
2--4
3--8
4--16        ……
幂次运算函数flaot pow(float x, float y)包含在math.h中, 返回值为xy (float型): 

        for (dis_index = 0; dis_index < 8; dis_index++)
        {
            P0 = dis_code[dis_index]; // 段码送P0口
            P2 = (char) pow(2, dis_index);         // 位码送P2口
            delayms(255);
        }

再次下载到板上发现仍有问题, 即延时很小的时候显示混乱,但加大延时时间(如程序中的值)可以观查到数码管是按位正确显示的。另外用这种方法产生的代码量也很大(从写入速度看,很明显)。这里仅提出了一个思路,只在此实验中适用,意义不大,到此为止。

[补充结束]


AS中绐出的例程是利用定时中断做的延时,参考修改到我的板上,程序如下:

#include <reg51.h>
#include <intrins.h>            // 包含了左移函数_crol_()

unsigned char data dis_digit;   // 位选通值, 传送到P2口用于选通当前数码管的数值,
                                // 如等于0x01时,选通P2.0口数码管

unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1,        // 0,1,2,3,4
                                 0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9,off

unsigned char data dis_buf[8];  // dis_buf 显于缓冲区基地址

unsigned char data dis_index;   // 显示索引, 用于标识当前显示的数码管和缓冲区的偏移量

void main()
{
    P0 = 0xff;    //关闭所有数码管
    P2 = 0x00;

    TMOD = 0x01;  // 00000001B 定时计数器0工作在方式1,16位定时器/计数器
    TH0 = 0xFC;     
    TL0 = 0x17;   // 预置初值 FC17H=64535D, 216-64535=1001us=1ms

    IE = 0x82;    // 10000010B T0溢出中断允许

    dis_buf[0] = dis_code[0x0];
    dis_buf[1] = dis_code[0x1];
    dis_buf[2] = dis_code[0x2];
    dis_buf[3] = dis_code[0x3];
    dis_buf[4] = dis_code[0x4];
    dis_buf[5] = dis_code[0x5];
    dis_buf[6] = dis_code[0x6];
    dis_buf[7] = dis_code[0x7];
   
    dis_digit = 0x01;   // 选通第0位数码管
    dis_index = 0;      // 偏移初值为0
   
    TR0 = 1;    // 启动T0
    while(1);   // 循环等待中断

}

void timer0() interrupt 1  // 定时器0中断服务程序, 用于数码管的动态扫描

{
    TH0 = 0xFC;                       // 发生中断定时/计数器重装初值
    TL0 = 0x17;                       // 感觉此处(及上)应该是0x18,而不是17,分析如下
    
    P2 = 0x00;                        // 先关闭所有数码管
    P0 = dis_buf[dis_index];          // 段码送P0口
    P2 = dis_digit;                   // 位码送P2口

    dis_digit = _crol_(dis_digit,1);  // 位选通值左移, 下次中断时选通下一位数码管
    dis_index++;
                   
    dis_index &= 0x07;     // 8个数码管全部扫描完一遍之后,再回到第一个开始下一次扫描
}

定时器/计数器的输入脉冲周期与机器周期一样, 为时钟振荡频率的1/12。晶振用12M时,输入脉冲周期间隔为1us。机器周期为 1us。设T0的初值为X,计算初值的方法:本例中定时器用方式1,是16位的定时器,即最大值为216=65536,超过此值将发生溢出,引起中断,进入中断处理程序。这里要让其延时1ms,即1000us, 则有式216-X=1000,可得X=64536,换算为16进制为FC18,即初值TH0=0xFC,TL0=0x18。即定时器由64536开始计数,经1000次计数后值为65536,将发生定时中断,再进入中断处理子程序后,重新装和初值,如此循环下去。
    而在上例中其装入的初值并非FC18(64536),而是FC17(64535)。我想大概认为其计数范围在0~65565的原因吧,我也想过这个问题,是用216-计数初值=中断间隔 呢,还是用(216-1)-计数初值=中断间隔呢? 随手查了几本书, 说法不一,不过用前者的较多, 我自己也认为前者比较合理, 因为在计算机中16位的二进制不能表示65536, 在各位均为1时表示的值为65535, 即65535H=1111111111111111B, 也可以说65536是溢出得到的。而何时响应中断就成了关键,拿上例来说,如设初值为64535(FC17),则计数到65535时,已经计数为1000个,即1ms,但此时并未发生溢出,因此也没有触发中断。而是在下一个计数后才发生。确切值应为1001us。若初值为64536(FC18),则恰好为所需值,所以上例中的初值应该用FC18而不是FC17。这仅仅是我自己的一点看法,至于是不是这样,还有待进一步考证。


最终下载到实验板上结果:

该电路段码是按与板上接法对应的,即按前面的段码表次序连接。另外这个八位的仿真数码管最左端是第一位,最右端是第八位,与板上的顺序相反,所以接为了统一,该图以板为准连接。上图不加上拉电阻也可仿真出结果,只是P0口高电平显示为灰,即高阻。

阅读(23487) | 评论(20)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册