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與蜂鳴器