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