ARM中斷學習一/[友善2440test中的中斷處理部分]

《 嵌入式開源網:www.embedos.org ,不可錯過的網站,有興趣的朋友還可以做版主,開源、共享、免費!——博主推薦 》

一直在看2440的中斷處理部分,不懂的實在太多了,百度到這篇文章,實在有聽君一席話,勝養十年豬的感覺啊,下面上文章:

中斷向量 
     b     HandlerIRQ     ;handler for IRQ interrupt 
很自然,因爲所有的單片機都是那樣,中斷向量一般放在開頭,用過單片機的人都會很熟悉,那就不多說了。

異常服務程序 
這裏不用中斷(interrupt)而用異常(exception),畢竟中斷只是異常的一種情況,

下面主要分析的是“中斷異常”說白了,就是我們平時單片機裏面用的中斷!!!所有由器件引起的中斷,例如TIMER中斷,UART中斷,外部中斷等等,都有一個統一的入口,那就是中斷異常 IRQ ! 然後從IRQ的服務函數裏面分辨出,當前究竟是什麼中斷,再跳轉到相應的中斷服務程序。這樣看來,ARM比單片機要複雜一些了,不過原理是不變的。

上面說的就是思路,跟着這個思路來接着分析。

HandlerIRQ 很明顯是一個標號,我們找到了 
HandlerIRQ HANDLER HandleIRQ

這裏是一個宏定義,我們再找到這個宏,看他是怎麼定義的:

MACRO 
$HandlerLabel HANDLER $HandleLabel

$HandlerLabel 
     sub     sp,sp,#4      ;decrement sp(to store jump address) 
     stmfd     sp!,{r0}      ;PUSH the work register to stack(lr does not push because it return to original

address) 
     ldr r0,=$HandleLabel ;load the address of HandleXXX to r0 
     ldr r0,[r0]      ;load the contents(service routine start address) of HandleXXX 
     str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack 
     ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR) 
MEND

用 HandlerIRQ 將這個宏展開之後得到的結果實際是這樣的

HandlerIRQ 
     sub     sp,sp,#4      ;decrement sp(to store jump address) 
     stmfd     sp!,{r0}      ;PUSH the work register to stack(lr does not push because it return to original

address) 
     ldr r0,=HandleIRQ ;load the address of HandleXXX to r0 
     ldr r0,[r0]      ;load the contents(service routine start address) of HandleXXX 
     str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack 
     ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)

至於具體的跳轉原理下面再說,     
好了,這樣的話就容易看的多了,很明顯,     HandlerIRQ 還是一個標號,IRQ異常向量就是跳轉到這裏執行的,這裏粗略看一下,應該是保存現場,然後跳轉到真正的處理函數,那麼很容易發現了這麼一句 ldr r0,=HandleIRQ ,沒錯,我們又找到了一個標號 HandleIRQ ,看來真正的處理函數應該是這個 HandleIRQ ,繼續尋找

     AREA RamData, DATA, READWRITE

     ^ _ISR_STARTADDRESS         ; _ISR_STARTADDRESS=0x33FF_FF00 
HandleReset      # 4 
HandleUndef      # 4 
HandleSWI         # 4 
HandlePabort # 4 
HandleDabort # 4 
HandleReserved # 4 
HandleIRQ         # 4

最後我們發現在這裏找到了 HandleIRQ ,^ 其實就是 MAP ,這段程序的意思是,從 _ISR_STARTADDRESS 開始,預留一個變量,每個變量一個標號,預留的空間爲 4個字節,也就是 32BIT,其實這裏放的是真正的C寫的處理函數的地址,說白了,就是函數指針 - -    這樣做的話就很靈活了 
     
接着,我們需要安裝IRQ處理句柄,說白了,就是設置處理函數的地址,讓PC指針可以正確的跳轉。 
於是我們在接着的找到安裝句柄的語句 
     
       ; Setup IRQ handler 
     ldr     r0,=HandleIRQ ;This routine is needed 
     ldr     r1,=IsrIRQ      ;if there is not ''''subs pc,lr,#4'''' at 0x18, 0x1c 
     str     r1,[r0]

說白了就是將 IsrIRQ 的地址填到 HandleIRQ對應的地址裏面,前面說了 HandleIRQ 放的是中斷處理的函數的入口地址,我們繼續找 IsrIRQ

IsrIRQ 
     sub     sp,sp,#4 ;reserved for PC 
     stmfd     sp!,{r8-r9} 
     ldr     r9,=INTOFFSET 
     ldr     r9,[r9] 
     ldr     r8,=HandleEINT0 
     add     r8,r8,r9,lsl #2 
     ldr     r8,[r8] 
     str     r8,[sp,#8] 
     ldmfd     sp!,{r8-r9,pc} 
     
要理解這個代碼,得先學學2440的中斷系統了,INTOFFSET存放的是當前中斷的偏移號,根據偏移就知道當前是哪個中斷源發生的中斷。


注意了,我們說的是中斷,而不是異常,看看原來的表是啥樣子的

     ^ _ISR_STARTADDRESS         ; _ISR_STARTADDRESS=0x33FF_FF00

HandleReset      # 4 
HandleUndef      # 4 
HandleSWI         # 4 
HandlePabort # 4 
HandleDabort # 4 
HandleReserved # 4 
HandleIRQ         # 4 
HandleFIQ         # 4

HandleEINT0         # 4 
HandleEINT1         # 4 
HandleEINT2         # 4 
HandleEINT3         # 4 
.......

可以看到,前面幾個是異常,從      HandleEINT0 就是 IRQ異常的向量存放的地方了,這樣就可以理解爲什麼上面 IsrIRQ 裏面裏面要執行那條指令 
     ldr     r8,=HandleEINT0 
     add     r8,r8,r9,lsl #2 
道理很簡單, HandleEINT0 就是所有IRQ中斷向量表的入口,在這個地址上面,加上一個適當的偏移量,INTOFFSET ,那麼我們知道現在,到底是哪個IRQ在申請中斷了。

至於具體怎麼跳轉的? 
首先,我們說了,HandleEINT0 開始的一段內存裏面,存放的就是中斷服務函數的函數指針,ARM的體系的話,每個指針變量就是佔4個字節,這裏就解釋了,爲什麼這裏爲每個標號分配了4個字節的空間,裏面放的就是函數指針!!!下面再看看怎麼跳轉,繼續看 IsrIRQ 裏面就實現了跳轉了 
     str         r8,[sp,#8] 
     ldmfd     sp!,{r8-r9,pc} 
其實最核心就是這兩句了,先查找到當前中斷服務程序的地址,將他放到 R8 裏面,然後出棧,彈出給PC那麼PC很自然就跳到中斷服務程序了。至於這裏的堆棧問題又是一個非常棘手的,需要好好的參透ARM的中斷架構,需要了解的可以自己仔細的閱讀 《ARM體系結構與編程》裏面說的很詳細。我們這裏的重點是研究怎麼跳轉。 
最後,我們看看在C代碼中是怎麼安裝終端向量的,例如看 按鍵的外部中斷,是怎麼具體設置的,參看/src/keyscan.c 裏面的代碼很簡單,裏面只有3個函數

KeyScan_Test 是按鍵測試的主函數 
Key_ISR 是按鍵中斷服務函數

在 KeyScan_Test裏面,我們發現了有這麼一句

     pISR_EINT0 = pISR_EINT2 = pISR_EINT8_23 = (U32)Key_ISR;

可以理解否? Key_ISR就是上面提到的按鍵中斷服務函數,函數的名字,代表的就是函數的地址!!!!
將中斷服務函數的地址,注意了,是地址,這是一個 U32型的變量。送到幾個變量,我們以pISR_EINT0 作爲例子,查看頭文件定義,在 2440addr.h 裏面找到

// Interrupt vector 
#define pISR_EINT0         (*(unsigned *)(_ISR_STARTADDRESS+0x20))

_ISR_STARTADDRESS有沒有似曾相識的感覺?沒錯,剛纔分析的彙編代碼裏面就提到了 
     ^ _ISR_STARTADDRESS         ; _ISR_STARTADDRESS=0x33FF_FF00

HandleReset      # 4 
HandleUndef      # 4 
......

對,地址就是這裏,然後 _ISR_STARTADDRESS+0x20 就是跳過前面的異常向量,進入IRQ中斷向量的入口,所以說到尾 
     pISR_EINT0 = (U32)Key_ISR; 
完成的操作就是,將 Key_ISR 的地址存放到 
HandleEINT0         # 4 
這個IRQ向量表裏面!!!!


當按鍵中斷髮生的時候,發生IRQ異常中斷當前PC值-4 保存到LR_IRQ裏面,然後執行 
b     HandlerIRQ 
然後是執行

HandlerIRQ 
     sub     sp,sp,#4      ; 預留一個用來存放PC地址 
     stmfd     sp!,{r0}      ; 保存R0,因爲下面使用了 
     ldr r0,=HandleIRQ ; 將HandleIRQ(服務程序)的地址裝載到R0 
     ldr r0,[r0] 
     str r0,[sp,#4] ; 保存到剛纔預留的地方 
     ldmfd sp!,{r0,pc} ; 彈出堆棧,恢復R0,並且將剛纔計算好的 HandleIRQ 地址彈出到 PC堆棧是向下生長的,所以 SUB SP,SP,#4 就相當於 PUSH XX,但是這個XX這個時候並沒有用,因爲這裏用的是強制移動 SP 指針實現的。然後得到服務程序的地址,再將這個值放回剛纔預留的棧的空位上面,最後就是POP出R0恢復,並且將剛纔得到的服務程序的地址送到 PC,那麼實現的效果就是跳轉到 HandleIRQ 裏面了。

接着看剛纔是怎麼安裝的HandleIRQ 的 
       ; Setup IRQ handler 
     ldr     r0,=HandleIRQ ;This routine is needed 
     ldr     r1,=IsrIRQ      ;if there is not ''''subs pc,lr,#4'''' at 0x18, 0x1c 
     str     r1,[r0] 
可以看出,這裏將 IsrIRQ 的地址的值保存到 HandleIRQ 中,也就是說,上面的 IRQ 服務程序,這個時候實際上就是指 IsrIRQ !

所以接着的事情就是轉移到 IsrIRQ 中執行:

IsrIRQ 
     sub     sp,sp,#4 ; 預留一個值來保存PC 
     stmfd     sp!,{r8-r9} 
     ldr     r9,=INTOFFSET ; 計算偏移量,下面解釋 
     ldr     r9,[r9] 
     ldr     r8,=HandleEINT0 
     add     r8,r8,r9,lsl #2 
     ldr     r8,[r8] 
     str     r8,[sp,#8] ; 因爲保存了2個寄存器R8 R9 ,所以SP下移了8位 
     ldmfd     sp!,{r8-r9,pc} ; 恢復寄存器,彈出到PC,同上面的一樣

怎麼保存,操作SP,跟最後彈出到PC的部分和上面的例子一樣,下面說說中間的計算部分計算偏移量,其實原理很簡單,首先 INTOFFSET 保存着當前是哪個IRQ中斷,例如 0代表着 HandleEINT0,1代表HandleEINT1 ..... 等等,這不是亂來,有一個表的,這個是由 S3C2440 的datasheet說的,自己可以去查看。 
然後得到 中斷處理函數的向量表,這個表的首地址就是 HandleEINT0,那麼很自然的想到,怎麼查表?那還不簡單?HandleEINT0 + INTOFFSET 不就完了?基地址加偏移量就得到表中某項了,當然,因爲這裏是中斷處理向量 
每一項佔用4個字節,所以用lsl #2處理一下,左移2位相當於乘以4,偏移量乘以4,這應該很好理解的。

我們這個例子找到的就是 HandleEINT0 ,將裏面的值讀出來,裏面放的是 HandleEINT0 服務函數的地址,這個地址怎麼來的?是在C程序裏面設置的。我們看 keyscan.c 程序,找到一個 void KeyScan_Test(void) 函數, 
裏面有這麼一句: 
pISR_EINT0 = pISR_EINT2 = pISR_EINT8_23 = (U32)Key_ISR; 
這裏是安裝了3個按鍵中斷服務程序,我們只關注 0號中斷,也就是 
pISR_EINT0 = (U32)Key_ISR; 
這句話什麼意思?先看看pISR_EINT0的定義,在 2440addr.h 中定義 
#define pISR_EINT0         (*(unsigned *)(_ISR_STARTADDRESS+0x20))

   看到沒有?_ISR_STARTADDRESS 不就是剛纔說的那個異常向量的入口地址?加上一個 0x20 之後實際上指向的,就是 HandleEINT0 !!!這麼說來,上面的意思就是,將 Key_ISR 處理函數的入口地址,送到 HandleEINT0 中。 
   再來看 Key_ISR ,這是一個典型的服務程序,加了_irq 作爲編譯關鍵字,告訴編譯器,這個函數是中斷服務程序 
得保存需要的寄存器,免得被破壞。具體可以參考 《ARM體系結構與編程》P283 頁的描述。

static void __irq Key_ISR(void) 

     ....... 
}

加上 _irq 關鍵字之後,編譯器就會處理好所有的保存動作了,並不需要多關心。但是這個是 ARM-CC 編譯器的關鍵字,GCC中並沒有這個東西,所以GCC處理中斷的時候最好還是自己保存一下。

到這裏爲止,整個中斷的過程就解釋完畢。

 

原文鏈接:http://blog.mcuol.com/User/xiaoxiaopig/article/20469_1.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章