天天看點

S5PV210系列 (裸機十)之按鍵和CPU的中斷系統(一)

什麼是按鍵

按鍵的實體特性

(1)、平時沒人按的時候,彈簧把按鍵按鈕彈開。此時内部斷開的。

(2)、有人按下的時候,手的力量克服彈簧的彈力,将按鈕按下,此時内部保持接通(閉合)狀态;如果手拿開,則彈簧作用下按鈕又彈開,同時内部又斷開。

(3)、一般的按鍵都有 4 個引腳,這 4 個引腳成 2 對:其中一對是常開觸點(像上面描述的不按則斷開,按下則閉合);一對是常閉觸點(平時不按時是閉合的,按下後是斷開的)

按鍵的電學原理(結合原理圖分析)

S5PV210系列 (裸機十)之按鍵和CPU的中斷系統(一)

(1)硬體接法: SW5 : GPH0_2 SW6 : GPH0_3 SW78910 : GPH2_0123

(2)按鍵的電路連接配接分析:平時按鈕沒有按下時,按鈕内部斷開,GPIO 引腳處電壓為高電平;當有人按下按鈕時,按鈕内部導通,外部 VDD 經過電阻和按鈕連接配接到地,形成回路,此時 GPIO 引腳處電壓就變成了低電平。此時 VDD 電壓全部分壓在了電阻上(這個電阻就叫分壓電阻,這個電阻不能太小,因為電阻的功率是 U*U/R )

(3)總結:按鍵的工作方法:其實就是按鍵的按下與彈開,分别對應 GPIO 的兩種電平狀态(按下則 GPIO 為低電平,彈開則 GPIO 為高電平)。此時 SoC 内部可以通過檢測這個 GPIO 的電平高低來判斷按鍵有沒有被按下,這個判斷結果即可作為 SoC 的輸入信号。

按鍵屬于輸入類裝置

(1)按鍵一般用來做輸入裝置(由人向 SoC 發送資訊的裝置,叫輸入裝置),由人向 SoC 發送按鍵信号(按鍵信号有 2 種:按下信号和彈開信号)。

(2)有些裝置就是單純的輸入裝置,譬如按鍵、觸摸屏等;有些裝置就是單純的輸出裝置,譬如 LCD;還有一些裝置是既能輸入又能輸出的,叫輸入輸出裝置(IO),譬如序列槽。

按鍵的 2 種響應方法

(1) SoC 處理按鍵有 2 種思路:輪詢方式和中斷方式。

(2)輪詢方式,就是 SoC 主動的每隔一段時間去讀取(按鍵所對應的)GPIO 的電平高低,以此獲得按鍵資訊;缺點在于 CPU 要一直注意按鍵事件,會影響 CPU做其他事情。

(3)中斷方式,就是 SoC 事先設定好 GPIO 觸發的中斷所對應的中斷處理程式ISR,當外部按鍵按下或彈開時會自動觸發 GPIO 對應的外部中斷,導緻 ISR 執行,進而自動處理按鍵資訊。

輪詢方式處理按鍵

X210 開發闆的按鍵接法

(1)查原理圖,找到按鍵對應的 GPIO :SW5 : GPH0_2 SW6 : GPH0_3 SW78910:GPH2_0123

(2)原理圖上可以看出:按下時是低電平,彈起時是高電平

按鍵對應的 GPIO 模式設定

(1)按鍵接到 GPIO 上,按鍵按下還是彈起,決定外部電路的接通與否,進而決定這個 GPIO 引腳的電壓是高還是低;這個電壓可以作為這個 GPIO 引腳的輸入信号,此時 GPIO 配置為輸入模式,即可從 SoC 内部讀取該引腳的電平為 1 還是 0(1 對應高電平,0 對應低電平)。

(2) GPH0CON( 0xE0200C00 ) GPH2DAT( 0xE0200C04 ) GPH2CON( 0xE0200C40 ) GPH2DAT( 0xE0200C44 )

(3)應該在 CON 寄存器中将 GPIO 設定為 input 模式,然後去讀取 DAT 寄存器(讀取到的相應位的值為1表示外部是高電平(對應按鍵彈起),讀取到的位的值為 0 表明外部是低電平(按鍵按下))

輪詢方式處理按鍵的程式流程

(1)第一步,先初始化 GPIO 模式為 input

(2)第二步,循環讀取 GPIO 的電平值,然後判斷有無按鍵按下

代碼編寫和調試

// 定義操作寄存器的宏
#define GPH0CON     xE0200C00
#define GPH0DAT     xE0200C04
#define GPH2CON     xE0200C40
#define GPH2DAT     xE0200C44

#define rGPH0CON    (*(volatile unsigned int *)GPH0CON)
#define rGPH0DAT    (*(volatile unsigned int *)GPH0DAT)
#define rGPH2CON    (*(volatile unsigned int *)GPH2CON)
#define rGPH2DAT    (*(volatile unsigned int *)GPH2DAT)

// 初始化按鍵
void key_init(void)
{
    // 設定GPHxCON寄存器,設定為輸入模式
    // GPH0CON的bit8~15全部設定為0,即可
    rGPH0CON &= ~(0xFF<<8);
    // GPH2CON的bit0~15全部設定為0,即可
    rGPH2CON &= ~(0xFFFF<<0);
}

void key_polling(void)
{
    // 依次,挨個去讀出每個GPIO的值,判斷其值為1還是0.如果為1則按鍵按下,為0則彈起

    // 輪詢的意思就是反複循環判斷有無按鍵,隔很短時間
    while (1)
    {
        // 對應開發闆上标着LEFT的那個按鍵
        if (rGPH0DAT & (1<<2))
        {
            // 為1,說明沒有按鍵
            led_off();
        }
        else
        {
            // 為0,說明有按鍵
            led1();
        }

        // 對應開發闆上标着DOWN的那個按鍵
        if (rGPH0DAT & (<<))
        {
            // 為1,說明沒有按鍵
            led_off();
        }
        else
        {
            // 為0,說明有按鍵
            led2();
        }

        // 對應開發闆上标着UP的那個按鍵
        if (rGPH2DAT & (<<))
        {
            // 為1,說明沒有按鍵
            led_off();
        }
        else
        {
            // 為0,說明有按鍵
            led3();
        }
    }
}
           

按鍵消抖

什麼是按鍵消抖

(1) 按鍵這種實體器件本身會有抖動信号,抖動信号指的是在電平由高到低(也就是按鍵按下時)或者電平由低到高(也就是按鍵彈起時)過程中,電平的變化不是立刻變化,而是經過了一段時間的不穩定期才完成變化,在這個不穩定期間電平可能會時高時低反複變化,這個不穩定期就叫抖動(抖動期内擷取按鍵資訊是不可靠的,要想辦法消抖)。

(2) 什麼叫消抖?

消抖就是用硬體或者軟體方法來盡量減小抖動期對按鍵擷取的影響。消抖常用 2 種思路:第一是硬體消抖,消抖思路就是盡量減小抖動時間,方法是通過硬體添加電容等元件來減小抖動;第二是軟體消抖,消抖思路是發現一次按鍵按下/彈起事件後,不立即處理按鍵,而是延時一段時間(一般 10〜20ms,這就是消抖時間)後再次擷取按鍵鍵值,如果此次擷取和上次一樣是按下/彈起,那就認為真的按下/彈起了。

(3) 一般比較精密需要的時候,需要硬體消抖和軟體消抖一起配合。

#include "stdio.h"

// 定義操作寄存器的宏
#define GPH0CON     0xE0200C00
#define GPH0DAT     0xE0200C04
#define GPH2CON     0xE0200C40
#define GPH2DAT     0xE0200C44

#define rGPH0CON    (*(volatile unsigned int *)GPH0CON)
#define rGPH0DAT    (*(volatile unsigned int *)GPH0DAT)
#define rGPH2CON    (*(volatile unsigned int *)GPH2CON)
#define rGPH2DAT    (*(volatile unsigned int *)GPH2DAT)

// 初始化按鍵
void key_init(void)
{
    // 設定GPHxCON寄存器,設定為輸入模式
    // GPH0CON的bit8~15全部設定為0,即可
    rGPH0CON &= ~(<<);
    // GPH2CON的bit0~15全部設定為0,即可
    rGPH2CON &= ~(<<);
}

void delay20ms(void)
{
    // 這個函數作用是延時 20ms
    // 因為我們這裡是裸機程式,且重點不是真的要消抖,而是教學
    // 是以我這裡這個程式隻是象征性的,并沒有實體
    // 如果是研發,那就要花時間真的調試出延時 20ms 的程式
    int i, j;
    for (i=; i<; i++)
    {
        for (j=; j<; j++)
        {
            i * j;
        }
    }
}

void key_polling(void)
{
    // 依次,挨個去讀出每個GPIO的值,判斷其值為1還是0.如果為1則按鍵按下,為0則彈起
    // 輪詢的意思就是反複循環判斷有無按鍵,隔很短時間
    while ()
    {
        // 對應開發闆上标着LEFT的那個按鍵
        if (rGPH0DAT & (<<))
        {
            // 為1,說明沒有按鍵
            led_off();
        }
        else
        {
            // 添加消抖
            // 第一步,延時
            delay20ms();
            // 第二步,再次檢驗按鍵狀态
            if (!(rGPH0DAT & (<<)))
            {
                // 為0,說明有按鍵
                led1();
                printf("key left.\n");
            }
        }

        // 對應開發闆上标着DOWN的那個按鍵
        if (rGPH0DAT & (<<))
        {
            // 為1,說明沒有按鍵
            led_off();
        }
        else
        {
            // 為0,說明有按鍵
            led2();
            printf("key down.\n");
        }

        // 對應開發闆上标着UP的那個按鍵
        if (rGPH2DAT & (<<))
        {
            // 為1,說明沒有按鍵
            led_off();
        }
        else
        {
            // 為0,說明有按鍵
            led3();
        }
    }
}
           

S5PV210的中斷體系介紹

什麼是中斷

(1)中斷的發明是用來解決宏觀上的并行需要的。宏觀就是從整體上來看,并行就是多件事情都完成了。

(2)微觀上的并行,就是指的真正的并行,就是精确到每一秒甚至每一刻,多個事情都是在同時進行的。宏觀上面的并行并不等于圍觀的并行,有時候宏觀上是并行的,微觀上是串行的。

(3)例子中一個人在看電影,快遞來了暫停電影跑去收快遞,收完快遞繼續回來看電影,這個例子就是宏觀上的并行和微觀上的串行。例子中一個人等同于 SoC 中 1 個 CPU(也就是單核 CPU ),這個 CPU 看電影就不能收快遞,收快遞就不能看電影(也就是說不能真正的并行)。單核心 CPU 在微觀角度是串行的,但是因為 CPU 很快,是以在宏觀看來可以并行。

(4)上例中大部分時間在看電影,中間少量時間去收快遞,那麼類比于 CPU 來說,看電影就應該是 CPU 的正常任務,而收快遞則應該是中斷例程。也就是說CPU 平時一直在進行看電影任務,等快遞來了(中斷發生了)快遞員(類似于中斷源)會打電話叫人去收快遞(中斷源會觸發中斷通知 CPU 去進行中斷),人收到電話( CPU 收到中斷信号)後會暫定電影( CPU 儲存正常任務的現場)跑去收快遞( CPU 去執行中斷處理程式ISR進行中斷),收完快遞(執行完 ISR )回來繼續看電影( CPU 恢複正常任務的現場,繼續執行正常任務)

(5)為什麼需要中斷?因為單核 CPU 實際無法并行的,但是通過中斷機制,可以實作假并行(宏觀上的并行,微觀上實際還是串行的)。

SoC對中斷的實作機制:異常向量表

(1)異常向量表是CPU中某些特定位址的特定定義。當中斷發生的時候,中斷要想辦法通知CPU去進行中斷,怎麼做到?這就要靠異常向量表。

(2)在CPU設計時,就事先定義了CPU中一些特定位址作為特定異常的入口位址(譬如定義0x00000000位址為複位異常向量位址,則發生複位異常時CPU會自動跳轉到0x00000000位址去執行指令。又譬如外部中斷對應的異常向量位址為0x30000008,則發生外部中斷後,CPU會硬體自動跳轉到0x30000008位址去執行指令。)

(3)以上講的是CPU硬體設計時對異常向量表的支援,下來就需要軟體支援了。硬體已經決定了發生什麼異常CPU自動跳轉PC到哪個位址去執行,軟體需要做的就是把處理這個異常的代碼的首位址填入這個異常向量位址。

S5PV210的異常向量表

(1)異常向量表中各個向量的相對位置是固定的,但是他們的起始位址是不固定的,各種 SoC 可以不一樣,而且複雜 ARM 中還可以讓使用者來軟體設定這個異常向量表的基位址。

(2)擴充到所有架構的 CPU 中:所有架構(譬如 51 單片機、PIC 單片機)的 CPU實作中斷都是通過異常向量表實作的,這個機制是不變的;但是不同 CPU 異常向量表的構造和位置是不同的。

異常和中斷的差別和聯系

(1)針對 SoC 來說,發生複位、軟中斷、中斷、快速中斷、取指令異常、資料異常等,我們都統一叫異常。是以說:中斷其實是異常的一種。

(2)異常的定義就是突發事件,打斷了 CPU 的正常正常業務,CPU 不得不跳轉到異常向量表中去執行異常處理程式;中斷是異常的一種,一般特指 SoC 内的内部外設産生的打斷 SoC 正常業務,或者外部中斷( SoC 的 GPIO 引腳傳回來的中斷)。

異常向量表的程式設計處理

像記憶體一樣去通路異常向量表

(1) S5PV210 的異常向量表可以改變(在 CP15 協處理器中),以适應作業系統的需求。但是目前系統剛啟動時,此時 DRAM 尚未初始化,程式都在 SRAM 中運作。210 在 iRAM 中設定了異常向量表,供暫時性使用。

(2)查 210 的 iROM application note 文檔中 iRAM 的位址配置設定,可知,iRAM 中的異常向量表起始位址為 0xD0037400。知道了異常向量表的起始位址後,各個異常對應的入口就很好知道了。

S5PV210系列 (裸機十)之按鍵和CPU的中斷系統(一)

函數名的實質就是函數的首位址

(1)函數名在 C 語言中的了解方法和變量名其實沒差別。編譯器會把這個函數的函數體對應的代碼段和這個函數的函數名(實質是符号)對應起來,等我們在使用這個函數名符号時,編譯器會将函數的函數體實際上做替換。因為函數體都不止 4 位元組,而函數名這個符号隻能對應 1 個位址,是以實際對應的是函數體那一個代碼段的首位址。

(2)拿C語言中的文法來講,函數名就是這個函數的函數指針。

總結:當我們将異常處理程式的首位址和異常向量表綁定起來後,異常處理初步階段就完成了。到目前可以保證相應異常發生後,硬體自動跳轉到對應異常向量表入口去執行時,可以執行到我們事先綁定的函數。

為什麼中斷處理要先在彙編中進行

(1)中斷處理要注意保護現場(中斷從 SVC 模式來,則儲存 SVC 模式下的必要寄存器的值)和恢複現場(中斷處理完成後,準備傳回 SVC 模式前,要将儲存的SVC 模式下的必要寄存器的值恢複回去,不然到了 SVC 模式後寄存器的值亂了,SVC 模式下原來正在進行的正常任務就被你搞壞了)

(2)儲存現場包括:第一:設定 IRQ 棧;第二,儲存 LR;第三,儲存 R0 〜 R12

(3)為什麼要儲存 LR 寄存器?要考慮中斷傳回的問題。中斷 ISR 執行完後如何傳回 SVC 模式下去接着執行原來的代碼。中斷傳回其實取決于我們進入中斷時如何儲存現場。中斷傳回時關鍵的 2 個寄存器就是 PC 和 CPSR。是以我們在進入IRQ 模式時,應該将 SVC 模式下的下一句指令的位址(中斷傳回位址)和 CPSR儲存起來,将來恢複時才可以将中斷傳回位址給 PC,将儲存的 CPSR 給 CPSR。

(4)中斷傳回位址就儲存在 LR 中,而 CPSR(自動)儲存在( IRQ 模式下的)SPSR 中

彙編儲存現場和恢複現場

(1)保護現場關鍵是儲存:中斷處理程式的傳回位址,r0-r12(cpsr是自動儲存的)

(2)恢複現場主要是恢複:r0-r12,pc,cpsr

S5PV210的向量中斷控制器

異常處理的 2 個階段

(1)可以将異常處理分為 2 個階段來了解。第一個階段是 異常向量表 跳轉;第二個階段就是進入了真正的異常處理程式 irq_handler 之後的部分。

回顧:中斷處理的第一階段(異常向量表階段)處理。

(1)第一個階段之是以能夠進行,主要依賴于 CPU 設計時提供的異常向量表機制。第一個階段的主要任務是從異常發生到響應異常并且儲存/恢複現場、跳轉到真正的異常處理程式處。

(2)第二個階段的目的是識别多個中斷源中究竟哪一個發生了中斷,然後調用相應的中斷處理程式來處理這個中斷。

S3C2440的第二階段處理過程

(1)第一個問題,怎麼找到具體是哪個中斷:S3C2440 的中斷控制器中有一個寄存器( 32 位的),寄存器的每一個位對應一個中斷源(為了解決支援更多中斷源,2440 又設計了一個子中斷機制。在一級中斷寄存器中有一些中斷是共用的一個 bit 位,譬如 AC97 和 WDT 。對于共用中斷,用子中斷來區分究竟是哪一個發生了中斷)

(2)第二個問題,怎麼找到對應的 isr 的問題:首先給每個中斷做了個編号,進入isr_handler 之後先通過查閱中斷源寄存器和子中斷寄存器(中哪一位為 1 )确定中斷的編号,然後用這個編号去 isr 數組( isr 數組是中斷初始化時事先設定好的,就是把各個中斷的 isr 的函數名組成一個數組,用中斷對應的編号作為索引來查詢這個數組)中查閱得到 isr 位址。

評價:2440 的中斷處理設計不是特别優秀:第一個過程中使用子中斷搞成 2 級的很麻煩;第二個過程中計算中斷編号是個麻煩事,很耗費時間。而中斷處理的時間是很寶貴的(系統有一個性能名額,叫實時性。實時性就是中斷發生到響應的時間,這個時間越短越好。)

S5PV210的第二階段處理過程

(1)第一個問題,怎麼找到具體是哪個中斷:S5PV210中因為支援的中斷源很多,是以直接設計了4個中斷寄存器,每個32位,每位對應一個中斷源。(理論上210最多支援 128 個中斷,實際支援不足 128 個,有些位是空的);210 沒有子中斷寄存器,每個中斷源都是并列的。當中斷發生時,在 irq_handler 中依次去查詢4個中斷源寄存器,看哪一個的哪一位被置 1,則這個位對應的寄存器就發生了中斷,即找到了中斷編号。

(2)第二個問題,怎麼找到對應的 isr 的問題:210 中支援的中斷源多了很多,如果還使用 2440 的那一套來尋找 isr 位址就太慢了,太影響實時性了。于是 210 開拓了一種全新的尋找 isr 的機制。210 提供了很多寄存器來解決每個中斷源對應isr 的尋找問題,具體尋找過程和建立過程見下節,實作的效果是當發生相應中斷時,硬體會自動的将相應isr推入一定的寄存器中,我們軟體隻要去這個寄存器中執行函數就行了。

S5PV210中斷處理的主要寄存器

VICnINTENABLE 和 VICnINTENCLEAR

(1) VICnINTENABLE 對應 interrupt enable,INTENCLEAR 對應 interrupt enable clear

(2) INTENABLE 寄存器 負責相應的中斷的使能,INTENCLEAR 寄存器 負責相應的中斷的禁止。

(3)當我們想使能(意思就是啟用這個中斷,意思就是當硬體産生中斷時 CPU 能接收的到)某個中斷時,隻要在這個中斷編号對應的 VICnINTENABLE 的相應bit 位寫 1 即可(注意這個位寫 1 其他位寫 0 對其他位沒有影響);如果我們想禁止某個中斷源時,隻要向 VICnINTENCLEAR 中相應的位寫 1 即可。

注意:這裡的設計一共有 2 種:有些 CPU 是中斷使能和禁止是一個寄存器位,寫1就使能寫 0 就進制(或者反過來寫 1 就進制寫 0 就使能),這樣的中斷使能設計就要非常小心,要使用我們之前說過的讀改寫三部曲來操作;另一種就是使能和禁止分開為 2 個寄存器,要使能就寫使能寄存器,要禁止就寫禁止寄存器。這樣的好處是我們使能/禁止操作時不需要讀改寫,直接寫即可。

VICnINTSELECT

(1)設定各個中斷的模式為 irq 還是 fiq。一般都設定成 irq

(2) IRQ 和 FIQ 究竟有何差別。210 中支援 2 種中斷,irq 和 fiq。irq 是普通中斷,fiq 是快速中斷。快速中斷提供一種更快響應處理的中斷通道,用于對實時性要求很高的中斷源。fiq 在 CPU 設計時預先提供了一些機制保證 fiq 可以被快速處理,進而保證明時性。fiq 的限制就是隻能有一個中斷源被設定為 fiq,其他都是 irq。

(3) CPU 如何保證 fiq比 irq 快?有 2 個原因:第一,fiq 模式有專用的 r8〜r12,是以在 fiq 的 isr 中可以直接使用 r8-r12 而不用儲存,這就能節省時間;第二,異常向量表中 fiq 是最後一個異常向量入口。是以 fiq 的 isr 不需要跳轉,可以直接寫在原地,這樣就比其他異常少跳轉一次,省了些時間。

VICnIRQSTATUS 和 VICnFIQSTATUS

(1)中斷狀态寄存器,是隻讀的。當發生了中斷時,硬體會自動将該寄存器的對應位置為 1,表示中斷發生了。軟體在進行中斷第二階段的第一階段,就是靠查詢這個寄存器來得到中斷編号的。

VICnVECTPRIORITY0〜VICnVECTPRIORITY31

(1)中斷優先級設定寄存器,設定多個中斷同時發生時先處理誰後處理誰的問題。一般來說高優先級的中斷可以打斷低優先級的中斷,進而嵌套進行中斷。當然了有些硬體/軟體可以設定不支援中斷嵌套。

VICnVECTADDR0 〜 VICnVECTADDR31、VICnADDR

(1)這三個寄存器和 210 中斷處理第二階段的第二階段有關。

(2) VICnVECTADDR0 到 31 這 32 個寄存器分别用來存放真正的各個中斷對應的isr 的函數位址。相當于每一個中斷源都有一個 VECTADDR 寄存器,程式員在設定中斷的時候,把這個中斷的 isr 位址直接放入這個中斷對應的 VECTADDR 寄存器即可。

(3) VICnADDR 這個寄存器是隻需要讀的,它裡面的内容是由硬體自動設定的。當發生了相應中斷時,硬體會自動識别中斷編号,并且會自動找到這個中斷的VECTADDR 寄存器,然後将其讀出複制到 VICnADDR 中,供我們使用。這樣的設計避免了軟體查找中斷源和 isr,節省了時間,提高了 210 的中斷響應速度。

S5PV210中斷處理的程式設計實踐

main.c

#include "stdio.h"

void uart_init(void);

#define KEY_INTNUM      NUM_EINT9       // 暫時随便找的,下節課會具體改


void key_isr(void)
{
    // 暫時随便寫的,下節課正式的會寫到key.c中去
}

int main(void)
{
    uart_init();
    key_init();

    // 如果程式中要使用中斷,就要調用中斷初始化來初步初始化中斷控制器
    system_init_exception();

    // 綁定isr到中斷控制器硬體
    intc_setvectaddr(KEY_INTNUM, key_isr);

    return ;
}
           

start.S

IRQ_handle:
    // 設定IRQ模式下的棧
    ldr sp, =IRQ_STACK
    // 儲存LR
    // 因為ARM有流水線,是以PC的值會比真正執行的代碼+,
    sub lr, lr, #4
    // 儲存r0-r12和lr到irq模式下的棧上面
    stmfd sp!, {r0-r12, lr}
    // 在此調用真正的isr來進行中斷
    bl irq_handler
    // 處理完成開始恢複現場,其實就是做中斷傳回,關鍵是将r0-r12,pc,cpsr一起回複
    ldmfd sp!, {r0-r12, pc}^
           

interrup.h

#ifndef __INT_H__
#define __INT_H__

void intc_init(void);
void intc_enable(unsigned long intnum);
void intc_disable(unsigned long intnum);
void intc_setvectaddr(unsigned long intnum, void (*handler)(void));
void intc_clearvectaddr(void);
unsigned long intc_getvicirqstatus(unsigned long ucontroller);
void irq_handler(void);
void IRQ_handle(void);
void system_init_exception(void);

//// Interrupt
#define VIC0_BASE                   (0xF2000000)
#define VIC1_BASE                   (0xF2100000)
#define VIC2_BASE                   (0xF2200000)
#define VIC3_BASE                   (0xF2300000)

// VIC0
#define     VIC0IRQSTATUS           ( *((volatile unsigned long *)(VIC0_BASE + 0x00)) )
#define     VIC0FIQSTATUS           ( *((volatile unsigned long *)(VIC0_BASE + 0x04)) )
#define     VIC0INTSELECT           ( *((volatile unsigned long *)(VIC0_BASE + 0x0c)) )
#define     VIC0INTENABLE           ( *((volatile unsigned long *)(VIC0_BASE + 0x10)) )
#define     VIC0INTENCLEAR          ( *((volatile unsigned long *)(VIC0_BASE + 0x14)) )
#define     VIC0VECTADDR            (VIC0_BASE + 0x100)
#define     VIC0ADDR                ( *((volatile unsigned long *)(VIC0_BASE + 0xf00)) )

// VIC1
#define     VIC1IRQSTATUS           ( *((volatile unsigned long *)(VIC1_BASE + 0x00)) )
#define     VIC1FIQSTATUS           ( *((volatile unsigned long *)(VIC1_BASE + 0x04)) )
#define     VIC1INTSELECT           ( *((volatile unsigned long *)(VIC1_BASE + 0x0c)) )
#define     VIC1INTENABLE           ( *((volatile unsigned long *)(VIC1_BASE + 0x10)) )
#define     VIC1INTENCLEAR          ( *((volatile unsigned long *)(VIC1_BASE + 0x14)) )
#define     VIC1VECTADDR            (VIC1_BASE + 0x100)
#define     VIC1ADDR                ( *((volatile unsigned long *)(VIC1_BASE + 0xf00)) )

// VIC2
#define     VIC2IRQSTATUS           ( *((volatile unsigned long *)(VIC2_BASE + 0x00)) )
#define     VIC2FIQSTATUS           ( *((volatile unsigned long *)(VIC2_BASE + 0x04)) )
#define     VIC2INTSELECT           ( *((volatile unsigned long *)(VIC2_BASE + 0x0c)) )
#define     VIC2INTENABLE           ( *((volatile unsigned long *)(VIC2_BASE + 0x10)) )
#define     VIC2INTENCLEAR          ( *((volatile unsigned long *)(VIC2_BASE + 0x14)) )
#define         VIC2VECTADDR            (VIC2_BASE + 0x100)
#define         VIC2ADDR                ( *((volatile unsigned long *)(VIC2_BASE + 0xf00)) )

// VIC3
#define     VIC3IRQSTATUS           ( *((volatile unsigned long *)(VIC3_BASE + 0x00)) )
#define     VIC3FIQSTATUS           ( *((volatile unsigned long *)(VIC3_BASE + 0x04)) )
#define     VIC3INTSELECT           ( *((volatile unsigned long *)(VIC3_BASE + 0x0c)) )
#define     VIC3INTENABLE           ( *((volatile unsigned long *)(VIC3_BASE + 0x10)) )
#define     VIC3INTENCLEAR          ( *((volatile unsigned long *)(VIC3_BASE + 0x14)) )
#define         VIC3VECTADDR            (VIC3_BASE + 0x100)
#define         VIC3ADDR                ( *((volatile unsigned long *)(VIC3_BASE + 0xf00)) )


#define exception_vector_table_base     0xD0037400
#define exception_reset                 (exception_vector_table_base + 0x00)
#define exception_undef                 (exception_vector_table_base + 0x04)
#define exception_sotf_int              (exception_vector_table_base + 0x08)
#define exception_prefetch              (exception_vector_table_base + 0x0C)
#define exception_data                  (exception_vector_table_base + 0x10)
#define exception_irq                   (exception_vector_table_base + 0x18)
#define exception_fiq                   (exception_vector_table_base + 0x1C)

#define r_exception_reset       (*(volatile unsigned int *)exception_reset)
#define r_exception_undef       (*(volatile unsigned int *)exception_undef)
#define r_exception_sotf_int    (*(volatile unsigned int *)exception_sotf_int)
#define r_exception_prefetch    (*(volatile unsigned int *)exception_prefetch)
#define r_exception_data        (*(volatile unsigned int *)exception_data)
#define r_exception_irq         (*(volatile unsigned int *)exception_irq)
#define r_exception_fiq         (*(volatile unsigned int *)exception_fiq)

// 中斷源編号
#define INT_LIMIT               (96)

//INT NUM - VIC0
#define NUM_EINT0               (0)
#define NUM_EINT1               (1)
#define NUM_EINT2               (2)
#define NUM_EINT3               (3)
#define NUM_EINT4               (4)
#define NUM_EINT5               (5)
#define NUM_EINT6               (6)
#define NUM_EINT7               (7)
#define NUM_EINT8               (8)
#define NUM_EINT9               (9)
#define NUM_EINT10              (10)
#define NUM_EINT11              (11)
#define NUM_EINT12              (12)
#define NUM_EINT13              (13)
#define NUM_EINT14              (14)
#define NUM_EINT15              (15)
#define NUM_EINT16_31           (16)
#define NUM_Reserved17          (17) 
#define NUM_MDMA                (18)
#define NUM_PDMA0               (19)
#define NUM_PDMA1               (20)
#define NUM_TIMER0              (21)
#define NUM_TIMER1              (22)
#define NUM_TIMER2              (23)
#define NUM_TIMER3              (24)
#define NUM_TIMER4              (25)
#define NUM_SYSTIMER            (26)
#define NUM_WDT                 (27)
#define NUM_RTC_ALARM           (28)
#define NUM_RTC_TICK            (29)
#define NUM_GPIOINT             (30)
#define NUM_FIMC3               (31)

//INT NUM - VIC1
#define NUM_CORTEX0             (32+0)
#define NUM_CORTEX1             (32+1)
#define NUM_CORTEX2             (32+2)
#define NUM_CORTEX3             (32+3)
#define NUM_CORTEX4             (32+4)
#define NUM_IEM_APC             (32+5)
#define NUM_IEM_IEC             (32+6)
#define NUM_Reserved39          (32+7)
#define NUM_NFC                 (32+8)
#define NUM_CFC                 (32+9)
#define NUM_UART0               (32+10)
#define NUM_UART1               (32+11)
#define NUM_UART2               (32+12)
#define NUM_UART3               (32+13)
#define NUM_I2C                 (32+14)
#define NUM_SPI0                (32+15)
#define NUM_SPI1                (32+16)
#define NUM_SPI2                (32+17)
#define NUM_AUDIO               (32+18)
#define NUM_I2C_PMIC            (32+19)
#define NUM_I2C_HDMI            (32+20)
#define NUM_HSIRX               (32+21)
#define NUM_HSITX               (32+22)
#define NUM_UHOST               (32+23)
#define NUM_OTG                 (32+24)
#define NUM_MSM                 (32+25)
#define NUM_HSMMC0              (32+26)
#define NUM_HSMMC1              (32+27)
#define NUM_HSMMC2              (32+28)
#define NUM_MIPI_CSI            (32+29)
#define NUM_MIPI_DSI            (32+30)
#define NUM_ONENAND_AUDI        (32+31)

//INT NUM - VIC2
#define NUM_LCD0                (64+0)
#define NUM_LCD1                (64+1)
#define NUM_LCD2                (64+2)
#define NUM_LCD3                (64+3)
#define NUM_ROTATOR             (64+4)
#define NUM_FIMC_A              (64+5)
#define NUM_FIMC_B              (64+6)
#define NUM_FIMC_C              (64+7)
#define NUM_JPEG                (64+8)
#define NUM_2D                  (64+9)
#define NUM_3D                  (64+10)
#define NUM_MIXER               (64+11)
#define NUM_HDMI                (64+12)
#define NUM_HDMI_I2C            (64+13)
#define NUM_MFC                 (64+14)
#define NUM_TVENC               (64+15)
#define NUM_I2S0                (64+16)
#define NUM_I2S1                (64+17)
#define NUM_I2S2                (64+18)
#define NUM_AC97                (64+19)
#define NUM_PCM0                (64+20)
#define NUM_PCM1                (64+21)
#define NUM_SPDIF               (64+22)
#define NUM_ADC                 (64+23)
#define NUM_PENDN               (64+24)
#define NUM_KEYPAD              (64+25)
#define NUM_Reserved90          (64+26) 
#define NUM_HASH                (64+27) 
#define NUM_FEEDCTRL            (64+28) 
#define NUM_PCM2                (64+29)
#define NUM_SDM_IRQ             (64+30)
#define NUM_SMD_FIQ             (64+31)

//INT NUM - VIC3
#define NUM_IPC                 (96+0)
#define NUM_HOSTIF              (96+1)
#define NUM_HSMMC3              (96+2)
#define NUM_CEC                 (96+3)
#define NUM_TSI                 (96+4)
#define NUM_MDNIE0              (96+5)
#define NUM_MDNIE1              (96+6)
#define NUM_MDNIE2              (96+7)
#define NUM_MDNIE3              (96+8)
#define NUM_ADC1                (96+9)
#define NUM_PENDN1              (96+10)
#define NUM_ALL                 (200)

#endif
           

interrup.c

#include "int.h"
#include "stdio.h"


void reset_exception(void)
{
    printf("reset_exception.\n");
}

void undef_exception(void)
{
    printf("undef_exception.\n");
}

void sotf_int_exception(void)
{
    printf("sotf_int_exception.\n");
}

void prefetch_exception(void)
{
    printf("prefetch_exception.\n");
}

void data_exception(void)
{
    printf("data_exception.\n");
}

// 主要功能:綁定第一階段異常向量表;禁止所有中斷;選擇所有中斷類型為IRQ;
// 清除VICnADDR為0
void system_init_exception(void)
{
    // 第一階段處理,綁定異常向量表
    r_exception_reset = (unsigned int)reset_exception;
    r_exception_undef = (unsigned int)undef_exception;
    r_exception_sotf_int = (unsigned int)sotf_int_exception;
    r_exception_prefetch = (unsigned int)prefetch_exception;
    r_exception_data = (unsigned int)data_exception;
    r_exception_irq = (unsigned int)IRQ_handle;
    r_exception_fiq = (unsigned int)IRQ_handle;

    // 初始化中斷控制器的基本寄存器
    intc_init();
}

// 清除需要處理的中斷的中斷處理函數的位址
void intc_clearvectaddr(void)
{
    // VICxADDR:目前正在處理的中斷的中斷處理函數的位址
    VIC0ADDR = ;
    VIC1ADDR = ;
    VIC2ADDR = ;
    VIC3ADDR = ;
}

// 初始化中斷控制器
void intc_init(void)
{
    // 禁止所有中斷
    // 為什麼在中斷初始化之初要禁止所有中斷?
    // 因為中斷一旦打開,因為外部或者硬體自己的原因産生中斷後一定就會尋找isr
    // 而我們可能認為自己用不到這個中斷就沒有提供isr,這時它自動拿到的就是亂碼
    // 則程式很可能跑飛,是以不用的中斷一定要關掉。
    // 一般的做法是先全部關掉,然後再逐一打開自己感興趣的中斷。一旦打開就必須
    // 給這個中斷提供相應的isr并綁定好。
    VIC0INTENCLEAR = ;
    VIC1INTENCLEAR = ;
    VIC2INTENCLEAR = ;
    VIC3INTENCLEAR = ;

    // 選擇中斷類型為IRQ
    VIC0INTSELECT = ;
    VIC1INTSELECT = ;
    VIC2INTSELECT = ;
    VIC3INTSELECT = ;

    // 清VICxADDR
    intc_clearvectaddr();
}


// 綁定我們寫的isr到VICnVECTADDR寄存器
// 綁定過之後我們就把isr位址交給硬體了,剩下的我們不用管了,硬體自己會處理
// 等發生相應中斷的時候,我們直接到相應的VICnADDR中去取isr位址即可。
// 參數:intnum是int.h定義的實體中斷号,handler是函數指針,就是我們寫的isr

// VIC0VECTADDR定義為VIC0VECTADDR0寄存器的位址,就相當于是VIC0VECTADDR0~31這個
// 數組(這個數組就是一個函數指針數組)的首位址,然後具體計算每一個中斷的時候
// 隻需要首位址+偏移量即可。
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
    //VIC0
    if(intnum<)
    {
        *( (volatile unsigned long *)(VIC0VECTADDR + *(intnum-)) ) = (unsigned)handler;
    }
    //VIC1
    else if(intnum<)
    {
        *( (volatile unsigned long *)(VIC1VECTADDR + *(intnum-)) ) = (unsigned)handler;
    }
    //VIC2
    else if(intnum<)
    {
        *( (volatile unsigned long *)(VIC2VECTADDR + *(intnum-)) ) = (unsigned)handler;
    }
    //VIC3
    else
    {
        *( (volatile unsigned long *)(VIC3VECTADDR + *(intnum-)) ) = (unsigned)handler;
    }
    return;
}


// 使能中斷
// 通過傳參的intnum來使能某個具體的中斷源,中斷号在int.h中定義,是實體中斷号
void intc_enable(unsigned long intnum)
{
    unsigned long temp;
    // 确定intnum在哪個寄存器的哪一位
    // <32就是0~31,必然在VIC0
    if(intnum<)
    {
        temp = VIC0INTENABLE;
        temp |= (<<intnum);        // 如果是第一種設計則必須位操作,第二種設計可以
                                    // 直接寫。
        VIC0INTENABLE = temp;
    }
    else if(intnum<)
    {
        temp = VIC1INTENABLE;
        temp |= (<<(intnum-));
        VIC1INTENABLE = temp;
    }
    else if(intnum<)
    {
        temp = VIC2INTENABLE;
        temp |= (<<(intnum-));
        VIC2INTENABLE = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENABLE;
        temp |= (<<(intnum-));
        VIC3INTENABLE = temp;
    }
    // NUM_ALL : enable all interrupt
    else
    {
        VIC0INTENABLE = ;
        VIC1INTENABLE = ;
        VIC2INTENABLE = ;
        VIC3INTENABLE = ;
    }

}

// 禁止中斷
// 通過傳參的intnum來禁止某個具體的中斷源,中斷号在int.h中定義,是實體中斷号
void intc_disable(unsigned long intnum)
{
    unsigned long temp;

    if(intnum<)
    {
        temp = VIC0INTENCLEAR;
        temp |= (<<intnum);
        VIC0INTENCLEAR = temp;
    }
    else if(intnum<)
    {
        temp = VIC1INTENCLEAR;
        temp |= (<<(intnum-));
        VIC1INTENCLEAR = temp;
    }
    else if(intnum<)
    {
        temp = VIC2INTENCLEAR;
        temp |= (<<(intnum-));
        VIC2INTENCLEAR = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENCLEAR;
        temp |= (<<(intnum-));
        VIC3INTENCLEAR = temp;
    }
    // NUM_ALL : disable all interrupt
    else
    {
        VIC0INTENCLEAR = ;
        VIC1INTENCLEAR = ;
        VIC2INTENCLEAR = ;
        VIC3INTENCLEAR = ;
    }

    return;
}


// 通過讀取VICnIRQSTATUS寄存器,判斷其中哪個有一位為1,來得知哪個VIC發生中斷了
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
    if(ucontroller == )
        return  VIC0IRQSTATUS;
    else if(ucontroller == )
        return  VIC1IRQSTATUS;
    else if(ucontroller == )
        return  VIC2IRQSTATUS;
    else if(ucontroller == )
        return  VIC3IRQSTATUS;
    else
    {}
    return ;
}


// 真正的中斷處理程式。意思就是說這裡隻考慮中斷處理,不考慮保護/恢複現場
void irq_handler(void)
{
    printf("irq_handler.\n");
    // SoC支援很多個(在低端CPU例如2440中有30多個,在210中有100多個)中斷
    // 這麼多中斷irq在第一個階段走的是一條路,都會進入到irq_handler來
    // 我們在irq_handler中要去區分究竟是哪個中斷發生了,然後再去調用該中斷
    // 對應的isr。


    // 雖然硬體已經自動幫我們把isr放入了VICnADDR中,但是因為有4個,是以我們必須
    // 先去軟體的檢查出來到底哪個VIC中斷了,也就是說isr到底在哪個VICADDR寄存器中
    unsigned long vicaddr[] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
    int i=;
    void (*isr)(void) = NULL;

    for(i=; i<; i++)
    {
        // 發生一個中斷時,4個VIC中有3個是全0,1個的其中一位不是0
        if(intc_getvicirqstatus(i) != )
        {
            isr = (void (*)(void)) vicaddr[i];
            break;
        }
    }
    (*isr)();       // 通過函數指針來調用函數
}