找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 109694|回復(fù): 41
打印 上一主題 下一主題
收起左側(cè)

第八章 獨(dú)立按鍵和矩陣按鍵

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:1 發(fā)表于 2013-7-11 22:35 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式

  我們和單片機(jī)之間進(jìn)行信息交互,主要包含兩大類,輸入設(shè)備和輸出設(shè)備。前邊講的LED小燈、數(shù)碼管、點(diǎn)陣都是輸出設(shè)備,這節(jié)課我們學(xué)習(xí)一下最常用的輸入設(shè)備——按鍵。在本節(jié)課的學(xué)習(xí)過程中我們還會(huì)穿插介紹一點(diǎn)硬件設(shè)計(jì)的基礎(chǔ)知識(shí)。

8.1 單片機(jī)最小系統(tǒng)電路解析

8.1.1 電源

我們在學(xué)習(xí)過程中,很多指標(biāo)都是直接用的概念指標(biāo),比如我們說+5V代表1,GND代表0等等這些。但在實(shí)際電路中是沒有這么精準(zhǔn)的,那這些指標(biāo)允許范圍是什么呢?隨著我們所學(xué)的內(nèi)容不斷增多,大家要慢慢培養(yǎng)一種閱讀手冊的能力。

比如我們使用STC89C52RC單片機(jī)的時(shí)候,我們找到他的手冊的11頁,第二個(gè)選項(xiàng),工作電壓:5.5V-3.4V(5V單片機(jī)),這個(gè)地方就說明我們這個(gè)單片機(jī)正常的工作電壓是個(gè)范圍值,只要電源VCC5.5V3.4V之間都可以正常工作,電壓超過5.5V是絕對(duì)不允許的,會(huì)燒壞單片機(jī),電壓如果低于3.4V,單片機(jī)不會(huì)損壞,但是也不能正常工作。而在這個(gè)范圍內(nèi),最典型、最常用的電壓值就是5V,這就是后面括號(hào)里“5V單片機(jī)”這個(gè)名稱的由來。除此之外,還有一種常用的工作電壓范圍是2.7V-3.6V、典型值是3.3V的單片機(jī),也就是所謂的“3.3V單片機(jī)”了。日后隨著大家接觸的東西慢慢增多,對(duì)這點(diǎn)會(huì)有更深刻的理解。

現(xiàn)在我們再順便多了解一點(diǎn),大家打開74HC138的數(shù)據(jù)手冊,會(huì)發(fā)現(xiàn)74HC138手冊的第二頁也有一個(gè)表格,上邊寫了74HC138的工作電壓范圍,最小值是4.75V,額定值是5V,最大值是5.25V,可以得知它的工作電壓范圍是4.75V-5.25V。這個(gè)地方講這些目的是讓大家清楚的了解,我們獲取器件工作參數(shù)的一個(gè)最重要,也是最權(quán)威的途徑,就是通過器件的數(shù)據(jù)手冊。

8.1.2 晶振

晶振通常分為無源晶振和有源晶振兩種類型,無源晶振一般稱之為crystal(晶體),而有源晶振則叫做oscillator(振蕩器)。

有源晶振是一個(gè)完整的諧振振蕩器,他是利用石英晶體的壓電效應(yīng)來起振,所以有源晶振需要供電,當(dāng)我們把有源晶振電路做好后,不需要外接電路,它就可以主動(dòng)產(chǎn)生振蕩頻率,并且可以提供高精度的頻率基準(zhǔn),信號(hào)質(zhì)量比無源信號(hào)好。

而無源晶振自身無法振蕩起來,它需要芯片內(nèi)部的振蕩電路一起工作才能振蕩,它允許不同的電壓,但是信號(hào)質(zhì)量和精度較有源晶振差一些。相對(duì)價(jià)格來說,無源晶振要比有源晶振價(jià)格便宜很多。無源晶振兩側(cè)通常都會(huì)有兩個(gè)電容,一般其容值都選在10pF~40pF之間,如果手冊中有具體電容大小的要求則要根據(jù)要求來選電容,如果手冊沒有要求,我們用20pF就是比較好的選擇,這是一個(gè)長久以來的經(jīng)驗(yàn)值,具有極其普遍的適用性。

我們來認(rèn)識(shí)下比較常用的兩種晶振的樣貌,如圖8-1和圖8-2所示。

                                              
           

                                                       圖8-1  27Mhz有源晶振                   圖8-2  11.0592M無源晶振

有源晶振通常有4個(gè)引腳,VCC,GND,晶振輸出引腳和一個(gè)沒有用到的懸空引腳。無源晶振有2個(gè)或3個(gè)引腳,如果是3個(gè)引腳的話,中間引腳是晶振的外殼,使用時(shí)要接到GND,兩側(cè)的引腳就是晶體的2個(gè)引出腳了,這兩個(gè)引腳作用是等同的,就像是電阻的2個(gè)引腳一樣,沒有正負(fù)之分。對(duì)于無源晶振,就是用我們的單片機(jī)上的兩個(gè)晶振引腳接上去即可,而有源晶振,只接到單片機(jī)的晶振的輸入引腳上,輸出引腳上不需要接,如圖8-3和圖8-4所示。關(guān)于晶振的更多資料可參考:http://www.torrancerestoration.com/dianzi/300.html 上面有更深層的原理剖析與詳細(xì)的分類.

                         
       


                                        圖8-3 無源晶振接法                           圖8-4 有源晶振接法

8.1.3 復(fù)位電路

我們先來分析一下我們的復(fù)位電路,如圖8-5所示。

 



8-5 單片機(jī)復(fù)位電路

當(dāng)這個(gè)電路處于穩(wěn)態(tài)時(shí),電容起到隔離直流的作用,隔離了+5V,而左側(cè)的復(fù)位按鍵是彈起狀態(tài),下邊部分電路就沒有電壓差的產(chǎn)生,所以按鍵和電容C11以下部分的電位都是和GND相等的,也就是0V電壓。我們這個(gè)單片機(jī)是高電平復(fù)位,低電平正常工作,所以正常工作的電壓是0V電壓,完全OK,沒有問題。

我們再來分析從沒有電到上電的瞬間,電容C11上方是5V電壓,下方是0V電壓,根據(jù)我們初中所學(xué)的知識(shí),這個(gè)時(shí)候電容C11要進(jìn)行充電,正離子從上往下充電,負(fù)電子從GND往上充電,這個(gè)時(shí)候電容對(duì)電路來說相當(dāng)于一根導(dǎo)線,全部電壓都加在了R31這個(gè)電阻上,那么RST端口位置是+5V電壓,隨著電容充電越來越多,即將充滿的時(shí)候,電流會(huì)越來越小,那RST端口上的電壓值等于電流乘以R31的阻值,也就會(huì)越來越小,一直到電容完全充滿后,線路上不再有電流,這個(gè)時(shí)候RSTGND的電位就相等了也就是0V了。

從這個(gè)過程上來看,我們加上這個(gè)電路,單片機(jī)系統(tǒng)上電后,RST引腳會(huì)先保持一小段時(shí)間的高電平而后變成低電平,這個(gè)過程就是上電復(fù)位的過程。那這個(gè)“一小段時(shí)間”到底是多少才合適呢?每種單片機(jī)不完全一樣,51單片機(jī)手冊里寫的是持續(xù)時(shí)間不少于2個(gè)機(jī)器周期的時(shí)間。復(fù)位電壓值,每種單片機(jī)不完全一樣,我們按照通常值0.7Vcc作為復(fù)位電壓值,復(fù)位時(shí)間的計(jì)算過程比較復(fù)雜,我這里只給大家一個(gè)結(jié)論,時(shí)間t=1.2RC,我們用的R4700,C0.0000001,那計(jì)算得知t564us,遠(yuǎn)遠(yuǎn)大于2個(gè)機(jī)器周期(2us),在電路設(shè)計(jì)的時(shí)候一般留夠余量就行。

按鍵復(fù)位(即手動(dòng)復(fù)位)有2個(gè)過程,按下按鍵之前,RST的電壓值是0V,當(dāng)按下按鍵后電路導(dǎo)通,同時(shí)電容也會(huì)在瞬間進(jìn)行放電,RST電壓值變化為4700Vcc/(4700+18),會(huì)處于高電平復(fù)位狀態(tài)。當(dāng)松開按鍵后就和上電復(fù)位類似了,先是電容充電,后電流逐漸減小直到RST電壓變0V的過程。我們按下按鍵的時(shí)間通常都會(huì)有上百毫秒,這個(gè)時(shí)間足夠復(fù)位了。按下按鍵的瞬間,電容兩端的5V電壓(注意不是電源的5VGND之間)會(huì)被直接接通,此刻會(huì)有一個(gè)瞬間的大電流沖擊,會(huì)在局部范圍內(nèi)產(chǎn)生電磁干擾,為了抑制這個(gè)大電流所引起的干擾,我們這里在電容放電回路中串入一個(gè)18歐的電阻來限流。

如果有的同學(xué)已經(jīng)開始DIY設(shè)計(jì)自己的電路板的時(shí)候,那單片機(jī)最小系統(tǒng)的設(shè)計(jì)現(xiàn)在已經(jīng)有了足夠的理論依據(jù)了,可以考慮嘗試了。如在制作過程有有問題可到:單片機(jī)論壇http://www.torrancerestoration.com/bbs/ 求助作者會(huì)不定期回復(fù)的,基礎(chǔ)比較薄弱的同學(xué)先不要著急,繼續(xù)跟著往下學(xué),把課程都學(xué)完了再動(dòng)手操作也不遲,磨刀不誤砍柴工。

8.2 函數(shù)的調(diào)用

隨著我們編程的程序量的增多,如果把所有的語句都寫到main函數(shù)中,一方面程序會(huì)寫的比較亂,另外一個(gè)方面,當(dāng)我們一個(gè)功能需要多次執(zhí)行的時(shí)候,我們就得不斷重復(fù)寫語句,這個(gè)時(shí)候,就引入了函數(shù)調(diào)用的概念。

一個(gè)程序一般由若干個(gè)子程序模塊組成,一個(gè)模塊實(shí)現(xiàn)一個(gè)特定的功能,在C語言中,這個(gè)模塊就用函數(shù)來表示。一個(gè)C程序一般由一個(gè)主函數(shù)和若干個(gè)其他函數(shù)構(gòu)成。主函數(shù)可以調(diào)用其他函數(shù),其他函數(shù)也可以相互調(diào)用,但其它函數(shù)不能調(diào)用主函數(shù)。在我們的51單片機(jī)程序中,還有中斷服務(wù)函數(shù),是當(dāng)相應(yīng)的中斷到來后自動(dòng)調(diào)用執(zhí)行的,不需要也不能由其他函數(shù)調(diào)用。

函數(shù)調(diào)用的一般形式是:

函數(shù)名(實(shí)參列表)

函數(shù)名就是需要調(diào)用的函數(shù)的名稱,實(shí)參列表就是根據(jù)實(shí)際調(diào)用函數(shù)要傳遞給被調(diào)用函數(shù)的參數(shù)列表,不需要傳遞參數(shù)的只加括號(hào)就可以,傳遞多個(gè)參數(shù)時(shí)要用逗號(hào)隔開。在這里我以上節(jié)課的點(diǎn)陣IU的縱向移動(dòng)的程序改動(dòng)一下,大家先了解一下基本的函數(shù)調(diào)用。另外,大家不要偷懶,一定把這個(gè)程序抄下來做一下實(shí)驗(yàn)加深一下自己的印象。

#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 graph[] = {

    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,

    0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,0xFF,

    0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,

    0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,

    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF

};

unsigned char index = 0;   //圖片刷新索引

 

void refresh();             //函數(shù)聲明

 

void main()

{

    P0 = 0xFF;      //P0口初始化

    ADDR3 = 0;      //選擇LED點(diǎn)陣

    ENLED = 0;      //LED顯示總使能

    TMOD = 0x01;    //設(shè)置定時(shí)器0為模式1

    TH0 = 0xFC;     //定時(shí)器初值,定時(shí)1ms

    TL0 = 0x67;

    TR0 = 1;        //打開定時(shí)器0

    ET0 = 1;        //使能定時(shí)器0中斷

    EA = 1;         //打開總中斷開關(guān)

    

    while(1);

}

 

void refresh()

{

    static unsigned char j = 0;

    P0 = 0xFF;      //LED點(diǎn)陣動(dòng)態(tài)刷新

    switch (j)

    {

        case 0: ADDR0=0; ADDR1=0; ADDR2=0; break;

        case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;

        case 2: ADDR0=0; ADDR1=1; ADDR2=0; break;

        case 3: ADDR0=1; ADDR1=1; ADDR2=0; break;

        case 4: ADDR0=0; ADDR1=0; ADDR2=1; break;

        case 5: ADDR0=1; ADDR1=0; ADDR2=1; break;

        case 6: ADDR0=0; ADDR1=1; ADDR2=1; break;

        case 7: ADDR0=1; ADDR1=1; ADDR2=1; break;

        default: break;

    }

    P0 = graph[index+j];

    j++;

    if (j >= 8)

    {

        j = 0;

    }

}

 

void InterruptTimer0() interrupt 1

{

    static unsigned char tmr = 0;

 

    TH0 = 0xFC;     //溢出后進(jìn)入中斷重新賦值

    TL0 = 0x67;

 

    refresh();      //函數(shù)調(diào)用

 

    tmr++;          //圖片刷新頻率控制

    if (tmr >= 250) //每隔250ms刷新一幀

    {

        tmr = 0;

        index++;

        if (index >= 32)

        {

            index = 0;

        }

    }

}

這個(gè)程序是對(duì)函數(shù)的簡單調(diào)用,但是有以下三個(gè)細(xì)節(jié)需要大家注意一下:

1、函數(shù)調(diào)用的時(shí)候,不需要加函數(shù)類型。在中斷函數(shù)內(nèi)調(diào)用刷新函數(shù)的時(shí)候我們只寫了refresh(); 而沒有加void。

2、調(diào)用函數(shù)與被調(diào)用函數(shù)的位置關(guān)系,C語言規(guī)定:函數(shù)在被調(diào)用之前,必須先被定時(shí)或聲明。意思就是說:在一個(gè)文件中,一個(gè)函數(shù)應(yīng)該先定義,然后才能被調(diào)用,也就是調(diào)用函數(shù)應(yīng)位于被調(diào)用函數(shù)的下方。但是作為一種通常的編程規(guī)范,我們推薦main函數(shù)寫在最前面(因?yàn)樗鸬教峋V挈領(lǐng)的作用),其后再定義各個(gè)子函數(shù),而中斷函數(shù)則寫在文件的最后。這時(shí)候,我們就在文件開頭,所有函數(shù)定義之前,開辟一塊區(qū)域,叫做函數(shù)聲明區(qū),用來把被調(diào)用的子函數(shù)聲明一下,如此,該函數(shù)就可以被隨意調(diào)用了。如上述例程所示。

3、函數(shù)聲明的時(shí)候必須加函數(shù)類型,函數(shù)的形式參數(shù),最后加上一個(gè)分號(hào)表示結(jié)束。這點(diǎn)請(qǐng)尤其注意,因?yàn)楹瘮?shù)定義時(shí)最后是不能有分號(hào)的,初學(xué)者很容易因粗心大意搞錯(cuò),導(dǎo)致程序編譯不過。

4、函數(shù)自身的類型、聲明的類型以及調(diào)用的類型必須一致。我們這個(gè)例子里refresh函數(shù)的類型是void

8.3 函數(shù)的形式參數(shù)和實(shí)際參數(shù)

上一個(gè)程序在進(jìn)行函數(shù)調(diào)用的時(shí)候,我們不需要任何參數(shù)傳遞,所以函數(shù)定義和調(diào)用時(shí)refresh()括號(hào)里是空的,但是更多的時(shí)候我們調(diào)用函數(shù),主調(diào)函數(shù)和被調(diào)用函數(shù)之間是要有參數(shù)傳遞關(guān)系的。在調(diào)用一個(gè)有參數(shù)的函數(shù)時(shí),函數(shù)名后邊括號(hào)里中的參數(shù)叫做實(shí)際參數(shù),簡稱實(shí)參。而被調(diào)用的函數(shù)在進(jìn)行定義的時(shí)候,括號(hào)里的參數(shù)就叫做形式參數(shù),簡稱形參,我們找個(gè)簡單程序例子做說明。

unsigned char add(unsigned char x, unsigned char y);

void main()

{

    unsigned char a = 1;

    unsigned char b = 2;

    unsigned char c = 0;

 

    c = add(a, b);          //調(diào)用時(shí),ab就是實(shí)參,把函數(shù)的返回值賦給c

                             //運(yùn)算完后,c的值就是3

    while(1);

}

 

unsigned char add(unsigned char x, unsigned char y) //xy就是形參

{

    unsigned char z = 0;

    z = x + y;

    return z;         //返回值z的類型就是函數(shù)add的類型

}

這個(gè)演示程序雖然很簡單,但是形參和實(shí)參以及函數(shù)返回值等全部內(nèi)容都囊括在內(nèi)了。主調(diào)函數(shù)main和被調(diào)函數(shù)add之間的數(shù)據(jù)通過形參和實(shí)參發(fā)生了傳遞關(guān)系,而函數(shù)運(yùn)算完了也把值傳遞給了變量c,函數(shù)只要不是void類型的函數(shù),都會(huì)有返回值,返回值類型就是函數(shù)的類型。關(guān)于形參和實(shí)參,還有以下幾點(diǎn)需要注意。

1、函數(shù)定義中指定的形參,在未發(fā)生函數(shù)調(diào)用時(shí)不占內(nèi)存,只有函數(shù)調(diào)用時(shí),函數(shù)add中的形參才被分配內(nèi)存單元。在調(diào)用結(jié)束后,形參所占的內(nèi)存單元也被釋放,這個(gè)前邊講過了,形參是局部變量。

2、實(shí)參可以是常量,也可以是簡單或者復(fù)雜的表達(dá)式,但是要求他們必須有確定的值,在調(diào)用發(fā)生時(shí)將實(shí)參的值傳遞給形參。

如上邊這個(gè)程序也可以寫成:  c = add(1, a+b);

3、形參必須要指定數(shù)據(jù)類型,和定義變量一樣。

4、實(shí)參和形參的數(shù)據(jù)類型應(yīng)該相同或者賦值兼容。和變量賦值一樣,當(dāng)形參和實(shí)參出現(xiàn)不同類型時(shí),則按照不同類型數(shù)值的賦值規(guī)則進(jìn)行轉(zhuǎn)換。

5、主調(diào)函數(shù)在調(diào)用函數(shù)之前,應(yīng)對(duì)被調(diào)函數(shù)做原型聲明。

6、實(shí)參向形參的數(shù)據(jù)傳遞是單向傳遞,不能有形參再回傳給實(shí)參。也就是說,實(shí)參值傳遞給形參后,調(diào)用結(jié)束,形參單元被釋放,而實(shí)參單元仍保留并且維持原值。

8.4 獨(dú)立按鍵

通常的按鍵分為獨(dú)立式按鍵和矩陣式按鍵兩種,獨(dú)立式按鍵比較簡單,并且與獨(dú)立的輸入線相連接,如圖8-6所示

 

8-6 獨(dú)立式按鍵電路圖

4條輸入線接到單片機(jī)的IO口上,當(dāng)按鍵K1按下時(shí),+5V通過電阻R1然后再通過按鍵K1最終進(jìn)入GND形成一條通路,那么這條線路的全部電壓都加到了R1這個(gè)電阻上,KeyIn1這個(gè)引腳就是個(gè)低電平。當(dāng)松開按鍵后,線路斷開,就不會(huì)有電流通過,那么KeyIn1+5V就應(yīng)該是等電位,是一個(gè)高電平。我們就可以通過KeyIn1這個(gè)IO口的高低電平來判斷是否有按鍵按下。

這個(gè)電路中按鍵的原理我們清楚了,但是實(shí)際上在我們的單片機(jī)IO口內(nèi)部,也有一個(gè)上拉電阻的存在。我們的按鍵是接到了P2口上,P2口上電默認(rèn)是準(zhǔn)雙向IO口,我們來簡單了解一下這個(gè)準(zhǔn)雙向IO口的電路,如圖8-7所示。

 


8-7 準(zhǔn)雙向IO口結(jié)構(gòu)圖

首先說明一點(diǎn),就是我們現(xiàn)在絕大多數(shù)單片機(jī)的IO口都是使用MOS管而非三極管,但用在這里的MOS管其原理和三極管是一樣的,因此在這里我用三極管替代它來進(jìn)行原理講解,把前面講過的三極管的知識(shí)搬過來,一切都是適用的,有助于理解。

8-7方框內(nèi)的電路都是指單片機(jī)內(nèi)部部分,方框外的就是我們外接的上拉電阻。這個(gè)地方大家要注意一下,就是當(dāng)我們要讀取外部按鍵信號(hào)的時(shí)候,首先單片機(jī)必須得給個(gè)‘1’,也就是高電平,這樣我們才能正常的讀取外部的按鍵信號(hào),我們來分析一下緣由。

當(dāng)內(nèi)部輸出是高電平,經(jīng)過一個(gè)反向器變成低電平,NPN三極管不會(huì)導(dǎo)通,那么單片機(jī)IO口從內(nèi)部來看,由于上拉電阻R的存在,所以是一個(gè)高電平。當(dāng)外部沒有按鍵按下將電平拉低的話,VCC也是+5V,他們之間雖然有2個(gè)電阻,但是沒有壓差,就不會(huì)有電流,線上所有的位置都是高電平,這個(gè)時(shí)候我們就可以正常讀取到按鍵的狀態(tài)了。

當(dāng)內(nèi)部輸出是個(gè)低電平,經(jīng)過一個(gè)反相器變成高電平,NPN三極管導(dǎo)通,那么單片機(jī)的內(nèi)部IO口就是個(gè)低電平,這個(gè)時(shí)候,外部雖然也有上拉電阻的存在,但是兩個(gè)電阻是并聯(lián)關(guān)系,不管按鍵是否按下,單片機(jī)的IO口上輸入到單片機(jī)內(nèi)部的狀態(tài)都是低電平,我們就無法正常讀取到按鍵的狀態(tài)了。

這個(gè)和水流其實(shí)很類似的。內(nèi)部和外部,只要有一邊是低電位,那么電流就會(huì)順流而下,由于只有上拉電阻,下邊沒有電阻分壓,直接到GND上了,所以不管另外一邊是高還是低,那電位肯定就是低電位了。

這里得到一個(gè)結(jié)論,這種具有上拉的準(zhǔn)雙向IO口,如果要正常讀取外部信號(hào)的狀態(tài),必須首先得保證自己輸出的電平是‘1’,如果輸出‘0’,則無論外部信號(hào)是高是低,這個(gè)引腳讀進(jìn)來的都是低。

8.5 矩陣按鍵

8.5.1 矩陣按鍵和獨(dú)立按鍵的關(guān)系

我們在使用按鍵的時(shí)候有這樣一種使用經(jīng)驗(yàn),當(dāng)需要多個(gè)按鍵的時(shí)候,如果做成獨(dú)立按鍵會(huì)大量占用IO口,因此我們引入了矩陣按鍵,如圖8-8所示,使用了8個(gè)IO口來實(shí)現(xiàn)16個(gè)按鍵。

 


8-8 矩陣按鍵

其實(shí)獨(dú)立按鍵理解了,矩陣按鍵也簡單,我們來分析一下。圖8-8中,一共有4組按鍵,我們只看其中一組,如圖8-9所示。大家認(rèn)真看一下,當(dāng)KeyOut1輸出一個(gè)低電平,KeyOut2、KeyOut3、KeyOut4這三個(gè)輸出高電平時(shí),是否相當(dāng)于4個(gè)獨(dú)立按鍵呢。

 


8-9 矩陣按鍵變獨(dú)立按鍵

我們先用一個(gè)簡單的程序來實(shí)現(xiàn)這4個(gè)獨(dú)立按鍵的使用。

#include <reg52.h>

 

sbit  ADDR0 = P1^0;

sbit  ADDR1 = P1^1;

sbit  ADDR2 = P1^2;

sbit  ADDR3 = P1^3;

sbit  ENLED = P1^4;

 

sbit  LED9 = P0^7;

sbit  LED8 = P0^6;

sbit  LED7 = P0^5;

sbit  LED6 = P0^4;

 

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

 

void main(void)

{

    //選擇獨(dú)立LED進(jìn)行顯示

    P0 = 0xFF;   //初始化P0

    ADDR0 = 0;

    ADDR1 = 1;

    ADDR2 = 1;

    ADDR3 = 1;

    ENLED = 0;

 

    P2 = 0xF7;    //選中第一行按鍵以進(jìn)行掃描

 

    while(1)

    {

        //將按鍵掃描引腳的值傳遞到LED

        LED9 = KEY1;  //按下時(shí)為0,對(duì)應(yīng)的LED點(diǎn)亮

        LED8 = KEY2;

        LED7 = KEY3;

        LED6 = KEY4;

    }

}

這個(gè)程序可以實(shí)現(xiàn)當(dāng)按下K1K2、K3或者K4任何一個(gè)按鍵或者多個(gè)按鍵的時(shí)候,我們對(duì)應(yīng)賦值的小燈就會(huì)點(diǎn)亮,松開按鍵的時(shí)候,小燈就熄滅。這里提醒一句,原理圖K1K4是豎著畫的,但是走線布局的時(shí)候是橫向排布的,注意一下。

從這里可以看出來,其實(shí)獨(dú)立按鍵本身就是矩陣按鍵中的一種情況而已,那這樣看來我們板子上就有4組每組4個(gè)獨(dú)立共16個(gè)獨(dú)立按鍵。

8.5.2 按鍵消抖

絕大多數(shù)情況下,我們按按鍵是不能一直按住的,所以我們通常是判斷按鍵從按下到彈起兩種狀態(tài)發(fā)生變化了,就認(rèn)為是有按鍵按下。

程序上,我們可以把每次按鍵狀態(tài)都存儲(chǔ)起來,當(dāng)下一次按鍵狀態(tài)讀進(jìn)來的時(shí)候,與當(dāng)前按鍵狀態(tài)做比較,如果發(fā)現(xiàn)這兩次按鍵狀態(tài)不一致,就說明按鍵發(fā)生動(dòng)作了,當(dāng)上一次的狀態(tài)是未按下、現(xiàn)在是按下,此時(shí)的按鍵動(dòng)作就是“按下”;當(dāng)上一次的狀態(tài)是按下、現(xiàn)在是未按下,此時(shí)的按鍵動(dòng)作就是“彈起”。顯然,每次按鍵動(dòng)作都會(huì)包含一次“按下”動(dòng)作和一次“彈起”動(dòng)作,我們可以任選一個(gè)動(dòng)作來執(zhí)行程序,或者兩個(gè)都用以執(zhí)行不同的程序也是可以的。下面還是用程序來直觀的看一下。

#include <reg52.h>

 

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

 

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

}; //數(shù)碼管真值表

 

void main(void)

{

    bit backup = 1;    //按鍵值備份,保存前一次的掃描值

    unsigned char counter = 0;  //計(jì)數(shù)器記錄按鍵按下的次數(shù)

    

    //選擇最右邊的數(shù)碼管進(jìn)行顯示

    P0 = LedChar[counter];

    ADDR0 = 0;

    ADDR1 = 0;

    ADDR2 = 0;

    ADDR3 = 1;

    ENLED = 0;

 

    //選中第一行按鍵以進(jìn)行掃描

    P2 = 0xF7;

    

    while(1)

    {

        if (KEY4 != backup)   //只取KEY4為例,當(dāng)前值與前一次值不相等時(shí),說明按鍵有動(dòng)作

        {

            if (backup == 0)  //如果前一次的值為0,則說明當(dāng)前狀態(tài)是由0變?yōu)?/font>1,即按鍵彈起

            {

                counter++;    //計(jì)數(shù)器+1

                if (counter >= 10)

                {             //只用1個(gè)數(shù)碼管顯示,所以記到10就清零重新開始

                    counter = 0;

                }

                P0 = LedChar[counter];  //計(jì)數(shù)值顯示到數(shù)碼管上

            }

            backup = KEY4;   //更新備份為當(dāng)前值,以備進(jìn)行下次比較

        }

    }

}

在這個(gè)程序中,我們以K4為例,按一次按鍵,就會(huì)產(chǎn)生“按下”和“彈起”兩個(gè)動(dòng)態(tài)的動(dòng)作,我們選擇在“彈起”時(shí)對(duì)數(shù)碼管進(jìn)行加1操作。理論是如此,大家可以在板子上用K4按鍵做做實(shí)驗(yàn)試試,多按幾次,是不是會(huì)發(fā)生這樣一種現(xiàn)象:有的時(shí)候我明明只按了一下按鍵,但數(shù)字卻加了不止1,而是2或者更多?但是我們的程序并沒有任何邏輯上的錯(cuò)誤,這是怎么回事呢?于是我們就得來說說按鍵抖動(dòng)和消抖了。

通常按鍵所用的開關(guān)都是機(jī)械彈性開關(guān),當(dāng)機(jī)械觸點(diǎn)斷開、閉合時(shí),由于機(jī)械觸點(diǎn)的彈性作用,一個(gè)按鍵開關(guān)在閉合時(shí)不會(huì)馬上就穩(wěn)定的接通,在斷開時(shí)也不會(huì)一下子徹底斷開,而是在閉合和斷開的瞬間伴隨了一連串的抖動(dòng),如圖8-10所示。

 


8-10 按鍵抖動(dòng)狀態(tài)圖

按鍵穩(wěn)定閉合時(shí)間長短是由操作人員決定的,通常都會(huì)在100ms以上,刻意快速按的話能達(dá)到40-50ms左右,很難再低了。抖動(dòng)時(shí)間是由按鍵的機(jī)械特性決定的,一般是都會(huì)在10ms以下,為了確保程序?qū)Π存I的一次閉合或者一次斷開只響應(yīng)一次,必須進(jìn)行按鍵的消抖處理。當(dāng)檢測到按鍵狀態(tài)變化時(shí),不是立即去響應(yīng)動(dòng)作,而是先等待閉合或斷開穩(wěn)定后再進(jìn)行處理。按鍵消抖可分為硬件消抖和軟件消抖。

硬件消抖就是在按鍵上并聯(lián)一個(gè)電容,如圖8-11所示,利用電容的充放電特性來對(duì)抖動(dòng)過程中產(chǎn)生的電壓毛刺進(jìn)行平滑處理,從而實(shí)現(xiàn)消抖。但實(shí)際應(yīng)用中,這種方式的效果往往不是很好,而且還增加了成本和電路復(fù)雜度。所以實(shí)際中使用的并不多。

 


8-11 電容消抖

在絕大多數(shù)情況下,我們是用軟件即程序來實(shí)現(xiàn)消抖的。最簡單的消抖原理,就是當(dāng)檢測到按鍵狀態(tài)變化后,先等待一個(gè)10ms左右的延時(shí)瞬間,讓抖動(dòng)消失后再進(jìn)行一次按鍵狀態(tài)檢測,如果與剛才檢測到的狀態(tài)相同,就刻意確認(rèn)按鍵已經(jīng)穩(wěn)定的動(dòng)作了。將上邊的程序稍加改動(dòng),如下所示。

#include <reg52.h>

 

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

 

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

};  //數(shù)碼管真值表

 

void delay(void);       //延時(shí)函數(shù)聲明

 

void main(void)

{

    bit keybuf = 1;    //按鍵值暫存,臨時(shí)保存按鍵的掃描值

    bit backup = 1;    //按鍵值備份,保存前一次的掃描值

    unsigned char counter = 0;  //計(jì)數(shù)器記錄按鍵按下的次數(shù)

    

    //選擇最右邊的數(shù)碼管進(jìn)行顯示

    P0 = LedChar[counter];

    ADDR0 = 0;

    ADDR1 = 0;

    ADDR2 = 0;

    ADDR3 = 1;

    ENLED = 0;

 

    //選中第一行按鍵以進(jìn)行掃描

    P2 = 0xF7;

    

    while(1)

    {

        keybuf = KEY4;            //只取KEY4為例,把當(dāng)前掃描值暫存

        if (keybuf != backup)    //當(dāng)前值與前一次值不相等說明此時(shí)按鍵有動(dòng)作

        {

            delay();              //延時(shí)大約10ms

            if (keybuf == KEY4)   //判斷掃描值有沒有發(fā)生改變,即按鍵抖動(dòng)

            {

                if (backup == 0)  //如果前一次的值為0,則說明當(dāng)前狀態(tài)是由0變?yōu)?/font>1,即按鍵彈起

                {

                    counter++;    //計(jì)數(shù)器+1

                    if (counter >= 10)

                    {         //只用1個(gè)數(shù)碼管顯示,所以記到10就清零重新開始

                        counter = 0;

                    }

                    P0 = LedChar[counter];  //計(jì)數(shù)值顯示到數(shù)碼管上

                }

                backup = keybuf;  //更新備份為當(dāng)前值,以備進(jìn)行下次比較

            }

        }

    }

}

 

void delay(void)

{

    unsigned int i = 1000;

 

    while (i--);  //通過debugKEIL軟件延時(shí)方式計(jì)算得出大概是10ms

}

 

    大家把這個(gè)程序下載到板子上再進(jìn)行試驗(yàn)試試,按一下按鍵而數(shù)字加了多次的問題是不是就這樣解決了?把問題解決掉的感覺是不是很爽呢?

這個(gè)程序用了一個(gè)簡單的算法實(shí)現(xiàn)了按鍵的消抖。作為這種很簡單的演示程序,我們可以這樣來寫,但是實(shí)際工程開發(fā)的時(shí)候,我們的程序量很大,各種狀態(tài)值也很多,我們while(1)的這個(gè)主循環(huán)要不停的掃描各種狀態(tài)值是否有發(fā)生變化的,如果程序中間加了這種delay延時(shí)操作后,很可能某一事件發(fā)生了,但是我們程序還在進(jìn)行delay延時(shí)操作中,當(dāng)這個(gè)事件發(fā)生完了,我們還在delay操作中,當(dāng)我們delay完事再去檢查的時(shí)候,已經(jīng)晚了,已經(jīng)檢測不到那個(gè)事件了。為了避免這種情況的發(fā)生,我們要盡量縮短while(1)循環(huán)一次所用的事件,而需要進(jìn)行長時(shí)間延時(shí)的操作,必須想其它的辦法來處理。

那么我們?nèi)绾翁幚磉@種延時(shí)問題呢?其實(shí)除了這種簡單的延時(shí),我們還有更優(yōu)異的方法來處理按鍵抖動(dòng)問題。舉個(gè)例子:我們啟用一個(gè)定時(shí)中斷,每2ms進(jìn)一次中斷,掃描一次按鍵狀態(tài)并且存儲(chǔ)起來,連續(xù)掃描8次后,看看這連續(xù)8次的按鍵狀態(tài)是否是一致的。8次按鍵的時(shí)間大概是16ms,這16ms內(nèi)如果按鍵狀態(tài)一直保持一種狀態(tài),那就可以確定現(xiàn)在按鍵是穩(wěn)定的階段,并非處于抖動(dòng)的階段,如圖8-12

 


8-12 按鍵連續(xù)判斷

假如左邊時(shí)間是起始0時(shí)刻,每經(jīng)過2ms左移一次,每移動(dòng)一次,判斷當(dāng)前連續(xù)的8次按鍵狀態(tài)是不是全1或者全0,如果是全1則判定為彈起,如果是全0則判定為按下,如果01交錯(cuò),就認(rèn)為是抖動(dòng),不做任何判定。想一下,這樣是不是比簡單的延時(shí)更加可靠?

利用這種方法,就可以避免通過直接延時(shí)按鍵消抖占用CPU時(shí)間,而是轉(zhuǎn)化成了一種按鍵狀態(tài)判定而非按鍵過程判斷,我們只對(duì)當(dāng)前按鍵的連續(xù)16ms8次狀態(tài)進(jìn)行判斷,而不再關(guān)心它在這16ms內(nèi)都做了什么事情,我們來看看這個(gè)程序怎么寫。

#include <reg52.h>

 

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

 

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

};

 

bit KeySta = 1;  //當(dāng)前按鍵狀態(tài)

 

void main(void)

{

    bit backup = 1;    //按鍵值備份,保存前一次的值

    unsigned char counter = 0;  //計(jì)數(shù)器記錄按鍵按下的次數(shù)

    

    //選擇最右邊的數(shù)碼管進(jìn)行顯示

    P0 = LedChar[counter];

    ADDR0 = 0;

    ADDR1 = 0;

    ADDR2 = 0;

    ADDR3 = 1;

    ENLED = 0;

 

    //選中第一行按鍵以進(jìn)行掃描

    P2 = 0xF7;

    

    //配置T0工作在模式1,定時(shí)2ms

    TMOD = 0x01;

    TH0 = 0xF8;

    TL0 = 0xCD;

    TR0 = 1;

    ET0 = 1;

    EA = 1;

 

    while(1)

    {

        if (KeySta != backup)  //當(dāng)前值與前一次值不相等說明此時(shí)按鍵有動(dòng)作

        {

            if (backup == 0)   //如果前一次的值為0,則說明當(dāng)前狀態(tài)是由0變?yōu)?/font>1,即按鍵彈起

            {

                counter++;     //計(jì)數(shù)器+1

                if (counter >= 10)

                {              //只用1個(gè)數(shù)碼管顯示,所以記到10就清零重新開始

                    counter = 0;

                }

                P0 = LedChar[counter];  //計(jì)數(shù)值顯示到數(shù)碼管上

            }

            backup = KeySta;   //更新備份為當(dāng)前值,以備進(jìn)行下次比較

        }

    }

}

 

void InterruptTimer0() interrupt 1

{

    static unsigned char keybuf = 0xFF;  //按鍵掃描緩沖區(qū),保存一段時(shí)間內(nèi)的掃描值

    

    TH0 = 0xF8;  //溢出后進(jìn)入中斷重新賦值

    TL0 = 0xCD;

    

    keybuf = (keybuf << 1) | KEY4;  //只取KEY4為例,緩沖區(qū)左移一位,并將當(dāng)前掃描值移入最低位

    if (keybuf == 0x00)

    { //當(dāng)連續(xù)8次掃描值都為0,即16ms內(nèi)都只檢測到按下狀態(tài)時(shí),可認(rèn)為按鍵已按下

        KeySta = 0;       //按鍵狀態(tài)值為按下

    }

    else if (keybuf == 0xFF)

    { //當(dāng)連續(xù)8次掃描值都為1,即16ms內(nèi)都只檢測到彈起狀態(tài)時(shí),可認(rèn)為按鍵已彈起

        KeySta = 1;      //按鍵狀態(tài)值為彈起

    }

    else

    {}  //其它情況下則說明按鍵狀態(tài)尚未穩(wěn)定,則不對(duì)KeySta變量值進(jìn)行更新

}

這個(gè)算法是我們在工程中經(jīng)常使用按鍵所總結(jié)的一個(gè)比較好的方法,介紹給大家,今后都可以用這種方法消抖了。當(dāng)然,按鍵消抖也還有其它的方法,程序?qū)崿F(xiàn)更是多種多樣,大家也可以再多考慮下其它的算法,拓展下思路。這個(gè)程序有一個(gè)新知識(shí)點(diǎn),就是bit類型的變量,這個(gè)在標(biāo)準(zhǔn)C語言里邊是沒有的。51單片機(jī)有一種特殊的變量類型就是bit型,比如unsigned char型是定義了一個(gè)無符號(hào)的8位的數(shù)據(jù),它占用一個(gè)字節(jié)(Byte)的內(nèi)存,而bit型是1位數(shù)據(jù),只占用1個(gè)位(bit)的內(nèi)存,用法和標(biāo)準(zhǔn)C中其他的基本數(shù)據(jù)類型是一致的。它的優(yōu)點(diǎn)就是節(jié)省內(nèi)存空間,8個(gè)bit型變量才相當(dāng)于1個(gè)char型變量所占用的空間。雖然它只有01兩個(gè)值,但也已經(jīng)可以表示很多東西了,比如:按鍵的按下和彈起、LED燈的亮和滅、三極管的導(dǎo)通與關(guān)斷、開關(guān)的閉合與斷開,聯(lián)想一下已經(jīng)學(xué)過的內(nèi)容,它是不是能用最小的內(nèi)存代價(jià)來完成很多工作呢?上面是c語言版的,匯編語言的鍵盤解說可參考:http://www.torrancerestoration.com/mcuteach/227.html 里面也講得比較詳細(xì).

8.5.3 矩陣按鍵

我們講獨(dú)立按鍵的時(shí)候,大家已經(jīng)簡單認(rèn)識(shí)了矩陣按鍵是什么樣子了。矩陣按鍵相當(dāng)于4組每組各4個(gè)獨(dú)立按鍵,一共是16個(gè)按鍵。那我們?nèi)绾螀^(qū)分這些按鍵呢?想一下我們生活所在的地球,要想確定我們所在的位置,就要借助經(jīng)緯線,而矩陣按鍵就是通過行線和列線來確定哪個(gè)按鍵被按下。在程序中我們是如何進(jìn)行的呢?

前邊講過,我們的按鍵按下通常都會(huì)保持100ms以上的,那我們程序上就每次快速的讓矩陣按鍵的KeyOut其中一個(gè)輸出低電平,其他三個(gè)輸出高電平,判斷當(dāng)前列的按鍵的狀態(tài),下次再讓另外一個(gè)KeyOut輸出低電平,另外三個(gè)高電平,再次判斷列,通過程序快速執(zhí)行不斷的循環(huán)判斷,就可以最終確定有哪個(gè)按鍵按下,這個(gè)是不是和我們動(dòng)態(tài)刷新數(shù)碼管有點(diǎn)類似?數(shù)碼管我們在動(dòng)態(tài)賦值,而按鍵這里我們在動(dòng)態(tài)讀取狀態(tài)。消抖方式依然采取檢測連續(xù)狀態(tài)的方式,只是我們現(xiàn)在連續(xù)檢測4次就可以了?聪挛覀兊某绦,這個(gè)程序是按下我們的16個(gè)按鍵K1~K16,對(duì)應(yīng)在最右邊的數(shù)碼管顯示0~F,大家學(xué)一下矩陣按鍵的基本用法和矩陣按鍵消抖的方法。

#include <reg52.h>

 

sbit KEY_IN_1  = P2^4;  //矩陣按鍵的掃描輸入引腳1

sbit KEY_IN_2  = P2^5;  //矩陣按鍵的掃描輸入引腳2

sbit KEY_IN_3  = P2^6;  //矩陣按鍵的掃描輸入引腳3

sbit KEY_IN_4  = P2^7;  //矩陣按鍵的掃描輸入引腳4

sbit KEY_OUT_1 = P2^3;  //矩陣按鍵的掃描輸出引腳1

sbit KEY_OUT_2 = P2^2;  //矩陣按鍵的掃描輸出引腳2

sbit KEY_OUT_3 = P2^1;  //矩陣按鍵的掃描輸出引腳3

sbit KEY_OUT_4 = P2^0;  //矩陣按鍵的掃描輸出引腳4

 

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

};  //數(shù)碼管真值表

 

unsigned char KeySta[4][4] = {  //全部矩陣按鍵的當(dāng)前狀態(tài),默認(rèn)都未按下

    {1, 1, 1, 1},      //bit類型不能定義數(shù)組,因此定義成unsigned char

    {1, 1, 1, 1},

    {1, 1, 1, 1},

    {1, 1, 1, 1}

};

 

void main(void)

{

    unsigned char i, j;

    unsigned char backup[4][4] = {  //按鍵值備份,保存前一次的值

        {1, 1, 1, 1},

        {1, 1, 1, 1},

        {1, 1, 1, 1},

        {1, 1, 1, 1}

    };

    

    //選擇最右邊的數(shù)碼管進(jìn)行顯示

    P0 = 0xFF;

    ADDR0 = 0;

    ADDR1 = 0;

    ADDR2 = 0;

    ADDR3 = 1;

    ENLED = 0;

    

    //配置T0工作在模式1,定時(shí)1ms

    TMOD = 0x01;

    TH0 = 0xFC;

    TL0 = 0x67;

    TR0 = 1;

    ET0 = 1;

    EA = 1;

 

    while(1)

    {

        //檢索按鍵狀態(tài)的變化

        for (i=0; i<4; i++) //i作為行循環(huán)變量

        {

            for (j=0; j<4; j++) //j作為列循環(huán)變量

            {

                if (backup[i][j] != KeySta[i][j]) //判斷按鍵動(dòng)作

                {

                    if (backup[i][j] == 0)   //判斷按鍵彈起

                    {

                        P0 = LedChar[i*4+j]; //執(zhí)行按鍵動(dòng)作

                    }

                    backup[i][j] = KeySta[i][j]; //更新前一次的值

                }

            }

        }

    }

}

 

void InterruptTimer0() interrupt 1

{

    unsigned char i;

    static unsigned char keyout = 0;  //矩陣按鍵掃描輸出計(jì)數(shù)器

    static unsigned char keybuf[4][4] = {  //按鍵掃描緩沖區(qū),保存一段時(shí)間內(nèi)的掃描值

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF}

    };

    

    TH0 = 0xFC;  //溢出后進(jìn)入中斷重新賦值

    TL0 = 0x67;

 

    //將一行的4個(gè)按鍵值移入緩沖區(qū)

    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

 

    //消抖后更新按鍵狀態(tài)

    for (i=0; i<4; i++)  //每行4個(gè)按鍵,所以循環(huán)4

    {

        if ((keybuf[keyout][i] & 0x0F) == 0x00)

        {   //連續(xù)4次掃描值為0,即16ms(4*4ms)內(nèi)都只檢測到按下狀態(tài)時(shí),可認(rèn)為按鍵已按下

            KeySta[keyout][i] = 0;

        }

        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)

        {   //連續(xù)4次掃描值為1,即16ms(4*4ms)內(nèi)都只檢測到彈起狀態(tài)時(shí),可認(rèn)為按鍵已彈起

            KeySta[keyout][i] = 1;

        }

    }

    

    //執(zhí)行下一次的掃描輸出

    keyout++;

    keyout &= 0x03;  //用跟0x03做“與”的方式,實(shí)現(xiàn)加到4即歸零,是不是很巧妙,學(xué)會(huì)它吧

    switch (keyout)

    {

        case 0:

            KEY_OUT_4 = 1;

            KEY_OUT_1 = 0;

            break;

        case 1:

            KEY_OUT_1 = 1;

            KEY_OUT_2 = 0;

            break;

        case 2:

            KEY_OUT_2 = 1;

            KEY_OUT_3 = 0;

            break;

        case 3:

            KEY_OUT_3 = 1;

            KEY_OUT_4 = 0;

            break;

        default:

            break;

    }

}

這個(gè)程序是一個(gè)比較簡單的按鍵程序,但是大家要把按鍵消抖和矩陣按鍵檢測機(jī)制充分理解透徹,這塊內(nèi)容今后就是你的一個(gè)技術(shù)積累了。

8.5.4 按鍵、數(shù)碼管簡單加法運(yùn)算

這一小節(jié)內(nèi)容只有一個(gè)程序,使用我們的矩陣按鍵實(shí)現(xiàn)計(jì)算器中簡單的整數(shù)加法運(yùn)算,大家可以先把程序復(fù)制到Keil中編譯下載到板子上試試效果。這是我們第一次做一個(gè)算的上的綜合性程序,實(shí)現(xiàn)了按鍵和數(shù)碼管以及C語言靈活運(yùn)用的一個(gè)例程。作為初學(xué)者針對(duì)這種程序的學(xué)習(xí)方式是,先從頭到尾讀一到三遍,邊讀邊理解,然后邊抄邊理解,徹底理解透徹后,自己嘗試獨(dú)立寫出來。完全采用記憶模式來學(xué)習(xí)這種例程,一兩個(gè)例程你感覺不到什么提高,當(dāng)這種例程背過上百八十個(gè)的時(shí)候,厚積薄發(fā)的感覺就會(huì)體現(xiàn)出來了。同時(shí),在抄讀的過程中注意學(xué)習(xí)我們程序的編程規(guī)范,盡量規(guī)整一些。

 

#include <reg52.h>

 

sbit KEY_IN_1  = P2^4;  //矩陣按鍵的掃描輸入引腳1

sbit KEY_IN_2  = P2^5;  //矩陣按鍵的掃描輸入引腳2

sbit KEY_IN_3  = P2^6;  //矩陣按鍵的掃描輸入引腳3

sbit KEY_IN_4  = P2^7;  //矩陣按鍵的掃描輸入引腳4

sbit KEY_OUT_1 = P2^3;  //矩陣按鍵的掃描輸出引腳1

sbit KEY_OUT_2 = P2^2;  //矩陣按鍵的掃描輸出引腳2

sbit KEY_OUT_3 = P2^1;  //矩陣按鍵的掃描輸出引腳3

sbit KEY_OUT_4 = P2^0;  //矩陣按鍵的掃描輸出引腳4

 

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

};   //數(shù)碼管真值表

const unsigned char code KeyCodeMap[4][4] = { //矩陣按鍵編號(hào)到PC標(biāo)準(zhǔn)鍵盤鍵碼的映射表

    { '1',  '2',  '3', 0x26 }, //數(shù)字鍵1、數(shù)字鍵2、數(shù)字鍵3、向上鍵

    { '4',  '5',  '6', 0x25 }, //數(shù)字鍵4、數(shù)字鍵5、數(shù)字鍵6、向左鍵

    { '7',  '8',  '9', 0x28 }, //數(shù)字鍵7、數(shù)字鍵8、數(shù)字鍵9、向下鍵

    { '0', 0x1B, 0x0D, 0x27 }  //數(shù)字鍵0、ESC鍵、  回車鍵、 向右鍵

};

unsigned char KeySta[4][4] = {  //全部矩陣按鍵的當(dāng)前狀態(tài)

    {1, 1, 1, 1},

    {1, 1, 1, 1},

    {1, 1, 1, 1},

    {1, 1, 1, 1}

};           //由于數(shù)組不能定義成bit型,這里定義成unsigned char

unsigned char LedBuf[6] = {    //數(shù)碼管動(dòng)態(tài)掃描顯示緩沖區(qū)

    0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};

 

void DisplayNum(unsigned long num);

void KeyAction(unsigned char keycode);

 

void main(void)

{

    unsigned char i, j;

    unsigned char backup[4][4] = {  //按鍵值備份,保存前一次的值

        {1, 1, 1, 1},

        {1, 1, 1, 1},

        {1, 1, 1, 1},

        {1, 1, 1, 1}

    };

    

    //選擇數(shù)碼管進(jìn)行顯示

    P0 = 0xFF;

    ADDR3 = 1;

    ENLED = 0;

    

    //配置T0工作在模式1,定時(shí)1ms

    TMOD = 0x01;

    TH0 = 0xFC;

    TL0 = 0x67;

    TR0 = 1;

    ET0 = 1;

    EA = 1;

 

    while(1)

    {

        //檢索按鍵狀態(tài)的變化

        for (i=0; i<4; i++)

        {

            for (j=0; j<4; j++)

            {

                if (backup[i][j] != KeySta[i][j])

                {

                    if (backup[i][j] == 0)  //按鍵彈起時(shí)執(zhí)行動(dòng)作

                    {

                        KeyAction(KeyCodeMap[i][j]);

                    }

                    backup[i][j] = KeySta[i][j];

                }

            }

        }

    }

}

 

void KeyAction(unsigned char keycode)

{

    static unsigned long result = 0;  //用于保存運(yùn)算結(jié)果

    static unsigned long addend = 0;  //用于保存輸入的加數(shù)

    

    if ((keycode>='0') && (keycode<='9'))  //輸入0-9的數(shù)字

    {

        addend = (addend*10) + (keycode-'0'); //原數(shù)據(jù)擴(kuò)大10倍,由新輸入的數(shù)字填充其個(gè)位

        DisplayNum(addend);    //運(yùn)算結(jié)果顯示到數(shù)碼管

    }

    else if (keycode == 0x26)  //向上鍵用作加號(hào),執(zhí)行加法或連加運(yùn)算

    {

        result += addend;      //進(jìn)行加法運(yùn)算

        addend = 0;

        DisplayNum(result);    //運(yùn)算結(jié)果顯示到數(shù)碼管

    }

    else if (keycode == 0x0D)  //回車鍵,執(zhí)行加法運(yùn)算(實(shí)際效果與加號(hào)并無區(qū)別)

    {

        result += addend;      //進(jìn)行加法運(yùn)算

        addend = 0;

        DisplayNum(result);    //運(yùn)算結(jié)果顯示到數(shù)碼管

    }

    else if (keycode == 0x1B)  //Esc鍵,清零結(jié)果

    {

        addend = 0;

        result = 0;

        DisplayNum(addend);    //清零后的加數(shù)顯示到數(shù)碼管

    }

}

 

void DisplayNum(unsigned long num)

{

    signed char i;

    unsigned char buf[6];

    

    for (i=0; i<6; i++)   //把長整型數(shù)轉(zhuǎn)換為6位十進(jìn)制的數(shù)組

    {

        buf[i] = num % 10;

        num /= 10;

    }

    for (i=5; i>=1; i--)  //從最高位起,遇到0即轉(zhuǎn)換為空格,遇到非0即退出

    {

        if (buf[i] == 0)

        {

            LedBuf[i] = 0xFF;

        }

        else

        {

            break;

        }

    }

    for ( ; i>=0; i--)    //剩余低位都如實(shí)轉(zhuǎn)換為數(shù)字

    {

        LedBuf[i] = LedChar[buf[i]];

    }

}

 

void InterruptTimer0() interrupt 1

{

    unsigned char i;

    static unsigned char ledcnt = 0;  //數(shù)碼管掃描計(jì)數(shù)器

    static unsigned char keyout = 0;  //矩陣按鍵掃描輸出計(jì)數(shù)器

    static unsigned char keybuf[4][4] = {  //按鍵掃描緩沖區(qū),保存一段時(shí)間內(nèi)的掃描值

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF}

    };

    

    TH0 = 0xFC;  //溢出后進(jìn)入中斷重新賦值

    TL0 = 0x67;

 

    //將一行的4個(gè)按鍵值移入緩沖區(qū)

    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

 

    //消抖后更新按鍵狀態(tài)

    for (i=0; i<4; i++)  //每行4個(gè)按鍵,所以循環(huán)4

    {

        if ((keybuf[keyout][i] & 0x0F) == 0x00)

        {   //連續(xù)4次掃描值為0,即16ms(4*4ms)內(nèi)都只檢測到按下狀態(tài)時(shí),可認(rèn)為按鍵已按下

            KeySta[keyout][i] = 0;

        }

        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)

        {   //連續(xù)4次掃描值為1,即16ms(4*4ms)內(nèi)都只檢測到彈起狀態(tài)時(shí),可認(rèn)為按鍵已彈起

            KeySta[keyout][i] = 1;

        }

    }

    

    //執(zhí)行下一次的掃描輸出

    keyout++;

    keyout &= 0x03;

    switch (keyout)

    {

        case 0:

            KEY_OUT_4 = 1;

            KEY_OUT_1 = 0;

            break;

        case 1:

            KEY_OUT_1 = 1;

            KEY_OUT_2 = 0;

            break;

        case 2:

            KEY_OUT_2 = 1;

            KEY_OUT_3 = 0;

            break;

        case 3:

            KEY_OUT_3 = 1;

            KEY_OUT_4 = 0;

            break;

        default:

            break;

    }

    

    //執(zhí)行數(shù)碼管動(dòng)態(tài)掃描顯示

    P0 = 0xFF;

    switch (ledcnt)

    {

        case 0: ADDR0=0; ADDR1=0; ADDR2=0; break;

        case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;

        case 2: ADDR0=0; ADDR1=1; ADDR2=0; break;

        case 3: ADDR0=1; ADDR1=1; ADDR2=0; break;

        case 4: ADDR0=0; ADDR1=0; ADDR2=1; break;

        case 5: ADDR0=1; ADDR1=0; ADDR2=1; break;

        default: break;

    }

    P0 = LedBuf[ledcnt];

    ledcnt++;

    if (ledcnt >= 6)

    {

        ledcnt = 0;

    }

}

8.6 作業(yè)

1、理解單片機(jī)最小系統(tǒng)三要素電路設(shè)計(jì)規(guī)則。

2、掌握函數(shù)間相互調(diào)用的方法和規(guī)則。

3、學(xué)會(huì)獨(dú)立按鍵和矩陣按鍵的電路設(shè)計(jì)方法和軟件編程思路。

4、用一個(gè)按鍵實(shí)現(xiàn)一個(gè)數(shù)碼管數(shù)字從F~0遞減的變化程序。

5、用矩陣按鍵做一個(gè)減法運(yùn)算

上一課:第七章 點(diǎn)陣LED的學(xué)習(xí)
下一課:第九章 步進(jìn)電機(jī)和蜂鳴器

評(píng)分

參與人數(shù) 2黑幣 +10 收起 理由
八月初 + 5 贊一個(gè)!
秋葉原48 + 5 贊一個(gè)!

查看全部評(píng)分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏11 分享淘帖 頂4 踩
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:58391 發(fā)表于 2013-12-31 10:00 | 只看該作者
學(xué)生一名,想問問題。我們用的是ATMEGA 16L。的AVR。
現(xiàn)在我們在做一個(gè)實(shí)訓(xùn),就是用矩陣按鍵與LCD顯示器結(jié)合。弄一個(gè)2位數(shù)的加法計(jì)算機(jī)!求解了。謝謝
QQ=1263046158
回復(fù)

使用道具 舉報(bào)

板凳
ID:62005 發(fā)表于 2014-5-18 17:16 | 只看該作者
寫的真好,剛頭痛怎么用按鈕來控制模塊功能
回復(fù)

使用道具 舉報(bào)

地板
ID:64575 發(fā)表于 2014-8-1 18:40 | 只看該作者
受益匪淺
回復(fù)

使用道具 舉報(bào)

5#
ID:72292 發(fā)表于 2015-1-15 21:48 | 只看該作者
寫的不錯(cuò),學(xué)習(xí)學(xué)習(xí),謝謝
回復(fù)

使用道具 舉報(bào)

6#
ID:77081 發(fā)表于 2015-4-15 21:17 | 只看該作者
獨(dú)立按鍵和矩陣按鍵
回復(fù)

使用道具 舉報(bào)

7#
ID:81338 發(fā)表于 2015-6-24 16:14 | 只看該作者
本帖最后由 無與倫比jay 于 2015-6-24 16:15 編輯

矩陣按鍵 最后switch (keyout)
    {
        case 0:
            KEY_OUT_4 = 1;
            KEY_OUT_1 = 0;
            break;
        case 1:
            KEY_OUT_1 = 1;
            KEY_OUT_2 = 0;
            break;
        case 2:
            KEY_OUT_2 = 1;
            KEY_OUT_3 = 0;
            break;
        case 3:
            KEY_OUT_3 = 1;
            KEY_OUT_4 = 0;
            break;
        default:
            break;
    }
是什么意思?例如keyout = 0的情況下,KEY_OUT_4 = 1;
            KEY_OUT_1 = 0;那么KEY_OUT_2和KEY_OUT_3,呢?
回復(fù)

使用道具 舉報(bào)

8#
ID:106857 發(fā)表于 2016-3-10 23:53 | 只看該作者
無與倫比jay 發(fā)表于 2015-6-24 16:14
**** 作者被禁止或刪除 內(nèi)容自動(dòng)屏蔽 ****

  完整的程序是這樣子的————     
      switch(keyout)
       {
         case 0:
            KEY_OUT_4 = 1;  KEY_OUT_3 = 1;  KEY_OUT_2= 1;  KEY_OUT_1 = 0; break;
         case 1:
             KEY_OUT_4 = 1;  KEY_OUT_3 = 1;  KEY_OUT_2= 0;  KEY_OUT_1 = 1; break;
        case 2:
             KEY_OUT_4 = 1;  KEY_OUT_3 = 0;  KEY_OUT_2= 1;  KEY_OUT_1 = 1; break;
        case 3:
             KEY_OUT_4 = 0;  KEY_OUT_3 = 1;  KEY_OUT_2= 1;  KEY_OUT_1 = 1; break;
        default: break;
       }
回復(fù)

使用道具 舉報(bào)

9#
ID:107120 發(fā)表于 2016-3-17 11:50 | 只看該作者
這里學(xué)到的思路,都跟別處的不一樣。
回復(fù)

使用道具 舉報(bào)

10#
ID:98839 發(fā)表于 2016-3-21 14:32 | 只看該作者
如果同時(shí)按下兩個(gè)或以上的按鍵會(huì)是什么效果?
回復(fù)

使用道具 舉報(bào)

11#
ID:106597 發(fā)表于 2016-4-17 21:51 來自觸屏版 | 只看該作者
一個(gè) bit變量怎么左移呢
回復(fù)

使用道具 舉報(bào)

12#
ID:115460 發(fā)表于 2016-4-29 14:14 | 只看該作者
非常好
回復(fù)

使用道具 舉報(bào)

13#
ID:145891 發(fā)表于 2016-11-3 11:20 | 只看該作者
老師講的很清楚,思路清晰獨(dú)特。很受用。借老師思路,今天改寫了下,支持連續(xù)按鍵,與大家共享,也希望得到指點(diǎn)。我這里是3*3的按鍵。
  1. while(1)
  2.     {
  3.         //¼ì2a°′¼ü×′ì¬μıä»ˉ
  4.         for (i=0; i<3; i++) //i×÷ÎaDDÑ-»·±äá¿
  5.         {
  6.             for (j=0; j<3; j++) //j×÷ÎaáDÑ-»·±äá¿
  7.             {
  8.                 if (backup[i][j] != KeySta[i][j]) //ÅD¶Ï°′¼ü¶ˉ×÷
  9.                 {
  10.                                                                                 if(backup[i][j] == 1)//±¸·YêÇ1£¬ÏÖÔú′|óú°′ÏÂ×′쬠      
  11.                                                                                 {
  12. //dosomething();                               
  13.                                                                                         if( Keycount > 30)//á¬Dø°′¼ü3¬1y15*30 = 450ms
  14.                                                                                         {
  15.                                                                                                 //á¬Dø°′¼ü¡£ DoSomething(times)   times =( Keycount- 30 )/7 =  7*15Ms = 105Ëãò»′Σ¬Ã¿Ãë¿éòÔÕÇ10′Î.1ÃëÕÇ10¡£25Ãë2Åòç3ö¡£
  16.                                                                                         }
  17.                                                                                         if(Keycount >= 255)
  18.                                                                                         {
  19.                                                                                         //ËùóDéèÖÃ3é×î′óÖμ»òÕß±¨¾ˉ,»òÕßKeycount = 31,′óD¿aê¼¼Æêy
  20.                                                                                         }
  21.                                                                                 }
  22.                     if (backup[i][j] == 0)   //ÅD¶Ï°′¼üμˉÆe,±¸·YêÇ0£¬ÔòÏÖÔúêÇ1.°′¼üòѾ-μˉÆe
  23.                     {
  24. //                        P0 = LedChar[i*4+j]; //Ö′DD°′¼ü¶ˉ×÷
  25.                                                                                         if(Keycount < 30)       
  26.                                                                                         {
  27.                                                                                                 //μ¥′ΰ′¼üê±3¤D¡óú15 * 30 = 450ms = 0.5s
  28.                                                                                                 //Ëã×÷μ¥′ΰ′¼ü
  29.                                                                                         }
  30.                                                                                         Keycount = 0;//°′¼üμˉÆe£¬ÎTÂÛêÇ·ñá¬Dø°′¼ü£¬¶¼ó|Çå¿ÕKeycount.
  31.                                                                                 }
  32.                     backup[i][j] = KeySta[i][j]; //¸üDÂǰò»′ÎμļüÖμ¡£òòÎaòѾ-¶¨òåμˉÆe   ËùòÔ15MSòÔÄú2»»áÔù′μÆêy°′¼ü°′Ï¡£
  33.                 }
  34.             }
  35.         }
  36.     }



  37.    //Ïû¶¶oó¸üD°′¼ü×′ì¬
  38.     for (i=0; i<3; i++)  //ÿDD3¸ö°′¼ü£¬ËùòÔÑ-»·3′Î
  39.     {
  40.         if ((keybuf[keyout][i] & 0x1F) == 0x00)//DT¸Ä0x1fÎa0x3f£¬¿éòÔ¶àé¨Ãèò»′Σ¬ò2¾íêÇ18msÎaÅD¶¨ê±¼ä¡£
  41.         {   //á¬Dø5′Îé¨ÃèÖμÎa0£¬¼′15ms£¨5*3msÄú£©¶¼Ö»¼ì2aμ½°′ÏÂ×′ì¬ê±£¬¿éèÏÎa°′¼üòѰ′Ï¡£°′¼ü°′ÏÂóÅÏè¼¶¸ßóúμˉÆeóÅÏè¼¶
  42.             KeySta[keyout][i] = 0;
  43.                                                 Keycount++;//á¬Dø°′¼üê±£¬15ms¿aê¼2ÅÄü¼Æêyò»′Ρ£
  44.                                           keybuf[keyout][i] = 0xff;//¼ì2aμ½°′Ï£¬¾í¸3ÖμÎa0xfe£¬ò»·½ÃæË¿oá2»ó°ÏìμˉÆe¼ì2a£¬áíò»·½Ã棬¿éòÔÔú12msoóÅD¶ÏêÇ·ñá¬Dø°′¼ü¡£è«3ìμ¥′ΰ′¼ü¼ì2a15Ms°′Ï£¬Dèòa¼ì2aμˉÆeê±£¬ò»12Dèòa30ms
  45.         }//ÿ′μì2aμ½°′¼üóDD§°′ÏÂ,¾íÖØD¸3Öμ0xff£¬Ôù′μì2aμ½°′¼ü°′ÏÂí¬ÑùDèòa15ms¡£
  46.         else if ((keybuf[keyout][i] & 0x1F) == 0x1F)
  47.         {   //á¬Dø5′Îé¨ÃèÖμÎa1£¬¼′15msÄú£¨5*3ms£©¶¼Ö»¼ì2aμ½μˉÆe×′ì¬ê±£¬¿éèÏÎa°′¼üòÑμˉÆe
  48.             KeySta[keyout][i] = 1;
  49.         }
  50.     }
復(fù)制代碼
回復(fù)

使用道具 舉報(bào)

14#
ID:155950 發(fā)表于 2016-12-19 16:22 | 只看該作者
單片機(jī)最小系統(tǒng)電路解析
回復(fù)

使用道具 舉報(bào)

15#
ID:154562 發(fā)表于 2016-12-22 08:00 來自觸屏版 | 只看該作者
pxL 發(fā)表于 2016-3-10 23:53
**** 作者被禁止或刪除 內(nèi)容自動(dòng)屏蔽 ****

這是什么意思??
回復(fù)

使用道具 舉報(bào)

16#
ID:154562 發(fā)表于 2016-12-22 08:08 來自觸屏版 | 只看該作者
switch (keyout)     {         case 0:             KEY_OUT_4 = 1;             KEY_OUT_1 = 0;             break;         case 1:             KEY_OUT_1 = 1;             KEY_OUT_2 = 0;             break;         case 2:             KEY_OUT_2 = 1;             KEY_OUT_3 = 0;             break;         case 3:             KEY_OUT_3 = 1;             KEY_OUT_4 = 0;             break;         default:             break;     }這一段代碼的作用是什么,沒看的懂,求大神指點(diǎn)
回復(fù)

使用道具 舉報(bào)

17#
ID:154562 發(fā)表于 2017-1-11 23:33 來自觸屏版 | 只看該作者
addend = (addend*10) + (keycode-'0'); 誰能解釋下keycode為什么要減0
回復(fù)

使用道具 舉報(bào)

18#
ID:142563 發(fā)表于 2017-2-15 15:20 | 只看該作者
//將一行的4個(gè)按鍵值移入緩沖區(qū)

    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
這一段看不懂。。
這一段看不懂。。
這一段看不懂。。
求指教:
keybuf[keyout][0] << 1=0xFE,而KEY_IN_0=0(或1),兩者是如何實(shí)現(xiàn)按位或運(yùn)算的?
回復(fù)

使用道具 舉報(bào)

19#
ID:162190 發(fā)表于 2017-3-6 11:41 | 只看該作者
本帖最后由 tlone51hei 于 2017-3-6 14:29 編輯

抱歉,仔細(xì)看了看,終于看懂了
回復(fù)

使用道具 舉報(bào)

20#
ID:162190 發(fā)表于 2017-3-6 15:31 | 只看該作者
看來過反都逼傻 發(fā)表于 2017-1-11 23:33
addend = (addend*10) + (keycode-'0'); 誰能解釋下keycode為什么要減0

減“0”才是個(gè)位數(shù),不減的話keycode的對(duì)應(yīng)碼應(yīng)該要大的多,注意減的是字符0不是數(shù)字0
回復(fù)

使用道具 舉報(bào)

21#
ID:185897 發(fā)表于 2017-4-7 16:49 | 只看該作者
值得收藏
回復(fù)

使用道具 舉報(bào)

22#
ID:210006 發(fā)表于 2017-6-11 07:26 | 只看該作者
不錯(cuò)|!!
回復(fù)

使用道具 舉報(bào)

23#
ID:212352 發(fā)表于 2017-6-20 13:46 | 只看該作者
學(xué)習(xí)了  謝謝大佬
回復(fù)

使用道具 舉報(bào)

24#
ID:222685 發(fā)表于 2017-7-27 17:24 | 只看該作者
應(yīng)該多支持這樣的人才!請(qǐng)努力高產(chǎn),我們就受益了
回復(fù)

使用道具 舉報(bào)

25#
ID:137999 發(fā)表于 2017-7-27 19:22 | 只看該作者
樓主,這不是宋老師的程序嗎?
回復(fù)

使用道具 舉報(bào)

26#
ID:227864 發(fā)表于 2017-8-16 11:43 | 只看該作者
這個(gè)好,很詳細(xì)
回復(fù)

使用道具 舉報(bào)

27#
ID:258232 發(fā)表于 2017-12-6 16:25 | 只看該作者
馬克!
回復(fù)

使用道具 舉報(bào)

28#
ID:279640 發(fā)表于 2018-1-27 09:56 | 只看該作者
值得收藏,仔細(xì)研讀!
回復(fù)

使用道具 舉報(bào)

29#
ID:241700 發(fā)表于 2018-2-20 22:53 | 只看該作者
最后的計(jì)算器代碼我花了整整一天才讀懂,又花了半天時(shí)間才成功移植到我的開發(fā)板中。和樓主的差距太大!
回復(fù)

使用道具 舉報(bào)

30#
ID:287967 發(fā)表于 2018-3-5 22:20 | 只看該作者
很好,點(diǎn)贊。
回復(fù)

使用道具 舉報(bào)

31#
ID:281447 發(fā)表于 2018-3-6 09:51 | 只看該作者
把矩陣按鍵當(dāng)獨(dú)立按鍵時(shí),是不是這樣:KeyOUT3 = P2^0;KeyOUT2 = P2^1;KeyOUT1 = P2^2;KeyOUT0 = P2^3;
回復(fù)

使用道具 舉報(bào)

32#
ID:281447 發(fā)表于 2018-3-6 09:56 | 只看該作者
把矩陣按鍵當(dāng)獨(dú)立按鍵使用的時(shí)候聲明的IO口是不是少了, 如果是這樣的話我就能理解了: KeyOUT3 = P2^0; KeyOUT2 = P2^1; KeyOUT1 = P2^2; KeyOUT0 = P2^3; KeyIN1 = P2^4; KeyIN2 = P2^5; KeyIN3 = P2^6; KeyIN4 = P2^7; P2 = 0XF7;
回復(fù)

使用道具 舉報(bào)

33#
ID:320487 發(fā)表于 2018-5-5 07:06 | 只看該作者
小學(xué)水平 應(yīng)該達(dá)不到這個(gè)能力的  哈哈
回復(fù)

使用道具 舉報(bào)

34#
ID:328620 發(fā)表于 2018-6-21 23:07 | 只看該作者
非常有價(jià)值,單獨(dú)鍵盤知識(shí)匯總,對(duì)初學(xué)者有極大的幫助,謝謝老師。
回復(fù)

使用道具 舉報(bào)

35#
ID:93625 發(fā)表于 2018-6-29 10:49 | 只看該作者
這套教材真是通俗易懂!
回復(fù)

使用道具 舉報(bào)

36#
ID:374751 發(fā)表于 2018-7-19 12:43 | 只看該作者
太棒了 有幫助到我耶
回復(fù)

使用道具 舉報(bào)

37#
ID:402455 發(fā)表于 2018-9-25 23:19 | 只看該作者
非常有價(jià)值~好好學(xué)習(xí)下
回復(fù)

使用道具 舉報(bào)

38#
ID:418269 發(fā)表于 2018-12-1 18:42 | 只看該作者
zhuangx扛把子 發(fā)表于 2017-2-15 15:20
//將一行的4個(gè)按鍵值移入緩沖區(qū)

    keybuf[keyout][0] = (keybuf[keyout][0]

朋友,這段你現(xiàn)在看懂了嗎?我也有你的疑問。不懂老師的字節(jié)和位兩個(gè)不同的變量是如何進(jìn)行或運(yùn)算的。
回復(fù)

使用道具 舉報(bào)

39#
ID:9727 發(fā)表于 2019-1-23 10:40 | 只看該作者
這鍵盤應(yīng)用的真透徹
回復(fù)

使用道具 舉報(bào)

40#
ID:702974 發(fā)表于 2020-3-11 12:41 | 只看該作者
程序有點(diǎn)難度了
回復(fù)

使用道具 舉報(bào)

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

本版積分規(guī)則

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

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表