51單片機(jī)使用獨(dú)立按鍵
我們使用51單片機(jī)時(shí)要怎么來檢查按鍵操作呢?下面先根據(jù)我們仿真電路中使用的電路圖來具體說明。
圖中K1K4是四個(gè)獨(dú)立按鍵,分別接在了P3.0P3.3這4個(gè)端口上。那當(dāng)按鍵按下時(shí)我們要怎么讓單片機(jī)內(nèi)部檢測(cè)出是哪個(gè)按鍵按下的呢?從圖中可以看出在默認(rèn)情況下這四個(gè)按鍵的輸入電平都是高電平,當(dāng)按鍵被按下時(shí)其對(duì)應(yīng)引腳輸入電平被拉到地。這時(shí)我們?cè)诔绦蛑芯涂梢栽O(shè)置一個(gè)不斷掃碼端口輸入數(shù)據(jù)的代碼段,被按下的按鍵對(duì)應(yīng)的引腳數(shù)據(jù)寄存器中為0,其他引腳對(duì)應(yīng)位數(shù)據(jù)依舊為1,接下來我們通過電平對(duì)比是不是就可以得出是哪個(gè)按鍵被按下了?
但是當(dāng)按鍵按這個(gè)動(dòng)作并不像一個(gè)方波信號(hào)那樣干脆利落,就像我們平時(shí)拔插插頭時(shí)可能會(huì)出現(xiàn)火花一樣,按鍵過程中也會(huì)有雜波信號(hào)抖動(dòng)干擾,按鍵過程電平變化波形如下圖所示:
如果我們程序中一旦檢測(cè)到電平變化就進(jìn)行后續(xù)操作,這時(shí)如果只是一個(gè)抖動(dòng)干擾信號(hào),那不是就誤判了?這就會(huì)造成程序功能錯(cuò)誤,在某些應(yīng)用中可能造成嚴(yán)重的后果,那怎么才能避免這個(gè)問題呢?這些抖動(dòng)信號(hào)一般在幾個(gè)ms內(nèi)(這相對(duì)以秒計(jì)數(shù)的整個(gè)過程來說是非常的短暫的),所以在簡(jiǎn)單應(yīng)用中我們一般在檢測(cè)到第一次按鍵后隔幾個(gè)或十幾個(gè)ms之后再檢測(cè)引腳輸入電平。如果第二次確認(rèn)電平變化了才認(rèn)為按鍵按下了,否則就是干擾信號(hào)。可以發(fā)現(xiàn)這么處理就避免了干擾信號(hào)引發(fā)的程序錯(cuò)誤。下面我們來看一下程序的實(shí)現(xiàn)過程。
/*
*這是一個(gè)按鍵測(cè)試程序
*目的是實(shí)現(xiàn)按鍵監(jiān)測(cè)功能
*/
#include
#include
sbit com1 = P2^0; //定義數(shù)碼管com1引腳
sbit com2 = P2^1; //定義數(shù)碼管com2引腳
sbit key1 = P3^0;
sbit key2 = P3^1;
sbit key3 = P3^2;
sbit key4 = P3^3;
typedef unsigned char u8;
typedef unsigned int u16;
u8 temp_key = 0; //定義一個(gè)變量用來存放臨時(shí)按鍵值
u8 code num_codelist[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void delay(u8 ms);
void main()
{
//讀取P3端口,判斷是否有按鍵按下
if((P3&0x0f)^0x0f)
{
//保存當(dāng)前按鍵值
temp_key = ((P3&0x0f)^0x0f);
//進(jìn)行一個(gè)延時(shí)消抖
delay(15);
//再次讀取P3端口數(shù)據(jù),與temp_key對(duì)比,判斷按鍵值是否發(fā)生變化
if(temp_key == ((P3&0x0f)^0x0f))
{
//確定按鍵按下則不同按鍵顯示對(duì)應(yīng)的數(shù)字
switch(temp_key)
{
case 0x01://按鍵1
com1 = 0;//將第一位的com端設(shè)置為低電平
com2 = 1;
P0 = num_codelist[1];
break;
case 0x02://按鍵2
com1 = 0;//將第一位的com端設(shè)置為低電平
com2 = 1;
P0 = num_codelist[2];
break;
case 0x04://按鍵3
com1 = 0;//將第一位的com端設(shè)置為低電平
com2 = 1;
P0 = num_codelist[3];
break;
case 0x08://按鍵4
com1 = 0;//將第一位的com端設(shè)置為低電平
com2 = 1;
P0 = num_codelist[4];
break;
default:
break;
}
}
}
}
void delay(u8 ms)
{
u8 i,j;
for(i=0;i
}
程序功能設(shè)置為檢測(cè)到按鍵按下后數(shù)碼管就顯示對(duì)應(yīng)的按鍵數(shù)值,現(xiàn)在再來看一下仿真結(jié)果驗(yàn)證一下。待會(huì)在進(jìn)行代碼分析。
現(xiàn)在我們來分析程序的實(shí)現(xiàn)過程,一些以前說明過的程序就不做介紹了。主要講解關(guān)鍵程序段,如果有看不懂的可以留言。
u8 temp_key = 0; //定義一個(gè)變量用來存放臨時(shí)按鍵值。這里定義了一個(gè)變量來存放按鍵的臨時(shí)值,在第一次按鍵按下時(shí)用來存儲(chǔ)當(dāng)前按鍵值,在第二次檢測(cè)時(shí)用它與第二次掃描的按鍵值進(jìn)行對(duì)比判斷是否是真實(shí)按鍵操作。
if((P3&0x0f)^0x0f)這一行是讀取P3端口的數(shù)據(jù),&操作符時(shí)只獲取低4位數(shù)據(jù),^操作符是得到具體是哪個(gè)按鍵被按下。
temp_key = ((P3&0x0f)^0x0f); 這行是將當(dāng)前按鍵值暫存起來,后面使用時(shí)再調(diào)用。
delay(15);這一行是進(jìn)行延時(shí)消抖處理,避免干擾信號(hào)引起的誤判。
if(if(temp_key == ((P3&0x0f)^0x0f))== ((P3&0x0f)^0x0f))這一行是消抖后再次進(jìn)行鍵值判斷,如果當(dāng)前讀取到的端口數(shù)據(jù)與temp_key 一致說明當(dāng)前鍵值還是第一次檢測(cè)時(shí)的按鍵值,則說明確實(shí)有按鍵按下了,否則說明是干擾信號(hào)。
switch(temp_key)這里使用了switch語句,進(jìn)行處理不同鍵值按下時(shí)數(shù)碼管顯示對(duì)應(yīng)鍵值。
case 語句內(nèi)容就是依據(jù)對(duì)應(yīng)的按鍵值進(jìn)行相應(yīng)的顯示處理。
當(dāng)然這里也可以使用if判斷語句來處理,我這里使用switch語句是正好可以趁機(jī)會(huì)讓各位來溫習(xí)一下switch語句的用法。有興趣的朋友可以嘗試修改一下。
按鍵基礎(chǔ)程序就講解完了,當(dāng)然這里只是按鍵程序的最基礎(chǔ)例程,實(shí)際使用時(shí)根據(jù)具體需求做相應(yīng)修改。
51單片機(jī)外部中斷
上面鍵的按鍵程序大家應(yīng)該對(duì)著注釋和說明能夠看明白吧,但是如果讓程序一直掃描端口數(shù)據(jù)如果芯片還要處理其他事情那不就沒空處理了,這么使用是不是太浪費(fèi)芯片資源了呢?確實(shí),一般情況下我們做的產(chǎn)品不可能都是一直掃描按鍵吧,所以這時(shí)候我們就需要一些更好的辦法來解決這些資源浪費(fèi)的問題吧。這就是中斷系統(tǒng),在這里的具體表現(xiàn)就是程序中不需要一種掃描端口數(shù)據(jù),你按鍵按下了中斷系統(tǒng)檢查出來就將結(jié)果通知CPU,CPU在發(fā)出信號(hào),這時(shí)我們收到CPU的信號(hào)再進(jìn)行后續(xù)相應(yīng)的處理。如果沒按鍵按下CPU就去忙其他事情,有了這個(gè)中斷系統(tǒng)后CPU的工作效率就提高了吧!
前面介紹基礎(chǔ)內(nèi)容時(shí)簡(jiǎn)單說明了單片機(jī)中斷相關(guān)的知識(shí),但沒有講解中斷的具體使用方法,這節(jié)內(nèi)容我們一起來詳細(xì)了解一下51系列單片機(jī)的中斷控制器及其使用。
51系列單片機(jī)的中斷相關(guān)控制寄存器包括了中斷控制寄存器(Interrupt Enable register,IE)和中斷優(yōu)先級(jí)控制寄存器(Interrupt Priority register,IP),前者用于對(duì)單片機(jī)的中斷工作狀態(tài)進(jìn)行控制,后者用于對(duì)51單片機(jī)的中斷優(yōu)先級(jí)進(jìn)行控制。
以上兩個(gè)中斷寄存器是單片機(jī)所有中斷源的設(shè)置寄存器,當(dāng)然單片機(jī)還有其他一些中斷控制寄存器,TCON和SCON。TCON是外部中斷和定時(shí)器中斷的控制寄存器,SCON是串口輸入和輸出中斷控制寄存器。簡(jiǎn)單說這兩個(gè)寄存器就是使能這幾種中斷源的小開關(guān),開關(guān)打開之后各中斷事件才能被CPU獲取?,F(xiàn)在來認(rèn)識(shí)一下這幾一節(jié)內(nèi)容我們需要用到的TCON寄存器:
IT0:INT0 觸發(fā)方式控制位。
可由軟件進(jìn)行置位和復(fù)位,IT0=0,INT0 為低電平觸發(fā)方式,IT0=1,INT0 為下降沿觸發(fā)方式。
IE0:INT0 中斷請(qǐng)求標(biāo)志位。
當(dāng)有外部的中斷請(qǐng)求時(shí),這位就會(huì)置1(這由硬件來完成),在CPU 響應(yīng)中斷后,由硬件將IE0 清0,即不需要用指令來清0。
IT1:INT1 觸發(fā)方式控制位。
可由軟件進(jìn)行置位和復(fù)位,IT1=0,INT1 為低電平觸發(fā)方式,IT1=1,INT1 為下降沿觸發(fā)方式。
IE1:INT1 中斷請(qǐng)求標(biāo)志位。
當(dāng)有外部的中斷請(qǐng)求時(shí),這位就會(huì)置1(這由硬件來完成),在CPU 響應(yīng)中斷后,由硬件將IE1 清0。
TR0:T0 啟動(dòng)控制位。
TR0=1 時(shí),啟動(dòng)T0 工作;TR0=0 時(shí),T0 停止工作。
TF0:定時(shí)器T0 的溢出中斷標(biāo)志位。
當(dāng)T0 計(jì)數(shù)產(chǎn)生溢出時(shí),由硬件置位TF0。當(dāng)CPU 響應(yīng)中斷后,再由硬件將TF0清0。
TR1:T1 啟動(dòng)控制位。
TR1=1 時(shí),啟動(dòng)T1 工作;TR1=0 時(shí),T1 停止工作。
TF1:定時(shí)器T1 的溢出中斷標(biāo)志位。
當(dāng)T1 計(jì)數(shù)產(chǎn)生溢出時(shí),由硬件置位TF1。當(dāng)CPU 響應(yīng)中斷后,再由硬件將TF1清0。
外部中斷控制需要使用的就是其中的IE0和IE1這兩位,剩下的是與定時(shí)器相關(guān)的位,這將是我們下一節(jié)要講解的內(nèi)容。
現(xiàn)在市面上的51單片機(jī)擴(kuò)展了更多中斷源,肯定就會(huì)有更多其他的中斷相關(guān)的寄存器,所以使用時(shí)一定要參考芯片手冊(cè)上的寄存器內(nèi)容,不可照搬!但是使用方法都是類似的,根據(jù)數(shù)據(jù)手冊(cè)上的操作說明逐步配置相關(guān)寄存器就好,包括以后如果使用更復(fù)雜的單片機(jī)也是一樣的道理。
在51系列單片機(jī)中P3.2和P3.3兩個(gè)端口的第二功能就是外部中斷輸入端口,現(xiàn)在我們來看一下單片機(jī)內(nèi)部中斷控制系統(tǒng)處理機(jī)制:
從上圖可以看出TCON和SCON寄存器里面相當(dāng)于一個(gè)小開關(guān),直接決定各個(gè)中斷源的使用。IE寄存器中的前面幾位就像一個(gè)橋梁,決定現(xiàn)在你要接通哪些中斷源,而它的EA位就是一個(gè)總閘,決定是否把中斷信號(hào)發(fā)送給CPU。所以我們程序中配置中斷時(shí)這3個(gè)地方都有進(jìn)行配置,缺一不可!至于IP寄存器是用來確定中斷信號(hào)優(yōu)先級(jí)的,本來單片機(jī)內(nèi)部是有默認(rèn)的優(yōu)先級(jí)機(jī)制的,所以這個(gè)寄存器不設(shè)置也可以,它的作用就像皇帝身邊的小太監(jiān),大臣們上奏的奏折本來有規(guī)定的優(yōu)先順序,如果誰買通了它就可以給奏折換個(gè)順序。這么說這些寄存器之間的關(guān)系是不是就很好理解了?
現(xiàn)在再來了解一下CPU收到中斷之后的處理機(jī)制:
圖中硬件處理部分我們的程序是可以不用參與的,由CPU自動(dòng)完成(前提是中斷寄存器都配置好了),CPU處理完就會(huì)跳轉(zhuǎn)到我們的代碼段中執(zhí)行。
這里一定要注意我們使用C語言編程時(shí)編寫中斷函數(shù)的格式:
void your_fuction_name(void) interrupt 0 //外部中斷0服務(wù)例程
{
//中斷處理語句段
}
其中函數(shù)名可以自行確定一個(gè)合法,易懂的。interrupt 0 代表這是外部中斷0中斷源入口,其他中斷源就不會(huì)跳轉(zhuǎn)到這里來,這件相當(dāng)于各回各家,各找各媽。這是中斷函數(shù)中不可或缺的一部分,其他每種中斷源都有對(duì)應(yīng)的數(shù)字號(hào),可以自行查看中斷源入口地址表。另外值得注意的是,這些中斷函數(shù)不用聲明,也不可被調(diào)用,只要正確定義了編譯器編譯代碼時(shí)就能識(shí)別到它,這也是它與其他普通函數(shù)的區(qū)別。
現(xiàn)在再詳細(xì)了解一下我們的中斷程序中各部分的內(nèi)容。
關(guān)閉中斷與打開中斷:當(dāng)中斷被正確響應(yīng)后,如果中斷服務(wù)例程在執(zhí)行過程中,不想被更高級(jí)的中斷打斷。則可以在進(jìn)入中斷服務(wù)例程后置EA=0,關(guān)閉所有中斷,或者關(guān)閉某些中斷,這樣可以保證中斷服務(wù)例程的順利執(zhí)行。在中斷服務(wù)例程結(jié)束時(shí),可以將關(guān)閉的中斷開啟,以便于單片機(jī)能夠接收新的中斷請(qǐng)求。
現(xiàn)場(chǎng)保護(hù)與恢復(fù)現(xiàn)場(chǎng):一般來說,在主程序和中斷服務(wù)程序中都會(huì)用到累加器和寄存器等。51單片機(jī)在中斷服務(wù)程序中使用這些寄存器的時(shí)候?qū)?huì)改變其中的內(nèi)容,再返回主程序的時(shí)候容易引起錯(cuò)誤。因此,在進(jìn)入中斷服務(wù)程序后,應(yīng)該首先將相應(yīng)的寄存器保存起來,即保護(hù)現(xiàn)場(chǎng);當(dāng)中斷服務(wù)例程結(jié)束時(shí),應(yīng)該將這些寄存器的內(nèi)容恢復(fù),即現(xiàn)場(chǎng)恢復(fù)。當(dāng)然在C51語言程序中,這部分工作是由編譯器自動(dòng)完成的。
中斷服務(wù)程序:這是我們需要在中斷中進(jìn)行操作的內(nèi)容,根據(jù)程序功能確定。
現(xiàn)在你頭腦中對(duì)外部中斷使用應(yīng)該有個(gè)大致的框架了吧?我們使用前面的仿真電路運(yùn)用單片機(jī)外部中斷來做一個(gè)按鍵加減數(shù)字的程序,可以自己動(dòng)手試試,下面參考一下我寫的測(cè)試程序:
/*
*這是一個(gè)按鍵的外部中斷處理程序
*目的是通過外部中斷監(jiān)測(cè)按鍵功能
*/
#include
#include
sbit com1 = P2^0; //定義數(shù)碼管com1引腳
sbit com2 = P2^1; //定義數(shù)碼管com2引腳
sbit key1 = P3^0;
sbit key2 = P3^1;
sbit key3 = P3^2;
sbit key4 = P3^3;
typedef unsigned char u8;
typedef unsigned int u16;
u8 temp_key = 0; //定義一個(gè)變量用來存放臨時(shí)按鍵值
u8 key_num = 0;//定義一個(gè)數(shù)字,用來顯示實(shí)時(shí)數(shù)值
u8 code num_codelist[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void delay(u8 ms);
void main()
{
IT0=1; //外部中斷0為下降沿觸發(fā)
IT1=1; //外部中斷1為下降沿觸發(fā)
EX0=1; //開EX0中斷
EX1=1; //開EX1中斷
EA=1; //開總中斷
//有了中斷服務(wù)程序,在主循環(huán)中可以什么都不用做,當(dāng)然你也可以讓它干一些事情
while(1)
{
//在循環(huán)中顯示當(dāng)前數(shù)字
com1 = 0;//將第一位的com端設(shè)置為低電平
com2 = 1;
P0 = num_codelist[key_num];
}
}
void delay(u8 ms)
{
u8 i,j;
for(i=0;i
}
void P3_2_key_func(void) interrupt 0 //外部中斷0服務(wù)例程
{
//將外部中斷0對(duì)應(yīng)的按鍵K3設(shè)置為按鍵加功能
if(0 == key3)
{
delay(15);
if(0 == key3)
{
if(key_num < 9)
{
key_num++;
}
else
{
key_num = 0;
}
}
}
}
void P3_3_key_func(void) interrupt 2 //外部中斷1服務(wù)例程
{
//將外部中斷0對(duì)應(yīng)的按鍵K4設(shè)置為按鍵減功能
if(0 == key4)
{
delay(15);
if(0 == key4)
{
if(key_num > 0)
{
key_num--;
}
else
{
key_num = 9;
}
}
}
}
現(xiàn)在我們還是來看一下仿真結(jié)果:
有了前面的文字說明這個(gè)程序應(yīng)該是很簡(jiǎn)單的吧,下面來我們一起來分析一下代碼含義:
u8 key_num = 0;這一句是定義個(gè)變量來存放實(shí)時(shí)的按鍵值,在按鍵中斷程序中將對(duì)這個(gè)變量進(jìn)行加減操作。
IT0=1;和IT1=1;這兩句是將兩外部中斷都設(shè)為下降沿觸發(fā)中斷。
EX0=1;和EX1=1;這兩句是使能兩外部中斷源。
EA=1;這一句是開啟總中斷。
中斷配置中需要將這3步都配置好才能生效。
while(1)這一句是就是主循環(huán)程序了,需要重復(fù)進(jìn)行的程序都可以放在這里。
void P3_2_key_func(void) interrupt 0 和void P3_3_key_func(void) interrupt 2 這兩個(gè)函數(shù)就是我們定義的中斷處理函數(shù),在函數(shù)內(nèi)部我們確定對(duì)應(yīng)的按鍵之后在if(){}else{}語句中對(duì)key_num 變量進(jìn)行循環(huán)加減操作。這樣主函數(shù)中就可以顯示當(dāng)前數(shù)值是多少了。
看完了整個(gè)程序是不是感覺非常簡(jiǎn)單呢?平時(shí)可以把自己的想法寫下來通過程序?qū)崿F(xiàn),看是否可以正確運(yùn)用。
矩陣按鍵
說完了按鍵和外部中斷內(nèi)容,我們的按鍵部分基礎(chǔ)知識(shí)就介紹完了,但是實(shí)際使用過程中我們還會(huì)碰到各種各樣的按鍵電路,這就需要我們運(yùn)用所學(xué)基礎(chǔ)知識(shí)進(jìn)行分析了。比如矩陣按鍵就是我們運(yùn)用按鍵的擴(kuò)展知識(shí),它是怎么樣的呢?
如圖所示的矩陣按鍵電路就是最常見的一種。你是不是想問它使用8個(gè)引腳是怎么實(shí)現(xiàn)的那么多按鍵檢測(cè)的呢?答案就是通過行列掃描,還記得前面講動(dòng)態(tài)數(shù)碼管顯示的內(nèi)容中怎么實(shí)現(xiàn)兩位數(shù)碼管同時(shí)顯示的?那里是快速進(jìn)行端口輸出數(shù)據(jù)及com引腳切換,這里也是差不多的道理,在程序中快速的進(jìn)行段端口數(shù)據(jù)掃描按鍵掃描將每個(gè)按鍵按下時(shí)產(chǎn)生的鍵值存儲(chǔ)起來對(duì)結(jié)果進(jìn)行對(duì)比就實(shí)現(xiàn)了。運(yùn)用這個(gè)原理8個(gè)引腳甚至還可以接更多的按鍵,但這只是作為一個(gè)擴(kuò)展知識(shí),平時(shí)使用不多,有興趣的朋友可以查閱一下資料了解一下。那么矩陣鍵盤具體是怎么實(shí)現(xiàn)功能的呢?不妨自己先動(dòng)手試試。
評(píng)論
查看更多