找回密碼
         注冊會員
        搜索附件  
        MCU資訊論壇 附件中心 單片機論壇 51單片機論壇 第五章例程.rar

        第五章例程.rar

         

        “從單片機初學者邁向單片機工程師”之LED主題討論周第五章--多:
        [post]數碼管在實際應用中非常廣泛,尤其是在某些對成本有限制的場合。編寫一個好用的LED程序并不是那么的簡單。曾經有人這樣說過,如果用數碼管和按鍵,做一個簡易的可以調整的時鐘出來,那么你的單片機就算入門了60%了。此話我深信不疑。我遇到過很多單片機的愛好者,他們問我說單片機我已經掌握了,該如何進一步的學習下去呢?我并不急于回答他們的問題,而是問他們:會編寫數碼管的驅動程序了吧?“嗯”。會編寫按鍵程序了吧?“嗯”。好,我給你出一個小題目,你做一下。用按鍵和數碼管以及單片機定時器實現一個簡易的可以調整的時鐘,要求如下:
        8位數碼管顯示,顯示格式如下
        時-分-秒
        XX-XX-XX
        要求:系統有四個按鍵,功能分別是 調整,加,減,確定。在按下調整鍵時候,顯示時的兩位數碼管以1 Hz 頻率閃爍。如果再次按下調整鍵,則分開始閃爍,時恢復正常顯示,依次循環,直到按下確定鍵,恢復正常的顯示。在數碼管閃爍的時候,按下加或者減鍵可以調整相應的顯示內容。按鍵支持短按,和長按,即短按時,修改的內容每次增加一或者減小一,長按時候以一定速率連續增加或者減少。
        結果很多人,很多愛好者一下子都理不清楚思路。其實問題的根源在于沒有以工程化的角度去思考程序的編寫。很多人在學習數碼管編程的時候,都是照著書上或者網上的例子來進行試驗。殊不知,這些例子代碼僅僅只是具有一個演示性的作用,拿到實際中是很難用的。舉一個簡單的例子。
        下面這段程序是在網上隨便搜索到的:
        while(1)
        {
        for(num=0;num<9;num++)
        {
        P0=table[num];
        P2=code[num] ;
        delayms(2) ;
        }
        }
        看出什么問題來了沒有,如果沒有看出來請仔細想一下,如果還沒有想出來,請回過頭去,認真再看一遍“學會釋放CPU”這一章的內容。這個程序作為演示程序是沒有什么問題的,但是實際應用的時候,數碼管顯示的內容經常變化,而且還有很多其它任務需要執行,因此這樣的程序在實際中是根本就無法用的,更何況,它這里也調用了delayms(2)這個函數來延時2 ms這更是令我們深惡痛絕?

        本章的內容正是探討如何解決多任務環境下(不帶OS)的數碼管程序設計的編寫問題。理解了其中的思想,無論要求我們顯示的形式怎么變化(如數碼管閃爍,移位等),我們都可以很方便的解決問題。

        數碼管的顯示分為動態顯示和靜態顯示兩種。靜態顯示是每一位數碼管都用一片獨立的驅動芯片進行驅動。比較常見的有74LS164,74HC595等。利用這類芯片的好處就是可以級聯,留給單片機的接口只需要時鐘線,數據線,因此比較節省I/O口。如下圖所示:



        利用74LS164級聯驅動8個單獨的數碼管
        靜態顯示的優點是程序編寫簡單。但是由于涉及到的驅動芯片數量比較多,同時考慮到PCB的布線等等因素,在低成本要求的開發環境下,單純的靜態驅動并不合適。這個時候就可以考慮到動態驅動了。
        動態驅動的圖如下所示(以EE21開發板為例)



        由上圖可以看出。8個數碼管的段碼由一個單獨的74HC573驅動。同時每一個數碼管的公共端連接在另外一個74HC573的輸出上。當送出第一位數碼管的段碼內容時候,同時選通第一位數碼管的位選,此時,第一位數碼管就顯示出相應的內容了。一段時間之后,送出第二位數碼管段碼的內容,選通第二位數碼管的位選,這時顯示的內容就變成第二位數碼管的內容了……依次循環下去,就可以看到了所有數碼管同時顯示了。事實上,任意時刻,只有一位數碼管是被點亮的。由于人眼的視覺暫留效應以及數碼管的余輝效應,當數碼管掃描的頻率非常快的時候,人眼已經無法分辨出數碼管的變化了,看起來就是同時點亮的。我們假設數碼管的掃描頻率為50 Hz, 則完成一輪掃描的時間就是1 / 50 = 20 ms 。我們的系統共有8位數碼管,則每一位數碼管在一輪掃描周期中點亮的時間為20 / 8 = 2.5 ms 。
        動態掃描對時間要求有一點點嚴格,否則,就會有明顯的閃爍。
        假設我們程序 中所有任務如下:

        while(1)
        {
        LedDisplay() ; //數碼管動態掃描
        ADProcess() ; //AD采集處理
        TimerProcess() ; //時間相關處理
        DataProcess() ; //數據處理
        }
        LedDisplay() 這個任務的執行時間,如同我們剛才計算的那樣,50 Hz頻率掃描,則該函數執行的時間為20 ms 。 假設ADProcess()這個任務執行的的時間為2 ms ,TimerProcess()這個函數執行的時間為 1 ms ,DataProcess() 這個函數執行的時間為10 ms 。 那么整個主函數執行一遍的總時間為 20 + 2 + 1 + 10 = 33 ms 。即LedDisplay() 這個函數的掃描頻率已經不為50 Hz 了,而是 1 / 33 = 30.3 Hz 。這個頻率數碼管已經可以感覺到閃爍了,因此不符合我們的要求。為什么會出現這種情況呢? 我們剛才計算的50 Hz 是系統只有LedDisplay()這一個任務的時候得出來的結果。當系統添加了其它任務后,當然系統循環執行一次的總時間就增加了。如何解決這種現象了,還是離不開我們第二章所講的那個思想。
        系統產生一個2.5 ms 的時標消息。LedDisplay() , 每次接收到這個消息的時候, 掃描一位數碼管。這樣8個時標消息過后,所有的數碼管就都被掃描一遍了。可能有朋友會有這樣的疑問:ADProcess() 以及 DataProcess() 等函數執行的時間還是需要十幾ms 啊,在這十幾ms 的時間里,已經產生好幾個2.5 ms的時標消息了,這樣豈不是漏掉了掃描,顯示起來還是會閃爍。能夠想到這一點,很不錯,這也就是為什么我們要學會釋放CPU的原因。對于ADProcess(),TimerProcess(),DataProcess(),等任務我們依舊要采取此方法對CPU進行釋放,使其執行的時間盡可能短暫,關于如何做到這一點,在以后的講解如何設計多任務程序設計的時候會講解到。
        下面我們基于此思路開始編寫具體的程序。
        首先編寫Timer.c文件。該文件中主要為系統提供時間相關的服務。必要的頭文件包含。
        #include <reg52.h>
        #include "MacroAndConst.h"
        為了方便計算,我們取數碼管掃描一位的時間為2 ms。設置定時器0為2 ms中斷一次。
        同時聲明一個位變量,作為2 ms時標消息的標志
        bit g_bSystemTime2Ms = 0 ; // 2msLED動態掃描時標消息
        初始化定時器0
        void Timer0Init(void)
        {
        TMOD &= 0xf0 ;
        TMOD |= 0x01 ; //定時器0工作方式1
        TH0 = 0xf8 ; //定時器初始值
        TL0 = 0xcc ;
        TR0 = 1 ;
        ET0 = 1 ;
        }
        在定時器0中斷處理程序中,設置時標消息。
        void Time0Isr(void) interrupt 1
        {
        TH0 = 0xf8 ; //定時器重新賦初值
        TL0 = 0xcc ;
        g_bSystemTime2Ms = 1 ; //2MS時標標志位置位
        }
        然后我們開始編寫數碼管的動態掃描函數。
        新建一個C源文件,并包含相應的頭文件。
        #include <reg52.h>
        #include "MacroAndConst.h"
        #include "Timer.h"
        先開辟一個數碼管顯示的緩沖區。動態掃描函數負責從這個緩沖區中取出數據,并掃描顯示。而其它函數則可以修改該緩沖區,從而改變顯示的內容。
        uint8 g_u8LedDisplayBuffer[8] = {0} ; //顯示緩沖區
        然后定義共陽數碼管的段碼表以及相應的硬件端口連接。
        code uint8 g_u8LedDisplayCode[]=
        {
        0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
        0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,
        0xbf, //'-'號代碼
        } ;

        sbit io_led_seg_cs = P1^4 ;
        sbit io_led_bit_cs = P1^5 ;

        #define LED_PORT P0

        再分別編寫送數碼管段碼函數,以及位選通函數。
        static void SendLedSegData(uint8 dat)
        {
        LED_PORT = dat ;
        io_led_seg_cs = 1 ; //開段碼鎖存,送段碼數據
        io_led_seg_cs = 0 ;
        }

        static void SendLedBitData(uint8 dat)
        {
        uint8 temp ;
        temp = (0x01 << dat ) ; //根據要選通的位計算出位碼
        LED_PORT = temp ;
        io_led_bit_cs = 1 ; //開位碼鎖存,送位碼數據
        io_led_bit_cs = 0 ;
        }

        下面的核心就是如何編寫動態掃描函數了。
        如下所示:


        void LedDisplay(uint8 * pBuffer)
        {
        static uint8 s_LedDisPos = 0 ;
        if(g_bSystemTime2Ms)
        {
        g_bSystemTime2Ms = 0 ;

        SendLedBitData(8) ; //消隱,只需要設置位選不為0~7即可

        if(pBuffer[s_LedDisPos] == '-') //顯示'-'號
        {
        SendLedSegData(g_u8LedDisplayCode[16]) ;
        }
        else
        {
        SendLedSegData(g_u8LedDisplayCode[pBuffer[s_LedDisPos]]) ;
        }

        SendLedBitData(s_LedDisPos);

        if(++s_LedDisPos > 7)
        {
        s_LedDisPos = 0 ;
        }
        }
        }

        函數內部定義一個靜態的變量s_LedDisPos,用來表示掃描數碼管的位置。每當我們執行該函數一次的時候,s_LedDisPos的值會自加1,表示下次掃描下一個數碼管。然后判斷g_bSystemTime2Ms時標消息是否到了。如果到了,就開始執行相關掃描,否則就直接跳出函數。SendLedBitData(8) ;的作用是消隱。因為我們的系統的段選和位選是共用P0口的。在送段碼之前,必須先關掉位選,否則,因為上次位選是選通的,在送段碼的時候會造成相應數碼管的點亮,盡管這個時間很短暫。但是因為我們的數碼管是不斷掃描的,所以看起來還是會有些微微亮。為了消除這種影響,就有必要再送段碼數據之前關掉位選。
        if(pBuffer[s_LedDisPos] == '-') //顯示'-'號這行語句是為了顯示’-’符號特意加上去的,大家可以看到在定義數碼管的段碼表的時候,我多加了一個字節的代碼0xbf:
        code uint8 g_u8LedDisplayCode[]=
        {
        0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
        0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,
        0xbf, //'-'號代碼
        } ;
        通過SendLedSegData(g_u8LedDisplayCode[pBuffer[s_LedDisPos]]) ;送出相應的段碼數據后,然后通過SendLedBitData(s_LedDisPos);打開相應的位選。這樣對應的數碼管就被點亮了。
        if(++s_LedDisPos > 7)
        {
        s_LedDisPos = 0 ;
        }
        然后s_LedDisPos自加1,以便下次執行本函數時,掃描下一個數碼管。因為我們的系統共有8個數碼管,所以當s_LedDisPos > 7后,要對其進行清0 。否則,沒有任何一個數碼管被選中。這也是為什么我們可以用
        SendLedBitData(8) ; //消隱,只需要設置位選不為0~7即可
        對數碼管進行消隱操作的原因。

        下面我們來編寫相應的主函數,并實現數碼管上面類似時鐘的效果,如顯示10-20-30
        即10點20分30秒。
        Main.c
        #include <reg52.h>
        #include "MacroAndConst.h"
        #include "Timer.h"
        #include "Led7Seg.h"

        sbit io_led = P1^6 ;


        void main(void)
        {
        io_led = 0 ; //發光二極管與數碼管共用P0口,這里禁止掉發光二極管的鎖存輸出
        Timer0Init() ;
        g_u8LedDisplayBuffer[0] = 1 ;
        g_u8LedDisplayBuffer[1] = 0 ;
        g_u8LedDisplayBuffer[2] = '-' ;
        g_u8LedDisplayBuffer[3] = 2 ;
        g_u8LedDisplayBuffer[4] = 0 ;
        g_u8LedDisplayBuffer[5] = '-' ;
        g_u8LedDisplayBuffer[6] = 3 ;
        g_u8LedDisplayBuffer[7] = 0 ;
        EA = 1 ;
        while(1)
        {
        LedDisplay(g_u8LedDisplayBuffer) ;
        }
        }
        將整個工程進行編譯,看看效果如何?






        動起來

        既然我們想要模擬一個時鐘,那么時鐘肯定是要走動的,不然還稱為什么時鐘撒。下面我們在前面的基礎之上,添加一點相應的代碼,讓我們這個時鐘走動起來。
        我們知道,之前我們以及設置了一個掃描數碼管用到的2 ms時標。 如果我們再對這個時標進行計數,當計數值達到500,即500 * 2 = 1000 ms 時候,即表示已經逝去了1 S的時間。我們再根據這個1 S的時間更新顯示緩沖區即可。聽起來很簡單,讓我們實現它吧。
        首先在Timer.c中聲明如下兩個變量:
        bit g_bTime1S = 0 ; //時鐘1S時標消息
        static uint16 s_u16ClockTickCount = 0 ; //對2 ms 時標進行計數

        再在定時器中斷函數中添加如下代碼:
        if(++s_u16ClockTickCount == 500)
        {
        s_u16ClockTickCount = 0 ;
        g_bTime1S = 1 ;
        }
        從上面可以看出,s_u16ClockTickCount計數值達到500的時候,g_bTime1S時標消息產生。然后我們根據這個時標消息刷新數碼管顯示緩沖區:
        void RunClock(void)
        {
        if(g_bTime1S )
        {
        g_bTime1S = 0 ;
        if(++g_u8LedDisplayBuffer[7] == 10)
        {
        g_u8LedDisplayBuffer[7] = 0 ;
        if(++g_u8LedDisplayBuffer[6] == 6)
        {
        g_u8LedDisplayBuffer[6] = 0 ;
        if(++g_u8LedDisplayBuffer[4] == 10)
        {
        g_u8LedDisplayBuffer[4] = 0 ;
        if(++g_u8LedDisplayBuffer[3] == 6)
        {
        g_u8LedDisplayBuffer[3] = 0 ;
        if( g_u8LedDisplayBuffer[0]<2)
        {
        if(++g_u8LedDisplayBuffer[1]==10)
        {
        g_u8LedDisplayBuffer[1] = 0 ;
        g_u8LedDisplayBuffer[0]++;
        }
        }
        else
        {
        if(++g_u8LedDisplayBuffer[1]==4)
        {
        g_u8LedDisplayBuffer[1] = 0 ;
        g_u8LedDisplayBuffer[0] = 0 ;
        }
        }
        }
        }
        }

        }
        }
        }
        這個函數的作用就是對每個數碼管緩沖位的值進行判斷,判斷的標準就是我們熟知的24小時制。如秒的個位到了10 就清0,同時秒的十位加1….諸如此類,我就不一一詳述了。
        同時,我們再編寫一個時鐘初始值設置函數,這樣,可以很方便的在主程序開始的時候修改時鐘初始值。
        void SetClock(uint8 nHour, uint8 nMinute, uint8 nSecond)
        {
        g_u8LedDisplayBuffer[0] = nHour / 10 ;
        g_u8LedDisplayBuffer[1] = nHour % 10 ;
        g_u8LedDisplayBuffer[2] = '-' ;
        g_u8LedDisplayBuffer[3] = nMinute / 10 ;
        g_u8LedDisplayBuffer[4] = nMinute % 10 ;
        g_u8LedDisplayBuffer[5] = '-' ;
        g_u8LedDisplayBuffer[6] = nSecond / 10 ;
        g_u8LedDisplayBuffer[7] = nSecond % 10 ;
        }
        然后修改下我們的主函數如下:
        void main(void)
        {
        io_led = 0 ; //發光二極管與數碼管共用P0口,這里禁止掉發光二極管的鎖存輸出
        Timer0Init() ;
        SetClock(10,20,30) ; //設置初始時間為10點20分30秒
        EA = 1 ;
        while(1)
        {
        LedDisplay(g_u8LedDisplayBuffer) ;
        RunClock();
        }
        }
        編譯好之后,下載到我們的實驗板上,怎么樣,一個簡單的時鐘就這樣誕生了。



        至此,本章所訴就告一段落了。至于如何完成數碼管的閃爍顯示,就像本章開頭所說的那個數碼管時鐘的功能,就作為一個思考的問題留給大家思考吧。
        同時整個LED篇就到此結束了,在以后的文章中,我們將開始學習如何編寫實用的按鍵掃描程序。
        [/post

        本章所附例程在EE21學習板上調試通過,擁有板子的朋友可以直接下載附件對照學習。
        [ 此貼被紅金龍吸味在2010-01-09 13:54重新編輯 ]


        QQ|手機版|MCU資訊論壇 ( 京ICP備18035221號-2 )|網站地圖

        GMT+8, 2025-5-4 05:49 , Processed in 0.040939 second(s), 9 queries , Redis On.

        Powered by Discuz! X3.5

        © 2001-2025 Discuz! Team.

        返回頂部
        99久久免费国产精品热| 国产亚洲精品线观看动态图| 精品一区二区三区免费观看 | 久久精品aⅴ无码中文字字幕不卡| 久久精品aⅴ无码中文字字幕重口| 精品三级在线观看| 欧美成人精品一区二三区在线观看| 国产丝袜一区二区三区在线观看 | 精品视频一区二区三区在线观看| 国产成人亚洲精品影院| 亚洲美女在线视频| 97久久超碰国产精品2021| 国产精品一区二区av不卡| 色伊人色成人婷婷六月丁香| 国产精品无码久久久久久| 国产午夜精品一本在线观看| 国内精品免费网站牛牛| 久久99国产精品久久99| 亚洲?V无码乱码国产精品| 国产精品无码久久久久久| 久久九九久精品国产免费直播| 老汉精品免费AV在线播放| 国产玖玖玖九九精品视频| 亚洲一区爱区精品无码| 91精品免费久久久久久久久| 一二三四高清免费播放视频| 久久精品国产免费| 性xxxxfreexxxxx国产| 精品国产第1页| 欧美人成在线观看ccc36| 日本人精品video黑人| 久久国产成人亚洲精品影院老金| 久久精品嫩草影院| 八戒久久精品一区二区三区| 99精品视频在线观看婷| 亚洲AV无码成人精品区天堂| 国产色精品vr一区区三区| 国产乱子伦精品无码码专区| 日韩亚洲精品福利 | 四虎成人精品永久免费AV| 国产精品成人69XXX免费视频|