在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 數(shù)據(jù)庫/ 10.1 單片機(jī)數(shù)字秒表程序
8.3 C 語言函數(shù)的形參和實(shí)參
12.2 C 語言指針變量的聲明
12.5 ?C 語言字符數(shù)組和字符指針
7.3 單片機(jī) LED 點(diǎn)陣的介紹
11.5 UART 串口通信的基本應(yīng)用
9.9 單片機(jī)蜂鳴器控制程序和驅(qū)動電路
10. 單片機(jī)實(shí)例練習(xí)與經(jīng)驗(yàn)積累
10.3 單片機(jī)交通燈控制程序和設(shè)計(jì)原理
9.8 實(shí)用的 28BYJ-48 步進(jìn)電機(jī)控制程序
8.2 C 語言函數(shù)的調(diào)用
12.4 C 語言指向數(shù)組元素的指針
7.1 C 語言變量的作用域
11.2 RS232 通信接口
12.7 1602 液晶的讀寫時(shí)序介紹
7.2 C 語言變量的存儲類別
8. C 語言函數(shù)進(jìn)階與單片機(jī)按鍵
10.4 51單片機(jī) RAM 區(qū)域的劃分
12.1 C 語言變量的地址
11. UART 串口通信
7. 變量進(jìn)階與點(diǎn)陣 LED
8.4 單片機(jī)按鍵介紹
9.3 電機(jī)的分類
9.1 單片機(jī) IO 口的結(jié)構(gòu)
單片機(jī)通信實(shí)例與 ASCII 碼
8.1 單片機(jī)最小系統(tǒng)解析(電源、晶振和復(fù)位電路)
9.2 單片機(jī)上下拉電阻
11.4 單片機(jī) IO 口模擬 UART 串口通信
9.5 讓 28BYJ-48 步進(jìn)電機(jī)轉(zhuǎn)起來
9.7 28BYJ-48 步進(jìn)電機(jī)控制程序基礎(chǔ)
12.8 1602 液晶指令介紹
12.3 C 語言指針的簡單示例
8.7 單片機(jī)矩陣按鍵的掃描
7.4 單片機(jī) LED 點(diǎn)陣的圖形顯示
8.6 單片機(jī)按鍵消抖程序
10.2 單片機(jī)中 PWM 的原理與控制程序
7.6 單片機(jī) LED 點(diǎn)陣的橫向移動(動態(tài)顯示)
11.3 USB 轉(zhuǎn)串口通信
12.9 1602 液晶簡單顯示程序
9.4 28BYJ-48 步進(jìn)電機(jī)原理
8.5 ?單片機(jī)獨(dú)立按鍵掃描程序
12. C 語言指針基礎(chǔ)與1602液晶的初步認(rèn)識
9. 單片機(jī)中的步進(jìn)電機(jī)與蜂鳴器
10.1 單片機(jī)數(shù)字秒表程序
7.5 單片機(jī) LED 點(diǎn)陣的縱向移動(動態(tài)顯示)
8.8 單片機(jī)簡易加法計(jì)算器程序
11.1 單片機(jī)串行通信介紹
10.5 單片機(jī)長短按鍵的應(yīng)用
12.6 1602 液晶介紹(電路和引腳圖)
9.6 28BYJ-48 步進(jìn)電機(jī)轉(zhuǎn)動精度與深入分析

10.1 單片機(jī)數(shù)字秒表程序

不同數(shù)據(jù)類型間的相互轉(zhuǎn)換

在 C 語言中,不同數(shù)據(jù)類型之間是可以混合運(yùn)算的。當(dāng)表達(dá)式中的數(shù)據(jù)類型不一致時(shí),首先轉(zhuǎn)換為同一種類型,然后再進(jìn)行計(jì)算。C 語言有兩種方法實(shí)現(xiàn)類型轉(zhuǎn)換,一是自動類型轉(zhuǎn)換,另外一種是強(qiáng)制類型轉(zhuǎn)換。這塊內(nèi)容是比較繁雜的,因此我們根據(jù)常用的編程應(yīng)用來講部分相關(guān)內(nèi)容。

當(dāng)不同數(shù)據(jù)類型之間混合運(yùn)算的時(shí)候,不同類型的數(shù)據(jù)首先會轉(zhuǎn)換為同一類型,轉(zhuǎn)換的主要原則是:短字節(jié)的數(shù)據(jù)向長字節(jié)數(shù)據(jù)轉(zhuǎn)換。比如:

unsigned char a;
unsigned int b;
unsigned int c;
c = a *b;

在運(yùn)算的過程中,程序會自動全部按照 unsigned int 型來計(jì)算。比如 a=10,b=200,c 的結(jié)果就是2000。那當(dāng) a=100,b=700,那 c 是70000嗎?新手最容易犯這種錯誤,大家要注意每個(gè)變量類型的取值范圍,c 的數(shù)據(jù)類型是 unsigned int 型,取值范圍是 0~65535,而70000超過 65535了,其結(jié)果會溢出,最終 c 的結(jié)果是(70000 - 65536) = 4464。

那要想讓 c 正常獲得70000這個(gè)結(jié)果,需要把 c 定義成一個(gè) unsigned long 型。我們?nèi)绻麑懗桑?

unsigned char a=100;
unsigned int b=700;
unsigned long c=0;
c = a*b;

有做過實(shí)驗(yàn)的同學(xué),會發(fā)現(xiàn)這個(gè) c 的結(jié)果還是4464,這個(gè)是個(gè)什么情況呢?

大家注意,C 語言不同類型運(yùn)算的時(shí)候數(shù)值會轉(zhuǎn)換同一類型運(yùn)算,但是每一步運(yùn)算都會進(jìn)行識別判斷,不會進(jìn)行一個(gè)總的分析判斷。比如我們這段代碼中 a 和 b 相乘的時(shí)候,是按照 unsigned int 類型運(yùn)算的,運(yùn)算的結(jié)果也是 unsigned int 類型的4464,只是最終把 unsigned int 類型 4464賦值給了一個(gè) unsigned long 型的變量而已。我們在運(yùn)算的時(shí)候如何避免這類問題的產(chǎn)生呢?可以采用強(qiáng)制類型轉(zhuǎn)換的方法。

在一個(gè)變量前邊加上一個(gè)數(shù)據(jù)類型名,并且這個(gè)類型名用小括號括起來,就表示把這個(gè)變量強(qiáng)制轉(zhuǎn)換成括號里的類型。如 c = (unsigned long)a b;由于強(qiáng)制類型轉(zhuǎn)換運(yùn)算符優(yōu)先級高于,所以這個(gè)地方的運(yùn)算是先把 a 轉(zhuǎn)換成一個(gè) unsigned long 型的變量,而后與 b 相乘,根據(jù) C 語言的規(guī)則 b 會自動轉(zhuǎn)換成一個(gè) unsigned long 型的變量,而后運(yùn)算完畢結(jié)果也是一個(gè) unsigned long 型的,最終賦值給了 c。

不同類型變量之間的相互賦值,短字節(jié)類型變量向長字節(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位出來,也就是188,這就是長字節(jié)類型給短字節(jié)類型賦值的結(jié)果,會從長字節(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 的最低位,而是它會判斷 b 這個(gè)變量是0還是非0的值,如果 b 是0,那么 a 的結(jié)果就是0,如果 b 是任意非0的其它值,那么 a 的結(jié)果都是1。

定時(shí)時(shí)間精準(zhǔn)性調(diào)整

在6.5.2章節(jié)有一個(gè)數(shù)碼管秒表顯示程序,那個(gè)程序是1秒數(shù)碼管加1,但是細(xì)心的同學(xué)做了實(shí)驗(yàn)后,經(jīng)過長時(shí)間運(yùn)行會發(fā)現(xiàn),和我們實(shí)際的時(shí)間有了較大誤差了,那如何去調(diào)整這種誤差呢?要解決問題,先找到問題是什么原因造成的。

先對我們前面講過的中斷內(nèi)容做一個(gè)較深層次的補(bǔ)充。還是講解中斷的那個(gè)場景,當(dāng)我們在看電視的時(shí)候,突然發(fā)生了水開的中斷,我們必須去提水的時(shí)候,第一,我們從電視跟前跑到廚房需要一定的時(shí)間,第二,因?yàn)槲覀兛吹碾娨暿侵悄軘?shù)字電視,因此在去提水之前我們可以使用遙控器將我們的電視進(jìn)行暫停操作,方便回來后繼續(xù)從剛才的劇情往下進(jìn)行。

那么暫停電視,跑到廚房提水,這一點(diǎn)點(diǎn)時(shí)間是很短的,在實(shí)際生活中可以忽略不計(jì),但是在單片機(jī)秒表程序中,誤差是會累計(jì)的,每1秒鐘都差了幾個(gè)微妙,時(shí)間一久,造成的累計(jì)誤差就不可小覷了。

單片機(jī)系統(tǒng)里,硬件進(jìn)入中斷需要一定的時(shí)間,大概是幾個(gè)機(jī)器周期,還要進(jìn)行原始數(shù)據(jù)保護(hù),就是把進(jìn)中斷之前程序運(yùn)行的一些變量先保存起來,專業(yè)術(shù)語叫做中斷壓棧,進(jìn)入中斷后,重新給定時(shí)器 TH 和 TL 賦值,也需要幾個(gè)機(jī)器周期,這樣下來就會消耗一定的時(shí)間,我們得把這些時(shí)間補(bǔ)償回來。

方法一,使用軟件 debug 進(jìn)行補(bǔ)償。

我們在前邊講過使用 debug 來觀察程序運(yùn)行時(shí)間,那我們可以把我們2次進(jìn)入中斷的時(shí)間間隔觀察出來,看看和我們實(shí)際定時(shí)的時(shí)間相差了幾個(gè)機(jī)器周期,然后在進(jìn)行定時(shí)器初值賦值的時(shí)候,進(jìn)行一個(gè)調(diào)整。我們用的是 11.0592 M 的晶振,發(fā)現(xiàn)差了幾個(gè)機(jī)器周期,就把定時(shí)器初值加上幾個(gè)機(jī)器周期,這樣就相當(dāng)于進(jìn)行了一個(gè)補(bǔ)償。

方法二,使用累計(jì)誤差計(jì)算出來。

有的時(shí)候,除了程序本身存在的誤差外,硬件精度也可能會影響到時(shí)鐘的精度,比如晶振,會隨著溫度變化出現(xiàn)溫漂現(xiàn)象,就是實(shí)際值和標(biāo)稱值要差一點(diǎn)。那么我們還可以采取累計(jì)誤差的方法來提高精度。比如我們可以讓時(shí)鐘運(yùn)行半個(gè)小時(shí)或者一個(gè)小時(shí),看看最終時(shí)間差了幾秒,然后算算一共進(jìn)了多少次定時(shí)器中斷,把這差的幾秒平均分配到每次的定時(shí)器中斷中,就可以實(shí)現(xiàn)時(shí)鐘的調(diào)整。

大家要明白,這個(gè)世界上本就沒有絕對的精確,我們只能在一定程度上提高精確度,但是永遠(yuǎn)都不會使誤差為零,如果在這個(gè)基礎(chǔ)上還感覺精度不夠的話,不要著急,后邊我們會專門講時(shí)鐘芯片的,通常時(shí)鐘芯片計(jì)時(shí)的精度比單片機(jī)的精度要高一些。

字節(jié)操作修改位的技巧

這里再介紹個(gè)編程小技巧,在編程時(shí),有的情況下需要改變一個(gè)字節(jié)中的某一位或者幾位,但是又不想改變其它位原有的值,該如何操作呢?

比如我們學(xué)定時(shí)器的時(shí)候遇到一個(gè)寄存器 TCON,這個(gè)寄存器是可以進(jìn)行位操作的,可以直接寫 TR0=1;TR0 是 TCON 的一個(gè)位,因?yàn)檫@個(gè)寄存器是允許位操作,這樣寫是沒有任何問題的。還有一個(gè)寄存器 TMOD,這個(gè)寄存器是不支持位操作的,那如果我們要使用 T0 的模式1,我們希望達(dá)到的效果是 TMOD 的低4位是 0b0001,但如果我們直接寫成 TMOD =0x01 的話,實(shí)際上已經(jīng)同時(shí)操作到了高4位,即屬于 T1 的部分,設(shè)置成了 0b0000,如果 T1 定時(shí)器沒有用到的話,那我們隨便怎么樣都行,但是如果程序中既用到了 T0,又用到了 T1,那我們設(shè)置 T0 的同時(shí)已經(jīng)干擾到了 T1 的模式配置,這是我們不希望看到的結(jié)果。

在這種情況下,就可以用我們前邊學(xué)過的“&”和“|”運(yùn)算了。對于二進(jìn)制位操作來說,不管該位原來的值是0還是1,它跟0進(jìn)行&運(yùn)算,得到的結(jié)果都是0,而跟1進(jìn)行&運(yùn)算,將保持原來的值不變;不管該位原來的值是0還是1,它跟1進(jìn)行|運(yùn)算,得到的結(jié)果都是1,而跟0進(jìn)行|運(yùn)算,將保持原來的值不變。

利用上述這個(gè)規(guī)律,我們就可以著手解決剛才的問題了。如果我們現(xiàn)在要設(shè)置 TMOD 使定時(shí)器0工作在模式1下,又不干擾定時(shí)器1的配置,我們可以進(jìn)行這樣的操作:TMOD =TMOD & 0xF0; TMOD = TMOD | 0x01;第一步與 0xF0 做&運(yùn)算后,TMOD 的高4位不變,低4位清零,變成了 0bxxxx0000;然后再進(jìn)行第二步與 0x01 進(jìn)行|運(yùn)算,那么高7位均不變,最低位變成1了,這樣就完成了只將低4位的值修改位 0b0001,而高4位保持原值不變的任務(wù),即只設(shè)置了 T0 而不影響 T1。熟練掌握并靈活運(yùn)用這個(gè)方法,會給你以后的編程帶來便利。

另外,在 C 語言中,a &= b;等價(jià)于 a = a&b;同理,a |= b;等價(jià)于 a = a|b;那么剛才的一段代碼就可以寫成 TMOD &= 0xF0;TMOD |= 0x01 這樣的簡寫形式。這種寫法可以一定程度上簡化代碼,是 C 語言常用的一種編程風(fēng)格。

數(shù)碼管掃描函數(shù)算法改進(jìn)

在學(xué)習(xí)數(shù)碼管動態(tài)掃描的時(shí)候,為了方便大家理解,我們程序?qū)懙募?xì)致一些,給大家引入了 switch 的用法,隨著編程能力與領(lǐng)悟能力的增強(qiáng),對于 74HC138 這種非常有規(guī)律的數(shù)字器件,我們在編程上也可以改進(jìn)一下邏輯算法,讓程序變的更簡潔。這種邏輯算法,通常不是靠學(xué)一下可以全部掌握的,而是通過不斷的編寫程序以及研究他人程序的過程中一點(diǎn)點(diǎn)積累起來的,從今天開始,大家就要開始積累吧。

前邊動態(tài)掃描刷新函數(shù)我們是這么寫的:

P0 = 0xFF;
switch (i){
    case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
    case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
    case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
    case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
    case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
    case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
    default: break;
}

我們來分析每一個(gè) case 分支,它們的結(jié)構(gòu)是相同的,即改變 ADDR2~0、改變索引 i、取數(shù)據(jù)寫入 P0,只要把 case 后的常量與 ADDR2~0 和 LedBuff 的下標(biāo)對比,就可以發(fā)現(xiàn)它們其實(shí)是相等的,那么我們可以直接把常量值(實(shí)際上就是 i 在改變前的值)賦值給它們即可,而不必寫上6遍。還剩下一個(gè) i 的操作,它進(jìn)行了5次相同的++與一次歸0操作,那么很明顯用++和 if 判斷就可以替代這些操作。下面就是我們據(jù)此改進(jìn)后的代碼:

P0 = 0xFF;
P1 = (P1 & 0xF8) | i;
P0 = LedBuff[i];
if (i < 5){
    i++;
}else{
    i = 0;
}

大家看一下,P1 = (P1 & 0xF8) | i;這行代碼就利用了上面講到的&和|運(yùn)算來將 i 的低3位直接賦值到 P1 口的低3位上,而 P0 的賦值也只需要一行代碼,i 的處理也很簡單。這樣寫成的代碼是不是要簡潔的多,也巧妙的多,而功能與前面的 switch 是一樣的,同樣可以完美實(shí)現(xiàn)動態(tài)顯示刷新的功能。

秒表程序

做了一個(gè)秒表程序給同學(xué)們做參考,程序中涉及到的知識點(diǎn)我們都講過了,包括了定時(shí)器、數(shù)碼管、中斷、按鍵等多個(gè)知識點(diǎn)。多知識點(diǎn)同時(shí)應(yīng)用到一個(gè)程序中的小綜合,因此需要大家完全消化掉。此程序是一個(gè)“真正的”并且“實(shí)用的”秒表程序,第一它有足夠的分辨率,保留到小數(shù)點(diǎn)后兩位,即每 10ms 計(jì)一次數(shù),第二它也足夠精確,因?yàn)槲覀冄a(bǔ)償了定時(shí)器中斷延時(shí)造成的誤差,如果你愿意,它完全可以為用來測量你的百米成績。這種小綜合也是將來做大項(xiàng)目程序的基礎(chǔ),因此還是老規(guī)矩,大家邊抄邊理解,理解透徹后獨(dú)立寫出來就算此關(guān)通過。

#include <reg52.h>
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;
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] = { //按鍵當(dāng)前狀態(tài)
    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 KeyDriver();

void main(){
    EA = 1;  //開總中斷
    ENLED = 0;  //使能選擇數(shù)碼管
    ADDR3 = 1;
    P2 = 0xFE; //P2.0 置0,選擇第4行按鍵作為獨(dú)立按鍵
    ConfigTimer0(2); //配置 T0 定時(shí) 2 ms
    while (1){
        if (StopwatchRefresh){ //需要刷新秒表示數(shù)時(shí)調(diào)用顯示函數(shù)
            StopwatchRefresh = 0;
            StopwatchDisplay();
        }
        KeyDriver(); //調(diào)用按鍵驅(qū)動函數(shù)
    }
}
/* 配置并啟動 T0,ms-T0 定時(shí)時(shí)間 */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //臨時(shí)變量
    tmp = 11059200 / 12; //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000; //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;  //計(jì)算定時(shí)器重載值
    tmp = tmp + 18; //補(bǔ)償中斷響應(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; //啟動 T0
}
/* 秒表計(jì)數(shù)顯示函數(shù) */
void StopwatchDisplay(){
    signed char i;
    unsigned char buf[4]; //數(shù)據(jù)轉(zhuǎn)換的緩沖區(qū)
    //小數(shù)部分轉(zhuǎn)換到低 2 位
    LedBuff[0] = LedChar[DecimalPart%10];
    LedBuff[1] = LedChar[DecimalPart/10];
    //整數(shù)部分轉(zhuǎn)換到高 4 位
    buf[0] = IntegerPart%10;
    buf[1] = (IntegerPart/10)%10;
    buf[2] = (IntegerPart/100)%10;
    buf[3] = (IntegerPart/1000)%10;
    for (i=3; i>=1; i--){ //整數(shù)部分高位的0轉(zhuǎn)換為空字符
        if (buf[i] == 0){
            LedBuff[i+2] = 0xFF;
        }else{
            break;    
        }
    }
    for ( ; i>=0; i--){ //有效數(shù)字位轉(zhuǎn)換為顯示字符
        LedBuff[i+2] = LedChar[buf[i]];
    }
    LedBuff[2] &= 0x7F; //點(diǎn)亮小數(shù)點(diǎn)
}
/* 秒表啟停函數(shù) */
void StopwatchAction(){
    if (StopwatchRunning){ //已啟動則停止
        StopwatchRunning = 0;
    }else{  //未啟動則啟動
        StopwatchRunning = 1;
    }
}
/* 秒表復(fù)位函數(shù) */
void StopwatchReset(){
    StopwatchRunning = 0; //停止秒表
    DecimalPart = 0; //清零計(jì)數(shù)值
    IntegerPart = 0;
    StopwatchRefresh = 1; //置刷新標(biāo)志
}
/* 按鍵驅(qū)動函數(shù),檢測按鍵動作,調(diào)度相應(yīng)動作函數(shù),需在主循環(huán)中調(diào)用 */
void KeyDriver(){
    unsigned char i;
    static unsigned char backup[4] = {1,1,1,1};

    for (i=0; i<4; i++){ //循環(huán)檢測4個(gè)按鍵
        if (backup[i] != KeySta[i]){ //檢測按鍵動作
            if (backup[i] != 0){ //按鍵按下時(shí)執(zhí)行動作
                if (i == 1){ //Esc 鍵復(fù)位秒表
                    StopwatchReset();
                }else if (i == 2){//回車鍵啟停秒表
                    StopwatchAction();
                }
            }
            backup[i] = KeySta[i]; //刷新前一次的備份值
        }
    }
}
/* 按鍵掃描函數(shù),需在定時(shí)中斷中調(diào)用 */
void KeyScan(){
    unsigned char i;
    static unsigned char keybuf[4] = { //按鍵掃描緩沖區(qū)
        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){
            //連續(xù)8次掃描值為 0,即 16 ms 內(nèi)都是按下狀態(tài)時(shí),可認(rèn)為按鍵已穩(wěn)定的按下
            KeySta[i] = 0;
        }else if (keybuf[i] == 0xFF){
            //連續(xù)8次掃描值為 1,即 16 ms 內(nèi)都是彈起狀態(tài)時(shí),可認(rèn)為按鍵已穩(wěn)定的彈起
            KeySta[i] = 1;
        }
    }
}
/* 數(shù)碼管動態(tài)掃描刷新函數(shù),需在定時(shí)中斷中調(diào)用 */
void LedScan(){
    static unsigned char i = 0; //動態(tài)掃描索引
    P0 = 0xFF; //關(guān)閉所有段選位,顯示消隱
    P1 = (P1 & 0xF8) | i; //位選索引值賦值到 P1 口低3位
    P0 = LedBuff[i]; //緩沖區(qū)中索引位置的數(shù)據(jù)送到 P0 口
    if (i < 5){ //索引遞增循環(huán),遍歷整個(gè)緩沖區(qū)
        i++;
    }else{
        i = 0;
    }
}
/* 秒表計(jì)數(shù)函數(shù),每隔 10 ms 調(diào)用一次進(jìn)行秒表計(jì)數(shù)累加 */
void StopwatchCount(){
    if (StopwatchRunning){ //當(dāng)處于運(yùn)行狀態(tài)時(shí)遞增計(jì)數(shù)值
        DecimalPart++; //小數(shù)部分+1
        if (DecimalPart >= 100){ //小數(shù)部分計(jì)到100時(shí)進(jìn)位到整數(shù)部分
            DecimalPart = 0;
            IntegerPart++; //整數(shù)部分+1
            if (IntegerPart >= 10000){ //整數(shù)部分計(jì)到10000時(shí)歸零
                IntegerPart = 0;
            }
        }
        StopwatchRefresh = 1; //設(shè)置秒表計(jì)數(shù)刷新標(biāo)志
    }
}
/* T0 中斷服務(wù)函數(shù),完成數(shù)碼管、按鍵掃描與秒表計(jì)數(shù) */
void InterruptTimer0() interrupt 1{
    static unsigned char tmr10ms = 0;
    TH0 = T0RH; //重新加載重載值
    TL0 = T0RL;
    LedScan(); //數(shù)碼管掃描顯示
    KeyScan(); //按鍵掃描
    //定時(shí) 10ms 進(jìn)行一次秒表計(jì)數(shù)
    tmr10ms++;
    if (tmr10ms >= 5){
        tmr10ms = 0;
        StopwatchCount(); //調(diào)用秒表計(jì)數(shù)函數(shù)
    }
}

關(guān)于這個(gè)程序有兩點(diǎn)值得提一下:首先是定時(shí)器配置函數(shù),雖然這樣在程序里通過計(jì)算得出初值(重載值)增加了些許代碼,但它換來的是便利性和編程效率,因?yàn)橹灰阃瓿蛇@個(gè)函數(shù),之后所有需要用定時(shí)器定時(shí) x 毫秒的場合,你都可以直接把函數(shù)拿過去,用所需要的毫秒數(shù)作為實(shí)參調(diào)用它即可,不需要在用計(jì)算器埋頭算一通了,是不是很值呢。其次是我們沒有使用矩陣按鍵的程序,而是只用矩陣按鍵的第4行作為獨(dú)立按鍵來使用,因?yàn)槊氡碇恍枰?個(gè)鍵就夠了,這里是想告訴大家,處理問題要靈活,千萬不能墨守成規(guī),能用簡單方法解決的問題,就不要選擇復(fù)雜的方案。