INTERRUPT CONTROLLER
1,中斷的基本概念
CPU與外設之間傳輸數據的控制方式通常有3種:查詢方式,中斷方式和DMA方式。查詢方式的優點是硬件開銷小不需要額外的硬件支持只是通過軟件不斷的輪詢,使用起來也就比較簡單,但在此方式下,CPU要不斷地查詢外設的狀態,當外設未準備好時,CPU就只能循環的等待,不能執行其他程序,這樣就浪費了CPU的大量時間,降低了CPU的利用率,為了解決這個矛盾,通常采用中斷傳送方式,即當CPU進行主程序的操作時,外設的數據已經存入輸入端口的數據寄存器或者輸出端口的數據輸出寄存器已為空,則由外設通過接口電路向CPU發出中斷請求信號,CPU在滿足一定條件下,會暫停執行當前正在執行的主程序,然后轉入執行相應能夠進行輸入/輸出操作的中斷子程序,待輸入/輸出操作執行完畢之后,CPU再返回并繼續執行原來被中斷的主程序。這樣CPU就避免了把大量時間耗費在等待查詢狀態信號的操作上,使其工作效率得以大大提高。能夠向CPU發出中斷請求的設備或事件稱為中斷源,系統引入中斷機制后,CPU與外設(甚至多個外設)處于“并行”工作狀態,便于實現信息的實時處理和系統的故障處理。
(1)中斷響應
中斷源向CPU發出中斷請求,若優先級別是最高的,則CPU在滿足一定的條件時,可中斷當前程序的運行,保護好被中斷的主程序的斷點及現場信息,然后根據中斷源提供的信息,找到特定中斷服務子程序的入口地址,轉去執行新的程序段。
CPU響應中斷是有條件的,如內部要允許中斷,中斷未被屏蔽,關鍵一點CPU當前指令要執行完才回去響應中斷(只有在CPU執行完一條指令后才會停下當前的程序去響應中斷)等
(2)中斷服務子程序
CPU響應中斷以后,就會中止當前的程序,轉去執行一個中斷服務子程序,以完成為相應的設備服務,中斷服務子程序的一般結構為:
保護現場:由一系列壓棧指令完成,目的是為了保護那些與主程序中有沖突的寄存器也就是在中斷子程序中也需要用到的寄存器(如r0,r1,r2等),若中斷服務子程序中所使用的寄存器與主程序中所使用的寄存器沒有沖突,則這一步可省略。
中斷處理:中斷處理程序在檢查到相應的中斷源后,調用對應的中斷處理程序來執行。
恢復現場并返回:由一系列的出棧指令完成,與現場保護對應的,但要注意數據恢復的次序。
由于中斷服務子程序需要打斷主程序的執行,所以其處理應該及時完成,較長時間的延時會導致系統性能下降。
在進入正題之前,我想先把ARM920T的異常向量表(Exception Vectors)做一個簡短的介紹。ARM920T的異常向量表有兩種存放方式,一種是低端存放(從0x00000000處開始存放),另一種是高端存放(從0xfff000000處開始存放)。關于為什么要分兩種方式進行存放這點我將在介紹MMU的文章中進行說明,本文采用低端模式。ARM920T能處理有8個異常,他們分別是:
Reset,Undefined instruction,Software Interrupt,Abort (prefetch),Abort (data),Reserved,IRQ,FIQ
下面是某個采用低端模式的系統源碼片段:
_start:
b Handle_Reset
b HandleUndef
b HandleSWI
b HandlePrefetchAbort
b HandleDataAbort
b HandleNotUsed
b HandleIRQ
b HandleFIQ
…..
…
..
other codes
…
上面這部分片段一般出現在一個名叫“head.s”的匯編文件里,“b Handle_Reset”這條語句就是系統上電之后運行的第一條語句。也就是說這部分代碼的二進制碼必須位于內存的最開始部分(這正是低端存放模式),因為上電后CPU會從SDRAM的0x00000000處取第一條指令并執行。
Address Instruct
0x00000000: b Handle_Reset
0x00000004: b HandleUndef
0x00000008: b HandleSWI
0x0000000C: b HandlePrefetchAbort
0x00000010: b HandleDataAbort
0x00000014: b HandleNotUsed
0x00000018: b HandleIRQ
0x0000001C: b HandleFIQ
上面是該程序段在系統上電后加載到內存后的分布情況,我們可以看到每條指令占用了4個字節。
上電后,PC指針會跳轉到Handle_Reset處開始運行。以后系統每當有異常出現,則CPU會根據異常號,從內存的0x00000000處開始查表做相應的處理,比如系統觸發了一個IRQ異常,IRQ為第6號異常,則CPU將把PC指向0x00000018地址(4*6=24=0x00000018)處運行,該地址的指令是跳轉到“中斷異常服務例程”(HandleIRQ)處運行。以上就是我對異常向量表的一個簡單介紹。現在可以進入我們文章的主題 “中斷異常處理”,s3c2410的中斷分快中斷(FIQ)和普通中斷(IRQ),我們討論的重點是普通中斷(IRQ)。
s3c2410的中斷異常處理模塊總共由以下寄存器構成
SRCPND(SOURCE PENDING REGISTER)
INTMOD(INTERRUPT MODE REGISTER)
INTMSK(INTERRUPT MASK REGISTER)
PRIORITY( PRIORITY REGISTER)
INTPND(INTERRUPT PENDING REGISTER)
INTOFFSET(INTERRUPT OFFSET REGISTER)
SUBSRCPND (INTERRUPT SUB SOURCE PENDING)
INTSUBMSK (INTERRUPT SUB MASK REGISTER)
下面我將講解每個寄存器在一個中斷處理流程中所扮演的角色:
SRCPND/ SUBSRCPND這兩個寄存器在功能上是相同的,它們是中斷源引腳寄存器,在一個中斷異常處理流程中,中斷信號傳進中斷異常處理模塊后首先遇到的就是SRCPND/ SUBSRCPND,這兩個寄存器的作用是用于標示出哪個中斷請求被觸發。SRCPND的有效位為32,SUBSRCPND 的有效位為11,它們中的每一位分別代表一個中斷源。SRCPND為主中斷源引腳寄存器,SUBSRCPND為副中斷源引腳寄存器。
這里列舉出SRCPND的各個位信息:

INTMOD寄存器有效位為32位,每一位與SRCPND中各位相對應,它的作用是指定該位相應的中斷源處理模式(IRQ還是FIQ)。若某位為0,則該位相對應的中斷按IRQ模式處理,為1則以FIQ模式進行處理,該寄存器初始化值為0x00000000,即所有中斷皆以IRQ模式進行處理。(詳細請參考s3c2410操作手冊)。
INTMSK/ INTSUBMSK 寄存器為中斷屏蔽寄存器 ,INTMSK為主中斷屏蔽寄存器,INTSUBMSK為副中斷屏蔽寄存器。INTMSK有效位為32,INTSUBMSK有效位為11,這兩個寄存器各個位與SRCPND和SUBSRCPND分別對應。它們的作用是決定該位相應的中斷請求是否被處理。若某位被設置為1,則該位相對應的中斷產生后將被忽略(CPU不處理該中斷請求),設置為0則對其進行處理。這兩個寄存器初始化后的值是0xFFFFFFFF和0x7FF,既默認情況下所有的中斷都是被屏蔽的。
到目前為止我們總共講解了SRCPND,INTMOD,INTMSK,SUBSRCPND,INTSUBMSK
五個寄存器,在繼續講解PRIORITY寄存器之前我們先來看一張圖。

如上圖所示,SRCPND寄存器對應的32個中斷源總共被分為6個組,每個組由一個ARBITER(0~5)寄存器對其進行管理。中斷必須先由所屬組的ARBITER(0~5)進行第一次優先級判斷(第一級判斷)后再發往ARBITER6進行最終的判斷(第二級判斷)。ARBITER(0~5)這六個組的優先級已經固定,我們無法改變,也就是說由ARBITER0控制的該組中斷優先級最高(該組產生的中斷進行第一級判斷后永遠會以REQ0向ARBITER6傳遞過去)其次是ARBITER1, ARBITER2, ARBITER4, ARBITER4, ARBITER5.我們能夠控制的是某個組里面各個中斷的優先級順序。怎么控制?通過PRIORITY寄存器進行控制:]
以下是PRIORITY寄存器各個位的參數表

首先我們看到ARBITER2寄存器管理的該組中斷里包括了6個中斷,分別是INT_TIMER0,INT_TIMER1,INT_TIMER2,INT_TIMER3,INT_TIMER4,INT_UART2,她們的默認中斷請求號分別為REQ0,REQ1,REQ2,REQ3,REQ4,REQ5。我們先看PRIORITY寄存器中的ARB_SEL2,該參數由兩個位組成,初始值為00。從該表可以看出00定義了一個順序 0-1-2-3-4-5 ,這個順序就是這組中斷組的優先級排列,這個順序指明了以中斷請求號為0(REQ0)的INT_TIMER0具有最高的中斷優先級,其次是INT_TIMER1,INT_TIMER2…。假設現在ARB_SEL2的值被我們設置為01。則一個新的優先級次序將被使用,01對應的優先級次序為0-2-3-4-1-5,從中可以看出優先級最高和最低的中斷請求和之前沒有變化,但本來處于第2優先級的INT_TIMER1中斷現在變成了第5優先級。從ARB_SEL2被設置為00,01,10,11各個值所出現的情況我們可以看出,除最高和最低的優先級不變以外,其他各個中斷的優先級其實是在做一個旋轉排列(rotate)。為了達到對各個中斷平等對待這一目標,我們可以讓優先級次序在每個中斷請求被處理完之后自動進行一次旋轉,如何自動讓它旋轉呢?我們可以通過ARB_MODE2達到這個目的,該參數只有1個 bit,置1代表開啟對應中斷組的優先級次序旋轉,0則為關閉。事實上當該位置為1之后,每處里完某個組的一個中斷后,該組的ARB_SEL便遞增在1(達到11后恢復為00)。
現在我們另ARB_MODE2=1,ARB_SEL2=00,則當前ARBITER2的優先級順序為0-1-2-3-4-5,假設現在該組的1號中斷請求INT_TIMER1和2號中斷請求INT_TIMER2被同時觸發,CPU根據優先級判斷后決定先把INT_TIMER1中斷向ARBITER6進行發送(在ARBITER6做第最終優先級判斷),接著再向ARBITER6發送INT_TIMER2中斷。請注意,在INT_TIMER1被處理完畢后,該組中段的優先級次序被自動做了一次旋轉,旋轉后ARBITER2的優先級順序變為0-2-3-4-1-5。假設之后某個時刻該組的INT_TIMER1和INT_TIMER2又被同時觸發,則此時CPU優先處理的會是INT_TIMER2。若我們另ARB_MODE2=0,則改組的中斷優先級次序在任何情況下都不做任何改變,除非我們人為地重新設置了ARB_SEL2的值。
INTPND 寄存器可能是整個中斷處理過程中我們要特別注意的一個寄存器了,他的操作比較特別,先看一下該寄存器各位詳細功能列表

特別注意:每當某一個中斷被處理完之后,我們必須手動地把SRCPND/SUBSRCPND , INTPND三個寄存器中與該中斷相應的位由1設置為0,剛才我說INTPND的操作很特別,它的特別之處就在于對當我們要把該寄存器中某個值為1的位設置為0時,我們不是往該位置0,而是往該位置1。假設SRCPND=0x00000003,INTPND=0x00000001,該值說明當前0號中斷和1號中斷被觸發,但當前正在被處理的是0號中斷,處理完畢后我們應該這樣設置INTPND和SRCPND:
SRCPND=0x00000002 //位0被置為0
INTPND =0x00000001 //位0被置為0(方法是往該位寫入1)
INTOFFSET寄存器的功能則很簡單,它的作用只是用于表明哪個中斷正在被處理。下面是該寄存器各位詳細功能列表

現在我把整個中斷流程用一個圖加以說明

下面我用INT_TIMER0, INT_TIMER2和INT_UART0三個中斷完整地介紹一次中斷異常處理。首先我們得做幾個假設: 假設1:這三個中斷的屏蔽被取消。
假設2:PRIORITY寄存器中ARB_MODE2,ARB_MODE5皆為0,既不進行優先級的自動旋轉排序,任何時候
ARBITER2,ARBITER5控制的中斷組優先級次序分別為0-1-2-3-4-5和1-2-3-4。
假設3:這三個中斷皆為IRQ類型。
假設4:這三個中斷同時被觸發。
INT_TIMER0,INT_TIMER2和INT_UART0三個中斷被同時觸發,此時三個中斷信號流向SRCPND寄存器,使該寄存器中的第10位,12位,28位被置為1,中斷信號繼續向前流經INTMASK寄存器,這三個中斷都沒有被屏蔽,于是信號進一步流經INTMODE寄存器,這三個中斷皆為IRQ類型,故中斷信號繼續向前流向PRIORITY寄存器,經過優先級判斷,INT_TIMER0中斷信號使INTPND寄存器的第10位置1(INT_TIMER0優先級最高),此時INTOFFSET寄存器的值為10,CPU轉向相應的中斷服務例程進行處理。處理完畢后,我們的程序將INTPND和SRCPND的第10置為0,至此INT_TIMER0中斷處理完畢。此時SRCPND的第12位,28位仍為1(這兩個中斷請求未被處理),故他們會繼續被CPU已剛才描述的方式進行處理。 S3C2410X處理器的中斷處理與其他CPU的處理模式基本上是一致的,只是由于它引入了幾種不同的處理器模式,使中斷處理變得更加的容易,步驟如下: (1) 保存現場,當系統出現中斷時,處理器會首先保存現場,這一過程包括將主程序當前的PC值存入到LR中,保存當前的程序運行狀態也就是CPSR的值到SPSR中。 (2) 模式切換,當處理器完成現場保護后,就要進入到中斷模式,并將PC值設置為一個固定的值,也就是我們之前提到的通過從0x00000000開始查表查到,中斷處理是0x00000018,這也就是IRQ模式的中斷入口地址,在中斷模式下,有兩個獨立的寄存器r13,r14, 可以便于中斷程序使用自己特有的堆棧,但這樣就會產生一個問題,也就是中斷處理時堆棧溢出保護的問題,這需要認真的估計堆棧的大小,通常在中斷處理時也要盡量減少函數的調用層次,否則會有一些不可預知的錯誤。
(3) 獲取中斷源,所有的IRQ中斷都是從0x00000018開始執行,通常在該地址處放一條跳轉指令,進一步跳到中斷程序中,一般是如: b HandleIRQ,或 LDR pc,=0x30000018,也就是完成中斷三級跳的第一跳,從NAND FLASH跳到SDRAM的中斷入口。我們可以看到中斷入口的一部分匯編代碼: 0x00000018: LDR pc, =0x30000018 ......
0x30000018: b HandlerIRQ
HandlerIRQ:
sub sp,sp,#4 //將堆棧指針向上移動4個字節用來為中斷分發例程入口地址預留棧空間
stmfd sp!,{r0} //將r0保存到堆棧中sp自加4個字節
ldr r0, =HandleIRQ //將中斷分發例程入口地址的指針保存到r0中
ldr r0,[r0] //將中斷分發例程入口地址保存到r0中
str r0,[sp,#4] //將中斷分發入口地址保存到預留的堆棧空間中
ldmfd sp!,{r0,pc} //這個涉及到賦值的一個順序,堆棧中現在已經保存了兩個寄存器的值,最開始r0的初始值以及中斷分發例程入口地址值,這句代碼的意思也就是,從堆棧中取出中斷分發例程入口地址賦值給PC,sp自加4個字節指向第一次存放r0的位置,然后將該值取出來賦值給r0,所以sp的值最終不變,ldmfd的賦值順序是從右到左。sp完成自加運算。這條指令也實現了一個跳轉,因為將中斷分發入口地址賦值給了PC,所以會轉去執行中斷分發例程。這也就實現了中斷三級跳的第二跳。
在此有個前提條件,即必須在HandleIRQ地址處保存正確的分發例程入口地址,如使用下面代碼后,IsrIRQ就是中斷分發例程。
ldr r0, =HandleIRQ
ldr r1, =IsrIRQ
str r1, [r0]
IsrIRQ通常采用匯編語言編寫,通過讀取INIOFFSET寄存器獲取產生中斷的中斷源的偏移,然后根據該偏移值獲取該中斷對應向量表中的偏移,最后從中斷向量表中獲取到中斷服務函數入口地址,并跳轉到該地址處執行。 IsrIRQ: sub sp,sp,#4 //為保存PC預留棧區域 stmfd sp!,{r8-r9} //將r8,r9保存到堆棧中,sp對應自加8 ldr r9, =INTOFFSET //在某一時刻INTOFFSET寄存器中只存放在INTPND中的那個中斷的偏移值,將INTOFFSET地址保存到r9 ldr r9,[r9] //將該寄存器中存放的對應中斷的偏移地址取出來存放到r9中 ldr r8, =HandleEINT0 //HandleEINT0是中斷向量表的入口地址,每四個字節保存一個終端服務函數的入口地址。 add r8,r8,r9,lsl #2 //將r9獲取到的值左移2位也就是乘以4,然后加上中斷向量表的入口地址,結果保存到r8,此時r8也就是該中斷對應中斷函數入口地址的指針 ldr r8,[r8] //將該中斷函數入口地址保存到r8中 str r8,[sp,#8] //將r8保存到堆棧中 ldmfd sp!,{r8-r9,pc} //主要是將r8取出來賦值給pc,然后去執行對應的中斷服務函數。 IsrIR跳轉到相應的ISR執行后,必須由ISR在執行完畢后推出IRQ模式,因此必須需將該函數定義為中斷服務函數,若采用GNU編譯器,使用如下關鍵字:__attribute__((interrupt("IRQ"))) (4)處理中斷,在中斷程序中需要進一步獲取中斷源,即誰引發了中斷,然后通過查表獲取到相應的中斷的處理程序入口,并調用對應的函數。這一步在上面已經講敘過了。 (5) 中斷返回,恢復現場, 在返回時需要恢復處理器的模式,包括恢復中斷處理用到的所有寄存器,恢復被中斷的程序運行狀態CPSR,并跳轉到被中斷的主程序。RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成