欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
從單片機初學(xué)者邁向單片機工程師
  這個(gè)我從eehome貼過(guò)來(lái)的。寫(xiě)的非常的好。我們用學(xué)單片機不要停在演示的基礎上。只能讓單片機完成局部事。這樣我們永遠不會(huì )走出流水燈地獄?。?!

學(xué)習單片機也已經(jīng)有幾年了,藉此機會(huì )和大家聊一下我學(xué)習過(guò)程中的一些經(jīng)歷和想法吧。也感謝一線(xiàn)工人提供了這個(gè)機會(huì )。希望大家有什么好的想法和建議都直接跟帖說(shuō)出來(lái)。畢竟只有交流才能夠碰撞出火花來(lái)^_^。
    幾年前,和眾多初學(xué)者一樣,我接觸到了單片機,立刻被其神奇的功能所吸引,從此不能自拔。很多個(gè)日夜就這樣陪伴著(zhù)它度過(guò)了。期間也遇到過(guò)非常多的問(wèn)題,也一度被這些問(wèn)題所困惑……等到回過(guò)頭來(lái),看到自己曾經(jīng)走過(guò)的路,唏噓不已。經(jīng)?;燠E于論壇里,也看到了很多初學(xué)者發(fā)的求助帖子,看到他們走在自己曾走過(guò)的彎路上,忽然想到了自己的那段日子,心里竟然莫名的沖動(dòng),凡此總總,我總是盡自己所能去回帖。很多時(shí)候,都想寫(xiě)一點(diǎn)什么東西出來(lái),希望對廣大的初學(xué)者有一點(diǎn)點(diǎn)幫助。但總是不知從何處寫(xiě)起。今天借一線(xiàn)工人的臺,唱一唱我的戲?。“賣(mài)弄”也好,“吹噓”也罷,我只是想認真的寫(xiě)寫(xiě)我這一路走來(lái)歷經(jīng)的總總,把其中值得注意,以及經(jīng)驗的地方寫(xiě)出來(lái),權當是我對自己的一個(gè)總結吧。而作為看官的你,如果看到了我的錯誤,還請一定指正,這樣對我以及其它讀者都有幫助,而至于你如果從中能夠收獲到些許,那便是我最大的欣慰了。姑妄言之,姑妄聽(tīng)之。如果有啥好的想法和建議一定要說(shuō)出來(lái)。
    一路學(xué)習過(guò)來(lái)的過(guò)程中,幫助最大之一無(wú)疑來(lái)自于網(wǎng)絡(luò )了。很多時(shí)候,通過(guò)網(wǎng)絡(luò ),我們都可以獲取到所需要的學(xué)習資料。但是,隨著(zhù)我們學(xué)習的深入,我們會(huì )慢慢發(fā)現,網(wǎng)絡(luò )提供的東西是有限度的,好像大部分的資料都差不多,或者說(shuō)是適合大部分的初學(xué)者所需,而當我們想更進(jìn)一步提高時(shí),卻發(fā)現能夠獲取到的資料越來(lái)越少,相信各位也會(huì )有同感,鋪天蓋地的單片機資料中大部分不是流水燈就是LED,液晶,而且也只是僅僅作功能性的演示。于是有些人選擇了放棄,或者是轉移到其他興趣上面去了,而只有少部分人選擇了繼續摸索下去,結合市面上的書(shū)籍,然后在網(wǎng)絡(luò )上鍥而不舍的搜集資料,再從牛人的只言片語(yǔ)中去體會(huì ),不斷動(dòng)手實(shí)踐,慢慢的,也摸索出來(lái)了自己的一條路子。當然這個(gè)過(guò)程必然是艱辛的,而他學(xué)會(huì )了之后也不會(huì )在網(wǎng)絡(luò )上輕易分享自己的學(xué)習成果。如此惡性循環(huán)下去,也就不難理解為什么初級的學(xué)習資料滿(mǎn)天飛,而深入一點(diǎn)的學(xué)習資料卻很少的原因了。相較于其他領(lǐng)域,單片機技術(shù)的封鎖更加容易。盡管已經(jīng)問(wèn)世了很多年了,有價(jià)值的資料還是相當的欠缺,大部分的資料都是止于入門(mén)階段或者是簡(jiǎn)單的演示實(shí)驗。但是在實(shí)際工程應用中卻是另外一回事。有能力的高手無(wú)暇或者是不愿公開(kāi)自己的學(xué)習經(jīng)驗。
    很多時(shí)候,我也很困惑,看到國外愛(ài)好者毫不保留的在網(wǎng)絡(luò )上發(fā)布自己的作品,我忽然感覺(jué)到一絲絲的悲哀。也許,我們真的該轉變一下思路了,幫助別人,其實(shí)也是在幫助自己。啰啰嗦嗦的說(shuō)了這么多,相信大家能夠明白說(shuō)的是什么意思。在接下來(lái)的一段日子里,我將會(huì )結合電子工程師之家舉辦的主題周活動(dòng)寫(xiě)一點(diǎn)自己的想法。盡可能從實(shí)用的角度去講述。希望能夠幫助更多的初學(xué)者更上一層樓。而關(guān)于這個(gè)主題周的最大主題我想了這樣的一個(gè)名字“從單片機初學(xué)者邁向單片機工程師”。名字挺大挺響亮,給我的壓力也挺大的,但我會(huì )努力,爭取使這樣的一系列文章能夠帶給大家一點(diǎn)幫助,而不是看后大跌眼鏡。這樣的一系列文章主要的對象是初學(xué)者,以及想從初學(xué)者更進(jìn)一步提高的讀者。而至于老手,以及那些牛XX的人,希望能夠給我們這些初學(xué)者更多的一些指點(diǎn)哈~@_@








我們首先來(lái)看第一章節


從這一章開(kāi)始,我們開(kāi)始邁入單片機的世界。在我們開(kāi)始這一章具體的學(xué)習之前,有必要給大家先說(shuō)明一下。在以后的系列文章中,我們將以51內核的單片機為載體,C語(yǔ)言為編程語(yǔ)言,開(kāi)發(fā)環(huán)境為KEIL uv3。至于為什么選用C語(yǔ)言開(kāi)發(fā),好處不言而喻,開(kāi)發(fā)速度快,效率高,代碼可復用率高,結構清晰,尤其是在大型的程序中,而且隨著(zhù)編譯器的不斷升級,其編譯后的代碼大小與匯編語(yǔ)言的差距越來(lái)越小。而關(guān)于C語(yǔ)言和匯編之爭,就像那個(gè)啥,每隔一段時(shí)間總會(huì )有人挑起這個(gè)話(huà)題,如果你感興趣,可以到網(wǎng)上搜索相關(guān)的帖子自行閱讀。不是說(shuō)匯編不重要,在很多對時(shí)序要求非常高的場(chǎng)合,需要利用匯編語(yǔ)言和C語(yǔ)言混合編程才能夠滿(mǎn)足系統的需求。在我們學(xué)習掌握C語(yǔ)言的同時(shí),也還需要利用閑余的時(shí)間去學(xué)習了解匯編語(yǔ)言。

1.從點(diǎn)亮LED(發(fā)光二極管)開(kāi)始
在市面上眾多的單片機學(xué)習資料中,最基礎的實(shí)驗無(wú)疑于點(diǎn)亮LED了,即控制單片機的I/O的電平的變化。
如同如下實(shí)例代碼一般

void main(void)
{
    LedInit() ;
    While(1) 
    {
        LED = ON ;
        DelayMs(500) ;
        LED = OFF ;
        DelayMs(500) ;
}
}

    程序很簡(jiǎn)單,從它的結構可以看出,LED先點(diǎn)亮500MS,然后熄滅500MS,如此循環(huán)下去,形成的效果就是LED以1HZ的頻率進(jìn)行閃爍。下面讓我們分析上面的程序有沒(méi)有什么問(wèn)題。
看來(lái)看出,好像很正常的啊,能有什么問(wèn)題呢?這個(gè)時(shí)候我們應該換一個(gè)思路去想了。試想,整個(gè)程序除了控制LED = ON ; LED = OFF; 這兩條語(yǔ)句外,其余的時(shí)間,全消耗在了DelayMs(500)這兩個(gè)函數上。而在實(shí)際應用系統中是沒(méi)有哪個(gè)系統只閃爍一只LED就其它什么事情都不做了的。因此,在這里我們要想辦法,把CPU解放出來(lái),讓它不要白白浪費500MS的延時(shí)等待時(shí)間。寧可讓它一遍又一遍的掃描看有哪些任務(wù)需要執行,也不要讓它停留在某個(gè)地方空轉消耗CPU時(shí)間。

從上面我們可以總結出
(1)    無(wú)論什么時(shí)候我們都要以實(shí)際應用的角度去考慮程序的編寫(xiě)。
(2)    無(wú)論什么時(shí)候都不要讓CPU白白浪費等待,尤其是延時(shí)(超過(guò)1MS)這樣的地方。

下面讓我們從另外一個(gè)角度來(lái)考慮如何點(diǎn)亮一顆LED。
先看看我們的硬件結構是什么樣子的。


    我手上的單片機板子是電子工程師之家的開(kāi)發(fā)的學(xué)習板。就以它的實(shí)際硬件連接圖來(lái)分析吧。如下圖所示

  

 (原文件名:led.jpg) 
引用圖片


 

    一般的LED的正常發(fā)光電流為10~20MA而低電流LED的工作電流在2mA以下(亮度與普通發(fā)光管相同)。在上圖中我們可知,當Q1~Q8引腳上面的電平為低電平時(shí),LED發(fā)光。通過(guò)LED的電流約為(VCC - Vd)/ RA2 。其中Vd為L(cháng)ED導通后的壓降,約為1.7V左右。這個(gè)導通壓降根據LED顏色的不同,以及工作電流的大小的不同,會(huì )有一定的差別。下面一些參數是網(wǎng)上有人測出來(lái)的,供大家參考。
紅色的壓降為1.82-1.88V,電流5-8mA,
綠色的壓降為1.75-1.82V,電流3-5mA,
橙色的壓降為1.7-1.8V,電流3-5mA
蘭色的壓降為3.1-3.3V,電流8-10mA,
白色的壓降為3-3.2V,電流10-15mA,
(供電電壓5V,LED直徑為5mm)

74HC573真值表如下: 

 (原文件名:74hc573.jpg) 
引用圖片


 
    通過(guò)這個(gè)真值表我們可以看出。當OutputEnable引腳接低電平的時(shí)候,并且LatchEnable引腳為高電平的時(shí)候,Q端電平與D端電平相同。結合我們的LED硬件連接圖可以知道LED_CS端為高電平時(shí)候,P0口電平的變化即Q端的電平的變化,進(jìn)而引起LED的亮滅變化。由于單片機的驅動(dòng)能力有限,在此,74HC573的主要作用就是起一個(gè)輸出驅動(dòng)的作用。需要注意的是,通過(guò)74HC573的最大電流是有限制的,否則可能會(huì )燒壞74HC573這個(gè)芯片。

  
 
上面這個(gè)圖是從74HC573的DATASHEET中截取出來(lái)的,從上可以看出,每個(gè)引腳允許通過(guò)的最大電流為35mA 整個(gè)芯片允許通過(guò)的最大電流為75mA。在我們設計相應的驅動(dòng)電路時(shí)候,這些參數是相當重要的,而且是最容易被初學(xué)者所忽略的地方。同時(shí)在設計的時(shí)候,要留出一定量的余量出來(lái),不能說(shuō)單個(gè)引腳允許通過(guò)的電流為35mA,你就設計為35mA,這個(gè)時(shí)候你應該把設計的上限值定在20mA左右才能保證能夠穩定的工作。
(設計相應驅動(dòng)電路時(shí)候,應該仔細閱讀芯片的數據手冊,了解每個(gè)引腳的驅動(dòng)能力,以及整個(gè)芯片的驅動(dòng)能力)

    了解了相應的硬件后,我們再來(lái)編寫(xiě)驅動(dòng)程序。
    首先定義LED的接口
    #define LED  P0
        然后為亮滅常數定義一個(gè)宏,由硬件連接圖可以,當P0輸出為低電平時(shí)候LED亮,P0輸出為高電平時(shí),LED熄滅。
    #define LED_ON()    LED = 0x00  //所有LED亮
    #define LED_OFF()    LED = 0xff  //所有LED熄滅
    下面到了重點(diǎn)了,究竟該如何釋放CPU,避免其做延時(shí)空等待這樣的事情呢。很簡(jiǎn)單,我們?yōu)橄到y產(chǎn)生一個(gè)1MS的時(shí)標。假定LED需要亮500MS,熄滅500MS,那么我們可以對這個(gè)1MS的時(shí)標進(jìn)行計數,當這個(gè)計數值達到500時(shí)候,清零該計數值,同時(shí)把LED的狀態(tài)改變。
unsigned int g_u16LedTimeCount = 0 ;    //LED計數器
unsigned char g_u8LedState = 0 ;      //LED狀態(tài)標志, 0表示亮,1表示熄滅

void LedProcess(void)
{
    if(0 == g_u8LedState)  //如果LED的狀態(tài)為亮,則點(diǎn)亮LED
    {
        LED_ON() ;
    }
    else                //否則熄滅LED
    {
        LED_OFF() ;
    }
}


void LedStateChange(void)
{
    if(g_bSystemTime1Ms)            //系統1MS時(shí)標到
  {
        g_bSystemTime1Ms = 0 ;
        g_u16LedTimeCount++ ;      //LED計數器加一
    if(g_u16LedTimeCount >= 500)  //計數達到500,即500MS到了,改變LED的狀態(tài)。
    {
            g_u16LedTimeCount = 0 ;
            g_u8LedState  = ! g_u8LedState ;
      }
    }
}

上面有一個(gè)變量沒(méi)有提到,就是g_bSystemTime1Ms 。這個(gè)變量可以定義為位變量或者是其它變量,在我們的定時(shí)器中斷函數中對其置位,其它函數使用該變量后,應該對其復位(清0) 。
我們的主函數就可以寫(xiě)成如下形式(示意代碼)
void main(void)
{
    while(1)
    {
        LedProcess() ;
        LedStateChange() ;
    } 
}

因為L(cháng)ED的亮或者滅依賴(lài)于LED狀態(tài)變量(g_u8LedState)的改變,而狀態(tài)變量的改變,又依賴(lài)于LED計數器的計數值(g_u16LedTimeCount ,只有計數值達到一定后,狀態(tài)變量才改變)所以,兩個(gè)函數都沒(méi)有堵塞CPU的地方。讓我們來(lái)從頭到尾分析一遍整個(gè)程序的流程。

程序首先執行LedProcess() ;函數
因為g_u8LedState 的初始值為0 (見(jiàn)定義,對于全局變量,在定義的時(shí)候最好給其一個(gè)確定的值)所以L(fǎng)ED被點(diǎn)亮,然后退出LedStateChange()函數,執行下一個(gè)函數LedStateChange()
在函數LedStateChange()內部首先判斷1MS的系統時(shí)標是否到了,如果沒(méi)有到就直接退出函數,如果到了,就把時(shí)標清0以便下一個(gè)時(shí)標消息的到來(lái),同時(shí)對LED計數器加一,然后再判斷LED計數器是否到達我們預先想要的值500,如果沒(méi)有,則退出函數,如果有,對計數器清0,以便下次重新計數,同時(shí)把LED狀態(tài)變量取反,然后退出函數。
由上面整個(gè)流程可以知道,CPU所做的事情,就是對一些計數器加一,然后根據條件改變狀態(tài),再根據這個(gè)狀態(tài)來(lái)決定是否點(diǎn)亮LED。這些函數執行所花的時(shí)間都是相當短的,如果主程序中還有其它函數,則CPU會(huì )順次往下執行下去。對于其它的函數(如果有的話(huà))也要采取同樣的措施,保證其不堵塞CPU,如果全部基于這種方法設計,那么對于不是非常龐大的系統,我們的系統依舊可以保證多個(gè)任務(wù)(多個(gè)函數)同時(shí)執行。系統的實(shí)時(shí)性得到了一定的保證,從宏觀(guān)上看來(lái),就是多個(gè)任務(wù)并發(fā)執行。

好了,這一章就到此為止,讓我們總結一下,究竟有哪些需要注意的吧。

(1)    無(wú)論什么時(shí)候我們都要以實(shí)際應用的角度去考慮程序的編寫(xiě)。
(2)    無(wú)論什么時(shí)候都不要讓CPU白白浪費等待,尤其是延時(shí)(超過(guò)1MS)這樣的地方。
(3)    設計相應驅動(dòng)電路時(shí)候,應該仔細閱讀芯片的數據手冊,了解每個(gè)引腳的驅動(dòng)能力,
          以及整個(gè)芯片的驅動(dòng)能力
(4)    最重要的是,如何去釋放CPU(參考本章的例子),這是寫(xiě)出合格程序的基礎。


附完整程序代碼(基于電子工程師之家的單片機開(kāi)發(fā)板)

#include<reg52.h>

sbit LED_SEG  = P1^4;  //數碼管段選
sbit LED_DIG  = P1^5;  //數碼管位選
sbit LED_CS11 = P1^6;  //led控制位
sbit ir=P1^7;
#define LED P0            //定義LED接口
bit  g_bSystemTime1Ms = 0 ;              // 1MS系統時(shí)標
unsigned int  g_u16LedTimeCount = 0 ; //LED計數器
unsigned char g_u8LedState = 0 ;      //LED狀態(tài)標志, 0表示亮,1表示熄滅

#define LED_ON()      LED = 0x00 ;  //所有LED亮
#define LED_OFF()    LED = 0xff ;  //所有LED熄滅

void Timer0Init(void)
{
    TMOD &= 0xf0 ;
    TMOD |= 0x01 ;      //定時(shí)器0工作方式1
    TH0  =    0xfc ;      //定時(shí)器初始值
    TL0  =  0x66 ;
    TR0  = 1 ;
    ET0  = 1 ;
}
void LedProcess(void)
{
    if(0 == g_u8LedState)  //如果LED的狀態(tài)為亮,則點(diǎn)亮LED
    {
        LED_ON() ;
    }
    else                //否則熄滅LED
    {
        LED_OFF() ;
    }
}

void LedStateChange(void)
{
    if(g_bSystemTime1Ms)            //系統1MS時(shí)標到
  {
            g_bSystemTime1Ms = 0 ;
            g_u16LedTimeCount++ ;      //LED計數器加一
      if(g_u16LedTimeCount >= 500)  //計數達到500,即500MS到了,改變LED的狀態(tài)。
      {
      g_u16LedTimeCount = 0 ;
      g_u8LedState  = ! g_u8LedState    ;
          }
    }
}

void main(void)
{
    Timer0Init() ;
    EA = 1 ;
                    LED_CS11 = 1 ; //74HC595輸出允許
    LED_SEG = 0 ;  //數碼管段選和位選禁止(因為它們和LED共用P0口)
    LED_DIG = 0 ;
    while(1)
    {
          LedProcess() ;
          LedStateChange() ;
    }


void Time0Isr(void) interrupt 1
{
    TH0  =    0xfc ;            //定時(shí)器重新賦初值
    TL0  =  0x66 ;
    g_bSystemTime1Ms = 1 ;    //1MS時(shí)標標志位置位
}



“從單片機初學(xué)者邁向單片機工程師”
                                                第三章----模塊化編程初識


      好的開(kāi)始是成功的一半
  通過(guò)上一章的學(xué)習,我想你已經(jīng)掌握了如何在程序中釋放CPU了。希望能夠繼續堅持下去。一個(gè)良好的開(kāi)始是成功的一半。我們今天所做的一切都是為了在單片機編程上做的更好。
在談?wù)摻裉斓闹黝}之前,先說(shuō)下我以前的一些經(jīng)歷。在剛開(kāi)始接觸到C語(yǔ)言程序的時(shí)候,由于學(xué)習內容所限,寫(xiě)的程序都不是很大,一般也就幾百行而矣。所以所有的程序都完成在一個(gè)源文件里面。記得那時(shí)候大一參加學(xué)校里的一個(gè)電子設計大賽,調試了一個(gè)多星期,所有程序加起來(lái)大概將近1000行,長(cháng)長(cháng)的一個(gè)文件,從上瀏覽下來(lái)都要好半天。出了錯誤簡(jiǎn)單的語(yǔ)法錯誤還好定位,其它一些錯誤,往往找半天才找的到。那個(gè)時(shí)候開(kāi)始知道了模塊化編程這個(gè)東西,也嘗試著(zhù)開(kāi)始把程序分模塊編寫(xiě)。最開(kāi)始是把相同功能的一些函數(譬如1602液晶的驅動(dòng))全部寫(xiě)在一個(gè)頭文件(.h)文件里面,然后需要調用的地方包含進(jìn)去,但是很快發(fā)現這種方法有其局限性,很容易犯重復包含的錯誤。
而且調用起來(lái)也很不方便。很快暑假的電子設計大賽來(lái)臨了,學(xué)校對我們的單片機軟件編程進(jìn)行了一些培訓。由于學(xué)校歷年來(lái)參加國賽和省賽,因此積累了一定數量的驅動(dòng)模塊,那些日子,老師每天都會(huì )布置一定量的任務(wù),讓我們用這些模塊組合起來(lái),完成一定功能。而正是那些日子模塊化編程的培訓,使我對于模塊化編程有了更進(jìn)一步的認識。并且程序規范也開(kāi)始慢慢注意起來(lái)。此后的日子,無(wú)論程序的大小,均采用模塊化編程的方式去編寫(xiě)。很長(cháng)一段時(shí)間以來(lái),一直有單片機愛(ài)好者在QQ上和我一起交流。有時(shí)候,他們會(huì )發(fā)過(guò)來(lái)一些有問(wèn)題的程序源文件,讓我幫忙修改一下。同樣是長(cháng)長(cháng)的一個(gè)文件,而且命名極不規范,從頭看下來(lái),著(zhù)實(shí)是痛苦,說(shuō)實(shí)話(huà),還真不如我重新給他們寫(xiě)一個(gè)更快一些,此話(huà)到不假,因為手頭積累了一定量的模塊,在完成一個(gè)新的系統時(shí)候,只需要根據上層功能需求,在底層模塊的支持下,可以很快方便的完成。而不需要從頭到尾再一磚一瓦的重新編寫(xiě)。藉此,也可以看出模塊化編程的一個(gè)好處,就是可重復利用率高。下面讓我們揭開(kāi)模塊化神秘面紗,一窺其真面目。
    C語(yǔ)言源文件 *.c
        提到C語(yǔ)言源文件,大家都不會(huì )陌生。因為我們平常寫(xiě)的程序代碼幾乎都在這個(gè)XX.C文件里面。編譯器也是以此文件來(lái)進(jìn)行編譯并生成相應的目標文件。作為模塊化編程的組成基礎,我們所要實(shí)現的所有功能的源代碼均在這個(gè)文件里。理想的模塊化應該可以看成是一個(gè)黑盒子。即我們只關(guān)心模塊提供的功能,而不管模塊內部的實(shí)現細節。好比我們買(mǎi)了一部手機,我們只需要會(huì )用手機提供的功能即可,不需要知曉它是如何把短信發(fā)出去的,如何響應我們按鍵的輸入,這些過(guò)程對我們用戶(hù)而言,就是是一個(gè)黑盒子。
在大規模程序開(kāi)發(fā)中,一個(gè)程序由很多個(gè)模塊組成,很可能,這些模塊的編寫(xiě)任務(wù)被分配到不同的人。而你在編寫(xiě)這個(gè)模塊的時(shí)候很可能就需要利用到別人寫(xiě)好的模塊的借口,這個(gè)時(shí)候我們關(guān)心的是,它的模塊實(shí)現了什么樣的接口,我該如何去調用,至于模塊內部是如何組織的,對于我而言,無(wú)需過(guò)多關(guān)注。而追求接口的單一性,把不需要的細節盡可能對外部屏蔽起來(lái),正是我們所需要注意的地方。
    C語(yǔ)言頭文件 *.h
        談及到模塊化編程,必然會(huì )涉及到多文件編譯,也就是工程編譯。在這樣的一個(gè)系統中,往往會(huì )有多個(gè)C文件,而且每個(gè)C文件的作用不盡相同。在我們的C文件中,由于需要對外提供接口,因此必須有一些函數或者是變量提供給外部其它文件進(jìn)行調用。
假設我們有一個(gè)LCD.C文件,其提供最基本的LCD的驅動(dòng)函數
    LcdPutChar(char cNewValue) ;  //在當前位置輸出一個(gè)字符
而在我們的另外一個(gè)文件中需要調用此函數,那么我們該如何做呢?
    頭文件的作用正是在此??梢苑Q(chēng)其為一份接口描述文件。其文件內部不應該包含任何實(shí)質(zhì)性的函數代碼。我們可以把這個(gè)頭文件理解成為一份說(shuō)明書(shū),說(shuō)明的內容就是我們的模塊對外提供的接口函數或者是接口變量。同時(shí)該文件也包含了一些很重要的宏定義以及一些結構體的信息,離開(kāi)了這些信息,很可能就無(wú)法正常使用接口函數或者是接口變量。但是總的原則是:不該讓外界知道的信息就不應該出現在頭文件里,而外界調用模塊內接口函數或者是接口變量所必須的信息就一定要出現在頭文件里,否則,外界就無(wú)法正確的調用我們提供的接口功能。因而為了讓外部函數或者文件調用我們提供的接口功能,就必須包含我們提供的這個(gè)接口描述文件----即頭文件。同時(shí),我們自身模塊也需要包含這份模塊頭文件(因為其包含了模塊源文件中所需要的宏定義或者是結構體),好比我們平常所用的文件都是一式三份一樣,模塊本身也需要包含這個(gè)頭文件。
下面我們來(lái)定義這個(gè)頭文件,一般來(lái)說(shuō),頭文件的名字應該與源文件的名字保持一致,這樣我們便可以清晰的知道哪個(gè)頭文件是哪個(gè)源文件的描述。
        于是便得到了LCD.C的頭文件LCD.h 其內容如下。
        #ifndef    _LCD_H_
                #define     _LCD_H_
                extern   LcdPutChar(char cNewValue) ;
                #endif

    這與我們在源文件中定義函數時(shí)有點(diǎn)類(lèi)似。不同的是,在其前面添加了extern 修飾符表明其是一個(gè)外部函數,可以被外部其它模塊進(jìn)行調用。
        #ifndef     _LCD_H_
              #define     _LCD_H_
              #endif

              這個(gè)幾條條件編譯和宏定義是為了防止重復包含。假如有兩個(gè)不同源文件需要調用LcdPutChar(char cNewValue)這個(gè)函數,他們分別都通過(guò)#include “Lcd.h”把這個(gè)頭文件包含了進(jìn)去。在第一個(gè)源文件進(jìn)行編譯時(shí)候,由于沒(méi)有定義過(guò) _LCD_H_ 因此 #ifndef _LCD_H_ 條件成立,于是定義_LCD_H_ 并將下面的聲明包含進(jìn)去。在第二個(gè)文件編譯時(shí)候,由于第一個(gè)文件包含時(shí)候,已經(jīng)將_LCD_H_定義過(guò)了。因此#ifndef _LCD_H_ 不成立,整個(gè)頭文件內容就沒(méi)有被包含。假設沒(méi)有這樣的條件編譯語(yǔ)句,那么兩個(gè)文件都包含了extern  LcdPutChar(char cNewValue) ; 就會(huì )引起重復包含的錯誤。
    不得不說(shuō)的typedef 
          很多朋友似乎了習慣程序中利用如下語(yǔ)句來(lái)對數據類(lèi)型進(jìn)行定義
    #define uint  unsigned int 
        #define uchar  unsigned char
    然后在定義變量的時(shí)候 直接這樣使用
  uint  g_nTimeCounter = 0 ;
    不可否認,這樣確實(shí)很方便,而且對于移植起來(lái)也有一定的方便性。但是考慮下面這種情況你還會(huì ) 這么認為嗎?
  #define PINT unsigned int *  //定義unsigned int 指針類(lèi)型
  PINT  g_npTimeCounter, g_npTimeState ;
      那么你到底是定義了兩個(gè)unsigned int 型的指針變量,還是一個(gè)指針變量,一個(gè)整形變量呢?而你的初衷又是什么呢,想定義兩個(gè)unsigned int 型的指針變量嗎?如果是這樣,那么估計過(guò)不久就會(huì )到處抓狂找錯誤了。
    慶幸的是C語(yǔ)言已經(jīng)為我們考慮到了這一點(diǎn)。typedef 正是為此而生。為了給變量起一個(gè)別名我們可以用如下的語(yǔ)句
    typedef  unsigned  int    uint16 ;    //給指向無(wú)符號整形變量起一個(gè)別名 uint16
      typedef  unsigned  int  * puint16 ;  //給指向無(wú)符號整形變量指針起一個(gè)別名 puint16
    在我們定義變量時(shí)候便可以這樣定義了:

  uint16    g_nTimeCounter  =  0 ;  //定義一個(gè)無(wú)符號的整形變量
  puint16  g_npTimeCounter  ;    //定義一個(gè)無(wú)符號的整形變量的指針
  在我們使用51單片機的C語(yǔ)言編程的時(shí)候,整形變量的范圍是16位,而在基于32的微處理下的整形變量是32位。倘若我們在8位單片機下編寫(xiě)的一些代碼想要移植到32位的處理器上,那么很可能我們就需要在源文件中到處修改變量的類(lèi)型定義。這是一件龐大的工作,為了考慮程序的可移植性,在一開(kāi)始,我們就應該養成良好的習慣,用變量的別名進(jìn)行定義。
如在8位單片機的平臺下,有如下一個(gè)變量定義
    uint16    g_nTimeCounter  =  0 ;
        如果移植32單片機的平臺下,想要其的范圍依舊為16位。
    可以直接修改uint16 的定義,即
    typedef  unsigned  short  int    uint16 ; 
        這樣就可以了,而不需要到源文件處處尋找并修改。

將常用的數據類(lèi)型全部采用此種方法定義,形成一個(gè)頭文件,便于我們以后編程直接調用。
文件名 MacroAndConst.h
其內容如下:
#ifndef   _MACRO_AND_CONST_H_
#define   _MACRO_AND_CONST_H_

typedef    unsigned int    uint16; 
typedef    unsigned int   UINT; 
typedef    unsigned int   uint; 
typedef    unsigned int   UINT16; 
typedef    unsigned int   WORD; 
typedef    unsigned int   word;
typedef      int        int16; 
typedef      int        INT16; 
typedef    unsigned long  uint32; 

typedef    unsigned long     UINT32; 
typedef    unsigned long    DWORD; 
typedef    unsigned long    dword; 
typedef    long            int32; 
typedef    long            INT32; 
typedef    signed  char     int8;
typedef    signed  char     INT8; 
typedef    unsigned char      byte; 
typedef    unsigned char     BYTE; 
typedef    unsigned char     uchar;
typedef    unsigned char     UINT8; 
typedef    unsigned char    uint8;
typedef    unsigned char    BOOL; 

#endif

至此,似乎我們對于源文件和頭文件的分工以及模塊化編程有那么一點(diǎn)概念了。那么讓我們趁熱打鐵,將上一章的我們編寫(xiě)的LED閃爍函數進(jìn)行模塊劃分并重新組織進(jìn)行編譯。

在上一章中我們主要完成的功能是P0口所驅動(dòng)的LED以1Hz的頻率閃爍。其中用到了定時(shí)器,以及LED驅動(dòng)模塊。因而我們可以簡(jiǎn)單的將整個(gè)工程分成三個(gè)模塊,定時(shí)器模塊,LED模塊,以及主函數
對應的文件關(guān)系如下

main.c  
Timer.c  --?Timer.h
Led.c      --?Led.h
在開(kāi)始重新編寫(xiě)我們的程序之前,先給大家講一下如何在KEIL中建立工程模板吧,這個(gè)模板是我一直沿用至今。希望能夠給大家一點(diǎn)啟發(fā)。
下面的內容就主要以圖片為主了。同時(shí)輔以少量文字說(shuō)明。
我們以芯片AT89S52為例。


 (原文件名:1.jpg) 
引用圖片



 (原文件名:2.jpg) 
引用圖片



 (原文件名:3.jpg) 
引用圖片



 (原文件名:4.jpg) 
引用圖片



 (原文件名:5.jpg) 
引用圖片



 (原文件名:6.jpg) 
引用圖片



 (原文件名:7.jpg) 
引用圖片



 (原文件名:8.jpg) 
引用圖片



 (原文件名:9.jpg) 
引用圖片



 (原文件名:10.jpg) 
引用圖片



 (原文件名:11.jpg) 
引用圖片



 (原文件名:12.jpg) 
引用圖片



 (原文件名:13.jpg) 
引用圖片



 (原文件名:14.jpg) 
引用圖片



 (原文件名:15.jpg) 
引用圖片



 (原文件名:16.jpg) 
引用圖片



 (原文件名:17.jpg) 
引用圖片



 (原文件名:18.jpg) 
引用圖片



 (原文件名:19.jpg) 
引用圖片



 (原文件名:20.jpg) 
引用圖片



 (原文件名:21.jpg) 
引用圖片



 (原文件名:22.jpg) 
引用圖片


OK ,到此一個(gè)簡(jiǎn)單的工程模板就建立起來(lái)了,以后我們再新建源文件和頭文件的時(shí)候,就可以直接保存到src文件目錄下面了。
下面我們開(kāi)始編寫(xiě)各個(gè)模塊文件。
首先編寫(xiě)Timer.c 這個(gè)文件主要內容就是定時(shí)器初始化,以及定時(shí)器中斷服務(wù)函數。其內容如下。
#include <reg52.h>

bit g_bSystemTime1Ms = 0 ;              // 1MS系統時(shí)標


void Timer0Init(void)
{
    TMOD &= 0xf0 ;
    TMOD |= 0x01 ;      //定時(shí)器0工作方式1
    TH0  =    0xfc ;      //定時(shí)器初始值
    TL0  =  0x66 ;
    TR0  = 1 ;
    ET0  = 1 ;
}

void Time0Isr(void) interrupt 1
{
    TH0  =    0xfc ;            //定時(shí)器重新賦初值
    TL0  =  0x66 ;
    g_bSystemTime1Ms = 1 ;    //1MS時(shí)標標志位置位
}

由于在Led.c文件中需要調用我們的g_bSystemTime1Ms變量。同時(shí)主函數需要調用Timer0Init()初始化函數,所以應該對這個(gè)變量和函數在頭文件里作外部聲明。以方便其它函數調用。

Timer.h 內容如下。
#ifndef _TIMER_H_
#define _TIMER_H_

extern void Timer0Init(void) ;
extern bit g_bSystemTime1Ms ;

#endif

完成了定時(shí)器模塊后,我們開(kāi)始編寫(xiě)LED驅動(dòng)模塊。
Led.c 內容如下:

#include <reg52.h>
#include "MacroAndConst.h"
#include "Led.h"
#include "Timer.h"

static uint16  g_u16LedTimeCount = 0 ; //LED計數器
static uint8  g_u8LedState = 0 ;      //LED狀態(tài)標志, 0表示亮,1表示熄滅

#define LED P0            //定義LED接口
#define LED_ON()      LED = 0x00 ;  //所有LED亮
#define LED_OFF()    LED = 0xff ;  //所有LED熄滅

void LedProcess(void)
{
    if(0 == g_u8LedState)  //如果LED的狀態(tài)為亮,則點(diǎn)亮LED
    {
        LED_ON() ;
    }
    else                //否則熄滅LED
    {
        LED_OFF() ;
    }
}


void LedStateChange(void)
{
    if(g_bSystemTime1Ms)            //系統1MS時(shí)標到
    {
        g_bSystemTime1Ms = 0 ;
        g_u16LedTimeCount++ ;      //LED計數器加一
        if(g_u16LedTimeCount >= 500) //計數達到500,即500MS到了,改變LED的狀態(tài)。
        {
            g_u16LedTimeCount = 0 ;
            g_u8LedState  = ! g_u8LedState    ;
        }
    }
}


這個(gè)模塊對外的借口只有兩個(gè)函數,因此在相應的Led.h 中需要作相應的聲明。
Led.h 內容:
#ifndef _LED_H_
#define _LED_H_

extern void LedProcess(void) ;
extern void LedStateChange(void) ;

#endif

這兩個(gè)模塊完成后,我們將其C文件添加到工程中。然后開(kāi)始編寫(xiě)主函數里的代碼。
如下所示:

#include <reg52.h>
#include "MacroAndConst.h"
#include "Timer.h"
#include "Led.h"

sbit LED_SEG  = P1^4;  //數碼管段選
sbit LED_DIG  = P1^5;  //數碼管位選
sbit LED_CS11 = P1^6;  //led控制位

void main(void)
{
        LED_CS11 = 1 ; //74HC595輸出允許
    LED_SEG = 0 ;  //數碼管段選和位選禁止(因為它們和LED共用P0口)
        LED_DIG = 0 ;
        Timer0Init() ;
        EA = 1 ;
        while(1)
      {
    LedProcess() ;
    LedStateChange() ;
      }
}

整個(gè)工程截圖如下:

 

至此,第三章到此結束。
一起來(lái)總結一下我們需要注意的地方吧

[color=#FF0000]1.    C語(yǔ)言源文件(*.c)的作用是什么
2.    C語(yǔ)言頭文件(*.h)的作用是什么
3.    typedef 的作用
4.    工程模板如何組織
5.    如何創(chuàng )建一個(gè)多模塊(多文件)的工程


“從單片機初學(xué)者邁向單片機工程師”之KEY主題討論
                                               按鍵程序編寫(xiě)的基礎


  從這一章開(kāi)始,我們步入按鍵程序設計的殿堂。在基于單片機為核心構成的應用系統中,用戶(hù)輸入是必不可少的一部分。輸入可以分很多種情況,譬如有的系統支持PS2鍵盤(pán)的接口,有的系統輸入是基于編碼器,有的系統輸入是基于串口或者USB或者其它輸入通道等等。在各種輸入途徑中,更常見(jiàn)的是,基于單個(gè)按鍵或者由單個(gè)鍵盤(pán)按照一定排列構成的矩陣鍵盤(pán)(行列鍵盤(pán))。我們這一篇章主要討論的對象就是基于單個(gè)按鍵的程序設計,以及矩陣鍵盤(pán)的程序編寫(xiě)。
◎按鍵檢測的原理
常見(jiàn)的獨立按鍵的外觀(guān)如下,相信大家并不陌生,各種常見(jiàn)的開(kāi)發(fā)板學(xué)習板上隨處可以看到他們的身影。

 (原文件名:1.jpg) 
引用圖片


  
    總共有四個(gè)引腳,一般情況下,處于同一邊的兩個(gè)引腳內部是連接在一起的,如何分辨兩個(gè)引腳是否處在同一邊呢?可以將按鍵翻轉過(guò)來(lái),處于同一邊的兩個(gè)引腳,有一條突起的線(xiàn)將他們連接一起,以標示它們倆是相連的。如果無(wú)法觀(guān)察得到,用數字萬(wàn)用表的二極管擋位檢測一下即可。搞清楚這點(diǎn)非常重要,對于我們畫(huà)PCB的時(shí)候的封裝很有益。
它們和我們的單片機系統的I/O口連接一般如下:

 (原文件名:2.jpg) 
引用圖片


 
    對于單片機I/O內部有上拉電阻的微控制器而言,還可以省掉外部的那個(gè)上拉電阻。簡(jiǎn)單分析一下按鍵檢測的原理。當按鍵沒(méi)有按下的時(shí)候,單片機I/O通過(guò)上拉電阻R接到VCC,我們在程序中讀取該I/O的電平的時(shí)候,其值為1(高電平); 當按鍵S按下的時(shí)候,該I/O被短接到GND,在程序中讀取該I/O的電平的時(shí)候,其值為0(低電平) 。這樣,按鍵的按下與否,就和與該按鍵相連的I/O的電平的變化相對應起來(lái)。結論:我們在程序中通過(guò)檢測到該I/O口電平的變化與否,即可以知道按鍵是否被按下,從而做出相應的響應。一切看起來(lái)很美好,是這樣的嗎?

◎現實(shí)并非理想
在我們通過(guò)上面的按鍵檢測原理得出上述的結論的時(shí)候,其實(shí)忽略了一個(gè)重要的問(wèn)題,那就是現實(shí)中按鍵按下時(shí)候的電平變化狀態(tài)。我們的結論是基于理想的情況得出來(lái)的,就如同下面這幅按鍵按下時(shí)候對應電平變化的波形圖一樣:

 (原文件名:3.jpg) 
引用圖片


 
    而實(shí)際中,由于按鍵的彈片接觸的時(shí)候,并不是一接觸就緊緊的閉合,它還存在一定的抖動(dòng),盡管這個(gè)時(shí)間非常的短暫,但是對于我們執行時(shí)間以us為計算單位的微控制器來(lái)說(shuō),
它太漫長(cháng)了。因而,實(shí)際的波形圖應該如下面這幅示意圖一樣。


 (原文件名:4.jpg) 
引用圖片



 
這樣便存在這樣一個(gè)問(wèn)題。假設我們的系統有這樣功能需求:在檢測到按鍵按下的時(shí)候,將某個(gè)I/O的狀態(tài)取反。由于這種抖動(dòng)的存在,使得我們的微控制器誤以為是多次按鍵的按下,從而將某個(gè)I/O的狀態(tài)不斷取反,這并不是我們想要的效果,假如該I/O控制著(zhù)系統中某個(gè)重要的執行的部件,那結果更不是我們所期待的。于是乎有人便提出了軟件消除抖動(dòng)的思想,道理很簡(jiǎn)單:抖動(dòng)的時(shí)間長(cháng)度是一定的,只要我們避開(kāi)這段抖動(dòng)時(shí)期,檢測穩定的時(shí)候的電平不久可以了嗎?聽(tīng)起來(lái)確實(shí)不錯,而且實(shí)際應用起來(lái)效果也還可以。于是,各種各樣的書(shū)籍中,在提到按鍵檢測的時(shí)候,總也不忘說(shuō)道軟件消抖。就像下面的偽代碼所描述的一樣。(假設按鍵按下時(shí)候,低電平有效)


If(0 == io_KeyEnter)            //如果有鍵按下了
{
    Delayms(20) ;            //先延時(shí)20ms避開(kāi)抖動(dòng)時(shí)期
    If(0 == io_KeyEnter)        //然后再檢測,如果還是檢測到有鍵按下
    {
        return KeyValue ;          //是真的按下了,返回鍵值
    }
    else 
{
    return KEY_NULL        //是抖動(dòng),返回空的鍵值
}
    while(0 == io_KeyEnter) ;    //等待按鍵釋放
}

乍看上去,確實(shí)挺不錯,實(shí)際中呢?在實(shí)際的系統中,一般是不允許這么樣做的。為什么呢?首先,這里的Delayms(20) , 讓微控制器在這里白白等待了20 ms 的時(shí)間,啥也沒(méi)干,考慮我在《學(xué)會(huì )釋放CPU》一章中所提及的幾點(diǎn),這是不可取的。其次while(0 == io_KeyEnter) ;更是程序設計中的大忌(極少的特殊情況例外)。任何非極端情況下,都不要使用這樣語(yǔ)句來(lái)堵塞微控制器的執行進(jìn)程。原本是等待按鍵釋放,結果CPU就一直死死的盯住該按鍵,其它事情都不管了,那其它事情不干了嗎?你同意別人可不會(huì )同意?所以合理的分配好微控制的處理時(shí)間,是編寫(xiě)按鍵程序的基礎。


◎消除抖動(dòng)有必要嗎?

的確,軟件上的消抖確實(shí)可以保證按鍵的有效檢測。但是,這種消抖確實(shí)有必要嗎?有人提出了這樣的疑問(wèn)。抖動(dòng)是按鍵按下的過(guò)程中產(chǎn)生的,如果按鍵沒(méi)有按下,抖動(dòng)會(huì )產(chǎn)生嗎?如果沒(méi)有按鍵按下,抖動(dòng)也會(huì )在I/O上出現,我會(huì )立刻把這個(gè)微控制器錘了,永遠不用這樣一款微控制器。所以抖動(dòng)的出現即意味著(zhù)按鍵已經(jīng)按下,盡管這個(gè)電平還沒(méi)有穩定。所以只要我們檢測到按鍵按下,即可以返回鍵值,問(wèn)題的關(guān)鍵是,在你執行完其它任務(wù)的時(shí)候,再次執行我們的按鍵任務(wù)的時(shí)候,抖動(dòng)過(guò)程還沒(méi)有結束,這樣便有可能造成重復檢測。所以,如何在返回鍵值后,避免重復檢測,或者在按鍵一按下就執行功能函數,當功能函數的執行時(shí)間小于抖動(dòng)時(shí)間時(shí)候,如何避免再次執行功能函數,就成為我們要考慮的問(wèn)題了。這是一個(gè)仁者見(jiàn)仁,智者見(jiàn)智的問(wèn)題,就留給大家去思考吧。所以消除抖動(dòng)的目的是:防止按鍵一次按下,多次響應。

       “從單片機初學(xué)者邁向單片機工程師”之KEY主題討論

                                         基于狀態(tài)轉移的獨立按鍵程序設計


    本章所描述的按鍵程序要達到的目的:檢測按鍵按下,短按,長(cháng)按,釋放。即通過(guò)按鍵的返回值我們可以獲取到如下的信息:按鍵按下(短按),按鍵長(cháng)按,按鍵連_發(fā),按鍵釋放。不知道大家還記得小時(shí)候玩過(guò)的電子鐘沒(méi)有,就是外形類(lèi)似于CALL 機(CALL 機,好像是很古老的東西了?)的那種,有一個(gè)小液晶屏,還有四個(gè)按鍵,功能是時(shí)鐘,鬧鐘以及秒表。在調整時(shí)間的時(shí)候,短按+鍵每次調整值加一,長(cháng)按的時(shí)候調整值連續增加。小的時(shí)候很好奇,這樣的功能到底是如何實(shí)現的呢,今天就讓我們來(lái)剖析它的原理吧。
狀態(tài)在生活中隨處可見(jiàn)。譬如早上的時(shí)候,鬧鐘把你叫醒了,這個(gè)時(shí)候,你便處于清醒的狀態(tài),馬上你就穿衣起床洗漱吃早餐,這一系列事情就是你在這個(gè)狀態(tài)做的事情。做完這些后你會(huì )去等車(chē)或者開(kāi)車(chē)去上班,這個(gè)時(shí)候你就處在上班途中的狀態(tài)…..中午下班時(shí)間到了,你就處于中午下班的狀態(tài),諸如此類(lèi)等等,在每一個(gè)狀態(tài)我們都會(huì )做一些不同的事情,而總會(huì )有外界條件促使我們轉換到另外一種狀態(tài),譬如鬧鐘叫醒我們了,下班時(shí)間到了等等。對于狀態(tài)的定義出發(fā)點(diǎn)不同,考慮的方向不同,或者會(huì )有些許細節上面的差異,但是大的狀態(tài)總是相同的。生活中的事物同樣遵循同樣的規律,譬如,用一個(gè)智能充電器給你的手機電池充電,剛開(kāi)始,它是處于快速充電狀態(tài),隨著(zhù)電量的增加,電壓的升高,當達到規定的電壓時(shí)候,它會(huì )轉換到恒壓充電??偠灾?,細心觀(guān)察,你會(huì )發(fā)現生活中的總總都可以歸結為一個(gè)個(gè)的狀態(tài),而狀態(tài)的變換或者轉移總是由某些條件引起同時(shí)伴隨著(zhù)一些動(dòng)作的發(fā)生。我們的按鍵亦遵循同樣的規律,下面讓我們來(lái)簡(jiǎn)單的描繪一下它的狀態(tài)流程轉移圖。


 (原文件名:1.jpg) 
引用圖片

 
  下面對上面的流程圖進(jìn)行簡(jiǎn)要的分析。
首先按鍵程序進(jìn)入初始狀態(tài)S1,在這個(gè)狀態(tài)下,檢測按鍵是否按下,如果有按下,則進(jìn)入按鍵消抖狀態(tài)2,在下一次執行按鍵程序時(shí)候,直接由按鍵消抖狀態(tài)進(jìn)入按鍵按下?tīng)顟B(tài)3,在此狀態(tài)下檢測按鍵是否按下,如果沒(méi)有按鍵按下,則返回初始狀態(tài)S1,如果有則可以返回鍵值,同時(shí)進(jìn)入長(cháng)按狀態(tài)S4,在長(cháng)按狀態(tài)下每次進(jìn)入按鍵程序時(shí)候對按鍵時(shí)間計數,當計數值超過(guò)設定閾值時(shí)候,則表明長(cháng)按事件發(fā)生,同時(shí)進(jìn)入按鍵連_發(fā)狀態(tài)S5。如果按鍵鍵值為空鍵,則返回按鍵釋放狀態(tài)S6,否則繼續停留在本狀態(tài)。在按鍵連_發(fā)狀態(tài)下,如果按鍵鍵值為空鍵則返回按鍵釋放狀態(tài)S6,如果按鍵時(shí)間計數超過(guò)連_發(fā)閾值,則返回連_發(fā)按鍵值,清零時(shí)間計數后繼續停留在本狀態(tài)。
看了這么多,也許你已經(jīng)有一個(gè)模糊的概念了,下面讓我們趁熱打鐵,一起來(lái)動(dòng)手編寫(xiě)按鍵驅動(dòng)程序吧。
  下面是我使用的硬件的連接圖。

 

 (原文件名:2.jpg) 
引用圖片

  
  硬件連接很簡(jiǎn)單,四個(gè)獨立按鍵分別接在P3^0------P3^3四個(gè)I/O上面。
因為51單片機I/O口內部結構的限制,在讀取外部引腳狀態(tài)的時(shí)候,需要向端口寫(xiě)1.在51單片機復位后,不需要進(jìn)行此操作也可以進(jìn)行讀取外部引腳的操作。因此,在按鍵的端口沒(méi)有復用的情況下,可以省略此步驟。而對于其它一些真正雙向I/O口的單片機來(lái)說(shuō),將引腳設置成輸入狀態(tài),是必不可少的一個(gè)步驟。
下面的程序代碼初始化引腳為輸入。
void KeyInit(void)
{
    io_key_1 = 1 ;
    io_key_2 = 1 ;
    io_key_3 = 1 ;
    io_key_4 = 1 ;            
}
根據按鍵硬件連接定義按鍵鍵值
#define KEY_VALUE_1              0x0e
#define KEY_VALUE_2              0x0d
#define KEY_VALUE_3                0x0b
#define KEY_VALUE_4                0x07
#define KEY_NULL                    0x0f
下面我們來(lái)編寫(xiě)按鍵的硬件驅動(dòng)程序。
根據第一章所描述的按鍵檢測原理,我們可以很容易的得出如下的代碼:
static uint8 KeyScan(void)
{
    if(io_key_1 == 0)return KEY_VALUE_1 ;
    if(io_key_2 == 0)return KEY_VALUE_2 ;
    if(io_key_3 == 0)return KEY_VALUE_3 ;
    if(io_key_4 == 0)return KEY_VALUE_4 ;
    return KEY_NULL ;
}
其中io_key_1等是我們按鍵端口的定義,如下所示:
sbit io_key_1 = P3^0 ;
sbit io_key_2 = P3^1 ;
sbit io_key_3 = P3^2 ;
sbit io_key_4 = P3^3 ;

KeyScan()作為底層按鍵的驅動(dòng)程序,為上層按鍵掃描提供一個(gè)接口,這樣我們編寫(xiě)的上層按鍵掃描函數可以幾乎不用修改就可以拿到我們的其它程序中去使用,使得程序復用性大大提高。同時(shí),通過(guò)有意識的將與底層硬件連接緊密的程序和與硬件無(wú)關(guān)的代碼分開(kāi)寫(xiě),使得程序結構層次清晰,可移植性也更好。對于單片機類(lèi)的程序而言,能夠做到函數級別的代碼重用已經(jīng)足夠了。
在編寫(xiě)我們的上層按鍵掃描函數之前,需要先完成一些宏定義。
//定義長(cháng)按鍵的TICK數,以及連_發(fā)間隔的TICK數
#define KEY_LONG_PERIOD        100
#define KEY_CONTINUE_PERIOD    25

//定義按鍵返回值狀態(tài)(按下,長(cháng)按,連_發(fā),釋放)
#define KEY_DOWN                0x80
#define KEY_LONG                    0x40
#define KEY_CONTINUE            0x20
#define KEY_UP                  0x10

//定義按鍵狀態(tài)
#define KEY_STATE_INIT            0
#define KEY_STATE_WOBBLE            1
#define KEY_STATE_PRESS            2
#define KEY_STATE_LONG            3
#define KEY_STATE_CONTINUE      4
#define KEY_STATE_RELEASE        5

接著(zhù)我們開(kāi)始編寫(xiě)完整的上層按鍵掃描函數,按鍵的短按,長(cháng)按,連按,釋放等等狀態(tài)的判斷均是在此函數中完成。對照狀態(tài)流程轉移圖,然后再看下面的函數代碼,可以更容易的去理解函數的執行流程。完整的函數代碼如下:

void GetKey(uint8 *pKeyValue)
{
    static uint8 s_u8KeyState = KEY_STATE_INIT ;
    static uint8 s_u8KeyTimeCount = 0 ;
    static uint8 s_u8LastKey = KEY_NULL ;  //保存按鍵釋放時(shí)候的鍵值
    uint8 KeyTemp = KEY_NULL ;

    KeyTemp = KeyScan() ;        //獲取鍵值

    switch(s_u8KeyState)
    {
        case KEY_STATE_INIT :
                {
                    if(KEY_NULL != (KeyTemp))
                    {
                        s_u8KeyState = KEY_STATE_WOBBLE ;
                    }
                }
        break ;

        case KEY_STATE_WOBBLE :      //消抖
                {
                    s_u8KeyState = KEY_STATE_PRESS ;    
                }
        break ;

        case KEY_STATE_PRESS :
                {
                    if(KEY_NULL != (KeyTemp))
                    {
                        s_u8LastKey = KeyTemp ; //保存鍵值,以便在釋放按鍵狀態(tài)返回鍵值
                        KeyTemp |= KEY_DOWN ;  //按鍵按下
                        s_u8KeyState = KEY_STATE_LONG ;
                    }
                    else
                    {
                        s_u8KeyState = KEY_STATE_INIT ;
                    }
                }
        break ;

        case KEY_STATE_LONG :
                {
                    if(KEY_NULL != (KeyTemp))
                    {
                        if(++s_u8KeyTimeCount > KEY_LONG_PERIOD)
                        {
                            s_u8KeyTimeCount = 0 ;
                            KeyTemp |= KEY_LONG ;  //長(cháng)按鍵事件發(fā)生
                            s_u8KeyState = KEY_STATE_CONTINUE ;
                        }
                    }
                    else
                    {
                        s_u8KeyState = KEY_STATE_RELEASE ;
                    }
                }
        break ;

        case KEY_STATE_CONTINUE :
                {
                    if(KEY_NULL != (KeyTemp))
                    {
                        if(++s_u8KeyTimeCount > KEY_CONTINUE_PERIOD)
                        {
                            s_u8KeyTimeCount = 0 ;
                            KeyTemp |= KEY_CONTINUE ;
                        }
                    }
                    else
                    {
                        s_u8KeyState = KEY_STATE_RELEASE ;
                    }
                }
        break ;

        case KEY_STATE_RELEASE :
                {
                    s_u8LastKey |= KEY_UP ;
                    KeyTemp = s_u8LastKey ;
                    s_u8KeyState = KEY_STATE_INIT ;
                }
        break ;

        default : break ;
    }
    *pKeyValue = KeyTemp ; //返回鍵值    
}
關(guān)于這個(gè)函數內部的細節我并不打算花過(guò)多筆墨去講解。對照著(zhù)按鍵狀態(tài)流程轉移圖,然后去看程序代碼,你會(huì )發(fā)現其實(shí)思路非常清晰。最能讓人理解透徹的,莫非就是將整個(gè)程序自己看懂,然后想象為什么這個(gè)地方要這樣寫(xiě),抱著(zhù)思考的態(tài)度去閱讀程序,你會(huì )發(fā)現自己的程序水平會(huì )慢慢的提高。所以我更希望的是你能夠認認真真的看完,然后思考。也許你會(huì )收獲更多。
不管怎么樣,這樣的一個(gè)程序已經(jīng)完成了本章開(kāi)始時(shí)候要求的功能:按下,長(cháng)按,連按,釋放。事實(shí)上,如果掌握了這種基于狀態(tài)轉移的思想,你會(huì )發(fā)現要求實(shí)現其它按鍵功能,譬如,多鍵按下,功能鍵等等,亦相當簡(jiǎn)單,在下一章,我們就去實(shí)現它。
在主程序中我編寫(xiě)了這樣的一段代碼,來(lái)演示我實(shí)現的按鍵功能。
void main(void)
{    
    uint8 KeyValue = KEY_NULL;
    uint8 temp = 0 ;
      LED_CS11 = 1 ; //流水燈輸出允許
    LED_SEG = 0 ;
    LED_DIG = 0 ;
    Timer0Init() ;
    KeyInit() ;
    EA = 1 ;
    while(1)
    {
        Timer0MainLoop() ;
        KeyMainLoop(&KeyValue) ;
        
        if(KeyValue == (KEY_VALUE_1 | KEY_DOWN)) P0 = ~1 ;
        if(KeyValue == (KEY_VALUE_1 | KEY_LONG)) P0 = ~2 ;
        if(KeyValue == (KEY_VALUE_1 | KEY_CONTINUE)) { P0 ^= 0xf0;}
        if(KeyValue == (KEY_VALUE_1 | KEY_UP)) P0 = 0xa5 ;
    }

}
    按住第一個(gè)鍵,可以清晰的看到P0口所接的LED的狀態(tài)的變化。當按鍵按下時(shí)候,第一個(gè)LED燈亮,等待2 S后第二個(gè)LED亮,第一個(gè)熄滅,表示長(cháng)按事件發(fā)生。再過(guò)500 ms 第5~8個(gè)LED閃爍,表示連按事件發(fā)生。當釋放按鍵時(shí)候,P0口所接的LED的狀態(tài)為:
滅亮滅亮亮滅亮滅,這也正是P0 = 0xa5這條語(yǔ)句的功能。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
原來(lái)搞單片機也可以面向對象
STC51(點(diǎn)燈+五向開(kāi)關(guān))
晨輝教你輕松學(xué)51--------按鍵篇
《中學(xué)生C51單片機易學(xué)實(shí)戰入門(mén)教程》第二課
51單片機學(xué)習
單片機彩燈是怎樣點(diǎn)亮
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久