關於gcc中__irq

文章來源:http://blog.csdn.net/yaozhenguo2006/article/details/7015245

中斷問題與完整工程測試
一. 中斷問題
       中斷對編寫程序非常的重要,所以程序對中斷處理的好壞將直接影響程序的優劣,對實時性要求較高的系統更是如此。對於ADS2.0,在編寫中斷處理程序的時候,只需要在程序前面加上"_irq"這個關鍵字,ADS就會自動爲我們保存中斷現場,等程序返回的時候自動恢復現場,細節無須我們關心。當然,也可以不加這個關鍵字,如果這樣就得自己保存與恢復中斷現場,考慮的問題就多了。arm-linux-gcc開發環境下,目前我還沒有發現類似“_irq”這樣的關鍵字可以通知編譯器自動處理中斷過程。因此,如果使用arm-linux-gcc編譯帶有中斷的程序,就必須自己處理中斷現場。除了保護中斷現場外,程序還需要做的就是設置正確的中斷向量表,使得每個中斷髮生時都能找到合適的跳轉地址。這裏我主要參考了2440init.S中的方法。有如下幾步:
(1)首先定義宏
  1. .macro Handler Addr  
  2.     sub     sp, sp, #4        
  3.     stmfd   sp!, {r0}      
  4.     ldr     r0, =\Addr      
  5.     ldr     r0, [r0]            
  6.     str     r0, [sp,#4]      
  7.     ldmfd   sp!, {r0,pc}      
  8. .endm  
    這個宏的名稱是Handler,作用是取地址Addr處存放的值,賦值給PC。
(2)宏展開
  1. HandlerUndef:  
  2.     Handler     HandleUndef  
  3. HandlerSWI:  
  4.     Handler     HandleSWI  
  5. HandlerPabort:  
  6.     Handler     HandlePabort  
  7. HandlerDabort:  
  8.     Handler     HandleDabort  
  9. HandlerIRQ:  
  10.     Handler     HandleIRQ  
  11. HandlerFIQ:  
  12.     Handler     HandleFIQ  
    這樣一來,發生中斷後,PC就會跳轉到HandleIRQ存放的地址處執行(假設是普通中斷)
(3)在HandlerIRQ處有這樣的代碼
  1. HandleIRQ:  
  2.     .word       IsrIRQ  
    HandleIRQ處存放的地址是IsrIRQ,所以發生中斷後,PC跳轉到IsrIRQ處執行
(4) IsrIRQ 是interrupt.S中的標號
  1. .globl IsrIRQ  
  2. IsrIRQ:  
  3.         @ Fix the return address  
  4.         sub lr,lr,#4  
  5.         stmfd sp!,{r0-r3,r12,lr}    
  6.       
  7. #define rINTOFFSET 0x4a000014     
  8.     LDR     R0, =rINTOFFSET  
  9.         LDR     R0, [R0]  
  10.          
  11.         LDR     R1, =HandleEINT0  
  12.         MOV     LR, PC                          @ Save LR befor jump to the C function we need return back  
  13.         LDR     PC, [R1, R0, LSL #2]                 
  14.       
  15.     ldmfd sp!,{r0-r3,r12,pc}^  
       這段程序就是中斷處理的核心代碼:保存中斷現場,然後跳轉到相應的中斷服務程序中,返回後恢復中斷現場。這是一種最簡單的中斷處理方式。程序的開始修正了程序返回地址,我們知道由於流水線的問題,arm體系結構中pc不是指向正在執行的指令,而是指向正在取址的指令,因爲中斷而保存在lLR寄存器中的就是正在執行的指令的後兩個指令,比如正在執行第n條指令,而cpu爲我們保存的是第n+2條指令,中斷返回後我們要執行第n+1條指令。一個指令四個字節,所以這裏LR減4正好是程序應該返回的地址。把這個地址壓入棧纔是正確的。修正完返回地址,就可以保存寄存器了。這裏保存的寄存器有r0-r3,r12,還有返回地址LR。這些寄存器的保存我是參考一些書上介紹的,這樣保存事實上也可以正常工作。但是我一直有一個疑問:在arm不同的模式下共用的寄存器不止r0-r3,r0-r7都是各個模式共用的寄存器。那麼爲什麼就保存r0-r3就可以了呢。保存完現場,然後通過rINTOFFSET寄存器找到究竟發生了哪個中斷。rINTOFFSET是中斷控制器的寄存器,發生中斷後,這裏記錄了具體中斷的偏移。所有的中斷都是有號碼的,我們可以利用這個號碼來實現中斷服務程序的跳轉。這裏讀出中斷號,左移2位也就是乘以4,加上HandleEINT0的值,最後將這個地址處存放的值賦值個pc,從而完成了中斷的跳轉。
        rINTOFFSET 是start.S中的標號,在start.S的最後有這麼一段代碼:
  1. .equ    ISR_BADDR,  0x33ffff00          
  2.   
  3. HandleReset:  
  4.     .word       (ISR_BADDR+4*0)  
  5. HandleUndef:  
  6.     .word       (ISR_BADDR+4*1)  
  7. HandleSWI:  
  8.     .word       (ISR_BADDR+4*2)  
  9. HandlePabort:  
  10.     .word       (ISR_BADDR+4*3)  
  11. HandleDabort:  
  12.     .word       (ISR_BADDR+4*4)  
  13. HandleReserved:  
  14.     .word       (ISR_BADDR+4*5)  
  15. HandleIRQ:  
  16.     .word       IsrIRQ    
  17. HandleFIQ:  
  18.     .word       (ISR_BADDR+4*7)  
  19.   
  20.   
  21. .globl HandleEINT0   
  22. .equ    HandleEINT0,        (ISR_BADDR+4*8)  
  23. .equ    HandleEINT1,        (ISR_BADDR+4*9)  
  24. .equ    HandleEINT2,        (ISR_BADDR+4*10)  
  25. .equ    HandleEINT3,        (ISR_BADDR+4*11)  
  26.        ........  
        HandleEINT0等於ISR_BADDR+4*8,也就是0x33ffff00+4×8,這個地址處就是0號中斷(也就是外部中斷0)服務程序入口地址。其他中斷入口地址都是在此基礎上加上相應的偏移值。在應用程序中我們只需要將相應的中斷服務程序入口地址保存在這裏,發生中斷的時候就會實現正確跳轉。
        在2440addr.h中,定義了一些宏,方便我們賦值。比如
  1. #define _ISR_STARTADDRESS   0x33ffff0  
  2.   
  3.   
  4. #define pISR_EINT0      (*(unsigned *)(_ISR_STARTADDRESS+0x20))  
  5. #define pISR_EINT1      (*(unsigned *)(_ISR_STARTADDRESS+0x24))  
  6. #define pISR_EINT2      (*(unsigned *)(_ISR_STARTADDRESS+0x28))  
  7. #define pISR_EINT3      (*(unsigned *)(_ISR_STARTADDRESS+0x2c))  
  8. #define pISR_EINT4_7            (*(unsigned *)(_ISR_STARTADDRESS+0x30))  
        注意_ISR_STARTADDRESS 與 ISR_BADDR是相同的,這就保證了跳轉的一致性。假如我們中斷服務程序爲irq_int0,在應用程序中我們只需要一條賦值語句就實現了中斷程序的裝載
  1. pISR_EINT0 = irq_int0  
        以上的中斷處理,在Nandflash啓動下是正常工作的,但是在Norflash啓動的情況下,用supervivi的D功能下載程序到內存中運行,程序是不能正確響應中斷的,在串口終端輸出奇怪的信息。原因是當中斷髮生時,cpu默認去0x00地址處取得中斷向量表,而我們的程序的中斷向量表在0x30000000處,地址0x00是norfalsh的地址空間,而這裏存放的是supervivi的代碼,所以發生找不到中斷向量的錯誤。但是在Nandflash啓動的情況下,cpu自動將我們代碼前4k拷貝到SRAM裏,當然也包括中斷向量表。在這種情況下,0x00處是SRAM地址空間,正確存放着中斷向量。
        對於下載到內存中運行這種情況,爲了實現正確的中斷跳轉。解決的辦法有兩種:第一種是將內存中的中斷向量表拷貝到0x00處,但是這種方法是不可行的。原因一:麻煩,我們知道在norflash啓動下,0x00是norflash的地址空間,雖然norflash讀時序很簡單,只需要普通的賦值就可以了。但是寫時序很複雜,需要複雜的時序操作。這樣一來就需要額外的代碼。原因二:有副作用,在norflash裏我們存放了supervivi,如果重寫norflash就會破壞supervivi,導致下一次norflash不能正確啓動。所以這種方法不可取,另一種方法就是通過mmu重映射的方式,將物理地址0x30000000開始處的一小段代碼,映射到0x00處,cpu取地址取的是虛擬地址,所以當中斷髮生的時候,cpu照常到0x00出取中斷向量,但是卻是取到0x30000000的內容,而這裏就是正確的中斷向量。我就是用這種方式實現的內存中中斷正常跳轉的。
        驗證代碼在我的資源裏:http://download.csdn.net/detail/yaozhenguo2006/3845811
        程序驗證的是按鍵中斷,每按下一個按鍵,串口終端都會提示按下的按鍵號
二. 完整工程驗證
        至此arm-linux-gcc編譯裸機程序的所有問題都解決了,但是真正能否用於工程還是個未知數。所以得找一個完整的工程驗證一下。因爲在ADS下我曾經移植過ucosii,如果將ucosii編譯通過,並且能夠正確運行,那麼就表明方法確實可以用。所以我進行了ucosii的移植,有了上面的基礎,移植就簡單多了,無非就是將arm彙編轉換成gnu彙編,c代碼基本上不用動,最後編寫合適的makefile就可以了。
        編譯完成的代碼在我的資源裏:http://download.csdn.net/detail/yaozhenguo2006/3845817
        將ucosii_mini2440.bin下載到mini2440中運行,會看到串口終端交替輸出hello world 和my friend,同時led燈在閃爍。這說明程序能正確運行,兩個任務互不影響的運行。如果要重新編譯只需要根據自己系統的情況將鏈接選項修改一下就可以了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章