標(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=10,b=200,c的結(jié)果就是2000。那當(dāng)a=100,b=700,那c是70000嗎?新手最容易犯這種錯(cuò)誤,大家要注意每個(gè)變量的數(shù)據(jù)類型,c的數(shù)據(jù)類型是unsigned int型,取值范圍是0~65535,70000超過(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è)程序,a和b相乘的時(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的值,如果b是0,那么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í)器TH和TL賦值,也需要幾個(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;TR0是TCON的一個(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)看,ADDR0是P1的第0位,ADDR1是P1的第1位,ADDR2是P1的第2位,我們可以看出來(lái),程序中的case 0到case 5的過(guò)程中,P1的這低3位的值分別是000,001,010,011,100,101。轉(zhuǎn)換成十進(jìn)制,也就是從0到5。那我們程序就可以進(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è)什么東西。
PWM是Pulse 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所示。
1.JPG (34.26 KB, 下載次數(shù): 195)
下載附件
2013-9-28 01:42 上傳
圖10-1 PWM波形
這是一個(gè)周期是10ms,即頻率是100Hz的波形,但是每個(gè)周期內(nèi),高低電平脈沖各不相同,這就是PWM的本質(zhì)。在這里大家要記住一個(gè)概念,叫做“占空比”。占空比是指高電平的時(shí)間占整個(gè)周期的比例。比如第一部分波形的占空比是40%,第二部分波形占空比是60%,第三部分波形占空比是80%,這就是PWM的解釋。
那為何它會(huì)對(duì)模擬電路進(jìn)行控制呢?大家想一想,我們數(shù)字電路里,只有0和1兩種狀態(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)亮度都不一樣,這就是模擬電路的感覺了,不再是純粹的0和1,還有亮度不斷變化。大家會(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源有限,所以我把左邊LED8和LED9一起亮作為綠燈,把LED5和LED6一起亮作為黃燈,把LED2和LED3一起亮作為紅燈,用數(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~0x7F共128個(gè)字節(jié),而現(xiàn)在我們用的51系列的單片機(jī)都是帶擴(kuò)展片內(nèi)RAM的,RAM是從0x00~0xFF共256個(gè)字節(jié)。片外RAM最大可以擴(kuò)展到0x0000~0xFFFF共64K字節(jié)。這里有一點(diǎn)大家要明白,片內(nèi)RAM和片外RAM的地址不是連起來(lái)的,片內(nèi)是從0x00開始,片外也是從0x0000開始的。以下是幾個(gè)Keil C51語(yǔ)言中的關(guān)鍵字,代表了RAM不同區(qū)域的劃分,大家先記一下。
data:片內(nèi)RAM從0x00~0x7F
idata:片內(nèi)RAM從0x00~0xFF
pdata:片外RAM從0x0000~0x00FF
xdata:片外RAM從0x0000~0xFFFF
大家可以看出來(lái),data是idata的一部分,pdata是xdata的一部分。為什么還這樣去區(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定義的變量存到了外部RAM的0x00~0xFF的地址范圍里,這塊地址的訪問(wèn)和idata類似,都是用8位的通用寄存器進(jìn)行間接尋址,而如果你定義成xdata,可以訪問(wèn)的范圍更廣泛,從0到64k的地址都可以訪問(wèn)到,但是它需要使用2個(gè)8位的寄存器DPTRH和DPTRL來(lái)進(jìn)行間接尋址,速度是最慢的。
我們的STC89C52RC共有512字節(jié)的RAM,256字節(jié)的片內(nèi)RAM和256字節(jié)的片外RAM。一般情況下,我們是使用data區(qū)域,data不夠用了,我們就用xdata,如果希望程序執(zhí)行效率點(diǎn),可以使用pdata關(guān)鍵字來(lái)定義。其他型號(hào)的,有更大的RAM的51系列單片機(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ù)字鍵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}
};
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
|
亚洲免费毛片
|
香蕉成人|