一、介紹
在這篇文章中,我們以按鍵作為輸入器件對飛思卡爾XEP100單片機的GPIO的輸入功能進行測試。對應的硬體電路如下圖所示。
當按鍵未按下時,由于有上拉電阻R39~R312的作用,單片機檢測到的電平為高電平;當按鍵按下時,單片機引腳與地短接,單片機檢測到引腳為低電平。
當按鍵按下時,按鍵輸出端的原始電平如下圖所示:
由圖中可以看出,在按鍵按下和松開時,按鍵的電平信号存在波動,一般為10~15ms,這樣有可能導緻誤判。為了消除抖動,可行的方法有:硬體去抖和軟體去抖兩種方法。本文的電路上使用的方法為硬體去抖,即在按鍵的兩端加了一個去抖電容。軟體去抖的方法是在檢測到按鍵按下後,延時10~15ms,跳過抖動的這段信号,再進行檢測,如果是低電平則判斷按鍵按下。
本文的硬體電路的按鍵與單片機引腳的連接配接關系如下:
PTH3——KEY1
PTH2——KEY2
PTH1——KEY3
PTH0——KEY4
單片機的PH口是有中斷功能的I/O口,是以對按鍵狀态的讀入有查詢和中斷兩種方式。
所謂查詢方式是指,單片機以一定的時間間隔按時對輸入口的電平進行讀取。中斷是指計算機在執行程式的過程中,當出現異常情況或特殊請求時,計算機停止現在執行的程式,轉向對這些異常情況或特殊請求的處理,處理結束後再傳回之前的程式的間斷處,繼續執行原程式。中斷時單片機實時處理内部事件或外部事件的一種機制,當某種内部事件或外部事件發生時,單片機的中斷系統将迫使CPU暫停正在執行的程式轉而去執行中斷事件,中斷事件處理完畢後,再傳回被中斷的程式處,繼續執行下去。中斷機制如下圖所示。
在程式中使用中斷具有以下優點:
(1)提高了CPU的效率。CPU與外圍裝置的工作方式有查詢和中斷兩種方式,查詢方法是無論外圍裝置是否需要服務,CPU每隔一段時間都要依次查詢一遍,這種方法CPU需要花費時間做查詢工作。而中斷則是在外圍裝置需要服務時主動告訴CPU,CPU則停下目前的工作去進行中斷程式,進而可以提高CPU的效率。
(2)可以實作實時處理。外圍裝置任何時刻都可以送出請求中斷信号,CPU接到請求後及時處理,以滿足實時的需求。
(3)可以及時處理故障。單片機系統運作過程中難免會出現故障,有許多事情是無法預料到的。如:電源掉電、存儲器出錯、外圍裝置工作不正常等,這時可以通過中斷系統向CPU發送中斷請求,由CPU及時轉到相應的出錯處理程式,進而提高了系統的可靠性。
産生中斷時,中斷服務程式執行完畢後恢複單片機狀态,單片機從被中斷打斷的地方繼續執行。當某一中斷事件發生,與該事件相對應的标志将被置位。如果該中斷未使能,那麼單片機不會對該中斷作出反應。CCR寄存器中的全局中斷屏蔽位I在複位之後為1,屏蔽掉所有中斷事件。在使用者程式完成堆棧指針和其他設定之後,清除I,允許單片機響應中斷事件,程式中的 “EnableInterrupts;”語句就是實作使能中斷的功能。當單片機接收到一個有效的中斷請求,在響應之前,它會執行完目前運作的指令,然後逐周期執行以下過程:
儲存CPU寄存器到堆棧中
設定CCR寄存器中的I=1,禁止全局中斷
擷取目前所有挂起的待進行中斷事件中最高優先級中斷向量
預取中斷向量的位址排列到指令隊列中
當單片機響應中斷後,I自動置為1,避免中斷嵌套。通常,ISR執行完畢後,I恢複為0(CCR從堆棧彈出)。如果在ISR中,清除I(即設定I=0),那麼MCU有可能在目前中斷事件未完成之前響應其他中斷事件,即中斷嵌套。中斷嵌套可能對使用者程式調試帶來麻煩。
當ISR執行到RTI指令,CCR,A,X,PC從堆棧恢複。
使用者可在自己的中斷服務程式一開始将I清零,進而使能全局中斷。那麼目前運作的中斷服務程式可以被其他中斷打斷,MCU運作新的中斷服務程式,進而形成中斷嵌套。使用者在使用中斷嵌套時會對調試帶來不便,故建議避免使用中斷嵌套。在中斷服務程式的開始,使用者程式應清掉産生該中斷的标志位,其目的是同一中斷源産生另外一個中斷事件能夠被記錄,并在完成目前中斷服務程式之後能夠得到MCU的響應。
每一個中斷對應一個中斷向量。這個向量指向這個中斷的服務程式。用CodeWarrior程式設計時,在工程的“Project Settings\Linker Files”檔案夾下的“Project.prm”檔案中設定相應中斷的中斷向量。
二、查詢方式讀取按鍵
在這個實驗中,我們采用查詢方式對按鍵的狀态進行讀取,主要代碼如下,(完整代碼可以從本文的資源中下載下傳)
void main(void) {
DisableInterrupts;
init_led_key();
EnableInterrupts;
for(;;)
{
delay();
data=data<<1; //左移一位
if(data==0)
data=0x01;
if(KEY1==0&&KEY1_last==1) //按鍵F1按下
mode=1;
if(KEY2==0&&KEY2_last==1) //按鍵F2按下
mode=2;
KEY1_last=KEY1; //儲存F1的狀态
KEY2_last=KEY2; //儲存F2的狀态
if(mode==1)
LED = ~data;
else
LED = data;
}
}
在這段代碼中,發光二極管以流水燈的方式進行閃爍,每個周期經過delay();函數延時之後,讀取KEY1和KEY2的狀态,并且與上個周期的轉态進行比較,如果上個周期為1(沒有按下),這個周期為0(按鍵按下),則改變燈閃的模式,如果按下按鍵“KEY2”,則8個燈中隻有一個熄滅,并且熄滅的燈循環向右移動。按下按鍵“KEY1”,則8個燈中隻有一個點亮,并且點亮的燈循環向右移動。
三、中斷方式讀取按鍵
在這個實驗中,我們通過中斷來進行按鍵的讀取,為了使能中斷功能,首先要對按鍵進行初始化,代碼如下所示。
void init_key(void)
{
KEY1_dir =0; //設定為輸入
KEY2_dir=0;
KEY3_dir=0;
KEY4_dir=0;
PPSH = 0x00; //極性選擇寄存器,選擇下降沿;
PIFH = 0x0f; //對PIFH的每一位寫1來清除标志位;
PIEH = 0x0f; //中斷使能寄存器;
}
在這段代碼中,首先将每個按鍵的方向寄存器設定為輸入狀态,然後通過PPSH寄存器設定PH0~PH3的輸入極性為下降沿觸發中斷。PIFH = 0x0f; 将标志位清除,PIEH=0x0f; 将中斷功能使能。
這個程式的主函數代碼如下所示。
void main(void) {
DisableInterrupts;
init_led();
init_key();
EnableInterrupts;
for(;;)
{
delay(time);
if(direction==1)
{
data=data<<1; //左移一位
if(data==0)
data=0x01;
}
else
{
data=data>>1; //右移一位
if(data==0)
data=0x80;
}
LED = ~data;
}
}
這段代碼中,初始化了按鍵和LED,然後主循環中控制LED閃爍,LED的狀态根據time和direction進行變化,time決定了延時的長短,進而影響閃爍的快慢,direction影響燈閃爍的方向。
中斷的處理函數如下所示
interrupt void PTH_inter(void)
{
if(PIFH != 0) //判斷中斷标志
{
PIFH = 0xff; //清除中斷标志
if(KEY1 == 0) //按鍵1按下
{
time-=1;
if(time==0)
time=1;
}
if(KEY2 == 0)
{
time+=1;
if(time>10)
time=10;
}
if(KEY3 == 0)
direction=0;
if(KEY4 == 0)
direction=1;
}
}
由于中斷設定的是下降沿觸發中斷,當有按鍵按下時,會進入中斷函數,在中斷函數中,首先确認标志位,判斷是否确實有中斷發生,然後判斷是哪個按鍵按下,用KEY1和KEY2來改變time的值,用KEY3和KEY4來改變direction的值。
需要注意的是,要設定中斷向量,在工程的“Project Settings\Linker Files”檔案夾下的“Project.prm”檔案中設定,如下所示,
VECTOR ADDRESS 0xffcc PTH_inter
這個實驗的現象是,8個LED燈中有一個點亮,并且點亮的燈循環向右移動。按下按鍵“KEY1”,燈移動的速度加快;按下按鍵“KEY2”,燈移動的速度減慢;按下按鍵“KEY3”,燈向左移動;按下按鍵“KEY4”,燈向右移動。