欧美极品高清xxxxhd,国产日产欧美最新,无码AV国产东京热AV无码,国产精品人与动性XXX,国产传媒亚洲综合一区二区,四库影院永久国产精品,毛片免费免费高清视频,福利所导航夜趣136

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 1|回復: 0
收起左側

第6章 中斷與數碼管動態顯示6.4

[復制鏈接]
ID:1167894 發表于 2026-3-30 14:35 | 顯示全部樓層 |閱讀模式
6.4數碼管的動態顯示
6.4.1 動態顯示的基本原理
        學習數碼管靜態顯示的時候提到,74HC138只能在同一時刻導通一個三極管,而數碼管是靠了6個三極管來控制,那如何來讓數碼管同時顯示呢?這就用到了動態顯示的概念。
多個數碼管顯示數字的時候,實際上是輪流點亮數碼管(一個時刻內只有一個數碼管是亮的),利用人眼的視覺暫留現象(也叫余輝效應),就可以做到看起來是所有數碼管都同時亮了,這就是動態顯示,也叫做動態掃描。
        例如:有2個數碼管,要顯示“12”這個數字,先讓高位的位選三極管導通,然后控制段選讓其顯示“1”,延時一定時間后再讓低位的位選三極管導通,控制段選讓其顯示“2”。把這個流程以一定的速度循環運行就可以讓數碼管顯示出“12”,由于交替速度非常快,人眼識別到的就是“12”這兩位數字同時亮了。
        那么一個數碼管需要點亮多長時間呢?也就是說要多長時間完成一次全部數碼管的掃描呢(很明顯:整體掃描時間=單個數碼管點亮時間*數碼管個數)?答案是10ms以內。當電視機和顯示器還處在CRT(電子顯像管)時代的時候,有一句很流行的廣告語——“100Hz無閃爍”,只要刷新率大于100Hz,即刷新時間小于10ms,就可以做到無閃爍,這也就是動態掃描的硬性指標。有最小值的限制嗎?理論上沒有,但實際上做到更快的刷新卻沒有任何進步的意義了,因為已經無閃爍了,再快也還是無閃爍,只是徒然增加CPU的負荷而已(因為1秒內要執行更多次的掃描程序)。所以,通常設計程序的時候,都是取一個接近10ms,又比較規整的值就行了。Kingst51開發板上有6個數碼管,現在就來著手寫一個數碼管動態掃描的程序,實現兼驗證動態顯示原理。
        目標還是實現秒表功能,只不過這次有6個位了,最大可以計到999999秒。現在要實現的這個程序相對于前幾章的例程來說就要復雜的多了,既要處理秒表計數,又要處理動態掃描。在編寫這類稍復雜的程序時,建議初學者們先用程序流程圖來把程序的整個流程理清,在動手寫程序之前先把整個程序的結構框架搭好,把每一個環節要實現的功能先細化出來,然后再用程序代碼一步一步的去實現出來。這樣就可以避免無處下筆的迷茫感了。如圖6-1就是本例的程序流程圖,先根據流程圖把程序的執行經過在大腦里走一遍,然后再看接下來的程序代碼,體會一下流程圖的作用,看是不是能幫助你更順暢的理清程序流程。
6-1.png
圖6-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];
            }
            //以下代碼完成數碼管動態掃描刷新
            if (i == 0)
            { ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; }
            else if (i == 1)
            { ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; }
            else if (i == 2)
            { ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; }
            else if (i == 3)
            { ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; }
            else if (i == 4)
            { ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; }
            else if (i == 5)
            { ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; }
        }
    }
}
        這段程序可以先抄到Keil中,結合程序流程圖理解,最終下載到實驗板上看一下運行結果。其中if...else語句就是每1ms快速的刷新一個數碼管,這樣6個數碼管整體刷新一遍的時間就是6ms,視覺感官上就是6個數碼管同時亮起來了。
         在C語言中,“/”等同于數學里的除法運算,而“%”等同于小學數學的求余數運算,這個前邊已有介紹。如果是123456這個數字,要正常顯示在數碼管上,個位顯示,就是直接對10取余數,這個“6”就出來了,十位數字就是先除以10,然后再對10取余數,以此類推,就把6個數字全部顯示出來了。
        對于多選一的動態刷新數碼管的方式,如果用switch會有更好的效果,來看一下用switch語句完成的情況。
#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語句是不是比if...else語句顯得要整齊清爽呢。
6.4.2數碼管顯示消隱
不知道讀者是否發現了,這兩個數碼管動態顯示程序的運行效果似乎并不是那么完美,第一個小問題,仔細看的話數碼管的不應該亮的段,似乎有微微的發亮,這種現象叫做“鬼影”,這個“鬼影”嚴重影響了視覺效果,該如何解決呢?
在今后可能會遇到各種各樣的實際問題,可能很多都是教材沒有講過的,遇到問題怎么辦呢?作為初學者,遇到的問題肯定不是第一個遇到的,肯定有前輩已經遇到過相同的或類似的問題,一般都會在網上發表各種帖子,各種討論,所以遇到問題,首先就應該形成一個到網上搜索的條件反射,這個問題大家可以到網上搜:“數碼管消隱”或者“數碼管鬼影解決”,多找相關關鍵詞搜索試試,會搜索也是一種能力。而且隨著技術發展,各種AI大模型也越來越強大,也可以直接咨詢AI大模型。
解決這類問題的方法有兩個,其中之一是延時,延時之后肉眼就可能看不到這個“鬼影”了。但是延時是一個非常拙劣的手段,且不說延時多久能看不到“鬼影”,延時后,數碼管亮度會普遍降低。解決問題,不能只知其然,還要知其所以然,那么首先就來弄明白為什么會出現“鬼影”。
“鬼影”的出現,主要是在數碼管位選和段選產生的瞬態造成的。舉個簡單例子,在數碼管動態顯示的那部分程序中,實際上每一個數碼管點亮的持續時間是1ms的時間,1ms后進行下個數碼管的切換。在進行數碼管切換的時候,比如從case 5要切換到case 0的時候,case 5的位選用的是
ADDR0=1; ADDR1=0; ADDR2=1;
假如此刻case 5也就是最高位數碼管對應的值是0,要切換成的case 0的數碼管位選是 ADDR0=0; ADDR1=0; ADDR2=0;
而對應的數碼管的值假如是1
又因為C語言程序是一句一句順序往下執行的,每一條語句的執行都會占用一定的時間,即使這個時間非常非常短暫case5case0,要改變2條語句。當把ADDR0=1”改變成“ADDR0=0”的時候,這個瞬間存在了一個中間狀態
ADDR0=0; ADDR1=0; ADDR2=1;
在這個瞬間上,就給case 4對應的數碼管DS5瞬間賦值了0。當全部寫完了ADDR0=0; ADDR1=0; ADDR2=0;后,這個時候,P0還沒有正式賦值,而P0此刻保持了前一次的值,也就是在這個瞬間,又給case 0對應的數碼管DS1賦值了一個0。直到把case 0后邊的語句全部完成后刷新才正式完成。在這個刷新過程中,有2個瞬間給錯誤的數碼管賦了值,雖然很弱(因為亮的時間很短),但是還是能夠發現。
搞明白了原理后,解決起來就不是困難的事情了,只要避開這個瞬間錯誤就可以了。不產生瞬間錯誤的方法是,在進行位選切換期間,避免一切數碼管的賦值即可。方法有兩個,一個方法是刷新之前關閉所有的段,改變好了位選后,再打開段即可;第二個方法是關閉數碼管的位,賦值過程都做好后,再重新打開即可。
關閉段:switch(i)這句程序之前,加一句P0=0xFF;這樣就把數碼管所有的段都關閉了,當把“ADDR”的值全部完成后,再給P0賦對應的值即可。
關閉位:switch(i)這句程序之前,加上一句ENLED=1;等到把ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0];這幾條刷新程序全部寫完后,再加上一句ENLED=0;然后再進行break操作即可。
這個地方邏輯思路上稍微有點復雜,但是一定要理解深刻,徹底弄明白,把這個瞬間的問題弄明白了,后邊很多牽扯到此類情況的問題,就可以一通百通。
上邊的數碼管程序還有第二個問題,數碼管上的數字每一秒變化一次,變化的時候,不參加變化的數碼管可能出現一次抖動,這個抖動沒有什么專業的名字,稱之為數碼管抖動吧。這種數碼管抖動是什么原因造成的呢?為何在數據改變的時候才抖動呢?
來分析一下程序,程序在定時到1秒的時候,執行了“秒數+1并轉換為數碼管顯示字符”這個操作,一個32位整型數的除法運算,實際上是比較耗費時間的,至于這一段程序究竟耗費了多少時間,可以通過前邊講的調試方法來看看這段程序運行用了多少時間。由于每次定時到1秒的時候,程序都多運行了這么一段,導致了某個數碼管的點亮時間比其他情況下要長一些,總時間就變成了1ms+本段程序運行時間,于此同時,其它的數碼管就熄滅了5ms+本段程序運行時間,如果這段程序運行時間非常短,可以忽略不計,但很明顯,現在這段程序運行時間已經比較長了,以致于嚴重影響到視覺效果了,所以要采取另外一種思路去解決這個問題。
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表