1. 程式人生 > >TQ2440開發板學習紀實(6)--- 啟用IRQ中斷,告別低效的輪詢!

TQ2440開發板學習紀實(6)--- 啟用IRQ中斷,告別低效的輪詢!

前面的實驗中,無論是按鍵的狀態,還是串列埠的傳送接收狀態,都是通過輪詢的方式進行讀取,不僅低效而且嚴重浪費CPU計算週期,在實際的生產環境下決不能使用!從本文開始,我們進進入一個新的時代,中斷處理。

0 中斷、異常、軟中斷,傻傻分不清

在CPU中斷處理領域,術語比較混亂。而且有時候同一個術語在不同的語境含義也不相同。這裡我們以S3C2440的資料手冊為準解釋相關概念。

  • 異常,英文名Exception。是相對於正常Normal而言的,所有打斷正常執行流程的情況都叫做異常
  • 中斷,Interrupt。屬於異常的一種,指的是CPU執行流程被來自CPU之外的訊號打斷,更加嚴格的叫法是外部中斷 External Interrupt
  • 軟中斷,Software Interrupt。實際上它不是中斷,只是異常,因為其來源於CPU內部。

在很多文章和書籍中,中斷的概念被放大,指的不僅僅是外部中斷,而是與這裡的異常表示一個含義。比如常見的中斷向量,實際上更加嚴格的名字應該是異常向量。實際使用時,往往對中斷和異常不加區分,因為其實際處理流程也是一樣的,只是訊號來源不同而已。

有些異常和中斷還是有區別的,例如未知指令、記憶體方位失敗、軟中斷的異常是不能夠被遮蔽的,CPU必須處理。而外部中斷一般都是可以有選擇的進行遮蔽和不處理的。

本文也沒有區分這兩個概念,請讀者根據語境自行判斷。

1 S3C2440中斷原理

S3C2440片內集成了中斷控制器,直接讀寫相關的暫存器,就可以完成中斷處理相關的各種設定。中斷控制器

用來決定哪些中斷可以傳送至CPU核,而CPU的狀態暫存器F、I位則決定是否對來自中斷控制器的中斷訊號進行處理。CPU啟動後,中斷控制器預設是遮蔽所有中斷的,但是CPU的CPSR允許處理中斷。

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個以上中斷訊號的情況,是按鍵硬體設計問題?如何通過軟體解決呢?
請大牛們不吝賜教。