嵌入式學習-uboot-lesson11-按鍵中斷
一、中斷處理流程分析
1.中斷概念
CPU在工作的過程中,經常需要與外設進行互動,互動的方式包括”輪詢方式”,”中斷方式”。
輪詢方式:
CPU不斷地查詢裝置的狀態。該方式實現比較簡單,但CPU利用率很低,不適合多工的系統。
中斷方式:
CPU在告知硬體開始一項工作後,就去做別的事去了,當硬體完成了該項工作後,向CPU傳送一個訊號,告知CPU它已經完成了這項工作。
2.中斷生命週期
事件會送到中斷控制器裡面,中斷控制器對其進行過濾,然後將其送到CPU核
3.中斷源
在中斷的生命週期中,中斷源的作用是負責產生中斷訊號。
S3C2440支援60箇中斷源;
S3C6410支援64箇中斷源;
S5PV210支援93箇中斷源;
6410的中斷:
6410一共有個兩個中斷組,為VIC0和VIC1,每個VIC支援32箇中斷源,一共64箇中斷源,如下圖所示
從上面三幅圖可以看到6410的64箇中斷源,同時可以看到有的中斷源例如 INT_EINT0 包含了4箇中斷,主要是為了節約中斷,就用這一個中斷源,當 這四個中斷某一個發生時,中斷源INT_EINT0 就會產生中斷,然後會查詢相應的暫存器來判斷是發生了那個中斷。
4.中斷處理方式
4.1非向量方式(2440)
如上圖,非向量中斷,當中斷產生的時候,會跳轉到一個統一的入口,然後在這個入口函式中判斷是那個中斷髮生了,然後再去執行相應的中斷處理程式處去
4.2向量方式(6410/210)
向量中斷,就是當中斷髮生的時候,硬體自動判斷到是那個中斷產生,然後就會跳轉到相應的中斷暫存器裡面,中斷暫存器裡面存放著這個中斷的處理程式的地址,然後根據這個地址,就是我們的中斷處理程式了。
二、6410中斷程式設計分析
其總體流程如下所示:
1.初始化按鍵
從上面兩幅圖可以看到,ok6410的6個按鍵對應的GPIO分別為GPN0 ~ GPN5
根據上圖,對按鍵1和按鍵6進行操作,需要將控制暫存器設為外部中斷模式,因此要設定為0b10
#define GPNCON (volatile unsigned long*)0x7f008830
void button_init()
{
*(GPNCON) = 0b10 | (0b10<<10); //設定按鍵1與按鍵6 外部中斷
}
2.初始化中斷控制器
2.1配置為下降沿觸發
#define EXT_INT_0_CON *((volatile unsigned int *)0x7f008900)
將EINT0 和 EINT5 設定為下降沿觸發
EXT_INT_0_CON = (0b010)|(0b010<<8); /* 配置按鍵1和按鍵6為下降沿觸發 */
2.2取消中斷遮蔽
#define EXT_INT_0_MASK *((volatile unsigned int *)0x7f008920)
使能中斷,則使暫存器的值為0即可
EXT_INT_0_MASK = 0; /* 取消遮蔽外部中斷 */
2.3使能中斷向量控制器
#define VIC0INTENABLE *((volatile unsigned int *)0x71200010)
根據上面兩幅圖,使能中斷暫存器需要設定其值為1,同時要設定EINT0 EINT5,則需要使能中斷源INT_EINT0 INT_EINT1
VIC0INTENABLE |= (0b1)|(0b10); /* 使能外部中斷*/
2.4 設定CPSR狀態暫存器,開啟總中斷
__asm__(
"mrs r0,cpsr\n" /*讀出值*/
"bic r0, r0, #0x80\n"
"msr cpsr_c, r0\n" /*寫入*/
:
:
2.5使能向量中斷
向量中斷的方式,當中斷產生的時候,並沒有統一的入口,6410的64箇中斷有64個相對應的暫存器,每個暫存器裡面存放著相對應的中斷處理程式的地址,當中斷產生的時候,硬體自己判斷髮生了那個中斷,cpu就直接從發生中斷對應的暫存器中取出處理程式的地址,從而處理中斷。
#define EINT0_VECTADDR *((volatile unsigned int *)0x71200100)
#define EINT5_VECTADDR *((volatile unsigned int *)0x71200104)
EINT0_VECTADDR = (int)key1_handle;
EINT5_VECTADDR = (int)key6_handle;
使能向量中斷,6410也可向下相容2440非向量中斷方式,因此需要設定其為向量中斷方式
__asm__(
"mrc p15,0,r0,c1,c0,0\n" /*使能向量中斷*/
"orr r0,r0,#(1<<24)\n"
"mcr p15,0,r0,c1,c0,0\n"
:
:
);
3.中斷處理
3.1儲存環境
儲存環境變數到棧上
__asm__(
"sub lr, lr, #4\n"
"stmfd sp!, {r0-r12, lr}\n"
:
:
);
3.2中斷處理
led_on(); //點亮led
3.3清除中斷
此暫存器的功能是當發生中斷後,就會產生計數,從0變為1
當處理完中斷之後,需要清除掉這次中斷,使其恢復到原始的狀態。
#define EXT_INT_0_PEND *((volatile unsigned int *)0x7f008924)
EXT_INT_0_PEND = ~0x0;
當產生中斷的時候,中斷暫存器裡面的地址也會放在VIC0ADDRESS 裡面,因此在處理完中斷的時候,也需要將其清零。
#define VIC0ADDRESS *((volatile unsigned int *)0x71200f00)
#define VIC1ADDRESS *((volatile unsigned int *)0x71300f00)
VIC0ADDRESS = 0;
VIC1ADDRESS = 0;
3.4恢復環境
__asm__(
"ldmfd sp!, {r0-r12, pc}^ \n"
:
:
);
4.棧的處理
中斷環境下的棧和SVC模式下的棧有所不同,因此需要設定中斷模式下的棧
init_stack:
msr cpsr_c, #0xd2 @進入中斷模式
ldr sp, =0x53000000 @初始化r13_irq
msr cpsr_c, #0xd3 @進入svc模式
ldr sp,=0x54000000 @初始化r13_svc 50000000 + 64 MB
mov pc,lr
貼上這次改動過的程式碼:
button.c
/********************************************
*file name: button.c
*author : stone
*date : 2016.7.3
*function : 按鍵中斷進行相關的操作
*********************************************/
#define GPNCON (volatile unsigned long*)0x7f008830
void button_init()
{
*(GPNCON) = 0b10 | (0b10<<10); //設定按鍵1與按鍵6 為外部中斷模式
}
interrupt.c
/********************************************
*file name: interrupt.c
*author : stone
*date : 2016.7.3
*function : 中斷處理
*********************************************/
#define EXT_INT_0_CON *((volatile unsigned int *)0x7f008900) //外部中斷配置暫存器
#define EXT_INT_0_MASK *((volatile unsigned int *)0x7f008920) //外部中斷遮蔽暫存器
#define EXT_INT_0_PEND *((volatile unsigned int *)0x7f008924) //外部中斷0懸掛暫存器
#define VIC0INTENABLE *((volatile unsigned int *)0x71200010) //中斷使能暫存器
#define EINT0_VECTADDR *((volatile unsigned int *)0x71200100) //向量地址0暫存器
#define EINT5_VECTADDR *((volatile unsigned int *)0x71200104) //向量地址1暫存器
#define VIC0ADDRESS *((volatile unsigned int *)0x71200f00) //向量地址暫存器
#define VIC1ADDRESS *((volatile unsigned int *)0x71300f00)
//按鍵1處理程式
void key1_handle()
{
__asm__( /*3.1儲存環境*/
"sub lr, lr, #4\n"
"stmfd sp!, {r0-r12, lr}\n"
:
:
);
//3.2中斷處理程式
led_on();
//3.3清除中斷
EXT_INT_0_PEND = ~0x0;
VIC0ADDRESS = 0;
VIC1ADDRESS = 0;
//3.4恢復環境
__asm__(
"ldmfd sp!, {r0-r12, pc}^ \n"
:
:
);
}
//按鍵6處理程式
void key6_handle()
{
__asm__(
"sub lr, lr, #4\n"
"stmfd sp!, {r0-r12, lr}\n"
:
:
);
led_off();
//清除中斷
EXT_INT_0_PEND = ~0x0;
VIC0ADDRESS = 0;
VIC1ADDRESS = 0;
__asm__(
"ldmfd sp!, {r0-r12, pc}^ \n"
:
:
);
}
//中斷初始化
void init_irq()
{
EXT_INT_0_CON = (0b010)|(0b010<<8); /* 2.1 配置按鍵1和按鍵6為下降沿觸發 */
EXT_INT_0_MASK = 0; /* 2.2取消遮蔽外部中斷 */
VIC0INTENABLE |= (0b1)|(0b10); /* 2.3使能外部中斷*/
EINT0_VECTADDR = (int)key1_handle; /* 2.5使用者按下key時,CPU就會自動的將VIC0VECTADDR0的值賦給VIC0ADDRESS並跳轉到這個地址去執 */
EINT5_VECTADDR = (int)key6_handle;
__asm__(
"mrc p15,0,r0,c1,c0,0\n" /*2.5使能向量中斷*/
"orr r0,r0,#(1<<24)\n"
"mcr p15,0,r0,c1,c0,0\n"
"mrs r0,cpsr\n" /*2.4設定cpsr狀態暫存器*/
"bic r0, r0, #0x80\n"
"msr cpsr_c, r0\n"
:
:
);
}
main.c
/********************************************
*file name: main.c
*author : stone
*date : 2016.7.3
*function : 總程式
*********************************************/
int gboot_main()
{
/*mmu 初始化,暫時不用*/
#ifdef MMU_ON
mmu_init();
#endif
/*led初始化*/
led_init();
/*中斷初始化*/
init_irq();
/*按鍵初始化*/
button_init();
//led_off();
while(1);
return 0;
}
mmu.c
/********************************************
*file name: mmu.c
*author : stone
*date : 2016.7.3
*function : mmu的初始化
*********************************************/
#define MMU_FULL_ACCESS (3 << 10) /* 訪問許可權 [11:10]*/
#define MMU_DOMAIN (0 << 5) /* 屬於哪個域 [8:5]*/
#define MMU_SPECIAL (1 << 4) /* 必須是1 [4]*/
#define MMU_CACHEABLE (1 << 3) /* cacheable [3]*/
#define MMU_BUFFERABLE (1 << 2) /* bufferable [2]*/
#define MMU_SECTION (2) /* 表示這是段描述符 [1:0]*/
#define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_SECTION)
#define MMU_SECDESC_WB (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION)
void creat_page_table()
{
unsigned long *ttb = (unsigned long *)0x50000000; //表在記憶體的基地址處
unsigned long vaddr; //虛擬地址
unsigned long paddr; //實體地址
vaddr = 0xa0000000; //虛擬地址
paddr = 0x7F000000;
*(ttb + (vaddr >> 20)) = (paddr&0xfff00000) | MMU_SECDESC;
//*(ttb + (vaddr >> 20)) 為表項的位置
//(paddr&0xfff00000) 獲取高12位資料
//MMU_SECDESC 訪問led的gpio很簡單,就不需要cache和buffer
//對映記憶體
vaddr = 0x50000000;
paddr = 0x50000000; //其虛擬地址和實體地址是一致的
while (vaddr < 0x54000000) //對映64mb
{
*(ttb + (vaddr >> 20)) = (paddr & 0xFFF00000) | MMU_SECDESC_WB;
vaddr += 0x100000;
paddr += 0x100000;
}
}
void mmu_enable()
{
__asm__(
/*設定TTB*/
"ldr r0, =0x50000000\n" /* 頁表的基地址 */
"mcr p15, 0, r0, c2, c0, 0\n"
/*不進行許可權檢查*//*cp15 c3 domain 控制權限*/
"mvn r0, #0\n"
"mcr p15, 0, r0, c3, c0, 0\n"
/*使能MMU*/
"mrc p15, 0, r0, c1, c0, 0\n"
"orr r0, r0, #0x0001\n"
"mcr p15, 0, r0, c1, c0, 0\n"
:
:
);
}
void mmu_init()
{
create_page_table();
mmu_enable();
}
菜鳥一枚,如有錯誤,多多指教。。。