ARM之S5pv210的按鍵和中斷部分
一、按鍵和中斷分析
要使用中斷,首先要做好兩個部分的工作:CPU中斷的初始化和相應器件的中斷的初始化。
CPU中斷初始化:就是要設定號CPU有關中斷的東西。
相關器件的中斷初始化:例如按鍵,就要設定好按鍵,就觸發中斷。
(1)、先看看按鍵的原理圖
從上圖我們可以得知:按鍵是接在EINT2和EINT3處,還有KP_COL0-3,一共是6個按鍵。
(2)、接下來看SoC介面處
從上圖EINT2和EINT3接在了GPH0_2,3處,KP_COL0-3接在了GPH2_0-3處。
(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_0_PEND:我們初始化的時候可以通過寫1來進行清除中斷,中斷處理完之後,我們也要向這個暫存器寫1來
清除中斷。
設定好上面這些暫存器,我們按鍵部分的中斷初始化就設定好了
// 以中斷方式來處理按鍵的初始化 void key_init_interrupt(void) { // 1. 外部中斷對應的GPIO模式設定 rGPH0CON |= 0xFF<<8; // GPH0_2 GPH0_3設定為外部中斷模式 // 2. 中斷觸發模式設定 rEXT_INT_0_CON &= ~(0xFF<<8); // bit8~bit15全部清零 rEXT_INT_0_CON |= ((2<<8)|(2<<12)); // EXT_INT2和EXT_INT3設定為下降沿觸發 // 3. 中斷允許 rEXT_INT_0_MASK &= ~(3<<2); // 外部中斷允許 // 4. 清掛起,清除是寫1,不是寫0 rEXT_INT_0_PEND |= (3<<2); }
二、設定CPU的中斷模式
1、中斷產生CPU所做的工作的大概過程
需要注意的是:
(1)、外部中斷髮生,EXT_INT_O_PEND暫存器(按鍵那邊的暫存器)置1,中斷掛起,這
就相當於告訴了CPU中斷髮生了,然後CPU就發生了上圖所對應的這些響應。
(2)、中斷髮生後,cpsr暫存器中的IRQ中斷位就置1了,所以,CPU進來處理中斷後,其他硬
件在這段時間內發生中斷的話,CPU是不理的。
根據上圖:我們需要完成的東西就是
(1)、把我們的異常處理入口的地址放到我們異常向量表所對應的記憶體處:0x00000018
程式碼實現為:
IRQ_handle:
// 設定IRQ模式下的棧
ldr sp, =IRQ_STACK
// 儲存LR
// 因為ARM有流水線,所以PC的值會比真正執行的程式碼+8,
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}^
三、設定中斷相關的暫存器,讓CPU找到相應的執行程式,然後執行它(這是中斷的目的:執行中斷處理程式)
相關的暫存器有:VICnADDR VICnINTENCLEAR VICnINTSELECT VICnIRQSTATUS
VIC0VECTADDRn VICnINTENABLE
1、VICnADDR:一共有四個暫存器,VIC(0-3)一人一個,用來存放我們想要執行的中斷處理程式(isr)的地址,
它裡面的地址是從VIC0VECTADDRn這個暫存器裡面來的,當中斷髮生之後,VIC0VECTADDRn裡面的地址就會被
硬體自動刷到這個暫存器上。(中斷處理完之後,我們要清除這個暫存器)
// 清除需要處理的中斷的中斷處理函式的地址
void intc_clearvectaddr(void)
{
// VICxADDR:當前正在處理的中斷的中斷處理函式的地址
VIC0ADDR = 0;
VIC1ADDR = 0;
VIC2ADDR = 0;
VIC3ADDR = 0;
}
2、VINCnINTENCLEAR:清中斷暫存器,也就是中斷處理完成之後,我們要往這個暫存器裡面寫1,把中斷清理掉。
我們可以通過相應的中斷號,來禁止那個中斷。
// 禁止中斷
// 通過傳參的intnum來禁止某個具體的中斷源,中斷號在int.h中定義,是物理中斷號
void intc_disable(unsigned long intnum)
{
unsigned long temp;
if(intnum<32)
{
temp = VIC0INTENCLEAR;
temp |= (1<<intnum);
VIC0INTENCLEAR = temp;
}
else if(intnum<64)
{
temp = VIC1INTENCLEAR;
temp |= (1<<(intnum-32));
VIC1INTENCLEAR = temp;
}
else if(intnum<96)
{
temp = VIC2INTENCLEAR;
temp |= (1<<(intnum-64));
VIC2INTENCLEAR = temp;
}
else if(intnum<NUM_ALL)
{
temp = VIC3INTENCLEAR;
temp |= (1<<(intnum-96));
VIC3INTENCLEAR = temp;
}
// NUM_ALL : disable all interrupt
else
{
VIC0INTENCLEAR = 0xFFFFFFFF;
VIC1INTENCLEAR = 0xFFFFFFFF;
VIC2INTENCLEAR = 0xFFFFFFFF;
VIC3INTENCLEAR = 0xFFFFFFFF;
}
return;
}
3.VICnINTSELECT :中斷選擇暫存器,我們在這裡選擇FIQ模式還是IRQ模式
// 初始化中斷控制器
void intc_init(void)
{
// 禁止所有中斷
// 為什麼在中斷初始化之初要禁止所有中斷?
// 因為中斷一旦開啟,因為外部或者硬體自己的原因產生中斷後一定就會尋找isr
// 而我們可能認為自己用不到這個中斷就沒有提供isr,這時它自動拿到的就是亂碼
// 則程式很可能跑飛,所以不用的中斷一定要關掉。
// 一般的做法是先全部關掉,然後再逐一開啟自己感興趣的中斷。一旦開啟就必須
// 給這個中斷提供相應的isr並繫結好。
VIC0INTENCLEAR = 0xffffffff;
VIC1INTENCLEAR = 0xffffffff;
VIC2INTENCLEAR = 0xffffffff;
VIC3INTENCLEAR = 0xffffffff;
// 選擇中斷型別為IRQ
VIC0INTSELECT = 0x0;
VIC1INTSELECT = 0x0;
VIC2INTSELECT = 0x0;
VIC3INTSELECT = 0x0;
// 清VICxADDR
intc_clearvectaddr();
}
4.VIC0VECTADDRn :一共有128個這樣的暫存器,一箇中斷號對應一個這樣的暫存器,我們可以通過中斷號和VICnADDR這個基地址來計算VIC0VECTADDRn這個暫存器的地址,然後把我們的執行程式放到這個暫存器中,這樣我們就不需要定義太多的巨集了。// 繫結我們寫的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<32)
{
*( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
}
//VIC1
else if(intnum<64)
{
*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
}
//VIC2
else if(intnum<96)
{
*( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
}
//VIC3
else
{
*( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
}
return;
}
5.VICnIRQSTATUS :IRQ模式下的中斷狀態暫存器(一共有4個),中斷髮生後,這個暫存器就會自動置1了,然後我們是通過判斷這4個暫存器中哪個暫存器寫了1,然後得知我們的中斷執行程式的地址是放到了哪個VICnADDR暫存器上,最後在相應的VICADDR暫存器上找到中斷執行程式的地址。unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
void (*isr)(void) = NULL;
for(i=0; i<4; i++)
{
// 發生一箇中斷時,4個VIC中有3個是全0,1個的其中一位不是0
if(intc_getvicirqstatus(i) != 0)
{
isr = (void (*)(void)) vicaddr[i];
break;
}
}
(*isr)(); // 通過函式指標來呼叫函式
6.VICnINTENABLE :中斷使能暫存器,通過中斷號,來向中斷使能暫存器的相應位寫1,就可以使能相應的中斷了// 使能中斷
// 通過傳參的intnum來使能某個具體的中斷源,中斷號在int.h中定義,是物理中斷號
void intc_enable(unsigned long intnum)
{
unsigned long temp;
// 確定intnum在哪個暫存器的哪一位
// <32就是0~31,必然在VIC0
if(intnum<32)
{
temp = VIC0INTENABLE;
temp |= (1<<intnum); // 如果是第一種設計則必須位操作,第二種設計可以
// 直接寫。
VIC0INTENABLE = temp;
}
else if(intnum<64)
{
temp = VIC1INTENABLE;
temp |= (1<<(intnum-32));
VIC1INTENABLE = temp;
}
else if(intnum<96)
{
temp = VIC2INTENABLE;
temp |= (1<<(intnum-64));
VIC2INTENABLE = temp;
}
else if(intnum<NUM_ALL)
{
temp = VIC3INTENABLE;
temp |= (1<<(intnum-96));
VIC3INTENABLE = temp;
}
// NUM_ALL : enable all interrupt
else
{
VIC0INTENABLE = 0xFFFFFFFF;
VIC1INTENABLE = 0xFFFFFFFF;
VIC2INTENABLE = 0xFFFFFFFF;
VIC3INTENABLE = 0xFFFFFFFF;
}
}
接下來是主函式:
int main(void)
{
//串列埠初始化
uart_init();
//按鍵的中斷初始化
key_init_interrupt();
// 如果程式中要使用中斷,就要呼叫中斷初始化來初步初始化中斷控制器
system_init_exception();
// 繫結isr到中斷控制器硬體
intc_setvectaddr(KEY_EINT2, isr_eint2);
// 使能中斷
intc_enable(KEY_EINT2);
return 0;
}
歡迎各位指出不足之處