TQ2440開發板學習紀實(6)--- 啟用IRQ中斷,告別低效的輪詢!
前面的實驗中,無論是按鍵的狀態,還是串列埠的傳送接收狀態,都是通過輪詢的方式進行讀取,不僅低效而且嚴重浪費CPU計算週期,在實際的生產環境下決不能使用!從本文開始,我們進進入一個新的時代,中斷處理。
0 中斷、異常、軟中斷,傻傻分不清
在CPU中斷處理領域,術語比較混亂。而且有時候同一個術語在不同的語境含義也不相同。這裡我們以S3C2440的資料手冊為準解釋相關概念。
- 異常,英文名Exception。是相對於正常Normal而言的,所有打斷正常執行流程的情況都叫做異常。
- 中斷,Interrupt。屬於異常的一種,指的是CPU執行流程被來自CPU之外的訊號打斷,更加嚴格的叫法是外部中斷 External Interrupt
- 軟中斷,Software Interrupt。實際上它不是中斷,只是異常,因為其來源於CPU內部。
在很多文章和書籍中,中斷的概念被放大,指的不僅僅是外部中斷,而是與這裡的異常表示一個含義。比如常見的中斷向量,實際上更加嚴格的名字應該是異常向量。實際使用時,往往對中斷和異常不加區分,因為其實際處理流程也是一樣的,只是訊號來源不同而已。
有些異常和中斷還是有區別的,例如未知指令、記憶體方位失敗、軟中斷的異常是不能夠被遮蔽的,CPU必須處理。而外部中斷一般都是可以有選擇的進行遮蔽和不處理的。
本文也沒有區分這兩個概念,請讀者根據語境自行判斷。
1 S3C2440中斷原理
S3C2440片內集成了中斷控制器,直接讀寫相關的暫存器,就可以完成中斷處理相關的各種設定。中斷控制器
1.1 中斷產生流程
其資料手冊上給出了非常清晰中斷處理流程,如下圖所示。
這裡簡單說一下幾個特點:
(1)有兩種中斷模式,一般模式(IRQ)和快速模式(FIQ),任何中斷源都可以設定為兩種模式之一,但是隻能有一箇中斷源被設定FIQ模式。
(2)處理中斷時,CPU會自動切換執行模式,並儲存當前程式狀態暫存器
(3)經過優先順序過濾後,最後同一時刻只能有一箇中斷訊號被髮送到CPU核。
1.2 中斷導致CPU執行模式切換
中斷處理需要CPU進入中斷前儲存現場,並且在返回被中斷程式時恢復現場,這涉及到大量的操作。為加速這一過程,ARM引出了CPU執行模式,每種模式都有自己獨佔的一組暫存器供其使用,這樣就能夠減少現場儲存與恢復的暫存器數量。ARM920T共有7種執行模式:
切換模式有兩種方式,一是軟體設定CPSR暫存器中的模式位;二是當發生異常時,CPU自動切換到特定的模式執行。每種模式看到的暫存器是不完全相同的,尤其是SP暫存器為各模式各有自己的實體,所以需要各個模式分別設定自己的堆疊指標。
1.3 中斷向量
處理中斷時,CPU在儲存現場後,自動跳轉到中斷源對應的中斷向量地址。對於ARM920T,中斷過向量表定義如下:
例如,當處理IRQ型別中斷時, CPU自動跳轉到0x00000018處執行。由於中斷向量只有4個位元組大小,所以一般都是隻存放一條跳轉指令。
2 S3C2440中斷控制器配置
S3C2440的中斷控制器可以接受來自60個不同的中斷源。由於其控制暫存器只有32位,只能直接控制32箇中斷源,所以引入了分組的概念。把一些相關的中斷源分成一組,然後再通過另一個暫存器來甄別一組中的具體的中斷源。
2.1 中斷遮蔽暫存器
通過INTMSK和SUBINTMSK這兩個暫存器來控制60箇中斷源的使能與遮蔽。本文我們將測試開發板上的4個按鍵,它們分別對應EINT1, EINT4, EINT2, EINT0四個中斷源。其中EINT0,EINT1,EINT2,EINT3是直接對應到INTMSK的第0、1、2、3位,而EINT4-EINT7則共享(或運算)INTMSK的第4位,具體遮蔽哪一個還需要繼續設定EINTMASK暫存器的4-7位。
2.2 中斷源狀態暫存器
如何判斷中斷源是否產生中斷請求訊號了呢?這要通過讀取SRCPEND的位來獲取。對於EINT4中斷源來說,由於其和其他三個中斷源共享一位,所以要想判斷具體是哪個中斷源產生的訊號,還需要繼續讀取ESRCPEND來判斷。
2.3 中斷狀態暫存器
無論中斷是否被遮蔽,SRCPEND,ESRCPEND,SUBSRCPEND等中斷源狀態暫存器都會由中斷訊號自動設定。而只有沒有被遮蔽的中斷源訊號才能通過優先順序處理後來到INTPEND和EINTPEND中斷狀態暫存器中,進而進入CPU核。
為了確定INTPEND中哪個位被置1,可以逐位進行測試,這會很麻煩耗時,為此提供了INTOFFSET暫存器來記錄INTPEND中置1位的偏移量。
2.4 外部中斷訊號型別控制暫存器
對於外部中斷而言,中斷源對應實際的物理針腳,至於何種訊號才能產生中斷是通過EXTINTn暫存器來控制的。以EXTINT0為例。
對於按鍵,電路圖如下:
可見,其擡起狀態時對應的引腳電壓為3.3V,按下時電壓為0。如果採用低電平觸發,那麼按下時會一直不斷的產生中斷訊號,不適合作為按鍵功能使用,為此我們選擇下降沿觸發方式,只在按下的瞬間產生中斷訊號。
3 IRQ中斷處理程式規範
3.1 IRQ中斷處理流程
發生IRQ中斷後,ARM處理器首先需要執行完當前的指令,然後自動完成如下工作:
- 把當前執行指令的下一條指令的地址存入r14_irq
- 把當前的CPSR儲存到IRQ模式下的SPSR_irq
- 使CPSR的模式控制位為0B10010,即切換至IRQ模式
- 似CPSR的I控制位為1,即禁用IRQ中斷(這就阻止了IRQ的自我巢狀)
- 將PC暫存器賦值為0x00000018
以上是CPU硬體自動完成的,軟體無需參與。當中斷處理程式返回時,則需要軟體來完成。
- 清除SRCPND,INTPND,EINTPEND等標誌
- 把R14-4賦值給PC,實現跳轉
- 同時,複製SPSR_irq到CPSR(返回到中斷前模式)
上面兩個動作,可以使用一個命令來完成:
subs pc, r14, #4
3.2 IRQ中斷向量的安裝
只需要在0x00000018處填寫一個跳轉指令即可。完整的異常向量表如下圖
b ResetHandler
b UndHandler
b SwiHandler
b PabortHandler
b DabortHandler
b .
b IrqHandler
b FiqHandler
4 實驗程式碼說明
前面已經分析了中斷的處理原理與相關暫存器設定細節。本節直接給出具體的原始碼片段。
首先是設定IRQ模式堆疊,因為每個模式有自己的專用堆疊指標,所以必須單獨設定。這裡順便把所有7中模式的堆疊都設定了,堆疊大小均為1MB。
/* --------------set statck----------------- */
mrs r0, cpsr
bic r0, r0, #0x1F
/* go into Undef mode */
ldr r1, =(0xC0 | UNDEFMODE)
orr r1, r0, r1
msr cpsr_cxsf, r1
ldr sp, =0x34000000
/* go into Abort mode */
ldr r1, =(0xC0 | ABORTMODE)
orr r1, r0, r1
msr cpsr_cxsf, r1
ldr sp, =0x33f00000
/* go into IRQ mode */
ldr r1, =(0xC0 | IRQMODE)
orr r1, r0, r1
msr cpsr_cxsf, r1
ldr sp, =0x33e00000
/* go into FIQ mode */
ldr r1, =(0xC0 | FIQMODE)
orr r1, r0, r1
msr cpsr_cxsf, r1
ldr sp, =0x33d00000
/* go into SYS mode */
ldr r1, =(0xC0 | SYSMODE)
orr r1, r0, r1
msr cpsr_cxsf, r1
ldr sp, =0x33c00000
/* return to SVC mode */
ldr r1, =(0xc0 | SVCMODE)
orr r1, r0, r1
msr cpsr_cxsf, r1
ldr sp, =0x33b00000 /* stack of svc mode */
然後是使能CPU中斷功能,只有在初始化完畢後才能啟動中斷功能,否則中斷程式尚未準備好,萬一來了中斷會造成混亂。
/*-------------- enable IRQ/FIQ ------------*/
mrs r0, cpsr
bic r0, r0, #0xC0
msr cpsr_cxsf, r0
最後是,清除中斷遮蔽,使得EINT0~4能把中斷訊號傳送到CPU。
void irq_init(void)
{
/* KEY 1,2,3,4: EINT 1,4,2,0 */
rINTMSK &= ~( 1<<1 | 1<<4 | 1<< 2 | 1);
rEINTMASK &= ~(1<<4);
rEXTINT0 &= ~(7 | 7<<4 | 7<<8 | 7<<16);
rEXTINT0 |= (2 | 2<<4 | 2<<8 | 2<<16); /* 010 - Falling edge triggered */
}
具體的中斷處理。這裡把所有的異常處理框架都寫出來了。需要注意的是應為中斷處理程式裡使用BL指令呼叫C函式,而BL指令會修改lr暫存器的值,而之前lr暫存器儲存的是中斷返回地址,所以需要在BL指令之前,先把LR的值入棧儲存。
每種異常的異常返回地址計算方式是不同的,官方手冊給出了具體的演算法,直接使用即可。
/* ======== IRQ Mode ============= */
IrqHandler:
sub lr, lr, #4
stmfd sp!, {r0-r12, lr}
mrs r0, cpsr
bl c_irq_handler
ldmfd sp!, {r0-r12, pc}^
FiqHandler:
sub lr, lr, #4
stmfd sp!, {r0-r7, lr}
bl c_fiq_handler
ldmfd sp!, {r0-r7, pc}^
UndHandler:
stmfd sp!, {r0-r12, lr}
bl c_und_handler
ldmfd sp!, {r0-r12, pc}^
SwiHandler:
stmfd sp!, {r0-r12, lr}
bl c_swi_handler
ldmfd sp!, {r0-r12, pc}^
DabortHandler:
sub lr, lr, #8
stmfd sp!, {r0-r12, lr}
bl c_dabort_handler
ldmfd sp!, {r0-r12, pc}^
PabortHandler:
sub lr, lr, #4
stmfd sp!, {r0-r12, lr}
bl c_pabort_handler
ldmfd sp!, {r0-r12, pc}^
IRQ中斷處理C函式 c_irq_handler定義在exception.c檔案中。
void on_key_down(int key);
/* IRQ handler */
void c_irq_handler(unsigned int cpsr)
{
if((cpsr & (1<<7)) != 0) {
puts("IRQ disabled\n");
}else {
puts("IRQ enabled\n");
}
int offset = rINTOFFSET;
/* clear SRCPEND and INTPEND */
rSRCPND |= (1<<offset);
rINTPND |= (1<<offset);
if(offset == 4) {
rEINTPEND |= (1 << 4);
}
/* handle */
switch (offset) {
case 0:
on_key_down(4);
break;
case 1:
on_key_down(1);
break;
case 2:
on_key_down(3);
break;
case 3:
break;
case 4:
on_key_down(2);
break;
default:
on_unkown_irq();
break;
}
}
按鍵響應程式on_key_down定義如下:
/* key - the key no, from 1 to 4 */
void on_key_down(int key)
{
if(key == 1) {
puts("key 1 press\n");
}
if(key == 2) {
puts("key 2 press\n");
}
if(key == 3) {
puts("key 3 press\n");
}
if(key == 4) {
puts("key 4 press\n");
}
}
5 完整原始碼下載
完整原始碼下載 v0.7。
6 遺留問題
雖然按鍵採用了邊沿觸發方式,但是一次按下時,仍然會出現收到2個以上中斷訊號的情況,是按鍵硬體設計問題?如何通過軟體解決呢?
請大牛們不吝賜教。