ARM中斷基礎知識

ARM中斷基礎知識

1、ARM內核工做模式
由於中斷會設計到ARM內核工做模式的切換,因此先簡要介紹一下各個模式:
ARM模式的切換要設計到寄存器CPSR,下面是各個位表示的含義,CPSR[4:0]是工做模式切換控制位
T=0時是ARM指令模式,T=1時是Thumb指令模式。
F=0時是容許FIQ,F=1是禁止FIQ
I=0時是容許IRQ,I=1是禁止IRQ
在開發板剛剛啓動起來的時候首先得關閉全部中斷,等把開發板的硬件初始化,各類參數設置好以後就能夠打開中斷了。
CPSR有4個8位區域:標誌域(F)、狀態域(S)、擴展域(X)、控制域(C)
C 控制域屏蔽字節( CPSR[7:0] )
X 擴展域屏蔽字節( CPSR[15:8] )
S 狀態域屏蔽字節( CPSR[23:16] )
F 標誌域屏蔽字節( CPSR[31:24] )
經常使用於MRS或MSR指令,用於cpsr中的值轉移到寄存器或把寄存器的內容加載到cpsr中.
如: MSR CPSR_c,#0xd3
ARM 內核工做模式的切換是要本身手動切換的, 設置CPSR 寄存器低5位進行切換(第五位始終是1)
cpsr[4:0]
處理器模式
英文表示
1,0000
用戶模式
usr
1,0001
快速中斷模式
fiq
1,0010
中斷模式
irq
1,0011
管理模式
svc
1,0111
停止模式
abt
1,1011
未定義
und
1,1111
系統模式
sys

CPU剛剛復位後的模式是管理模式(SVC),若是咱們寫純裸機代碼(不使用uboot),剛開始是在stepping stone中運行的,咱們在這個裏面得完成代碼的一些硬件的初始化,好比時鐘初始化,sdram初始化,nandflash初始化(從nandflash啓動)等等,而後把程序從nandflash中拷貝到sdram中運行,在這段啓動代碼中咱們免不了要初始化各類堆棧,並且是各個模式下的堆棧最好都提早設置好,這裏就要考慮各類因素了。
好比咱們是在OK6410平臺上面測試,當復位或者開機後處於8K 大小的stepping stone中,此時咱們設置堆棧是SVC下的堆棧,若是咱們要設置其餘模式下的堆棧的話咱們就必須手動的設置CPSR下的模式位,來跳轉到各個模式下設定好對應的堆棧。由於之後是要在sdram中運行了,因此這些堆棧應當設定爲sdram中的地址。OK6410中的sdram是256M的,因此咱們能夠從頂部開始設置,好比說切換到irq模式,把堆棧設置爲SP = 0x60000000 (由於S3C6410中內存起始地址是從0x50000000開始的,加上256M,即0x10000000,獲得棧頂爲0x60000000),而後切換到fiq模式下,把堆棧設置爲SP = 0x5f000000,一次類推。可是要注意的是,咱們還在stepping stone中運行的時候SVC的堆棧仍是得保持在片內內存的頂部即SP = 8*1024,直到要跳轉到sdram中運行的時候了才把SVC的堆棧設置到sdram中去。
有些人喜歡剛開始只設置SVC的堆棧(由於剛開啓的時候就是這個模式,因此必須設置一下),而不設置別的模式的堆棧,等到中斷髮生了以後,在中斷函數中設置堆棧的地址。我以爲這樣有些不妥,提早都設置好,之後中斷進來後就省去了每次設置堆棧的步驟,提升了效率。
中斷向量表:
如下代碼就是切換CPU工做模式的示例
;********** Begin init stact ***********/
;在6種模式下切換並設置堆棧指針
MRS R0,CPSR ;把CPSR讀取到R0
BIC R0,#0x1f ;低5位清零
LDR R1,=MODE_Fiq ;設置R1 爲0b10001,跳轉到fiq模式
ORR R0,R0,R1 ;R0和R1相或,設置低5位
MSR CPSR_c,R0 ;把R0的值從新賦值到CPSR
LDR SP,=Stact_Fiq ;設置fiq的棧指針
BIC R0,#0x1f ;低5位清零
LDR R1,=MODE_Irq ;跳轉Irq模式
ORR R0,R0,R1
MSR CPSR_c,R0
LDR SP,=Stact_Irq ;設置irq的棧指針
BIC R0,#0x1f
LDR R1,=MODE_Svc ;跳轉到svc模式
ORR R0,R0,R1
MSR CPSR_c,R0
LDR SP,=Stact_Svc ;設置svc的棧指針
BIC R0,#0x1f
LDR R1,=MODE_Abort ;跳轉到abort模式
ORR R0,R0,R1
MSR CPSR_c,R0
LDR SP,=Stact_Abort ;設置abort的棧指針
BIC R0,#0x1f
LDR R1,=MODE_Undef ;跳轉到undef模式
ORR R0,R0,R1
MSR CPSR_c,R0
LDR SP,=Stact_Undef ;設置undef棧指針
BIC R0,#0x1f
LDR R1,=MODE_Sys ;跳轉到sys模式
ORR R0,R0,R1
MSR CPSR_c,R0
LDR SP,=Stact_Sys ;設置sys棧指針
與S3C2440相比,S3C6410增長中斷向量控制器,這樣在S3C2440裏須要用軟件來跳轉的中斷處理機制,在S3C6410中能夠徹底由硬件來跳轉。只要把ISR(中斷處理函數)地址存在連續向量寄存器空間,而沒必要像在S3C2440自行分配空間進行管理。換句話說,在S3C2440下是由cpu觸發IRQ/FIQ異常,由異常處理函數裏再查找相關中斷寄存器來跳到指定的ISR,而能夠所有由S3C6410的VIC硬件來自動處理。這樣就大大簡化了中斷處理編程。
另一變化是S3C6410外部中斷加入了濾波電路,這樣原來須要軟件去毛刺的地方都可以採用硬件來進行濾波了,這樣大大簡化了外部中斷處理。
 
S3C6410具備187個多功能IO端口,其中有127個用於外部中斷。這127個引腳能夠分爲10組:
外部中斷分組
對應GPIO
External interrupt Group 0
GPN0~GPN15 GPL8~GPL14 GPM0~GPM4
(16+7+5=28,因此EINT0PEND有28bit來識別這28箇中斷)
External interrupt Group 1
GPA0~GPA7 GPB0~GPB6
External interrupt Group 2
GPC0~GPC7
External interrupt Group 3
GPD0~GPD5
External interrupt Group 4
GPF0~GPF14
External interrupt Group 5
GPG0~GPG7
External interrupt Group 6
GPH0~GPH9
External interrupt Group 7
GPO0~GPO15
External interrupt Group 8
GPP0~GPP14
External interrupt Group 9
GPQ0~GPQ9
6410支持64種中斷源,即有64箇中斷號。INT_EINT0僅僅對應0號中斷,INT_EINT0又包含幾個中斷。
在VIC中,10組外部中斷佔用的中斷源狀況以下:
中斷號 中斷源 對應外部中斷 VIC組
0 INT_EINT0 External interrupt 0~3 VIC0
1 INT_EINT1 External interrupt 4~11 VIC0
32 INT_EINT2 External interrupt 12~19 VIC1
33 INT_EINT3 External interrupt 20~27 VIC1
53 INT_EINT4 External interrupt group1~group9 VIC1
舉個例子(OK6410的6個按鍵作外部中斷):
按鍵 對應引腳 對應外部中斷 中斷源
KEY1 GPN0 External interrupt 0 INT_EINT0
KEY2 GPN1 External interrupt 1 INT_EINT0
KEY3 GPN2 External interrupt 2 INT_EINT0
KEY4 GPN3 External interrupt 3 INT_EINT0
KEY5 GPN4 External interrupt 4 INT_EINT1
KEY6 GPN5 External interrupt 5 INT_EINT1
KEY1~KEY4都是屬於INT_EINT0,即都是屬於中斷號爲0的中斷,這四個按鍵產生的外部中斷所調用的中斷處理函數的地址都是存在VIC0VECTADDR0寄存器中的,這四個隨便哪一個按下都會調用同一個中斷處理函數,因此在處理函數裏面必須判別是哪一個按鍵所觸發的中斷。
中斷相關寄存器的設計演變(寄存器之外部按鍵中斷爲例)
IC
ARM7(4510)
IC
ARM9(2440)
IC
ARM11(6410)
內核
(core)
CPSR I-bit CPSR I-bit
CPSR I-bit
VIC Port(Enable)
VIC interface(PC <--> A0~A31)
中斷控制器
(IC)
INTMOD
INTPND
INTMSK
INTOFFSET
INTPRI
INTPND
INTMOD
INTMSK
SRCPND
VectADDRESS(32bit -> A0~A31,中斷函數地址的註冊 )
Vectors(handlers中斷處理函數)
Priority(優先級的判別)
VIC0IRQSTATUS/VIC0FIQSTATUS(IRQ/FIQ的中斷懸起位)
VIC0INTSELECT (選擇是IRQ仍是FIQ模式)
VIC0INTENABLE(MASK功能)
VIC0RAWINTR(顯示FIQ中斷是否置位,從EINT0PND上傳輸過來的)
中斷源控制器
(GPIO)
EINTCON
(F/R/L)
GPXCON
(EINT)
EINTCON
(F/R/L)
GPXCON
(EXIT)
INTMASK
EINT0PND (須要手動清除)
EINT0CON0 (Low/High level Falling/Rising/Both edge)
GPxCON (EINT)
硬件層 key/UART/USB/Timer key/UART/USB/Timer key/UART/USB/Timer
我用的是OK6410,分析一下最後一列:
最後一列從下到上,是從最外層一直到內核各個寄存器的順序,之外部key按鍵中斷爲例(GPN0~GPN5-->key1~key6)
中斷源 GPIO Controller
GPNCON [1:0] --> key1 Set the pin mux function as Ext.Interrupt[0]
00 = Input
01 = Output
* 10 = Ext. Interrupt[0]
11 = Key pad ROW[0]
EINT0CON0[2:0] --> EINT1,0 Sets the signaling method of Ext.Interrupt[0]
000 = Low level
001 = High level
* 01x = Falling edge triggered
10x = Rising edge triggered
11x = Both edge triggered
EINT0PEND[0]
0 = Not occur
1 = Occur interrupt
EINT0MASK[0]
* 0 = Enables Interrupt
1 = Masked
咱們這裏的中斷是使用中斷向量控制器VIC
如何測試和中斷相關的各個寄存器究竟是怎麼工做的,上下層的相互關係是怎麼樣的?
先看下面的關係圖(從下到上愈來愈接近cpu內核):
 
首先咱們不採用中斷方式來看各個寄存器的變化,咱們用查詢的方式查看,以按鍵中斷爲例,當有按鍵按下的時候,各個寄存器怎麼變化,相互之間的關係如何:
準備一個可以在串口輸出字符的程序。
OK6410的KEY1是接在 GPN0上面的,經過GPNCON把它設置成中斷方式,且經過EINT0CON0設置成降低沿觸發方式,代碼以下:
 
/* set GPNIO to EINT mode , 10 --> Eint */
temp = GPNCON ;
temp &= ~(0x3<<0);
temp |= (0x2<<0);
GPNCON = temp;

/* set EINT triger mode to falling eage ,01x = Falling edge */
temp = EINT0CON0 ;
temp &= ~(0x7<<0);
temp |= (0x3<<0);
EINT0CON0 = temp;
當按下KEY的時候,會觸發中斷,而且把這個信號傳送到最下層的中斷懸起位寄存器EINT0PEND,這個寄存器不一樣的bit對應不一樣的中斷。EINT0PEND的上層是中斷屏蔽寄存器INTMASK,當這個寄存器設置爲屏蔽的時候中斷觸發信號是沒法經過它傳送到上層去的。咱們先來看看當中斷髮生的時候EINT0PEND是如何變化的,他的變化會帶來怎麼樣的結果。
 
while (1)
{
    for(ch='a';ch<='z';ch++){
        if( (EINT0PEND & 0x1)==0x1){ //輪詢EINT0PEND有沒有被置位
              uart_putchar('+');
        }
        uart_putchar(ch);
        delay();
     }
}
 
上面的程序是在不停的查詢EINT0PEND[0],該bit對應的是外部中斷0,若是按下KEY產生一個降低沿,對應位會置位,if語句知足,會打印出一個「+」,而後出來打印字母,按鍵鬆開後是否還會知足if呢?看現象:
發現按下一次以後「+」會不停的打印出來,也就是說if語句都是知足的,這就意味着EINT0PEND置位以後沒有被清除,每次查詢都還有,因此會不停的打印加號,因此這個寄存器咱們必須手動清除,不然觸發一次中斷後就會不停的響應該中斷。
清除的方式就是向對應位寫1,咱們這裏是:
至於清除中斷懸起位的方式:
3種清0的寫法,只有最後一種是正確的清除。
PEND |= 1<<0;      (not good)
PEND = 0xFFFFFFFF; (not good)
PEND = 1<<0;       (Good!)
若是PEND寄存器某個bit是0,你寫一個1進去,那麼會變成1。只有當這位是1的時候寫個1進去纔會變成0.
第一種:用或的方式,若PEND = 00011111(二進制),PEND | (1<<0) = 00011111,也就是將00011111寫入到值爲00011111的PEND中,那麼PEND的值變成00000000,全部的位都被清0了,而不是咱們本來的意思要清除第一位。
第二種:直接賦值,其實和第一種方式是同樣的,只不過第一種是先求出或的值而後寫進寄存器,若是本來是00011111的PEND,寫進0xFFFFFFFF那麼PEND中本來是0的位仍是0,是1的位所有都清爲0 ,仍是不是咱們想要的結果。
第三種:PEND = 1<< 0 ,加入PEND是00011111,把00000001賦值進來,那麼獲得PEND的值爲00011110,恰好是咱們想要的,清除咱們想要清除的位。
下面咱們看看EINT0MASK的屏蔽做用是怎樣的效果:
首先看看EINT0MASK的描述信息:
 
能夠看出哪位置1就是屏蔽對應的中斷信號
/* EINT0MASK[0] = 1 : Mask EINT0 */
temp = EINT0MASK ;
temp &= ~(0x1<<0);
temp |= (0x1<<0);
EINT0MASK = temp;
屏蔽外部中斷0,看中斷觸發信號能不能經過它傳達到上面的寄存器VIC0RAWINTR。
看看VIC0RAWINTR的描述:
 
先不屏蔽中斷,看看可否在VIC0RAWINTR看到中斷信號:
/* EINT0MASK[0] = 0 : disMask EINT0 */
temp = EINT0MASK ;
temp &= ~(0x1<<0);
EINT0MASK = temp;

uart_init();
while (1)
{
    for(ch='a';ch<='z';ch++){
        if( (VIC0RAWINTR & 0x1)==0x1){
            uart_putchar('+');
        }
        uart_putchar(ch);
        delay();
    }
}
 
按下KEY,觸發中斷,發現現象以下(沒有清理EINT0PEND):
 
說明在 VIC0RAWINTR 檢測到中斷觸發信號了。
若是把EINT0MASK[0]設置爲1,即屏蔽信號,看看結果:
 
怎麼按KEY也檢測不到中斷觸發信號,說明EINT0MASK是這裏的一道關卡,中斷觸發信號可否傳到上級要看這裏有沒有屏蔽掉,它至關於一個開關做用。
繼續dismask中斷觸發信號,讓它傳遞到VIC0RAWINTR ,而後清除中斷懸起位,看現象是怎樣的:
/* EINT0MASK[0] = 0 : disMask EINT0 */
temp = EINT0MASK ;
temp &= ~(0x1<<0);
EINT0MASK = temp;

uart_init();
while (1)
{
    for(ch='a';ch<='z';ch++){
        if( (VIC0RAWINTR & 0x1)==0x1){
            uart_putchar('+');
            EINT0PEND = 0x1;
        }
        uart_putchar(ch);
        delay();
    }
}
 
按一下KEY打印出一個「+」,而後查詢VIC0RAWINTR 寄存器,發現中斷觸發信號沒有了,說明EINT0PEND的值直接影響到VIC0RAWINTR 的值,VIC0RAWINTR 跟隨EINT0PEND變化,EINT0PEND的變化實時反映到VIC0RAWINTR上來,固然得看MASK有沒有屏蔽掉中斷觸發信號啦。
 
信號接着往上面傳送,直到cpu內核檢測到中斷觸發信號併產生對應的操做爲止:
使用VIC(中斷向量控制器)
VIC0INTENABLE的描述:
 
對應的清除寄存器VIC0INTENCLEAR的描述:
 
VIC0SELECT的描述:
 
咱們這裏選擇IRQ方式,也能夠是FIQ方式。
VIC0IRQSTATUS的描述:
 
VIC0FIQSTATUS描述:
 
咱們選擇IRQ方式,而且使能中斷功能,看能不能到VIC0IRQSTATUS中查詢到中斷觸發信號:
void mymain(void){
unsigned char ch;
unsigned int temp=0;

/* set GPNIO to EINT mode , 10 --> Eint */
temp = GPNCON ;
temp &= ~(0x3<<0);
temp |= (0x2<<0);
GPNCON = temp;

/* set EINT triger mode to falling eage ,01x = Falling edge */
temp = EINT0CON0 ;
temp &= ~(0x7<<0);
temp |= (0x3<<0);
EINT0CON0 = temp;

/* EINT0MASK[0] = 0 : disMask EINT0 */
temp = EINT0MASK ;
temp &= ~(0x1<<0);
//temp |= (0x1<<0);
EINT0MASK = temp;

/* VIC0INTENABLE[0]=1 : enable interrupt */
/* clear bit by VIC0INTENCLEAR */
temp = VIC0INTENABLE ;
temp &= ~(0x1<<0);
temp |= (0x1<<0);
VIC0INTENABLE = temp;

/* VIC0INTSELECT[0] = 0 : set to IRQ */
temp = VIC0INTSELECT ;
temp &= ~(0x1<<0);
VIC0INTSELECT = temp;

uart_init();
while (1)
{
    for(ch='a';ch<='z';ch++){
        if( (VIC0IRQSTATUS & 0x1)==0x1){
            uart_putchar('+');
            EINT0PEND = 0x1;
        }
        uart_putchar(ch);
        delay();
    }
}
按下KEY出現下面的現象:
按一次打印一個「+」,說明
 
若是是把VIC0INTENABLE對應位設置成disable,那麼就會屏蔽掉中斷信號,這裏至關於又創建了一道關卡,它沒贊成就不能把信號傳達到上一層。
/* VIC0INTENABLE[0]=0 : disable interrupt */
/* clear bit by VIC0INTENCLEAR */
temp = VIC0INTENABLE ;
temp &= ~(0x1<<0);
VIC0INTENABLE = temp;
現象:
怎麼按KEY也不會打印出「+」,說明中斷信號被屏蔽了。
 
當信號傳達到這裏的時候還得繼續往上,還須要通過中斷優先級的寄存器,判別那個中斷優先級最高,這個隻影響到中斷的順序,不影響中斷信號的有無。可是在cpu內核的最近的地方還有個中斷的開關,是個總開關——CPSR的I/F-bit,默認是關閉的。這個一打開了就會把中斷信號送給cpu內核了。
打開最後這個總開關有兩個寫法,一個是在彙編裏面寫,一個是在c語言裏面的嵌入彙編裏面寫,咱們這裏在c語言潛入彙編來寫:
/* init CPSR I-bit */
//0101,0011
__asm{
    mov r0,#0x53
    msr cpsr,r0
}
可是編譯的時候有警告,說應該吧這個r0定義成一個變量:
 
解決方法:
聲明一個變量來代替寄存器
int val;
/* init CPSR I-bit */
//0101,0011
__asm
{
    mov val,#0x53
    msr cpsr,val
}
編譯警告就沒有了,這裏出現警告是由於R0可能被別的使用的,這裏使用會衝突,咱們定義一個變量來代替R0就好了。
可是編譯運行後發現仍是按下就有「+」打印出來,沒有什麼跳轉現象啊!
cpsr修改爲爲cpsr_cxsf或者CPSR_cxsf,再次編譯運行後,按下按鍵會發現,不打印了,停下來了:
 
說明cpu檢測到了中斷信號,跳出了原來運行的主函數,可是按理來講是要重啓的,這裏多是VIC沒有打開。下面介紹怎麼打開VIC
打開VIC:
int val2;
/* VIC Enable (cp15) */
__asm
{
    mrc p15,0,val2,c1,c0,0
    orr val2,val2,#(1<<24)
    mcr p15,0,val2,c1,c0,0
}
這樣cpu內核就能捕捉到中斷了。編譯運行,果真重啓了。
可是怎麼跳轉的,跳轉到哪裏去呢??
中斷髮生了以後怎麼辦?
接下來有2種處理方法:
A) 簡單的辦法就是使用VIC 向量中斷控制器的功能
一、跳轉的地址向量要提早設置好
二、通知ARM11內核,啓動VIC Port 功能
緊接着的問題是,如何在執行完beep以後返回主程序?(假設咱們的目的是蜂鳴beep)
緣由:beep程序不能做爲IRQ_handler
1)保存cpu現場STMFD
2)清除掉Pending bit,調用beep
3)恢復cpu現場LDMFD (LR-4)->PC ,SPSR -> CPSR
修改start.s,實現IRQ_handler
1)IRQ模式下的sp指針須要初始化
2)除了清除pending bit以外,還須要清除VIC0ADDRESS =0 ;
B)複雜的辦法就是不用VIC,本身實現全過程
一、當 IRQ 異常發生的時候,cpu 跳轉到 0x18
二、背景知識:reset 0 地址被映射 map 到 iROM (內容不可修改)
0 地址 在 iROM 中 (0xD0000000)
iRAM (0xD0020000) -> 0x20000 (iROM 被映射到了 0x20000)
經過 md 命令,查看相關內存單元值,發現 0x18: 0xEA000018
通過一系列分析,最終在 iROM 中的跳轉指令會加載從 0xD0037400 地址開始的值,
做爲異常發生後要跳轉的地址+offset ,所以只須要修改 0xD0037418 的向量便可。
三、(int)IRQ_handler -> 0xD0037400 + 0x18
若是是 SWI 軟件中斷,則在 0xD0037408 處填寫swi_handler的地址
第一種方法
當程序有中斷且cpu檢測到中斷的時候輪詢就沒有意義了,因此去掉輪訓。在主函數外面聲明一個handler函數:
void C_IRQ_handler(void)
{
    EINT0PEND = 0x1;
    uart_putchar(' ');
    uart_putchar('+');
    uart_putchar(' ');
}
那要怎麼樣才能調用到這個handler呢?
這就要註冊這個函數地址到向量地址裏面去了
/* VIC0VECTADDR[31:0] --> 0x71200100 : 0x7120017C */
/* Contains ISR vector addresses */
#define VIC0VECTADDR0 (*((volatile unsigned int *)0x71200100 ))
#define VIC1VECTADDR0 (*((volatile unsigned int *)0x71300100 ))
/* install IRQ handler to Vectors */
/* EINT0 --> VIC0VECTADDR[0] */
VIC0VECTADDR0 = (int)C_IRQ_handler;
這樣註冊後是否是就能跳轉到這個函數去了呢?固然不是!VIC默認是關閉的(uboot裏面可能打開VIC了,可是徹底裸機的話,VIC默認是關閉的,要本身手動打開)。上面手動打開了VIC了,這裏註冊好函數以後編譯運行,發現開發板仍是重啓,沒有進入到handler裏面來,什麼 緣由呢??
************************************************************************************************************
下面是在彙編裏面寫的handler,要作中斷sp的設置,返回地址lr的計算,現場保存,而後調用c語言裏面的handler,返回後要恢復現場:
import C_IRQ_handler
export asm_IRQ_handler
asm_IRQ_handler
ldr sp,=0x58000000
;lr=lr-4
sub r14,r14,#4

stmfd r13!,{r1-r12,r14}
bl C_IRQ_handler
ldmfd r13!,{r1-r12,pc}^
下面是c語言的handler,它要完成中斷懸起位的清除,中斷處理函數地址寄存器的清除,而後作別的事情:
void C_IRQ_handler(void)
{
    EINT0PEND = 0x1;//clear pending bit
    VIC0ADDRESS = 0;//clear address 
    uart_putchar(' ');
    uart_putchar('+');
    uart_putchar(' ');
}
這個程序直接下載到ram中運行,按下按鍵,中斷觸發就直接重啓開發板了,可是放在ads上去調試卻沒有什麼毛病可以進入中斷正常運行(代碼參考OK6410裸機調試筆記--> 6),不知道什麼緣由。
相關文章
相關標籤/搜索