1. 程式人生 > >OK6410開發板學習之外部中斷(按鍵點亮led和蜂鳴器)

OK6410開發板學習之外部中斷(按鍵點亮led和蜂鳴器)

中斷在嵌入式裡面是很常見的一個功能了。通過這個功能,可以讓CPU減輕很多負擔,不用不斷的查詢裝置的狀態。提高了CPU的效率。

中斷的大體過程如下:

    

中斷源檢測中斷訊號產生,然後將中斷訊號傳送給中斷控制器,中斷控制器判斷該中斷是否被遮蔽,從而決定該中斷訊號是否要傳送給CPU。中斷訊號傳送給CPU後,CPU對中斷進行處理,也就是呼叫中斷函式。上述過程,基本上是嵌入式的通過中斷處理過程,只是不同的嵌入式在這三部分配置有區別而已。

 S3C6410共有64箇中斷源。

 上圖是S3C6410的中斷控制器,這裡就關心紅色框部分。這兩個是中斷控制器,分別管理各自的32箇中斷。 如下圖:




上圖紅框中的就是今天我們需要關心的中斷部分(外部中斷)。

S3C6410共有127個外部中斷,被分成10組,分組及引腳對應情況如下:


分組對應中斷號如下:


從上圖我們知道,並不是每一個外部中斷引腳都分配了中斷號,因此,在中斷服務程式中,為了知道具體是哪一個中斷,還需要去查詢暫存器以知道是哪一個中斷產生。

學習過2440的朋友可能知道,2440的中斷的處理採用的是非向量方式,就是當中斷產生時,都跳轉到中斷異常去,然後這個中斷異常中,編寫程式,判斷是哪一個中斷產生,然後去執行對應的中斷處理程式。如下圖:


為了向下相容,S3C6410中斷處理有向量模式和非向量方式。在向量模式中,提前設定每個中斷對應的入口地址,這樣當中斷產生的時候,就不用跳轉到中斷異常去了,直接跳轉到對應的中斷程式去了。這樣中斷處理的效率就提高了。向量中斷模式如下圖:


明顯看出,中斷向量方式的效率要高。因此,在學習ok6410開發板的中斷時,我職無旁貸的選擇了向量中斷模式。

綜上,可以總結出S3C6410的外部中斷程式設計基本步驟如下

1、  設定外部管腳為中斷;

2、  設定中斷觸發方式;

3、  取消中斷遮蔽,使外部中斷不遮蔽;

4、  設定中斷濾波(可不設定,這裡忽略);

5、  使能外部中斷;

6、  設定中斷號的入口地址;

7、  設定中斷號的中斷選擇,是irq還是fiq,預設為是irq;


8、  開啟向量中斷方式並開啟全域性中斷;

9、  編寫中斷處理函式,中斷函式前和後要使用嵌入彙編,儲存環境和恢復環境。中斷處理後,要清除中斷掛起位和中斷執行地址。

接下來我們就以OK6410開發板為載體來逐步的分析外部中斷的實現步驟。OK6410開發板上共有6個使用者按鍵,我們接下來就通過這6個按鍵完成外部中斷,實現按鍵控制led和蜂鳴器的執行情況。

要想實現上述的控制功能,就得知道這些按鍵、led和蜂鳴器對應的引腳情況,這些可以通過檢視OK6410開發板原理圖得到。原理圖部分截圖如下:

使用者按鍵與晶片部分

led

     

蜂鳴器

    

從截圖上可以看出來按鍵對應的引腳是GPN0~5、led對應的引腳是GPM0~3、蜂鳴器對應的引腳是GPF15。需要用到的引腳已經確認,下面開始按步驟配置外部中斷。

1.配置外部引腳位中斷

通過檢視s3c6410手冊GPIO章節,我們找到GPN相關配置暫存器如下:


這三個暫存器我們只需設定GPNCON,詳細說明如下:


從上圖可知,要想將開發板使用者按鍵設定成外部中斷,只需要將GPNCON暫存器的GPN0~5這幾個位設為10即可。實現程式碼如下:

#define  GPNCON *((volatile unsigned long*)0x7f008830)           /* GPN控制暫存器 */
#define  GPNDAT *((volatile unsigned long*)0x7f008834)           /* GPN資料暫存器 此處未使用*/
#define  GPNPUD *((volatile unsigned long*)0x7f008838)           /* GPN上下拉配置暫存器 此處未使用*/

/* 按鍵初始化 
*  設定按鍵對應引腳為外部中斷模式
*/
void button_init(void)
{
	/*	方式1  移位
	GPNCON = (0b10 << 0) | (0b10 << 2) | (0b10 << 4) | (0b10 << 6) | (0b10 << 8) | (0b10 << 10);	
	*/
	/* 方式2 邏輯運算 */
	GPNCON &= (~0x00000aaa);	
	GPNCON |= 0x00000aaa;
}

2.設定中斷觸發方式

通過原理圖我們發現,按鍵部分是作了上拉處理的,所以這裡設定外部中斷觸發方式為下降沿觸發。接著在s3c6410手冊的GPIO章節中找到外部中斷相關暫存器說明部分,如下:



由GPNCON暫存器我們知道GPN0~5對應的是外部中斷分組0 ,所以這裡我們只需要配置EINT0CON0暫存器即可,EINT0CON0說明如下:


紅框中的位就是我們需要設定,從圖中可知,只需將對應為設定位010即可,程式碼如下:

/*interrupt registes*/
#define EXT_INT_0_CON       *((volatile unsigned int *)0x7f008900)        /* 外部中斷0~27配置暫存器 */

    EXT_INT_0_CON &= ~(0x00000222);   
    EXT_INT_0_CON |= 0x00000222;                                   /* 配置為下降沿觸發 */  

3.取消中斷遮蔽,使外部中斷不遮蔽

既然想要取消中斷遮蔽,那就得配置中斷遮蔽暫存器,該暫存器在s3c6410手冊的GPIO章節中找到外部中斷相關暫存器說明部分。因為這裡是外部中斷分組0的0~5,所以只需配置EINT0MASK暫存器(0x7f008920)的bit0~5,如下紅框部分:


從上圖可以知道,需要將EINT0MASK暫存器的bit0~5配置為0,程式碼如下:

#define EXT_INT_0_MASK      *((volatile unsigned int *)0x7f008920)        /* 外部中斷0~27遮蔽暫存器 */  

EXT_INT_0_MASK &= 0xffffffc0;                                   /* 取消遮蔽外部中斷 */

4.設定中斷濾波

濾波主要是消除毛刺干擾,需要配置濾波暫存器,如下圖:


程式碼如下“

#define EXT_INT_0_FLTCON0   *((volatile unsigned int *)0x7f008910)        /* 外部中斷組0濾波暫存器 */ 

EXT_INT_0_FLTCON0 = (0xff) | (0xff << 8) | (0xff << 16);        /* 設定外部中斷0~5的濾波*/

5.使能外部中斷

要想使能外部中斷,就得配置中斷使能暫存器VICxINTENABLE,又因為這裡是外部中斷分組0的0~5,根據s3c6410手冊中斷章節中斷源介紹部分可知,這裡只需要配置VIC0INTENABLE,如下紅框:




中斷使能暫存器VIC0INTENABLE地址及說明如下:



從上圖可知,只需將中斷使能暫存器VIC0INTENABLE的bit0~1設定為1即可。配置程式碼如下:

#define VIC0INTENABLE       *((volatile unsigned int *)0x71200010)        /* 中斷使能暫存器 */

 VIC0INTENABLE &= ~(0x00000003);                                /* 使能外部中斷*/  
 VIC0INTENABLE |= (0x00000003);

6.設定中斷號的入口地址

要想設定中斷的入口地址,就得找到中斷向量地址暫存器。通過上面的講解可以知道外部中斷組0的0~5中斷由中斷源0、1產生,隸屬於VIC0,所以這裡的中斷向量地址暫存器就是VIC0VECRADDR0和VIC0VECRADDR1,地址如下:


中斷向量地址暫存器說明如下:


所以,這裡只需要將自定義的中斷處理函式的地址賦給VIC0VECRADDR0和VIC0VECRADDR1即可,程式碼如下:

#define EINT0_3_VECTADDR    *((volatile unsigned int *)0x71200100)        /* 外部中斷0~3向量地址暫存器 */  
#define EINT4_7_VECTADDR    *((volatile unsigned int *)0x71200104)        /* 外部中斷4~7向量地址暫存器 */

EINT0_3_VECTADDR = (int)INT_TINT0_ISR;                         /* 中斷產生時,CPU就會自動的將VIC0VECTADDR0的值賦給VIC0ADDRESS並跳轉到這個地址去執 */  
EINT4_7_VECTADDR = (int)INT_EINT1_ISR;                         /* INT_TINT0_ISR  INT_TINT1_ISR即為中斷處理函式 */          

7.設定中斷號的中斷選擇,是irq還是fiq,預設為是irq

既然這裡說了預設是irq,那我們就是使用預設的中斷模式,不去進行設定。如果想要更改,配置中斷選擇暫存器即可,如下:


8.開啟向量中斷方式並開啟全域性中斷

開啟向量中斷方式需要作業系統控制協處理器(P15),而開啟全域性中斷需要操作狀態暫存器(CPSR)。別問我是怎麼知道的,我不會告訴你是uboot告訴我這麼做的。

開啟向量中斷方式:首先從arm11核心技術參考手冊中找到系統控制協處理器章節(3.2節),找到與開啟向量中斷方式相關的協處理器控制暫存器,如下:




根據協處理器控制暫存器介紹得知,只需將控制暫存器的bit24置1就行了,程式碼如下:

/* 開啟向量中斷方式 */
__asm__(
    "mrc p15,0,r0,c1,c0,0\n"
    "orr r0,r0,#(1<<24)\n"
    "mcr p15,0,r0,c1,c0,0\n"          
    : 
    :
     );

開全域性中斷:從arm架構參考手冊中找到程式狀態暫存器相關章節,如下:


紅框中這兩個位就是關於開啟中斷的操作為,具體說明如下:

arm11核心技術參考手冊中也有相關的介紹,如下:

設定程式碼如下:

    /*
    開啟全域性中斷(開總中斷)
    */
	__asm__(
    "mrs r0,cpsr\n"
    "bic r0, r0, #0x80\n"
    "msr cpsr_c, r0\n"            
    : 
    :
);

至此,有關外部中斷的初始化就完成,下面就可以進行中斷服務函式的設計了。

9.編寫中斷處理函式,中斷函式前和後要使用嵌入彙編,儲存環境和恢復環境。中斷處理後,要清除中斷掛起位和中斷執行地址

儲存環境:這裡需要嵌入彙編,程式碼如下:

/* 儲存壞境 */  
    __asm__(
    "sub lr, lr, #4\n"  
    "stmfd sp!, {r0-r12, lr}\n"       
    : 
    :
);

這是一個固定的程式碼,目的是為了儲存環境,將r0-r12,lr暫存器的值給壓入棧中。這裡有sub lr,lr,#4。將lr的值減去4。這個原因就要從ARM的流水線說起了。ARM採用流水線,取址,譯碼,執行。所以pc的值永遠是當前執行指令的地址+8。中斷跳轉的時候,會將pc的值給lr,pc的值為當前執行程式地址+8,lr的值就是pc的值。而返回的地址應該是執行階段的下一條地址,也就是當前執行程式地址+4,所以直接返回lr的值就不對了,應該返回lr-4的值。

恢復環境:這裡也需要嵌入彙編程式碼,如下:

    __asm__( 
    "ldmfd sp!, {r0-r12, pc}^ \n"       
    : 
    : 
);

這段程式碼也是固定的,目的是為了中斷執行完後,在將這些值返回給r0-r12,pc暫存器。這樣r0-r12暫存器的內容就恢復了,同時pc得到返回地址,就返回到中斷前的程式地址去了。

有關於儲存環境和恢復環境的程式碼。參考arm架構參考手冊的A2,6章節,如下:


中斷處理程式碼主要實現按鍵控制led和蜂鳴器執行狀態,按下按鍵s2,點亮led1,再按一次s2,關閉led1;依次類推,詳見程式碼:

/* 外部中斷0~3處理函式 */ 
	if((EXT_INT_0_PEND & (1<<0)) == (1 << 0))
    	    led_Toggle(1);
	if((EXT_INT_0_PEND & (1<<1)) == (1 << 1))
    	    led_Toggle(2);
	if((EXT_INT_0_PEND & (1<<2)) == (1 << 2))
    	    led_Toggle(3);
	if((EXT_INT_0_PEND & (1<<3)) == (1 << 3))
          led_Toggle(4);
/* 外部中斷4~7處理函式 */ if((EXT_INT_0_PEND & (1<<4)) == (1 << 4))     beep_Toggle();if((EXT_INT_0_PEND & (1<<5)) == (1 << 5)){     led_Toggle(0);     beep_Toggle();}

中斷處理完後,需要將中斷掛起位給清零。這裡,很簡單的將所有中斷位都給清零。然後再將中斷執行地址給清0

中斷位掛起暫存器如下:


中斷向量0地址暫存器如下:


程式碼如下:

    /* 清除中斷 */
    EXT_INT_0_PEND = ~0x0; 
    VIC0ADDRESS = 0; 

這樣,也就完成了S3C6410的外部中斷程式設計了。但是,此時的程式碼下載至Ok6410開發板並不能正常工作,那是因為中斷處理函式的程式碼是用c程式碼寫的,c程式碼需要什麼?需要棧啊。不然怎麼時間環境保護和恢復環境了。可能有人會問之前不是設定過棧嗎?怎麼這裡還需要設定了?那是因為之前設定的棧是SVC模式下的棧,而不是irq模式下的棧。不同模式,有自己的備份暫存器,其中,棧SP是每個模式都有自己的。所以需要設定下irq模式下的棧。程式碼也是比較簡單的。在之前的設定棧的彙編程式碼中,將模式切換為irq模式,再設定sp。

程式碼如下:

@棧初始化 64M記憶體用於棧
init_stack:
    msr cpsr_c, #0xd2
    ldr sp, =0x53000000    @ 初始化r13_irq
  
    msr cpsr_c, #0xd3
    ldr sp, =0x54000000    @ 將0x54000000寫入sp暫存器中,棧大小64M    0x5400000000-0x500000000
    mov pc, lr	           @ 返回呼叫處繼續往下執行

這樣再將程式碼編譯後下載至開發板裡就可以正常工作了。

程式碼下載地址:OK6410實現外部中斷控制led與蜂鳴器




5 、    設定中斷號的中斷選擇,是irq 還是fiq,預設為是irq;