按鍵處理及外部中斷

按鍵和中斷部分

  以按鍵觸發中斷爲例,談談S5PV210的中斷體系

  要使用中斷,首先要做好兩個部分的工作:CPU中斷的初始化和相應器件的中斷的初始化。CPU中斷初始化:就是要設置好CPU有關中斷的東西。相關器件的中斷初始化:譬如我們的按鍵,就要設置好按鍵,使其一按下按鍵,就觸發中斷。

(1)我們先來說說按鍵的相關的中斷設置

先看看按鍵的硬件接法:我們可以看到,按鍵是接在EINT2和EINT3處,還有KP_COL0-3,一共是6個按鍵,

然後我們看SOC的接口處,可以看到EINT2和EINT3接在了GPH0_2,3處,KP_COL0-3接在了GPH2_0-3處

然後,我們就查找GPH0和GPH2的數據手冊,全部將其設置爲外部中斷模式(EXT_INT)模式,也就是1111,KP_COL模式是用來做矩陣鍵盤的。

設置好寄存器GPH0和GPH2之後,我們下面設置和外部中斷相關的寄存器:EXT_INT_0_CON,EXT_INT_2_CON,EXT_INT_0_MASK,EXT_INT_2_MASK,EXT_INT_0_PEND,EXT_INT_2_PEND.

總結:也就是說按鍵這邊,一個按鍵的話只需要設置好四個寄存器就可以工作了。

GPH0CON選擇外部中斷模式,

EXT_INT_0_CON選擇怎樣就觸發中斷(是高電平就觸發中斷,還是低電平,上升沿,下降沿,上升/下降沿觸發中斷),

EXT_INT_0_MASK:向該寄存器寫0來使能中斷;

EXT_INT_O_PEND:我們初始化的時候可以通過寫1來進行清除中斷,中斷處理完之後,我們也要向這個寄存器寫1來清除中斷。

設置好上面這些寄存器,我們按鍵部分的中斷初始化就設置好了。

 

複製代碼

 1 // 以中斷方式來處理按鍵的初始化
 2 void key_init_interrupt(void)
 3 {
 4     // 1. 外部中斷對應的GPIO模式設置
 5     rGPH0CON |= 0xFF<<8;        // GPH0_2 GPH0_3設置爲外部中斷模式
 6     
 7     // 2. 中斷觸發模式設置
 8     rEXT_INT_0_CON &= ~(0xFF<<8);    // bit8~bit15全部清零
 9     rEXT_INT_0_CON |= ((2<<8)|(2<<12));        // EXT_INT2和EXT_INT3設置爲下降沿觸發    
10     
11     // 3. 中斷允許
12     rEXT_INT_0_MASK &= ~(3<<2);            // 外部中斷允許
13     
14     // 4. 清掛起,清除是寫1,不是寫0
15     rEXT_INT_0_PEND |= (3<<2);
16 }

複製代碼

 

(2)設置CPU的中斷模式

1.中斷產生的時候,CPU所做的工作的大致過程

  

圖片來源:http://blog.csdn.net/mr_raptor/article/details/6556195

需要注意的是:1.外部中斷髮生,EXT_INT_O_PEND寄存器(按鍵那邊的寄存器)置爲1,中斷掛起,這就相當於告訴了CPU中斷髮生了,然後CPU就發生了上面圖所對應的這些響應

2.中斷髮生後,cpsr寄存器中的IRQ中斷位就置1了,所以,CPU進來處理中斷後,其他硬件在這段時間內發生了中斷的話,CPU是一概不理的。

根據上圖:我們要完成的的東西就是:

  1.把我們的異常處理入口地址放到我們的異常向量表所對應的內存處:0x00000018

  2.上面的異常處理入口那張圖所對應的過程,具體實現的代碼如下。

複製代碼

 1 IRQ_handle:
 2     // 設置IRQ模式下的棧
 3     ldr sp, =IRQ_STACK
 4     // 保存LR
 5     // 因爲ARM有流水線,所以PC的值會比真正執行的代碼+8,
 6     sub lr, lr, #4
 7     // 保存r0-r12和lr到irq模式下的棧上面
 8     stmfd sp!, {r0-r12, lr}
 9     // 在此調用真正的isr來處理中斷
10     bl irq_handler
11     // 處理完成開始恢復現場,其實就是做中斷返回,關鍵是將r0-r12,pc,cpsr一起回覆
12     ldmfd sp!, {r0-r12, pc}^

複製代碼

 

2.設置中斷相關的寄存器,讓CPU找到相應的執行程序,然後執行它(這是中斷的目的:執行中斷處理程序)

 

圖片來源:http://www.docin.com/p-961251144.html

相關的寄存器有:VICnADDR  VICnINTENCLEAR  VICnINTSELECT  VICnIRQSTATUS VIC0VECTADDRn VICnINTENABLE

1.VICnADDR  :一共有四個寄存器,VIC(0-3)一人一個,用來存放我們想要的執行的中斷處理程序(isr)的地址,它裏面的地址是從VIC0VECTADDRn這個寄存器裏面來的,當中斷髮生之後,VIC0VECTADDRn裏面的地址就會硬件自動刷到這個寄存器上。(中斷處理完成之後,我們要清除這個寄存器

 

複製代碼

1 // 清除需要處理的中斷的中斷處理函數的地址
2 void intc_clearvectaddr(void)
3 {
4     // VICxADDR:當前正在處理的中斷的中斷處理函數的地址
5     VIC0ADDR = 0;
6     VIC1ADDR = 0;
7     VIC2ADDR = 0;
8     VIC3ADDR = 0;
9 }

複製代碼

 

2.VICnINTENCLEAR  :清中斷寄存器,也就是中斷處理完成之後,我們要往這個寄存器裏面寫1,把中斷清理掉。我們也可以通過相應的中斷號,來禁止那個中斷。

 

複製代碼

 1 // 禁止中斷
 2 // 通過傳參的intnum來禁止某個具體的中斷源,中斷號在int.h中定義,是物理中斷號
 3 void intc_disable(unsigned long intnum)
 4 {
 5     unsigned long temp;
 6 
 7     if(intnum<32)
 8     {
 9         temp = VIC0INTENCLEAR;
10         temp |= (1<<intnum);
11         VIC0INTENCLEAR = temp;
12     }
13     else if(intnum<64)
14     {
15         temp = VIC1INTENCLEAR;
16         temp |= (1<<(intnum-32));
17         VIC1INTENCLEAR = temp;
18     }
19     else if(intnum<96)
20     {
21         temp = VIC2INTENCLEAR;
22         temp |= (1<<(intnum-64));
23         VIC2INTENCLEAR = temp;
24     }
25     else if(intnum<NUM_ALL)
26     {
27         temp = VIC3INTENCLEAR;
28         temp |= (1<<(intnum-96));
29         VIC3INTENCLEAR = temp;
30     }
31     // NUM_ALL : disable all interrupt
32     else
33     {
34         VIC0INTENCLEAR = 0xFFFFFFFF;
35         VIC1INTENCLEAR = 0xFFFFFFFF;
36         VIC2INTENCLEAR = 0xFFFFFFFF;
37         VIC3INTENCLEAR = 0xFFFFFFFF;
38     }
39 
40     return;
41 }

複製代碼

3.VICnINTSELECT  :中斷選擇寄存器,我們在這裏選擇FIQ模式還是IRQ模式

 

複製代碼

 1 // 初始化中斷控制器
 2 void intc_init(void)
 3 {
 4     // 禁止所有中斷
 5     // 爲什麼在中斷初始化之初要禁止所有中斷?
 6     // 因爲中斷一旦打開,因爲外部或者硬件自己的原因產生中斷後一定就會尋找isr
 7     // 而我們可能認爲自己用不到這個中斷就沒有提供isr,這時它自動拿到的就是亂碼
 8     // 則程序很可能跑飛,所以不用的中斷一定要關掉。
 9     // 一般的做法是先全部關掉,然後再逐一打開自己感興趣的中斷。一旦打開就必須
10     // 給這個中斷提供相應的isr並綁定好。
11     VIC0INTENCLEAR = 0xffffffff;
12     VIC1INTENCLEAR = 0xffffffff;
13     VIC2INTENCLEAR = 0xffffffff;
14     VIC3INTENCLEAR = 0xffffffff;
15 
16     // 選擇中斷類型爲IRQ
17     VIC0INTSELECT = 0x0;
18     VIC1INTSELECT = 0x0;
19     VIC2INTSELECT = 0x0;
20     VIC3INTSELECT = 0x0;
21 
22     // 清VICxADDR
23     intc_clearvectaddr();
24 }

複製代碼

 

4.VIC0VECTADDRn  :一共有128個這樣的寄存器,一箇中斷號對應一個這樣的寄存器,我們可以通過中斷號和VICnADDR這個基地址來計算VIC0VECTADDRn這個寄存器的地址,然後把我們的執行程序放到這個寄存器中,這樣我們就不需要定義太多的宏了。

 

複製代碼

 1 // 綁定我們寫的isr到VICnVECTADDR寄存器
 2 // 綁定過之後我們就把isr地址交給硬件了,剩下的我們不用管了,硬件自己會處理
 3 // 等發生相應中斷的時候,我們直接到相應的VICnADDR中去取isr地址即可。
 4 // 參數:intnum是int.h定義的物理中斷號,handler是函數指針,就是我們寫的isr
 5 
 6 // VIC0VECTADDR定義爲VIC0VECTADDR0寄存器的地址,就相當於是VIC0VECTADDR0~31這個
 7 // 數組(這個數組就是一個函數指針數組)的首地址,然後具體計算每一箇中斷的時候
 8 // 只需要首地址+偏移量即可。
 9 void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
10 {
11     //VIC0
12     if(intnum<32)
13     {
14         *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
15     }
16     //VIC1
17     else if(intnum<64)
18     {
19         *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
20     }
21     //VIC2
22     else if(intnum<96)
23     {
24         *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
25     }
26     //VIC3
27     else
28     {
29         *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
30     }
31     return;
32 }

複製代碼

 

5.VICnIRQSTATUS  :IRQ模式下的中斷狀態寄存器(一共有4個),中斷髮生後,這個寄存器就會自動置1了,然後我們是通過判斷這4個寄存器中哪個寄存器寫了1,然後得知我們的中斷執行程序的地址是放到了哪個VICnADDR寄存器上,最後在相應的VICADDR寄存器上找到中斷執行程序的地址。(自己補充:經過推理分析我覺着該位在中斷響應後該位自動由硬件清零,因爲處理中斷的過程中我們並麼有清零該位,只是查詢的時候用了,並且軟件沒有清零該位並不影響程序的運行,但是一定要清零PEND相應和4個VICnADDR寄存器,至於爲什麼要清零VICnADDR寄存器,還沒有搞清楚,如果不清除這幾個寄存器,中斷會響應一次,然後就不再響應了)

 

複製代碼

 1     unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
 2     int i=0;
 3     void (*isr)(void) = NULL;
 4 
 5     for(i=0; i<4; i++)
 6     {
 7         // 發生一箇中斷時,4個VIC中有3個是全0,1個的其中一位不是0
 8         if(intc_getvicirqstatus(i) != 0)
 9         {
10             isr = (void (*)(void)) vicaddr[i];
11             break;
12         }
13     }
14     (*isr)();        // 通過函數指針來調用函數

複製代碼

 

6.VICnINTENABLE  :中斷使能寄存器,通過中斷號,來向中斷使能寄存器的相應位寫1,就可以使能相應的中斷了

複製代碼

 1 // 使能中斷
 2 // 通過傳參的intnum來使能某個具體的中斷源,中斷號在int.h中定義,是物理中斷號
 3 void intc_enable(unsigned long intnum)
 4 {
 5     unsigned long temp;
 6     // 確定intnum在哪個寄存器的哪一位
 7     // <32就是0~31,必然在VIC0
 8     if(intnum<32)
 9     {
10         temp = VIC0INTENABLE;
11         temp |= (1<<intnum);        // 如果是第一種設計則必須位操作,第二種設計可以
12                                     // 直接寫。
13         VIC0INTENABLE = temp;
14     }
15     else if(intnum<64)
16     {
17         temp = VIC1INTENABLE;
18         temp |= (1<<(intnum-32));
19         VIC1INTENABLE = temp;
20     }
21     else if(intnum<96)
22     {
23         temp = VIC2INTENABLE;
24         temp |= (1<<(intnum-64));
25         VIC2INTENABLE = temp;
26     }
27     else if(intnum<NUM_ALL)
28     {
29         temp = VIC3INTENABLE;
30         temp |= (1<<(intnum-96));
31         VIC3INTENABLE = temp;
32     }
33     // NUM_ALL : enable all interrupt
34     else
35     {
36         VIC0INTENABLE = 0xFFFFFFFF;
37         VIC1INTENABLE = 0xFFFFFFFF;
38         VIC2INTENABLE = 0xFFFFFFFF;
39         VIC3INTENABLE = 0xFFFFFFFF;
40     }
41 
42 }

複製代碼

至此,全部準備工作處理完成,只需要在main函數那裏調用相應的函數就可以了

 

 

複製代碼

 1 int main(void)
 2 {
    //串口初始化
    uart_init();
 3     //按鍵的中斷初始化
 4     key_init_interrupt();
 5     
 6     // 如果程序中要使用中斷,就要調用中斷初始化來初步初始化中斷控制器
 7     system_init_exception();
 8     
 9     // 綁定isr到中斷控制器硬件
10     intc_setvectaddr(KEY_EINT2, isr_eint2);
11     
12     // 使能中斷
13     intc_enable(KEY_EINT2);
14 
15     return 0;
16 }

複製代碼

 

終極目標:執行中斷處理程序,只要我們一按下按鍵,就會在串口那裏打印”isr_eint2_LEFT“這句話了

複製代碼

 1 // EINT2通道對應的按鍵,就是GPH0_2引腳對應的按鍵,就是開發板上標了LEFT的那個按鍵
 2 void isr_eint2(void)
 3 {
 4     // 真正的isr應該做2件事情。
 5     // 第一,中斷處理代碼,就是真正幹活的代碼
 6     printf("isr_eint2_LEFT.\n");
 7     // 第二,清除中斷掛起
 8     rEXT_INT_0_PEND |= (1<<2);
 9     intc_clearvectaddr();
10 }
發佈了24 篇原創文章 · 獲贊 8 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章