久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

標(biāo)題: 第十章 習(xí)題練習(xí)與積累 [打印本頁(yè)]

作者: admin    時(shí)間: 2013-9-28 01:49
標(biāo)題: 第十章 習(xí)題練習(xí)與積累

      本章內(nèi)容主要通過(guò)一些相關(guān)例程,來(lái)提高大家的編程技巧,并且?guī)椭蠹疫M(jìn)行一些算法上的積累。同學(xué)們?cè)谧鲞@部分內(nèi)容的時(shí)候,還是那句話,一定要能夠達(dá)到不看教程,獨(dú)立把程序做出來(lái)的效果,那樣才能基本上掌握相關(guān)知識(shí)點(diǎn)和內(nèi)容。
10.1 數(shù)字秒表實(shí)驗(yàn)10.1.1 不同數(shù)據(jù)間的類型轉(zhuǎn)換
C語(yǔ)言中,不同數(shù)據(jù)類型之間是可以混合運(yùn)算的。當(dāng)表達(dá)式中的數(shù)據(jù)類型不一致時(shí),首先轉(zhuǎn)換為同一種類型,然后再進(jìn)行計(jì)算。C語(yǔ)言有兩種方法實(shí)現(xiàn)類型轉(zhuǎn)換,一是自動(dòng)類型轉(zhuǎn)換,另外一種是強(qiáng)制類型轉(zhuǎn)換。這塊內(nèi)容是比較繁雜的,因此我們根據(jù)我們常用的編程應(yīng)用來(lái)講部分相關(guān)內(nèi)容。
當(dāng)不同數(shù)據(jù)類型之間混合運(yùn)算的時(shí)候,不同類型的數(shù)據(jù)首先會(huì)轉(zhuǎn)換為同一類型,轉(zhuǎn)換的主要原則是:短字節(jié)的數(shù)據(jù)向長(zhǎng)字節(jié)數(shù)據(jù)轉(zhuǎn)換。
比如:unsigned char  a ;  unsigned int b;  unsigned int c;  c = a *b;
在運(yùn)算的過(guò)程中,程序會(huì)自動(dòng)全部按照unsigned int型來(lái)計(jì)算。比如a=10b=200c的結(jié)果就是2000。那當(dāng)a=100b=700,那c70000嗎?新手最容易犯這種錯(cuò)誤,大家要注意每個(gè)變量的數(shù)據(jù)類型,c的數(shù)據(jù)類型是unsigned int型,取值范圍是0~6553570000超過(guò)65535溢出了,所以最終c的結(jié)果是(70000 - 65536) = 4464
那要想讓c正常獲得70000這個(gè)結(jié)果,需要把c定義成一個(gè)unsigned long型。我們?nèi)绻麑懗桑?/font>unsigned char  a=100;  unsigned int  b=700;  unsigned long  c=0;  c = a *b;如果有做過(guò)實(shí)驗(yàn)的同學(xué),會(huì)發(fā)現(xiàn)這個(gè)c的結(jié)果還是4464,這個(gè)是個(gè)什么情況呢?
大家注意,C語(yǔ)言不同類型運(yùn)算的時(shí)候數(shù)值會(huì)轉(zhuǎn)換同一類型運(yùn)算,但是每一步運(yùn)算都會(huì)進(jìn)行識(shí)別判斷,不會(huì)進(jìn)行一個(gè)總的分析判斷。比如我們這個(gè)程序,ab相乘的時(shí)候,是按照unsigned int類型運(yùn)算的,運(yùn)算的結(jié)果也是unsigned int類型的4464,只是最終把unsigned int類型4464賦值給了一個(gè)unsigned long型的變量而已。我們?cè)谶\(yùn)算的時(shí)候如何避免這類問(wèn)題的產(chǎn)生呢?可以采用強(qiáng)制類型轉(zhuǎn)換的方法。
在一個(gè)變量前邊加上一個(gè)變量類型,并且這個(gè)變量類型用小括號(hào)括起來(lái),表示把這個(gè)變量強(qiáng)制轉(zhuǎn)換成括號(hào)里的變量類型。如 c = (unsigned long)a * b;由于強(qiáng)制類型轉(zhuǎn)換運(yùn)算優(yōu)先級(jí)高于*,所以這個(gè)地方的運(yùn)算是先把a轉(zhuǎn)換成一個(gè)unsigned long型的變量,而后與b相乘,根據(jù)C語(yǔ)言的規(guī)則b會(huì)自動(dòng)轉(zhuǎn)換成一個(gè)unsigned long型的變量,而后運(yùn)算完畢結(jié)果也是一個(gè)unsigned long型的,最終賦值給了c
當(dāng)不同類型變量相互賦值時(shí),短字節(jié)的數(shù)據(jù)向長(zhǎng)字節(jié)的變量賦值時(shí),值不變,比如unsigned char  a=100;  unsigned int  b=700;  b = a;那么最終b的值就是100了。但是如果我們的程序是unsigned char  a=100;  unsigned int  b=700;  a=b;那么a的值僅僅是取了b的低8位,我們首先要把700變成一個(gè)16位的二進(jìn)制數(shù)據(jù),然后取它的低8位出來(lái),也就是188,這就是長(zhǎng)字節(jié)給短字節(jié)賦值的結(jié)果。
51單片機(jī)里邊,有一種特殊情況,就是bit類型的變量,這個(gè)bit類型的強(qiáng)制類型轉(zhuǎn)換,是不符合上邊講的這個(gè)原則的,比如bit a = 0;  unsigned char b; a = (bit)b;這個(gè)地方要特別注意,使用bit做強(qiáng)制類型轉(zhuǎn)換,不是取b的最低位,而是他會(huì)判斷b這個(gè)變量是0還是非0的值,如果b0,那么a的結(jié)果就是0,如果b是任意非0的其他數(shù)字,那么a的結(jié)果都是1
1.1.2 定時(shí)時(shí)間精準(zhǔn)性調(diào)整
我們的6.5.2章節(jié)有一個(gè)數(shù)碼管秒表顯示程序,那個(gè)程序是1秒數(shù)碼管加1,但是細(xì)心的同學(xué)做了實(shí)驗(yàn)后,經(jīng)過(guò)長(zhǎng)時(shí)間運(yùn)行會(huì)發(fā)現(xiàn),和我們的實(shí)際的時(shí)間有了較大誤差了,那如何去調(diào)整這種誤差呢?要解決問(wèn)題,先找到問(wèn)題是什么原因造成的。
先補(bǔ)充介紹一下我們我們前邊講的中斷的內(nèi)容。其實(shí)單片機(jī)做的很智能的,當(dāng)我們?cè)诳措娨暤臅r(shí)候,突然發(fā)生了水開的中斷,我們必須去提水的時(shí)候,第一,我們從電視跟前跑到廚房需要一定的時(shí)間,第二,因?yàn)槲覀兛吹碾娨暿侵悄軘?shù)字電視,因此在去提水之前我們可以使用遙控器將我們的電視進(jìn)行暫停操作,方便回來(lái)后繼續(xù)從剛才的劇情往下進(jìn)行。那暫停電視,跑到廚房提水,這一點(diǎn)點(diǎn)時(shí)間是很短的,在實(shí)際生活中可以忽略不計(jì),但是在單片機(jī)秒表系統(tǒng),誤差會(huì)累計(jì)的,每1秒鐘都差了幾個(gè)微妙,時(shí)間一久,造成的累計(jì)誤差就不可小覷了。
單片機(jī)系統(tǒng)里,硬件進(jìn)入中斷需要一定的時(shí)間,大概是幾個(gè)機(jī)器周期,還有要進(jìn)行原始數(shù)據(jù)保護(hù),就是把進(jìn)中斷之前程序運(yùn)行的一些變量先保存起來(lái),這個(gè)專業(yè)詞匯叫做中斷壓棧,進(jìn)入中斷后,重新給定時(shí)器THTL賦值,也需要幾個(gè)機(jī)器周期,這樣下來(lái)就會(huì)消耗一定的時(shí)間,我們得把這些時(shí)間補(bǔ)償回來(lái)。
方法一,使用軟件debug進(jìn)行補(bǔ)償。
我們前邊教程講過(guò)使用debug來(lái)觀察程序運(yùn)行時(shí)間,那我們可以把我們2次進(jìn)入中斷的時(shí)間間隔觀察出來(lái),看看和我們實(shí)際定時(shí)的時(shí)間相差了幾個(gè)機(jī)器周期,然后在進(jìn)行定時(shí)器初值賦值的時(shí)候,進(jìn)行一個(gè)調(diào)整。我們用的是11.0592M的晶振,發(fā)現(xiàn)差了幾個(gè)機(jī)器周期,就把定時(shí)器初值加上幾個(gè)機(jī)器周期,這樣相當(dāng)于進(jìn)行了一個(gè)補(bǔ)償。
方法二,使用累計(jì)誤差計(jì)算出來(lái)。
有的時(shí)候,除了程序本身存在的誤差外,硬件精度也可能會(huì)影響到時(shí)鐘的精度,比如晶振,會(huì)隨著溫度變化出現(xiàn)溫漂現(xiàn)象,就是精度和標(biāo)稱值要差一點(diǎn)。那么我們還可以采取累計(jì)誤差的方法來(lái)提高精度。比如我們可以讓時(shí)鐘運(yùn)行半個(gè)小時(shí)或者一個(gè)小時(shí),看看最終時(shí)間差了幾秒,然后算算一共進(jìn)了多少次定時(shí)器中斷,然后把這差的幾秒平均分配到每次的定時(shí)器中斷中,就可以實(shí)現(xiàn)時(shí)鐘的調(diào)整。
大家要明白,這個(gè)世界上本就沒有絕對(duì)的精度,我們只能提高精度,但是不可能消除誤差的,如果在這個(gè)基礎(chǔ)上還感覺精度不夠的話,不要著急,后邊我們會(huì)專門講時(shí)鐘芯片的,通常時(shí)鐘芯片計(jì)時(shí)的精度比單片機(jī)的精度要高一些。
10.1.3 使用字節(jié)操作修改位的技巧
這里介紹個(gè)編程小技巧,在我們編程序的時(shí)候,有的情況下,想要操作一個(gè)字節(jié)中的某一位或者幾位的時(shí)候,但是又不想改變其他位原有的值,該如何操作呢?
比如我們學(xué)定時(shí)器的時(shí)候遇到一個(gè)寄存器TCON,這個(gè)寄存器是可以進(jìn)行位操作的,比如我們可以直接寫TR0 =1;TR0TCON的一個(gè)位,因?yàn)檫@個(gè)寄存器是允許位操作,這樣寫是沒有任何問(wèn)題的。還有一個(gè)寄存器TMOD,這個(gè)寄存器是不支持位操作的,那如果我們要使用T0的模式1,我們希望達(dá)到的效果是TMOD的低4位是0001,如果我們直接寫成TMOD = 0x01的話,實(shí)際上已經(jīng)同時(shí)操作到了高4位,即屬于T1的部分,設(shè)置成了0000,如果T1定時(shí)器沒有用到的話,那我們隨便怎么樣都行,但是如果程序中既用到了T0,又用到了T1,那我們?cè)O(shè)置T0的同時(shí)已經(jīng)干擾到了T1的模式配置,這我們不希望看到的結(jié)果。
在這種情況下,就可以用我們前邊學(xué)過(guò)的"&""|"運(yùn)算了。對(duì)于二進(jìn)制位操作來(lái)說(shuō),不管該位原來(lái)的值是0還是1,它跟"0"進(jìn)行"&&"運(yùn)算,得到的結(jié)果都是0,而跟"1"進(jìn)行"&&"運(yùn)算,將保持原來(lái)的值不變;不管該位原來(lái)的值是0還是1,它跟"1"進(jìn)行"||"運(yùn)算,得到的結(jié)果都是1,而跟"0"進(jìn)行"||"運(yùn)算,將保持原來(lái)的值不變。
利用上述這個(gè)規(guī)律,我們就可以著手解決剛才的問(wèn)題了。如果我們現(xiàn)在要設(shè)置TMOD的定時(shí)器0工作在模式1下,又不干擾定時(shí)器1的配置,我們可以進(jìn)行這樣的操作:TMOD = TMOD & 0xF0; TMOD = TMOD | 0x01;第一步與0xF0后,TMOD的高4位不變,低4位清零,變成了xxxx0000;然后再進(jìn)行第二步和0x01進(jìn)行或運(yùn)算,那高7位均不變,最低位變成1了,這樣就完成了只將低4位的值修改位0001,而高4位保持原不變的任務(wù),即只設(shè)置了T0而不影響T1。熟練掌握并靈活運(yùn)用這個(gè)方法,會(huì)給你以后的編程帶來(lái)便利。
另外,在C語(yǔ)言中,a &= b;等價(jià)于a = a&b;同理,a |= b;等價(jià)于a = a|b;那么前邊那一段代碼就可以寫成TMOD &= 0xF0;TMOD |= 0x01這樣的簡(jiǎn)寫形式。這種寫法可以一定程度上簡(jiǎn)化代碼,是C語(yǔ)言的一種編程風(fēng)格。
10.1.4 數(shù)碼管刷新函數(shù)算法改進(jìn)
在學(xué)習(xí)數(shù)碼管動(dòng)態(tài)刷新的時(shí)候,為了方便大家理解,我們程序?qū)懙募?xì)致一些,給大家引入了switch的用法,隨著我們編程能力的增強(qiáng),對(duì)于74HC138這種非常有規(guī)律的數(shù)字器件,我們?cè)诰幊躺弦部梢愿倪M(jìn)一下邏輯算法,讓程序變的更簡(jiǎn)潔。這種邏輯算法,通常不是靠學(xué)一下可以全部掌握的,而是通過(guò)不斷的編寫程序以及研究別人的程序一點(diǎn)點(diǎn)積累起來(lái)的,從今天開始,大家就要開始積累。
前邊動(dòng)態(tài)刷新函數(shù)我們是這么寫的:
        switch(j)
            {
                        case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; break;
                        case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; break;
                        case 2:         ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; break;
                        case 3:         ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; break;       
                        case 4:         ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; break;
                        case 5:         ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; break;
        default: break;
            }
首先我們進(jìn)行第一步改進(jìn),寫成:
        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;
        default: break;
            }                                                            
P0=LedChar[LedNumber[j++]];  
if(6==j) j=0;
這種寫法已經(jīng)比上邊那種寫法簡(jiǎn)單多了,我們還要繼續(xù)簡(jiǎn)化。我們來(lái)看,ADDR0P1的第0位,ADDR1P1的第1位,ADDR2P1的第2位,我們可以看出來(lái),程序中的case 0case 5的過(guò)程中,P1的這低3位的值分別是000001010011100101。轉(zhuǎn)換成十進(jìn)制,也就是從05。那我們程序就可以進(jìn)一步改進(jìn),寫成以下函數(shù)形式:
void LedScan()  //LED顯示掃描函數(shù)
{
    static unsigned char index = 0;
   
    P0 = 0xFF;                 //關(guān)閉所有段選位,顯示消隱
    P1 = (P1 & 0xF8) | index;  //位選索引值賦值到P1口低3
    P0 = LedNumber[index];       //相應(yīng)顯示緩沖區(qū)的值賦值到P0
    if (index < 5)             //位選索引0-5循環(huán),因有6個(gè)數(shù)碼管
        index++;
    else
        index = 0;
}
大家看看,P1 = (P1 & 0xF8) | index;這行代碼就利用了上面講到的"&""|"運(yùn)算來(lái)將index的低3位直接賦值到P1口的低3位上,這樣寫是不是要簡(jiǎn)潔的多,也巧妙的多,同樣可以完美實(shí)現(xiàn)動(dòng)態(tài)刷新的功能。
10.1.5 秒表程序
做了一個(gè)秒表程序給同學(xué)們做參考,程序中涉及到的知識(shí)點(diǎn)我們幾乎都講過(guò)了,涉及到了定時(shí)器、數(shù)碼管、中斷、按鍵等多個(gè)知識(shí)點(diǎn)。此程序是多知識(shí)點(diǎn)同時(shí)應(yīng)用到一個(gè)程序中的小綜合,因此需要大家完全消化掉。這種小綜合也是將來(lái)做大項(xiàng)目程序的一個(gè)基礎(chǔ),因此還是老規(guī)矩,大家邊抄邊理解,理解透徹后獨(dú)立寫出來(lái)就算此關(guān)通過(guò)。
#include <reg52.h>

sbit  KEY1 = P2^4;

sbit  KEY2 = P2^5;

sbit  KEY3 = P2^6;

sbit  KEY4 = P2^7;

sbit  ADDR3 = P1^3;

sbit  ENLED = P1^4;

unsigned char code LedChar[] = {  //數(shù)碼管顯示字符轉(zhuǎn)換表

    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

unsigned char LedBuff[6] = {  //數(shù)碼管顯示緩沖區(qū)

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

};

unsigned char KeySta[4] = {  //按鍵狀態(tài)緩沖區(qū)

    1, 1, 1, 1

};

bit StopwatchRunning = 0;  //秒表運(yùn)行標(biāo)志

bit StopwatchRefresh = 1;  //秒表計(jì)數(shù)刷新標(biāo)志

unsigned char DecimalPart = 0;  //秒表的小數(shù)部分

unsigned int  IntegerPart = 0;  //秒表的整數(shù)部分

unsigned char T0RH = 0;  //T0重載值的高字節(jié)

unsigned char T0RL = 0;  //T0重載值的低字節(jié)

void ConfigTimer0(unsigned int ms);

void StopwatchDisplay();

void KeyAction();

void main ()

{

    P2 = 0xFE;  //選擇第4行按鍵以進(jìn)行掃描

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

    ADDR3 = 1;  //選擇數(shù)碼管

    ENLED = 0;  //LED總使能

    EA = 1;     //開總中斷

        ConfigTimer0(2);  //配置T0定時(shí)2ms

   

    while(1)

    {

        KeyAction();

        StopwatchDisplay();

    }

}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)

{

    unsigned long tmp;

   

    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率

    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值

    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值

    tmp = tmp + 19;           //修正中斷響應(yīng)延時(shí)造成的誤差,運(yùn)行30分鐘修正值

   

    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)

    T0RL = (unsigned char)tmp;

    TMOD &= 0xF0;   //清零T0的控制位

    TMOD |= 0x01;   //配置T0為模式1

    TH0 = T0RH;     //加載T0重載值

    TL0 = T0RL;

    ET0 = 1;        //使能T0中斷

    TR0 = 1;        //啟動(dòng)T0

}

void StopwatchDisplay()  //秒表計(jì)數(shù)顯示函數(shù)

{

        unsigned char i;

    unsigned char buff[6];

   

    if (StopwatchRefresh)

    {

        StopwatchRefresh = 0;

        

        i = DecimalPart % 10;   //小數(shù)部分轉(zhuǎn)換到低2位

        buff[0] = LedChar[ I];

        i = DecimalPart / 10;

        buff[1] = LedChar[ I];

        

        buff[2] = IntegerPart % 10;         //整數(shù)部分轉(zhuǎn)換到高4位

        buff[3] = (IntegerPart / 10) % 10;

        buff[4] = (IntegerPart / 100) % 10;

        buff[5] = (IntegerPart / 1000) % 10;

        

        for (i=5; i>=3; i--) //高位的0轉(zhuǎn)換為空字符

        {

            if (buff[ I] == 0)

                buff[ I] = 0xFF;

            else

                break;

        }

        for ( ; i>=2; i--) //有效數(shù)字位轉(zhuǎn)換顯示字符

        {

            buff[ I] = LedChar[buff[ I]];

        }

        buff[2] &= 0x7F;  //點(diǎn)亮小數(shù)點(diǎn)

        

        for (i=0; i<=5; i++) //一次性拷貝到顯示緩沖區(qū),消除可能存在的顯示抖動(dòng)

        {

            LedBuff[ I] = buff[ I];

        }

    }

}

void StopwatchAction()  //秒表啟停函數(shù)

{

    if (StopwatchRunning)    //已啟動(dòng)則停止

        StopwatchRunning = 0;

    else                     //未啟動(dòng)則啟動(dòng)

        StopwatchRunning = 1;

}

void StopwatchReset()  //秒表復(fù)位函數(shù)

{

    StopwatchRunning = 0;  //停止秒表

    DecimalPart = 0;       //清零計(jì)數(shù)值

    IntegerPart = 0;

    StopwatchRefresh = 1;  //置刷新標(biāo)志

}

void KeyAction()  //按鍵動(dòng)作函數(shù)

{

    unsigned char i;

    static unsigned char backup[4] = {1,1,1,1};

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

    {

        if (backup[ I] != KeySta[ I])

        {

            if (backup[ I] != 0)  //按鍵按下時(shí)執(zhí)行動(dòng)作

            {

                switch (i)

                {

                    case 1: StopwatchReset(); break;   //Esc鍵復(fù)位秒表

                    case 2: StopwatchAction(); break;  //回車鍵啟停秒表

                    default: break;

                }

            }

            backup[ I] = KeySta[ I];

        }

    }

}

void LedScan()  //LED顯示掃描函數(shù)

{

    static unsigned char index = 0;

   

    P0 = 0xFF;                 //關(guān)閉所有段選位,顯示消隱

    P1 = (P1 & 0xF8) | index;  //位選索引值賦值到P1口低3位

    P0 = LedBuff[index];       //相應(yīng)顯示緩沖區(qū)的值賦值到P0口

    if (index < 5)             //位選索引0-5循環(huán),因有6個(gè)數(shù)碼管

        index++;

    else

        index = 0;

}

void KeyScan()  //按鍵掃描函數(shù)

{

    unsigned char i;

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

        0xFF, 0xFF, 0xFF, 0xFF

    };

   

    //按鍵值移入緩沖區(qū)

    keybuf[0] = (keybuf[0] << 1) | KEY1;

    keybuf[1] = (keybuf[1] << 1) | KEY2;

    keybuf[2] = (keybuf[2] << 1) | KEY3;

    keybuf[3] = (keybuf[3] << 1) | KEY4;

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

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

    {

        if (keybuf[ I] == 0x00)

        {

            KeySta[ I] = 0;

        }

        else if (keybuf[ I] == 0xFF)

        {

            KeySta[ I] = 1;

        }

    }

}

void StopwatchCount()  //秒表計(jì)數(shù)函數(shù)

{

    if (StopwatchRunning)

    {

        DecimalPart++;          //小數(shù)部分+1

        if (DecimalPart >= 100) //小數(shù)部分計(jì)到100時(shí)進(jìn)位到整數(shù)部分

        {

            DecimalPart = 0;

            IntegerPart++;

            if (IntegerPart >= 10000)  //整數(shù)部分計(jì)到10000時(shí)歸零

            {

                IntegerPart = 0;

            }

        }

        StopwatchRefresh = 1;

    }

}

void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)

{

    static unsigned char tmr10ms = 0;

    TH0 = T0RH;  //將重載值賦值到計(jì)數(shù)器

    TL0 = T0RL;

    KeyScan(); //按鍵掃描

    LedScan(); //數(shù)碼管掃描顯示

    //定時(shí)10ms進(jìn)行一次秒表計(jì)數(shù)

    tmr10ms++;

    if (tmr10ms >= 5)

    {

        tmr10ms = 0;

        StopwatchCount();

    }

}10.2 PWM的學(xué)習(xí)
PWM在我們今后的單片機(jī)應(yīng)用中非常非常多,應(yīng)用的方向也很多,它的原理很簡(jiǎn)單,但是往往應(yīng)用于不同場(chǎng)合上意義不完全一樣,這里我先把基本概念和基本原理給大家介紹一下,后邊遇到用的時(shí)候起碼知道是個(gè)什么東西。
PWMPulse Width Modulation的縮寫,它的中文名字是脈沖寬度調(diào)制,一種說(shuō)法是它利用微處理器的數(shù)字輸出來(lái)對(duì)模擬電路進(jìn)行控制的一種有效的技術(shù),其實(shí)就是使用數(shù)字信號(hào)達(dá)到一個(gè)模擬信號(hào)的效果。這是個(gè)什么概念呢?我們一步步來(lái)介紹。
首先從它的名字來(lái)看,脈沖寬度調(diào)制,就是改變脈沖寬度來(lái)實(shí)現(xiàn)不同的效果。我們先來(lái)看三組不同的脈沖信號(hào),如圖10-1所示。
10-1 PWM波形
這是一個(gè)周期是10ms,即頻率是100Hz的波形,但是每個(gè)周期內(nèi),高低電平脈沖各不相同,這就是PWM的本質(zhì)。在這里大家要記住一個(gè)概念,叫做“占空比”。占空比是指高電平的時(shí)間占整個(gè)周期的比例。比如第一部分波形的占空比是40%,第二部分波形占空比是60%,第三部分波形占空比是80%,這就是PWM的解釋。
那為何它會(huì)對(duì)模擬電路進(jìn)行控制呢?大家想一想,我們數(shù)字電路里,只有01兩種狀態(tài),比如我們教程第二課學(xué)會(huì)的點(diǎn)亮LED小燈那個(gè)程序,當(dāng)我們寫一個(gè)LED = 0;小燈就會(huì)長(zhǎng)亮,當(dāng)我們寫一個(gè)LED = 1;小燈就會(huì)滅掉。當(dāng)我們讓小燈亮和滅間隔運(yùn)行的時(shí)候,小燈是閃爍。如果我們把這個(gè)間隔不斷的減小,減小到我們的肉眼分辨不出來(lái),也就是100Hz以上的頻率,這個(gè)時(shí)候小燈表現(xiàn)出來(lái)的現(xiàn)象就是既保持亮的狀態(tài),但是亮度沒有LED = 0;的時(shí)候亮度高。那我們不斷改變時(shí)間參數(shù),讓LED = 0;的時(shí)間大于或者小于LED = 1;的時(shí)間,會(huì)發(fā)現(xiàn)亮度都不一樣,這就是模擬電路的感覺了,不再是純粹的01,還有亮度不斷變化。大家會(huì)發(fā)現(xiàn),如果我們是100Hz的信號(hào),如圖10-1所示,假如高電平熄滅小燈,低電平點(diǎn)亮小燈的話,第一部分波形熄滅4ms,點(diǎn)亮6ms,亮度最高,第二部分熄滅6ms,點(diǎn)亮4ms,亮度次之,第三部分熄滅8ms,點(diǎn)亮2ms,亮度最低。我們用程序驗(yàn)證一下。
#include <reg52.h>
sbit  PWMOUT = P0^0;
sbit  ADDR0 = P1^0;
sbit  ADDR1 = P1^1;
sbit  ADDR2 = P1^2;
sbit  ADDR3 = P1^3;
sbit  ENLED = P1^4;
unsigned char HReloadH = 0;  //高電平重載值的高字節(jié)
unsigned char HReloadL = 0;  //高電平重載值的低字節(jié)
unsigned char LReloadH = 0;  //低電平重載值的高字節(jié)
unsigned char LReloadL = 0;  //低電平重載值的低字節(jié)
void ConfigPWM(unsigned int fr, unsigned char dc);
void ClosePWM();
void main ()
{
    unsigned int i;
    P0 = 0xFF;  //P0口初始化
    ADDR0 = 0;  //選擇獨(dú)立LED
    ADDR1 = 1;
    ADDR2 = 1;
    ADDR3 = 1;
    ENLED = 0;  //LED總使能
    EA = 1;     //開總中斷
   
    while(1)
    {
        ConfigPWM(100, 10);  //頻率100Hz,占空比10%
        for (i=0; i<40000; i++);
        ClosePWM();
        ConfigPWM(100, 40);  //頻率100Hz,占空比40%
        for (i=0; i<40000; i++);
        ClosePWM();
        ConfigPWM(100, 90);  //頻率100Hz,占空比90%
        for (i=0; i<40000; i++);
        ClosePWM();
        for (i=0; i<40000; i++);
    }
}
void ConfigPWM(unsigned int fr, unsigned char dc)  //PWM配置函數(shù),fr-頻率,dc-占空比
{
    unsigned int  high, low;
    unsigned long tmp;
   
    tmp  = (11059200 / 12) / fr;  //計(jì)算一個(gè)周期所需的計(jì)數(shù)值
    high = (tmp * dc) / 100;      //計(jì)算高電平所需的計(jì)數(shù)值
    low  = tmp - high;            //計(jì)算低電平所需的計(jì)數(shù)值
    high = 65536 - high + 13;     //計(jì)算高電平的定時(shí)器重載值并修正
    low  = 65536 - low  + 13;     //計(jì)算低電平的定時(shí)器重載值并修正
   
    HReloadH = (unsigned char)(high >> 8);  //高電平重載值拆分為高低字節(jié)
    HReloadL = (unsigned char)high;
    LReloadH = (unsigned char)(low >> 8);   //低電平重載值拆分為高低字節(jié)
    LReloadL = (unsigned char)low;
   
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = HReloadH; //加載T0重載值
    TL0 = HReloadL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
    PWMOUT = 1;     //輸出高電平
}
void ClosePWM()  //關(guān)閉PWM
{
    TR0 = 0;     //停止定時(shí)器
    ET0 = 0;
    PWMOUT = 1;  //輸出高電平
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    if (PWMOUT == 1)  //當(dāng)前輸出為高電平時(shí),裝載低電平值并輸出低電平
    {
        TH0 = LReloadH;
        TL0 = LReloadL;
        PWMOUT = 0;
    }
    else              //當(dāng)前輸出為低電平時(shí),裝載高電平值并輸出高電平
    {
        TH0 = HReloadH;
        TL0 = HReloadL;
        PWMOUT = 1;
    }
}
大家下載了這個(gè)程序,會(huì)發(fā)現(xiàn)小燈從最亮到滅一共4個(gè)亮度等級(jí)。如果我們讓亮度等級(jí)更多,并且讓亮度等級(jí)連續(xù)起來(lái),會(huì)產(chǎn)生一個(gè)小燈漸變的效果,和人呼吸有點(diǎn)類似,所以我們習(xí)慣上稱之為呼吸燈,程序代碼如下,這個(gè)程序用了2個(gè)定時(shí)器2個(gè)中斷,這是我們第一次這樣用,大家可以學(xué)習(xí)一下。我們來(lái)試試這個(gè)程序,試完了大家一定要自己關(guān)閉教程把程序?qū)懗鰜?lái),切記。
#include <reg52.h>
sbit  PWMOUT = P0^0;
sbit  ADDR0 = P1^0;
sbit  ADDR1 = P1^1;
sbit  ADDR2 = P1^2;
sbit  ADDR3 = P1^3;
sbit  ENLED = P1^4;
unsigned long PeriodCnt = 0; //PWM周期計(jì)數(shù)值
unsigned char HReloadH = 0;  //高電平重載值的高字節(jié)
unsigned char HReloadL = 0;  //高電平重載值的低字節(jié)
unsigned char LReloadH = 0;  //低電平重載值的高字節(jié)
unsigned char LReloadL = 0;  //低電平重載值的低字節(jié)
unsigned char T1RH = 0;   //T1重載值的高字節(jié)
unsigned char T1RL = 0;   //T1重載值的低字節(jié)
void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);
void main ()
{
    P0 = 0xFF;  //P0口初始化
    ADDR0 = 0;  //選擇獨(dú)立LED
    ADDR1 = 1;
    ADDR2 = 1;
    ADDR3 = 1;
    ENLED = 0;  //LED總使能
    EA = 1;     //開總中斷
   
    ConfigPWM(100, 10); //配置并啟動(dòng)PWM
    ConfigTimer1(50);   //T1定時(shí)調(diào)整占空比
   
    while(1);
}
void ConfigTimer1(unsigned int ms)  //T1配置函數(shù)
{
    unsigned long tmp;
   
    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 11;           //修正中斷響應(yīng)延時(shí)造成的誤差
   
    T1RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T1RL = (unsigned char)tmp;
    TMOD &= 0x0F;   //清零T1的控制位
    TMOD |= 0x10;   //配置T1為模式1
    TH1 = T1RH;     //加載T1重載值
    TL1 = T1RL;
    ET1 = 1;        //使能T1中斷
    TR1 = 1;        //啟動(dòng)T1
}
void ConfigPWM(unsigned int fr, unsigned char dc)  //PWM配置函數(shù),fr-頻率,dc-占空比
{
    unsigned int high, low;
   
    PeriodCnt = (11059200 / 12) / fr;  //計(jì)算一個(gè)周期所需的計(jì)數(shù)值
    high = (PeriodCnt * dc) / 100;     //計(jì)算高電平所需的計(jì)數(shù)值
    low  = PeriodCnt - high;           //計(jì)算低電平所需的計(jì)數(shù)值
    high = 65536 - high + 13;          //計(jì)算高電平的定時(shí)器重載值并修正
    low  = 65536 - low  + 13;          //計(jì)算低電平的定時(shí)器重載值并修正
   
    HReloadH = (unsigned char)(high >> 8);  //高電平重載值拆分為高低字節(jié)
    HReloadL = (unsigned char)high;
    LReloadH = (unsigned char)(low >> 8);   //低電平重載值拆分為高低字節(jié)
    LReloadL = (unsigned char)low;
   
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = HReloadH; //加載T0重載值
    TL0 = HReloadL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
    PWMOUT = 1;     //輸出高電平
}
void AdjustDutyCycle(unsigned char dc)  //占空比調(diào)整函數(shù),頻率不變只調(diào)整占空比
{
    unsigned int  high, low;
   
    high = (PeriodCnt * dc) / 100;     //計(jì)算高電平所需的計(jì)數(shù)值
    low  = PeriodCnt - high;           //計(jì)算低電平所需的計(jì)數(shù)值
    high = 65536 - high + 13;          //計(jì)算高電平的定時(shí)器重載值并修正
    low  = 65536 - low  + 13;          //計(jì)算低電平的定時(shí)器重載值并修正
    HReloadH = (unsigned char)(high >> 8);  //高電平重載值拆分為高低字節(jié)
    HReloadL = (unsigned char)high;
    LReloadH = (unsigned char)(low >> 8);   //低電平重載值拆分為高低字節(jié)
    LReloadL = (unsigned char)low;
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù),產(chǎn)生PWM
{
    if (PWMOUT == 1)  //當(dāng)前輸出為高電平時(shí),裝載低電平值并輸出低電平
    {
        TH0 = LReloadH;
        TL0 = LReloadL;
        PWMOUT = 0;
    }
    else              //當(dāng)前輸出為低電平時(shí),裝載高電平值并輸出高電平
    {
        TH0 = HReloadH;
        TL0 = HReloadL;
        PWMOUT = 1;
    }
}
void InterruptTimer1() interrupt 3  //T1中斷服務(wù)函數(shù),定時(shí)動(dòng)態(tài)調(diào)整占空比
{
    static bit br = 0;
    static unsigned char index = 0;
    unsigned char code table[13] = {  //占空比調(diào)整表
        5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95
    };
    TH1 = T1RH;  //重新加載T1重載值
    TL1 = T1RL;
   
    AdjustDutyCycle(table[index]); //調(diào)整PWM的占空比
    if (br == 0)  //逐步增大占空比
    {
        index++;
        if (index >= 12)
        {
            br = 1;
        }
    }
    else          //逐步減小占空比
    {
        index--;
        if (index == 0)
        {
            br = 0;
        }
    }
}
    呼吸燈寫出來(lái)后,其他各種效果的燈光閃爍都應(yīng)該可以做出來(lái),大家看到的KTV里邊那絢麗的燈光閃爍,其實(shí)就是采用的PWM技術(shù)控制的。
10.3 交通燈實(shí)驗(yàn)
同學(xué)們?cè)趯W(xué)習(xí)技術(shù)的時(shí)候,一定要多動(dòng)腦筋,遇到問(wèn)題后,三思而后問(wèn)。有些時(shí)候你考慮的和真理就差一點(diǎn)點(diǎn)了,沒有堅(jiān)持下去,別人告訴你后你才恍然大悟。這樣得到的結(jié)論,可以讓你學(xué)到知識(shí),但是卻培養(yǎng)不了你的邏輯思維能力。不是不能問(wèn),而是要在認(rèn)真思考的基礎(chǔ)上提問(wèn)。
有同學(xué)有疑問(wèn),板子上只有8個(gè)流水燈,那如果我要做很多個(gè)流水燈一起花樣顯示怎么辦呢?那我們?cè)谥v課的時(shí)候其實(shí)都提到過(guò)了,板子上是有8個(gè)流水燈,還有6個(gè)數(shù)碼管,還有1個(gè)點(diǎn)陣LED,一個(gè)數(shù)碼管相當(dāng)于8個(gè)小燈,一個(gè)點(diǎn)陣LED相當(dāng)于64個(gè)小燈,那如果全部算上的話,我們板子上實(shí)際共接了8+6*8+64=120個(gè)小燈,你如果單獨(dú)只接小燈,花樣燈就做出來(lái)了。
還有同學(xué)問(wèn),板子上流水燈和數(shù)碼管可以一起工作嗎?如何一起工作呢?我們剛說(shuō)了,一個(gè)數(shù)碼管是8個(gè)小燈,但是大家反過(guò)來(lái)想一想,8個(gè)流水燈是不是相當(dāng)于一個(gè)數(shù)碼管嗎。那板子上6個(gè)數(shù)碼管我們可以讓他們同時(shí)亮,7個(gè)數(shù)碼管就不會(huì)了嗎?當(dāng)然了,思考的習(xí)慣是要慢慢培養(yǎng)的,想不到的同學(xué)繼續(xù)努力,每天前進(jìn)一小步,堅(jiān)持一段時(shí)間后回頭看看,就會(huì)發(fā)現(xiàn)你學(xué)會(huì)了很多。
發(fā)一個(gè)交通燈的程序給大家做學(xué)習(xí)參考。因?yàn)榘遄淤Y源有限,所以我把左邊LED8LED9一起亮作為綠燈,把LED5LED6一起亮作為黃燈,把LED2LED3一起亮作為紅燈,用數(shù)碼管做倒計(jì)時(shí),做了一個(gè)簡(jiǎn)易的交通燈程序給大家做參考學(xué)習(xí),讓LED和數(shù)碼管同時(shí)參與工作。
#include <reg52.h>
sbit  KEY1 = P2^4;
sbit  KEY2 = P2^5;
sbit  KEY3 = P2^6;
sbit  KEY4 = P2^7;
sbit  ADDR3 = P1^3;
sbit  ENLED = P1^4;
unsigned char code LedChar[] = {  //數(shù)碼管顯示字符轉(zhuǎn)換表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = {  //數(shù)碼管+獨(dú)立LED顯示緩沖區(qū)
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
bit flag1s = 1;          //1秒定時(shí)標(biāo)志
unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)
void ConfigTimer0(unsigned int ms);
void TrafficLight();
void main ()
{
    P2 = 0xFE;  //選擇第4行按鍵以進(jìn)行掃描
    P0 = 0xFF;  //P0口初始化
    ADDR3 = 1;  //選擇數(shù)碼管
    ENLED = 0;  //LED總使能
    EA = 1;     //開總中斷
            ConfigTimer0(1);  //配置T0定時(shí)1ms
   
    while(1)
    {
        if (flag1s)
        {   //每秒執(zhí)行一次
            flag1s = 0;
            TrafficLight();
        }
    }
}
void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)
{
    unsigned long tmp;
   
    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 17;           //修正中斷響應(yīng)延時(shí)造成的誤差
   
    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}
void TrafficLight()
{
    static unsigned char color = 2;  //交通燈顏色索引,0-綠色,1-黃色,2-紅色
    static unsigned char timer = 0;  //交通燈倒計(jì)時(shí)定時(shí)器
   
    if (timer == 0) //倒計(jì)時(shí)到0時(shí),切換交通燈
    {
        switch (color)
        {   //左端兩個(gè)LED代表綠燈,中間兩個(gè)LED代表黃燈,右端兩個(gè)LED代表紅燈,
            case 0: color=1; LedBuff[6]=0xE7; timer=2;  break;  //切換到黃色,亮3
            case 1: color=2; LedBuff[6]=0xFC; timer=29; break;  //切換到紅色,亮30
            case 2: color=0; LedBuff[6]=0x3F; timer=39; break;  //切換到綠色,亮40
            default: break;
        }
    }
    else //倒計(jì)時(shí)未到0時(shí),遞減其計(jì)數(shù)值
    {
        timer--;
    }
    LedBuff[0] = LedChar[timer%10];  //倒計(jì)時(shí)數(shù)值個(gè)位顯示
    LedBuff[1] = LedChar[timer/10];  //倒計(jì)時(shí)數(shù)值十位顯示
}
void LedScan()  //LED顯示掃描函數(shù)
{
    static unsigned char index = 0;  //LED位選索引
   
    P0 = 0xFF;                 //關(guān)閉所有段選位,顯示消隱
    P1 = (P1 & 0xF8) | index;  //位選索引值賦值到P1口低3
    P0 = LedBuff[index];       //相應(yīng)顯示緩沖區(qū)的值賦值到P0
    if (index < 6)             //位選索引0-6循環(huán),因有6個(gè)數(shù)碼管+一組獨(dú)立LED
        index++;
    else
        index = 0;
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    static unsigned int tmr1s = 0;  //1秒定時(shí)器
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
    LedScan(); //LED掃描顯示
    tmr1s++;   //1秒定時(shí)的處理
    if (tmr1s >= 1000)
    {
        tmr1s = 0;
        flag1s = 1;
    }
}
10.4 長(zhǎng)短按鍵的應(yīng)用10.4.1 51單片機(jī)RAM區(qū)域劃分
前邊介紹單片機(jī)資源的時(shí)候,我們提到過(guò)我們的STC89C52RC共有512字節(jié)的RAM,就是用來(lái)保存數(shù)據(jù)的,如我們定義的變量都是直接存在RAM里邊。51單片機(jī)的這512字節(jié)的RAM數(shù)據(jù)是分塊的,因此我們?cè)谠L問(wèn)的時(shí)候,也要注意一些問(wèn)題。
51單片機(jī)的RAM分為兩個(gè)部分,一塊是片內(nèi)RAM,一塊是片外RAM。標(biāo)準(zhǔn)的51的片內(nèi)RAM地址從0x00H~0x7F128個(gè)字節(jié),而現(xiàn)在我們用的51系列的單片機(jī)都是帶擴(kuò)展片內(nèi)RAM的,RAM是從0x00~0xFF256個(gè)字節(jié)。片外RAM最大可以擴(kuò)展到0x0000~0xFFFF64K字節(jié)。這里有一點(diǎn)大家要明白,片內(nèi)RAM和片外RAM的地址不是連起來(lái)的,片內(nèi)是從0x00開始,片外也是從0x0000開始的。以下是幾個(gè)Keil C51語(yǔ)言中的關(guān)鍵字,代表了RAM不同區(qū)域的劃分,大家先記一下。
data:片內(nèi)RAM0x00~0x7F
idata:片內(nèi)RAM0x00~0xFF
pdata:片外RAM0x0000~0x00FF
xdata:片外RAM0x0000~0xFFFF
大家可以看出來(lái),dataidata的一部分,pdataxdata的一部分。為什么還這樣去區(qū)分呢?因?yàn)?/font>RAM分塊的訪問(wèn)方式主要和匯編語(yǔ)言有關(guān),因此這塊內(nèi)容大家了解一下即可,只需要記住如何訪問(wèn)速度更快即可。
我們定義一個(gè)變量a,可以這樣:unsigned char data a=0,而我們前邊定義變量時(shí)都沒有加data這個(gè)關(guān)鍵字,是因?yàn)槲覀冊(cè)?/font>Keil默認(rèn)設(shè)置下,data是可以省略的,即什么都不加的時(shí)候變量就是定義到data區(qū)域中的。data區(qū)域RAM的訪問(wèn)在匯編語(yǔ)言中用的是直接尋址,訪問(wèn)運(yùn)行速度是最快的。如果你定義成idata,不僅僅可以訪問(wèn)data區(qū)域,還可以訪問(wèn)0x80H~0xFF的范圍,但加了idata關(guān)鍵字后,訪問(wèn)的時(shí)候是利用了51單片機(jī)的通用寄存器進(jìn)行間接尋址,速度較data速度慢一些,而且我們平時(shí)大多數(shù)情況下不太希望訪問(wèn)到0x80H~0xFF,因?yàn)檫@塊通常用于中斷和函數(shù)調(diào)用的堆棧,所以在絕大多數(shù)情況下,我們使用內(nèi)部RAM的時(shí)候,只用data就可以了。
對(duì)于外部RAM來(lái)說(shuō),使用pdata定義的變量存到了外部RAM0x00~0xFF的地址范圍里,這塊地址的訪問(wèn)和idata類似,都是用8位的通用寄存器進(jìn)行間接尋址,而如果你定義成xdata,可以訪問(wèn)的范圍更廣泛,從064k的地址都可以訪問(wèn)到,但是它需要使用2個(gè)8位的寄存器DPTRHDPTRL來(lái)進(jìn)行間接尋址,速度是最慢的。
我們的STC89C52RC共有512字節(jié)的RAM256字節(jié)的片內(nèi)RAM256字節(jié)的片外RAM。一般情況下,我們是使用data區(qū)域,data不夠用了,我們就用xdata,如果希望程序執(zhí)行效率點(diǎn),可以使用pdata關(guān)鍵字來(lái)定義。其他型號(hào)的,有更大的RAM51系列單片機(jī),如果要使用更大的RAM,就必須得用xdata來(lái)訪問(wèn)了。
10.4.2 長(zhǎng)短按鍵
在我們的單片機(jī)系統(tǒng)中,如果我們按下一次按鍵加1,那我們第八章學(xué)到的技術(shù)就可以完成,但是我們想連續(xù)加很多數(shù)字的時(shí)候,要一次次按下這個(gè)按鍵確實(shí)不方便,我們希望我們按住按鍵的時(shí)候,數(shù)字會(huì)持續(xù)增加,這就是這節(jié)課的長(zhǎng)短按鍵實(shí)例。
當(dāng)按下一個(gè)按鍵持續(xù)時(shí)間低于1秒的時(shí)候,運(yùn)行一次按鍵動(dòng)作,當(dāng)按下按鍵持續(xù)時(shí)間超過(guò)1秒后,每經(jīng)過(guò)200ms則自動(dòng)再執(zhí)行一次按鍵動(dòng)作,形成一個(gè)長(zhǎng)按鍵效果。這個(gè)程序做的是一個(gè)定時(shí)炸彈的效果,打開開關(guān)后,數(shù)碼管顯示數(shù)字0,按下向上的按鍵數(shù)字加1,按下向下的按鍵數(shù)字減1,長(zhǎng)按向上按鍵1秒后,數(shù)字會(huì)持續(xù)增加,長(zhǎng)按向下按鍵1秒后,數(shù)字會(huì)持續(xù)減小。設(shè)定好數(shù)字后,按下回車按鍵,時(shí)間就會(huì)進(jìn)行倒計(jì)時(shí),當(dāng)?shù)褂?jì)時(shí)到0的時(shí)候,用蜂鳴器和板子上的8個(gè)LED小燈做炸彈效果,蜂鳴器持續(xù)響,LED小燈全亮。
#include <reg52.h>
sbit BUZZ = P1^6;       //蜂鳴器控制引腳
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 ADDR3 = P1^3;      //LED選擇地址線3
sbit ENLED = P1^4;      //LED總使能引腳
unsigned char code LedChar[] = {  //數(shù)碼管顯示字符轉(zhuǎn)換表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = {  //數(shù)碼管+獨(dú)立LED顯示緩沖區(qū)
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
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ù)字鍵0ESC鍵、  回車鍵、 向右鍵
};
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}
};
unsigned long pdata KeyDownTime[4][4] = {  //每個(gè)按鍵按下的持續(xù)時(shí)間,單位ms
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}
};
bit enBuzz = 0;  //蜂鳴器使能標(biāo)志
unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)
bit flag1s = 0;     //1秒定時(shí)標(biāo)志
bit flagStart = 0;  //倒計(jì)時(shí)啟動(dòng)標(biāo)志
unsigned int CountDown = 0; //倒計(jì)時(shí)計(jì)數(shù)器
void ConfigTimer0(unsigned int ms);
void DisplayNumber(unsigned int dat);
void KeyDrive();
void main(void)
{
    P0 = 0xFF;  //P0口初始化
    ADDR3 = 1;  //選擇數(shù)碼管
    ENLED = 0;  //LED總使能
    EA = 1;     //開總中斷
    ConfigTimer0(1);  //配置T0定時(shí)1ms
    DisplayNumber(CountDown);
       
    while(1)
    {
        KeyDrive();
        if (flagStart && flag1s) //倒計(jì)時(shí)啟動(dòng)且1秒定時(shí)到達(dá)時(shí),處理倒計(jì)時(shí)
        {
            flag1s = 0;
            if (CountDown > 0)   //倒計(jì)時(shí)未到0時(shí),計(jì)數(shù)器遞減
            {
                CountDown--;
                DisplayNumber(CountDown);
                if (CountDown == 0)    //減到0時(shí),執(zhí)行聲光報(bào)警
                {
                    enBuzz = 1;        //啟動(dòng)蜂鳴器發(fā)聲
                    LedBuff[6] = 0x00; //點(diǎn)亮獨(dú)立LED
                }
            }
        }
    }
}
void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)
{
    unsigned long tmp;
   
    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 31;           //修正中斷響應(yīng)延時(shí)造成的誤差
   
    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}
void DisplayNumber(unsigned int dat)  //將一個(gè)無(wú)符號(hào)整型數(shù)轉(zhuǎn)到數(shù)碼管顯示緩沖區(qū)以供顯示
{
    signed char i;
    unsigned char buf[6];
   
    for (i=0; i<6; i++)  //拆分為十進(jìn)制的位
    {
        buf[ i] = dat % 10;
        dat /= 10;
    }
    for (i=5; i>=1; i--) //高位的0不予顯示
    {
        if (buf [ i] == 0)
            LedBuff[ i] = 0xFF;
        else
            break;
    }
    for ( ; i>=0; i--)   //有效數(shù)據(jù)位轉(zhuǎn)換為顯示字符
    {
        LedBuff[ i] = LedChar[buf[ i]];
    }
}
void KeyAction(unsigned char keycode)  //按鍵動(dòng)作函數(shù),根據(jù)鍵碼執(zhí)行相應(yīng)動(dòng)作
{
    if  ((keycode>='0') && (keycode<='9'))  //本程序中對(duì)0-9的數(shù)字按鍵不做響應(yīng)
    {}
    else if (keycode == 0x26)  //向上鍵,倒計(jì)時(shí)設(shè)定值遞增
    {
        if (CountDown < 9999)  //最大計(jì)時(shí)9999
        {
            CountDown++;
            DisplayNumber(CountDown);
        }
    }
    else if (keycode == 0x28)  //向下鍵,倒計(jì)時(shí)設(shè)定值遞減
    {
        if (CountDown > 1)     //最小計(jì)時(shí)1
        {
            CountDown--;
            DisplayNumber(CountDown);
        }
    }
    else if (keycode == 0x0D)  //回車鍵,啟動(dòng)倒計(jì)時(shí)
    {
        flagStart = 1;         //啟動(dòng)倒計(jì)時(shí)
    }
    else if (keycode == 0x1B)  //Esc鍵,取消倒計(jì)時(shí)
    {
        enBuzz = 0;            //關(guān)閉蜂鳴器
        LedBuff[6] = 0xFF;     //關(guān)閉獨(dú)立LED
        flagStart = 0;         //停止倒計(jì)時(shí)
        CountDown = 0;         //倒計(jì)時(shí)數(shù)歸零
        DisplayNumber(CountDown);
    }
}
void KeyDrive()  //按鍵動(dòng)作驅(qū)動(dòng)函數(shù)
{
    unsigned char i, j;
    static unsigned char backup[4][4] = {  //按鍵值備份,保存前一次的值
        {1, 1, 1, 1},
        {1, 1, 1, 1},
        {1, 1, 1, 1},
        {1, 1, 1, 1}
    };
    static unsigned long pdata TimeThr[4][4] = {  //保持按下時(shí)啟動(dòng)快速輸入的時(shí)間閾值
        {1000, 1000, 1000, 1000},
        {1000, 1000, 1000, 1000},
        {1000, 1000, 1000, 1000},
        {1000, 1000, 1000, 1000}
    };
   
    for (i=0; i<4; i++)  //循環(huán)掃描4*4的矩陣按鍵
    {
        for (j=0; j<4; j++)
        {
            if (backup[ i][j] != KeySta[ i][j])  //檢測(cè)按鍵動(dòng)作
            {
                if (backup[ i][j] != 0)  //按鍵按下時(shí)執(zhí)行動(dòng)作
                {
                    KeyAction(KeyCodeMap [ i][j]);  //調(diào)用按鍵動(dòng)作函數(shù)
                }
                backup[ i][j] = KeySta[ i][j];
            }
            if (KeyDownTime[ i][j] > 0)  //檢測(cè)執(zhí)行快速輸入
            {
                if (KeyDownTime[ i][j] >= TimeThr[ i][j]) //按下時(shí)間達(dá)到閾值時(shí)執(zhí)行一次動(dòng)作
                {
                    KeyAction(KeyCodeMap[ i][j]);  //調(diào)用按鍵動(dòng)作函數(shù)
                    TimeThr[ i][j] += 200;         //間隔200ms執(zhí)行下一次動(dòng)作
                }
            }
            else //按鍵彈起時(shí)復(fù)位閾值時(shí)間
            {
                TimeThr[ i][j] = 1000;  //啟動(dòng)快速輸入的條件為持續(xù)按下超過(guò)1000ms
            }
        }
    }
}
void LedScan()  //LED顯示掃描函數(shù)
{
    static unsigned char index = 0;
   
    P0 = 0xFF;                 //關(guān)閉所有段選位,顯示消隱
    P1 = (P1 & 0xF8) | index;  //位選索引值賦值到P1口低3
    P0 = LedBuff[index];       //相應(yīng)顯示緩沖區(qū)的值賦值到P0
    if (index < 6)             //位選索引0-6循環(huán),因有6個(gè)數(shù)碼管+一組獨(dú)立LED
        index++;
    else
        index = 0;
}
void KeyScan()  //按鍵掃描函數(shù)
{
    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}
    };
    //將一行的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)都只檢測(cè)到按下狀態(tài)時(shí),可認(rèn)為按鍵已按下
            KeySta[keyout][ i] = 0;
            KeyDownTime[keyout][ i] += 4;  //按下持續(xù)時(shí)間累加
        }
        else if ((keybuf[keyout] [ i] & 0x0F) == 0x0F)
        {   //連續(xù)4次掃描值為1,即16ms(4*4ms)內(nèi)都只檢測(cè)到彈起狀態(tài)時(shí),可認(rèn)為按鍵已彈起
            KeySta[keyout][ i] = 1;
            KeyDownTime[keyout][ i] = 0;   //按下的持續(xù)時(shí)間清零
        }
    }
   
    //執(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;
    }
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    static unsigned int tmr1s = 0;  //1秒定時(shí)器
   
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
   
    if (enBuzz)  //蜂鳴器發(fā)聲處理
        BUZZ = ~BUZZ;  //驅(qū)動(dòng)蜂鳴器發(fā)聲
    else
        BUZZ = 1;  //關(guān)閉蜂鳴器
   
    KeyScan();  //按鍵掃描
    LedScan();  //LED掃描顯示
   
    if (flagStart) //倒計(jì)時(shí)啟動(dòng)時(shí)處理1秒定時(shí)
    {
        tmr1s++;
        if (tmr1s >= 1000)
        {
            tmr1s = 0;
            flag1s = 1;
        }
    }
    else //倒計(jì)時(shí)為啟動(dòng)時(shí)1秒定時(shí)器始終歸零
    {
        tmr1s = 0;
    }
}
長(zhǎng)按鍵功能實(shí)現(xiàn)的重點(diǎn)有兩個(gè):第一,是在原來(lái)的矩陣按鍵掃描函數(shù)KeyScan內(nèi),當(dāng)檢測(cè)到按鍵按下后,持續(xù)的對(duì)一個(gè)時(shí)間變量進(jìn)行累加,其目的是用這個(gè)時(shí)間變量來(lái)記錄按鍵按下的時(shí)間;第二,是在按鍵驅(qū)動(dòng)函數(shù)KeyDrive里,除了原來(lái)的檢測(cè)到按鍵按下這個(gè)動(dòng)作時(shí)執(zhí)行按鍵動(dòng)作函數(shù)KeyAction外,還監(jiān)測(cè)表示按鍵按下時(shí)間的變量,根據(jù)它的值來(lái)完成長(zhǎng)按時(shí)的連續(xù)快速按鍵動(dòng)作功能。
1.5 作業(yè)
1、將第一個(gè)例程進(jìn)行倒計(jì)時(shí)處理,從9999.99開始進(jìn)行倒計(jì)時(shí),并且只顯示有效位。
2、理解PWM的實(shí)質(zhì),在點(diǎn)陣上實(shí)現(xiàn)不同亮度的小燈的花樣排列。
3、實(shí)現(xiàn)數(shù)碼管計(jì)時(shí)和流水燈同時(shí)運(yùn)行的效果。
4、學(xué)會(huì)長(zhǎng)短按鍵的用法,獨(dú)立把本章程序全部寫出來(lái)。

作者: liuxin198405    時(shí)間: 2014-7-23 14:38
這個(gè)好像全是金沙灘那個(gè)宋老師的吧
作者: hzb123    時(shí)間: 2015-8-29 17:01
超級(jí)贊,非常有用
作者: qyhok    時(shí)間: 2016-2-29 16:02
看到PWM一開始有點(diǎn)暈,抄一遍,自己寫一遍以后,對(duì)定時(shí)器的運(yùn)用理解更深刻了
作者: keneng    時(shí)間: 2018-7-6 08:28
循序漸進(jìn),繼續(xù)學(xué)習(xí)。
作者: mawuxi    時(shí)間: 2020-3-14 10:24
這一章內(nèi)容屬于編程技巧,還需慢慢來(lái)




歡迎光臨 (http://m.zg4o1577.cn/bbs/) Powered by Discuz! X3.1
主站蜘蛛池模板: 国产成人午夜 | 青青草国产在线视频 | 久久久久久久国产精品 | 天天射天天操天天干 | 黄色小视频免费观看 | 成人免费看片98欧美 | 日本精品国产 | 狠狠五月天| 欧美国产日韩视频 | 黄色小视频在线观看 | 天天射天天操天天干 | 特黄一级视频 | 欧美黄色一区 | 狼人色 | 日韩免费精品视频 | 99精品久久久久久中文字幕 | 三级视频在线 | 依人在线 | 高清一级片 | 精品视频在线观看免费 | 国产成人在线观看免费网站 | 日韩高清在线观看 | 看黄色大片 | 三级网站| 操日本老女人 | av一二三区| 99久久精品一区二区成人 | 精品一区二区三区四区五区 | 深夜福利网 | 亚洲在线视频观看 | 天堂一区二区三区 | 中文字幕在线观看网址 | 欧美久久精品 | 国产毛片毛片毛片 | 亚洲成人精品在线观看 | 欧美在线播放视频 | www.嫩草| 一区免费视频 | 国产精品久久久久久99 | 亚洲免费毛片 | 香蕉成人|