第6章 中断与数码管动态显示

数码管的动态显示

多个数码管快速(10ms 以内)依次轮流点亮,利用人类视觉暂留现象,可以做到看起来是所有数码管都同时亮,也叫做动态扫描。

六个数码管全部点亮,从0开始增加显示,间隔时间为1秒钟。代码如下:(存在数码管鬼影、抖动问题)

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值0xFF确保启动时都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

void main()
{
    unsigned char i = 0;    //动态扫描的索引
    unsigned int  cnt = 0;  //记录T0中断次数
    unsigned long sec = 0;  //记录经过的秒数

    ENLED = 0;    //使能U3,选择控制数码管
    ADDR3 = 1;    //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
    TMOD = 0x01;  //设置T0为模式1
    TH0  = 0xFC;  //为T0赋初值0xFC67,定时1ms
    TL0  = 0x67;
    TR0  = 1;     //启动T0
    
    while (1)
    {
        if (TF0 == 1)         //判断T0是否溢出
        {
            TF0 = 0;          //T0溢出后,清零中断标志
            TH0 = 0xFC;       //并重新赋初值
            TL0 = 0x67;
            cnt++;            //计数值自加1
            if (cnt >= 1000)  //判断T0溢出是否达到1000次
            {
                cnt = 0;      //达到1000次后计数值清零
                sec++;        //秒计数自加1
                //以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符
                LedBuff[0] = LedChar[sec%10];
                LedBuff[1] = LedChar[sec/10%10];
                LedBuff[2] = LedChar[sec/100%10];
                LedBuff[3] = LedChar[sec/1000%10];
                LedBuff[4] = LedChar[sec/10000%10];
                LedBuff[5] = LedChar[sec/100000%10];
            }
            //以下代码完成数码管动态扫描刷新
            switch (i)
            {
                case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
                case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
                case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
                case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
                case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
                case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
                default: break;
            }
        }
    }
}

鬼影是不该亮的数码管位有微微发亮。鬼影的出现,主要是在数码管位选和段选产生的瞬态造成的。比如进行数码管切换时候,由于代码是一句一句执行的,会造成中间存在其他瞬时状态,导致某一瞬间其他不想要的数码管被点亮。解决办法是关闭段,执行switch语句之前执行一次P0=0xFF,这样切换数码管过程中哪怕有其他瞬时态产生,也不会点亮数码管;另一个办法是关闭位,在执行switch语句之前,加一句 ENLED=1,关闭三八译码器,等数码管切换完成再执行 ENLED=0,启动三八译码器即可。

每一秒钟过后,不发生变化的数码管段可能会产生抖动。产生抖动的原因是每一秒钟更新缓存区数字内容所花费时间较多,导致每个数码管(总共六个)依次点亮时间变成5ms+缓冲区更新时间。因为没有在很短时间内完成数码管点亮刷新,导致数码管会变暗,然后点亮时候突然变量,人类视觉能够察觉这一亮度变化。

上述问题中第一个可以使用关闭段或者关闭位方法解决,第二个问题需要使用新知识中断方法进行解决。

IE——中断使能寄存器的位分配(地址 0xA8、可位寻址)

IE——中断使能寄存器的位描述

使用中断处理上述问题之后得到完整代码如下:

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值0xFF确保启动时都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0;   //动态扫描的索引
unsigned int cnt = 0;  //记录T0中断次数

void main()
{
    unsigned long sec = 0;  //记录经过的秒数

    EA = 1;       //使能总中断
    ENLED = 0;    //使能U3,选择控制数码管
    ADDR3 = 1;    //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
    TMOD = 0x01;  //设置T0为模式1
    TH0  = 0xFC;  //为T0赋初值0xFC67,定时1ms
    TL0  = 0x67;
    ET0  = 1;     //使能T0中断
    TR0  = 1;     //启动T0
    
    while (1)
    {
        if (cnt >= 1000)  //判断T0溢出是否达到1000次
        {
            cnt = 0;      //达到1000次后计数值清零
            sec++;        //秒计数自加1
            //以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符
            LedBuff[0] = LedChar[sec%10];
            LedBuff[1] = LedChar[sec/10%10];
            LedBuff[2] = LedChar[sec/100%10];
            LedBuff[3] = LedChar[sec/1000%10];
            LedBuff[4] = LedChar[sec/10000%10];
            LedBuff[5] = LedChar[sec/100000%10];
        }
    }
}
/* 定时器0中断服务函数 */
void InterruptTimer0() interrupt 1
{
    TH0 = 0xFC;  //重新加载初值
    TL0 = 0x67;
    cnt++;       //中断次数计数值加1
    //以下代码完成数码管动态扫描刷新
    P0 = 0xFF;   //显示消隐
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}

中断优先级:

中断优先级有两种,一种是抢占优先级,一种是固有优先级。

IP——中断优先级寄存器的位分配(地址 0xB8、可位寻址)

IP——中断优先级寄存器的位描述

抢占优先级中只有对IP寄存器中优先级置1,才能达到高优先级抢占低优先级的情况。在固定优先级中(表6-3),哪怕优先级更高的事件也不能抢占执行中的低优先级事件;固定优先级应用场景是当打开总中断时,同时有多个中断发生,在没有设置优先级的情况下,就按照固定优先级顺序依次执行。

使用六个数码管,实现从 999999 开始倒计时的程序,使用定时器 T1 中断完成。代码如下:

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值0xFF确保启动时都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0;   //动态扫描的索引
unsigned int cnt = 0;  //记录T1中断次数

void main()
{
    char j;  //循环变量
    unsigned long sec = 1000000;  //记录经过的秒数,倒计时初值为999999+1
    unsigned char buf[6];   //中间转换缓冲区

    EA = 1;       //使能总中断
    ENLED = 0;    //使能U3
    ADDR3 = 1;    //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
    TMOD = 0x10;  //设置T1为模式1
    TH1  = 0xFC;  //为T1赋初值0xFC67,定时1ms
    TL1  = 0x67;
    ET1  = 1;     //使能T1中断
    TR1  = 1;     //启动T1
    
    while (1)
    {
        if (cnt >= 1000)  //判断T1溢出是否达到1000次
        {
            cnt = 0;      //达到1000次后计数值清零
            sec--;        //秒计数自减1
            //将sec按十进制位从低到高依次提取到buf数组中
            buf[0] = sec%10;
            buf[1] = sec/10%10;
            buf[2] = sec/100%10;
            buf[3] = sec/1000%10;
            buf[4] = sec/10000%10;
            buf[5] = sec/100000%10;
            //从最高为开始,遇到0不显示,遇到非0退出循环
            for (j=5; j>=1; j--)
            {
                if (buf[j] == 0)
                    LedBuff[j] = 0xFF;
                else
                    break;
            }
            //将剩余的有效数字位如实转换
            for ( ; j>=0; j--)  //for()起始未对j操作,j即保持上个循环结束时的值
            {
                LedBuff[j] = LedChar[buf[j]];
            }
        }
    }
}
/* 定时器1中断服务函数 */
void InterruptTimer1() interrupt 3
{
    TH1 = 0xFC;  //重新加载初值
    TL1 = 0x67;
    cnt++;       //中断次数计数值加1
    //以下代码完成数码管动态扫描刷新
    P0 = 0xFF;   //显示消隐
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}

 

posted @   灵动24  阅读(521)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示