對于單片機來講,不同的中斷源,產(chǎn)生什么類型的中斷信號能夠觸發(fā)申請中斷,取決于芯片內(nèi)部的硬件結(jié)構(gòu),而且通常也可以通過用戶的軟件來設定。
單片機的硬件系統(tǒng)會自動對這些中斷信號進行檢測。一旦檢測到規(guī)定的信號出現(xiàn),將會把相應的中斷標志位置“1”(在I/O空間的控制或狀態(tài)寄存器中),通知CPU進行處理。
● 中斷向量
中斷源發(fā)出的請求信號被CPU檢測到之后,如果單片機的中斷控制系統(tǒng)允許響應中斷,CPU會自動轉(zhuǎn)移,執(zhí)行一個固定的程序空間地址中的指令。這個固定的地址稱作中斷入口地址,也叫做中斷向量。中斷入口地址往往是由單片機內(nèi)部硬件決定的。
一個單片機有若干個中斷源,每個中斷源都有著自己的中斷向量。這些中斷向量一般在程序存儲空間中占用一個連續(xù)的地址空間段,稱為中斷向量區(qū)。由于一個中斷
向量通常僅占幾個字節(jié)或一條指令的長度,所以在中斷向量區(qū)一般不放置中斷服務程序的。中斷服務程序一般放置在程序存儲器的其它地方,而在中斷向量處放置一
條跳轉(zhuǎn)到中斷服務程序的指令。這樣,CPU響應中斷后,首先自動轉(zhuǎn)向執(zhí)行中斷向量中的轉(zhuǎn)移指令,再跳轉(zhuǎn)執(zhí)行中斷服務程序。
● 中斷優(yōu)先級
單片機系統(tǒng)一般有多個中斷源,當某一時刻同時有多個中斷產(chǎn)生時,單片機該如何處理呢?這就有了中斷優(yōu)先級的概念。
通常,單片機可以接收若干個中斷源發(fā)出的中斷請求。但在同一時刻,MCU只能響應這些中斷請求中的其中一個。為了避免MCU同時響應多個中斷請求帶來的混
亂,在單片機中為每一個中斷源賦予一個特定的中斷優(yōu)先級。一旦有多個中斷請求信號,MCU先響應中斷優(yōu)先級高的中斷請求,然后再逐次響應優(yōu)先級次一級的中
斷。中斷優(yōu)先級也反映了各個中斷源的重要程度,同時也是分析中斷嵌套的基礎。
對于中斷優(yōu)先級的確定,通常是由單片機的硬件結(jié)構(gòu)規(guī)定的。一般的確定規(guī)則方式為兩種:
實際上,MCU在兩種情況下需要對中斷的優(yōu)先級進行判斷:
第一種情況為同時有兩(多)個中斷源申請中斷。在這種情況下,MCU首先響應中斷優(yōu)先級最高的那個中斷,而將其它的中斷掛起。待優(yōu)先級最高的中斷服務程序執(zhí)行完成返回后,再順序響應優(yōu)先級較低的中斷。
第二種情況是當MCU正處于響應一個中斷的過程中。如已經(jīng)響應了某個中斷,正在執(zhí)行為其服務的中斷程序時,此時又產(chǎn)生一個其它的中斷申請,這種情況也稱作中斷嵌套。
● 中斷嵌套
對于中斷嵌套的處理,不同的單片機處理的方式是不同的,應根據(jù)所使用單片機的特點正確實現(xiàn)中斷嵌套的處理。
按照通常的規(guī)則,當MCU正在響應一個中斷B的過程中,又產(chǎn)生一個其它的中斷A申請時,如果這個新產(chǎn)生中斷A的優(yōu)先級比正在響應的中斷B優(yōu)先級高的話,就
應該暫停當前的中斷B的處理,轉(zhuǎn)入響應高優(yōu)先級的中斷A,待高優(yōu)先級中斷A處理完成后,再返回原來的中斷B的處理過程。如果新產(chǎn)生中斷A的優(yōu)先級比正在處
理中斷B的優(yōu)先級低(或相同),則應在處理完當前的中斷B后,再響應那個后產(chǎn)生的中斷A申請(如果中斷A條件還成立的話)。
一些單片機(如8051結(jié)構(gòu))的硬件能夠自動實現(xiàn)中斷嵌套的處理,既單片機內(nèi)部的硬件電路能夠識別中斷的優(yōu)先級,并根據(jù)優(yōu)先級的高低,自動完成對高優(yōu)先級中斷的優(yōu)先響應,實現(xiàn)中斷的嵌套處理。
而另一類的單片機,如我們正在學習的AVR單片機,其硬件系統(tǒng)不支持自動實現(xiàn)中斷嵌套的處理。如果在系統(tǒng)設計中,必須使用中斷嵌套處理,則需要由用戶編寫相應的程序,通過軟件設置來實現(xiàn)中斷嵌套的功能。
● 中斷控制(屏蔽)
單片機擁有眾多中斷源,但在某一具體設計中通常并不需要使用所有的中斷源,或者在系統(tǒng)軟件運行的某些關(guān)鍵階段不允許中斷打斷現(xiàn)行程序的運行,這就需要一套
軟件可控制的中斷屏蔽/允許系統(tǒng)。在單片機的I/O寄存器中,通常存在一些特殊的標志位用于控制開放或關(guān)閉(屏蔽)MCU對中斷響應處理,這些標志稱為中
斷屏蔽標志位或中斷允許控制位。用戶程序可以改變這些標志位的設置,在需要的時候允許MCU響應中斷,而在不需要的時候則將中斷請求信號屏蔽(注意:不是
取消),此時盡管產(chǎn)生了中斷請求信號,MCU也不會響應中斷請求。
從對中斷源的控制角度講,中斷源還可分成2類:
● 中斷響應條件
單片機在工作時,在每個機器周期都會查詢一下各個中斷源的中斷標記,從而判斷是否有中斷申請,如果中斷標志為1,說明有中斷請求發(fā)生。
綜合前面的介紹,我們可以知道,在單片機中,對應每一個中斷源都有一個相應的中斷標志位,該中斷標志位將占據(jù)中斷控制寄存器中的一位。當單片機檢測到某一中斷源產(chǎn)生符合條件的中斷信號時,其硬件會自動將該中斷源對應的中斷標志位置“1”,這就意味著有中斷信號產(chǎn)生了,向MCU申請中斷。
但中斷標志位的置“1”,并不代表MCU一定響應該中斷。為了合理控制中斷響應,在單片機內(nèi)部還有相關(guān)的用于中斷控制的中斷允許標志位。最重要的一個中斷允許標志位是全局中斷允許標志位。當該標志位為“0”,表示禁止MCU響應所有的可屏蔽中斷的響應。此時不管有否中斷產(chǎn)生,MCU不會響應任何的中斷請求。只有全局中斷允許標志位為“1”,才允許單片機響應中斷。
MCU響應中斷請求的第二個條件是每個中斷源所具有的各自獨立的中斷允許標志位。當某個中斷允許標志位為“0”時,表示MCU不響應該中斷的中斷申請。
從上面的中斷響應條件看出,只有當全局中斷允許標志位為“1”(由用戶軟件設置),中斷A允許標志位為“1”(由用戶軟件設置),中斷A標志位為“1”
(符合中斷條件時由硬件自動設置或由用戶軟件設置)時,MCU才會響應中斷A的請求信號(如果有多個中斷請求信號同時存在的情況下,還要根據(jù)中斷A的優(yōu)先
級來確定)。
用戶程序?qū)善帘沃袛嗟目刂?,一般是通過設置相應的中斷控制寄存器來實現(xiàn)的。除了設置中斷的響應條件,用戶程序還需要通過中斷控制器來設置中斷的其他特性,如:中斷觸發(fā)信號的類型、中斷的優(yōu)先級、中斷信號產(chǎn)生的條件等等。
●中斷響應過程(中斷服務程序)
當所有的中斷響應條件都滿足了之后,就要進入中斷響應過程進行相應處理了。單片機響應中斷后,首先要把當前指令的下一條指令的地址送入堆棧(保護斷點),
然后根據(jù)中斷標記,將相應的中斷入口地址送入程序指針,程序轉(zhuǎn)到中斷入口處繼續(xù)執(zhí)行(中斷服務程序),中斷程序執(zhí)行完后,單片機再把堆棧中保存的地址取
出,程序從剛才的中斷處繼續(xù)向下執(zhí)行。
需要注意的是,單片機硬件所做的保護工作只是保護了程序的一個指令地址,如果中斷響應過程中修改了一些寄存器和變量的值,就需要在中斷響應程序里面自己加以保護。
2、AVR單片機的外部中斷
AVR單片機有很多中斷,在后面的實例中我們會逐一介紹。本例中只介紹AVR單片機的外部中斷,
ATmega16有INT0、INT1和INT23個外部中斷源,分別由芯片外部引腳PD2、PD3、PB2上的電平的變化或狀態(tài)作為中斷觸發(fā)信號。
●外部中斷觸發(fā)方式和特點
INT0、INT1、INT2的中斷觸發(fā)方式取決于用戶程序?qū)CU控制寄存器MCUCR以及MCU控制與狀態(tài)寄存器MCUCSR的設定。其中,INT0和INT1支持4種中斷觸發(fā)方式:上升沿觸發(fā)、下降沿觸發(fā)、任意電平變化觸發(fā)、低電平觸發(fā)。
INT2支持上升沿觸發(fā)和下降沿觸發(fā)。
任意電平變化觸發(fā)表示只要引腳上有邏輯電平的變化就會產(chǎn)生中斷申請(不管是上升沿還是下降沿都引起中斷觸發(fā))。在這4種觸發(fā)方式中,還有以下的一些不同的特點:
●低電平觸發(fā)是不帶中斷標志類型的,即只要中斷輸入引腳PD2或PD3保持低電平,那
么將一直會產(chǎn)生中斷申請。
●MCU對INT0和INT1的引腳上的上升沿或下降沿變化的識別(觸發(fā)),需要I/O時鐘信號的存在(由I/O時鐘同步檢測),屬于同步邊沿觸發(fā)的中斷類型。
●MCU對INT2的引腳上的上升沿或下降沿變化的識別(觸發(fā)),以及低電平的識別(觸發(fā))是通過異步方式檢測的,不需要I/O時鐘信號的存在。因此,這
類觸發(fā)類型的中斷經(jīng)常作為外部喚醒源,用于將處在Idle休眠模式,以及處在各種其它休眠模式的MCU喚醒。這是由于除了在空閑(Idel)模式
時,I/O時鐘信號還保持繼續(xù)工作,在其它各種休眠模式下,I/O時鐘信號均是處在暫停狀態(tài)的。
●如果使用低電平觸發(fā)方式的中斷作為喚醒源,將MCU從掉電模式(Power-down)中喚醒時,電平拉低后仍需要維持一段時間才能將MCU喚醒,這是
為了提高了MCU的抗噪性能。拉低的觸發(fā)電平將由看門狗的時鐘信號采樣兩次(在通常的5V電源和25℃時,看門狗的時鐘周期為1μs)。如果電平拉低保持
2次采樣周期的時間,或者一直保持到MCU啟動延時(start-up
time)過程之后,MCU將被喚醒并進入中斷服務。如果該電平的保持時間能夠滿足看門狗時鐘的兩次采樣,但在啟動延時(start-up
time)過程完成之前就消失了,那么MCU仍將被喚醒,但不會觸發(fā)中斷進入中斷服務程序。所以,為了保證既能將MCU喚醒,又能觸發(fā)中斷,中斷觸發(fā)電平
必須維持足夠長的時間。
●如果設置了允許響應外部中斷的請求,那么即便是引腳PD2、PD3、PB2設置為輸出方式工作,引腳上的電平變化也會產(chǎn)生外部中斷觸發(fā)請求。這一特性為用戶提供了使用軟件產(chǎn)生中斷的途徑。
(3)與外部中斷相關(guān)的寄存器和標志位
在ATmega16中,除了寄存器SREG中的全局中斷允許標志位I外,與外部中斷有關(guān)的寄存器有4個,共有11個標志位。分別是:MCU控制寄存器—MCUCR、MCU控制和狀態(tài)寄存器—MCUCSR、通用中斷控制寄存器—GICR、通用中斷標志寄存器—GIFR。其作用分別是3個外部中斷各自的中斷標志位、中斷允許控制位和用于定義外部中斷的觸發(fā)類型。
具體寄存器各個標志位的意義和如何設置,請查閱相關(guān)ATmega16的數(shù)據(jù)手冊,在此不做過多描述。
需要注意的是:在系統(tǒng)程序的初始化部分中對外部中斷進行設置時(定義或改變觸發(fā)方式),應先將GICR寄存器中該中斷的中斷允許位清零,禁止MCU響應該中斷后再設置ISCn位。
而在開放中斷允許前,一般應通過向GIFR寄存器中的中斷標志位INTFn寫入邏輯“1”,將該中斷的中斷標志位清除,然后開放中斷。這樣可以防止在改變ISCn的過程中誤觸發(fā)中斷。
3、按鍵電路
按鍵電路與上一實例相同,在此略去。
4、外部中斷程序的編寫
我們已經(jīng)知道,要實現(xiàn)中斷程序,首先要在主程序里面對相關(guān)中斷寄存器進行中斷產(chǎn)生條件的設置。然后就是編寫中斷服務程序。
本例中中斷寄存器的設置如下:
MCUCR |= (1 << ISC11) | (1 << ISC01) | (1 << ISC00);
//INT0設置為上升沿中斷,INT1為下降沿中斷請求
GICR |= (1 << INT0) | (1 << INT1); //允許INT0、INT1中斷
GIFR |= (1 << INTF1) | (1 << INTF0); //清除INT0、INT1中斷標志位
sei(); //使能全局中斷
中斷服務程序的編寫具有一定的格式,在不同編譯環(huán)境下各不相同,在WINAVR(GCC)環(huán)境下有兩種方式,分別是:
● SIGNAL(中斷向量名)
{
… //中斷服務程序內(nèi)容
}
● ISR(中斷向量名 )
{
… //中斷服務程序內(nèi)容
}
在這兩種方式中,需要分別添加頭文件:#include <avr/signal.h>和#include <avr/interrupt.h>。
宏INTERRUPT 的用法與SIGNAL 類似,區(qū)別在于SIGNAL 執(zhí)行時全局中斷觸發(fā)位被清除、其他中斷被禁止;INTERRUPT 執(zhí)行時全局中斷觸發(fā)位被置位、其他中斷可嵌套執(zhí)行。
另外avr-libc 提供兩個API 函數(shù)用于置位和清零全局中斷觸發(fā)位,它們是經(jīng)常用到的,
分別是:void sei(void) 和void cli(void) 由interrupt.h定義
在本實例中,我們采用包含頭文件#include <avr/interrupt.h>,的方式,使用ISR(中斷向量名 ){…}來編寫中斷函數(shù)。
2.2.3 電路
本實例的按鍵電路如圖2.2.1所示,數(shù)碼管接口電路與實例1.3中的電路相同,在此不再給出。
圖2.2.1 按鍵電路
1、電路原理
本實例用K1、K2兩個按鍵分別連接到單片機的PD2、PD3端口,PD2、PD3同時也是單片機的外部中斷INT0、INT1的兩個引腳。當按鍵按下時,外部中斷INT0、INT1的兩個引腳的電平發(fā)生變化,從而產(chǎn)生外部中斷。
2、元器件選擇
在這里列出和本例相關(guān)的、關(guān)鍵部分的器件名稱及其在電路中的作用。
● ATmega16:單片機,檢測按鍵按下情況并控制數(shù)碼管顯示數(shù)字。
●數(shù)碼管:顯示按鍵狀態(tài)。
● R10:阻值為10K的電阻,下拉限流電阻。
● K1、K2:按鍵,當按鍵按下時,與按鍵連接的單片機端口的電平發(fā)生變化,產(chǎn)生外部中斷。
3、管腳連接
在這里列出和本例相關(guān)的、關(guān)鍵部分的單片機端口與外圍電路的連接。
● PB0-PB7:連接數(shù)碼管的8個段,控制數(shù)碼管的顯示。
● PD2、PD3:連接按鍵K1、K2,檢測兩個按鍵的狀態(tài)。
●PC6:數(shù)碼管選通端口,該端口通過三極管9013控制數(shù)碼管的選通,當PC6輸出高電平時,數(shù)碼管選通。
2.1.4 程序設計
1、程序功能
程序的功能是控制一個8段數(shù)碼管顯示“0”-“F”16個十六進制的數(shù)字。當系統(tǒng)上電時,顯示“0”。K1鍵的作用是加“1”控制鍵:按1次K1鍵,顯示
數(shù)字加1,依次類推。當?shù)?5次按K1鍵時,顯示“F”,第16次按K1鍵,顯示又從“0”開始。K2鍵的作用是減1控制鍵:按1次K1鍵,顯示數(shù)字減
1,減到“0”后,再從“F”開始。
● 按鍵開關(guān)的軟件去抖和釋放
上一個實例已經(jīng)講過這種方法,本例不再重復。
● 單片機外部中斷的編程
在本例中,需要使用單片機的INT0、INT1,所以在程序中需要對相應的寄存器進行設置,并且編寫中斷服務程序。
● 控制1位數(shù)碼管的顯示
在INT0中斷服務程序中,當按鍵K1按下一次,數(shù)碼管顯示的數(shù)字加1;在INT1的中斷服務程序中,當按鍵K2按下一次,數(shù)碼管顯示的數(shù)字減1。
2、主要變量和函數(shù)說明
本例中需要編寫中斷函數(shù),函數(shù)的功能是:當有外部中斷發(fā)生時,程序跳轉(zhuǎn)到中斷函數(shù)執(zhí)行數(shù)碼管顯示的程序,處理完后跳回主程序繼續(xù)執(zhí)行。
程序中用到變量Counter和 Disp_Buff[16] 。變量Counter用于指示按鍵按下的次數(shù),數(shù)組 Disp_Buff[16]存放數(shù)碼管顯示的字形編碼。
3、使用WINAVR開發(fā)環(huán)境,makefile文件同前面的例子,直接復制到本實例程序的文件夾中即可。
4、程序代碼
[code="c"]
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> //中斷函數(shù)頭文件
unsigned char Disp_Buff[16] = {0xaf,0xa0,0xc7,0xe6,0xe8,0x6e,0x6f,0xa2,
0xef,0xee,0xeb,0x6d,0x0f,0xe5,0x4f,0x4b};
//數(shù)碼管字型碼表顯示:0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F
volatile unsigned char Counter; //按鍵按下次數(shù)變量,如果在中斷中調(diào)用全局變量,必須加
//volatile來定義,否則變量不會變化
int main(void)
{
PORTB = 0X00; //
DDRB = 0Xff; //
PORTC &= ~(1 << PC6); //配置數(shù)碼管0的位選通口為低電平,不導通數(shù)碼管
DDRC |= (1 << PC6); ///配置數(shù)碼管0的位選通口為輸出,選通數(shù)碼管0
PORTD = 0X08; //一定要使能K2的上拉電阻,否則會有干擾
DDRD = 0XF3; //K1、K2按鍵(PD2、PD3)設置為輸入端口
MCUCR |= (1 << ISC11) | (1 << ISC01) | (1 << ISC00);
//INT0設置為上升沿中斷,INT1為下降沿中斷請求
GICR |= (1 << INT0) | (1 << INT1); //允許INT0、INT1中斷
GIFR |= (1 << INTF1) | (1 << INTF0); //清除INT0、INT1中斷標志位
Counter = 0; //按鍵按下次數(shù)變量清零
PORTC |= (1 << PC6); //選通數(shù)碼管0
sei(); //使能全局中斷
while(1)
{
PORTB = Disp_Buff[Counter]; //數(shù)碼管顯示按鍵按下次數(shù)
}
}
//外部中斷0函數(shù),當按鍵K1按下后,進入此中斷
ISR(INT0_vect )
{
_delay_ms(20); //按鍵按下,延時一會再判斷是否按下, 以消除干擾
if((PIND & (1 << PD2))) // 按鍵真正按下后,進行相應處理
{
if(++Counter >= 16) Counter = 0; //次數(shù)大于15,清零
while((PIND & (1 << PD2)));//等待按鍵釋放
}
}
//外部中斷1函數(shù),當按鍵K2按下后,進入此中斷
ISR(INT1_vect)
{
_delay_ms(20); //判斷按鍵按下,延時一會再判斷是否按下, 以消除干擾
if(!(PIND & (1 << PD3))) // 按鍵真正按下后,進行相應處理
{
if(Counter) --Counter; // 次數(shù)減1
else Counter = 15; // 次數(shù)為零則改成15
while(!(PIND & (1 << PD3))); //
}
}
在語言C語言編寫單片機程序過程,如果要使用外部中斷服務程序時,要盡量減少中斷服務程序的內(nèi)容和長度。因為在主程序中可能還要相應別的中斷,如果一個中斷服務程序過長,很可能會影響到主程序?qū)ζ渌袛嗟捻憫?br />
常用的處理方法是:在中斷服務程序中只改變變量的值,或者設置各種標志,在主程序里面對變量或標志進行判斷和處理。本實例為了演示的方便,在中斷程序中進
行了所有的操作。應該說明的是,這種方法是極不可取的。更為合理的方法是:在中斷服務程序里面只改變Counter的值。其余部分都放到主程序里面進行處
理。
另外需要特別強調(diào)的一點是,在用WINAVR編寫中斷服務程序時,如果中斷服務程序中用到了全局變量,則在定義全局變量時,必須在變量的數(shù)據(jù)類型前加
volatile來定義,否則該變量在中斷服務程序中不會變化。這是因為在用C語言編寫單片機程序時,都會用到編譯器的“優(yōu)化”代碼功能,以使程序更加簡
潔、緊湊。但是畢竟編譯器的優(yōu)化是很死板的,他會把一些對變量的讀操作優(yōu)化掉。這樣就導致在全局中斷中使用的變量被優(yōu)化成一個靜止變量,即該變量的值不再
改變。所以我們要把這些變量定義為volatile,意思是提示編譯器:該變量是很容易變化的,不準對該變量的讀取進行優(yōu)化。這樣在中斷中每次對變量的讀
寫就都可以正確的執(zhí)行了。