一. 總體
要驅動按鍵中斷控制LED亮滅,程序要進行如下幾部分操作:
在start.S中對CPSR寄存器中清除I位,使能IRQ,這是大前提
根據原理圖找出按鍵對應的外部中斷,對外部中斷對應引腳做相應配置,使能相應的外部中斷:EINTMASK
開啟中斷使能:INTMSK要設置
編寫C中斷處理函數,通過INTOFFSET、EINTPEND確定哪個中斷觸發,并做相應處理,還要清除中斷標志位
編寫start.S中的IRQ異常處理函數
二. CPSR設置
CPSR的IRQ中斷使能位不使能,都行不通,我就找bug找了一天。。。
在代碼重定位之后就對CPSR的I位清零,并且分配棧指針,如下:
mrs r0, cpsr /* 讀出cpsr */
bic r0, r0, #0xf /* 修改M4-M0為0b10000, 進入usr模式 */
bic r0, r0, #(1<<7) /* 清除I位, 使能中斷 */
msr cpsr, r0
/* 設置 sp_usr */
ldr sp, =0x33f00000
CPU再運行程序的時候,會自動使用棧,棧指針sp在調用C程序之前就要設定。 CPU的內存RAM空間存放規律一般是分段的,從地址向高地址,依次為:程序段(.text),BSS段,然后上面還可能會有堆空間,然后最上面才是堆棧段,這樣安排堆棧,是因為堆棧的特點決定的,所以堆棧的指針SP初始化一般在堆棧段的高地址,也就是內存的高地址,然后讓堆棧指針向下增長(其實就是遞減)。這樣做的好處就是堆棧空間遠離了其他段,不會跟其他段重疊,造成修改其他段數據,而引起不可預料的后果,還有設置堆棧大小的原則,要保證棧不會下溢出到數據空間或者程序空間。所謂堆棧溢出,是指堆棧指針SP向下增長到其他段空間,如果棧指針向下增長到其他段空間,稱為堆棧溢出。堆棧溢出會修改其他空間的值,嚴重情況下可造成死機.
三. 中斷源設置
中斷源的設置,主要是三點:
配置按鍵引腳為外部中斷模式
配置外部中斷的觸發方式:上升沿、下降沿都觸發
使能相應的外部中斷線(EINT0~3默認開啟使能)
通過查看板子的原理圖獲取按鍵對應的引腳,以及對應的外部中斷線,中斷源配置的代碼如下:
/* 中斷源(按鍵引腳)初始化
* EINT0 EINT2 EINT11 EINT19
* GPF0 GPF2 GPG3 GPG11
* S2 S3 S4 S5
* 說明:配置按鍵對應的引腳、配置中斷觸發方式
*/
void Exti_SoucreKeyInit( void )
{
/* GPF0、2配置為外部中斷引腳模式 */
GPFCON &= ~( (3<<0)|(3<<4) );
GPFCON |= ( (2<<0)|(2<<4) );
/* GPG3、11配置為外部中斷引腳模式 */
GPGCON &= ~( (3<<6)|(3<<22) );
GPGCON |= ( (2<<6)|(2<<22) );
/* EXTINTx 外部中斷控制寄存器設置中斷觸發模式:上升沿、下降沿都觸發 配置相應位為11x */
EXTINT0 |= ( (7<<0)|(7<<8) ); //EXINT0、2
EXTINT1 |= (7<<12); //EXINT11
EXTINT2 |= (7<<12); //EXINT19
/* EINTMASK 外部中斷使能寄存器,開啟對應的外部中斷,清零開啟 */
EINTMASK &= ~( (1<<11)|(1<<19) ); //EINT0~3默認開啟使能
}
四. 中斷控制器設置
根據中斷的流程圖來配置,先分析一下各個寄存器:
MODE用來配置中斷的模式,可以選擇中斷模式為FIQ,這里我們只使用IRQ,所以MODE寄存器不需要設置,如圖:默認是IRQ
MASK相當于使能位(默認是屏蔽的),所以要清零INTMASK寄存器使能相應的中斷。設置INTMASK來使能:
讀取INTPND可以得到當前正在處理的中斷是哪一個,因為可能存在多個中斷請求在排隊,優先級問題。配合OFFSET寄存器使用效果更加。
而且不需要手動清除INTPND,在清除SRCPND的時候,INTPND會自動清除
SRCPND寄存器用來產看發生請求的中斷控制線(中斷源):
外部中斷是without sub-register的,所以中斷的請求直達SRCPND,所以可以通過SRCPND寄存器來顯示是否有中斷請求,相當于中斷標志位,所以在處理中斷之后需要清除SRCPND。
所以在初始化中斷控制器的時候,只要打開使能位即可,就是配置一下INTMASK就可以。SRCPND和INTPND是在中斷處理函數里使用的,判斷是哪一個中斷請求的
中斷控制器有關的初始化代碼如下:
/* 中斷控制器初始化
* 說明:開啟中斷使能,在EINTMASK、INTMSK中,只有EINT0~3是默認不屏蔽的
*/
void Exti_InterruptControlInit( void )
{
/* 開啟中斷使能 */
INTMSK &= ~( (1<<0)|(1<<2)|(1<<5) );
}
五. C中斷處理函數
在C中斷處理函數中,要分辨中斷請求,根據相應的中斷請求做出回應,我使用按鍵來控制三個LED的亮滅狀態,具體代碼如下:
#define LED1_ON GPFDAT &= ~(1<<4)
#define LED2_ON GPFDAT &= ~(1<<5)
#define LED3_ON GPFDAT &= ~(1<<6)
#define LED1_OFF GPFDAT |= (1<<4)
#define LED2_OFF GPFDAT |= (1<<5)
#define LED3_OFF GPFDAT |= (1<<6)
/* 中斷處理函數
* 說明:觸發外部中斷后,進入中斷處理函數,通過SRCPND來判斷觸發了哪一個中斷,從而進行相應的操作。退出時要清理中斷
*/
void Exti_ProcessingInterrupt( void )
{
unsigned int temp = INTOFFSET;
unsigned int reset = 0;//用來清零up,
puts("nrIRQ!nr");
/* 可以通過INTOFFSET寄存器的值來直接判斷哪個中斷觸發了 */
if( temp==0 ) //EINT0
{
if(GPFDAT&(1<<0)) //引腳高電平,按鍵按下,點亮LED
{
LED1_ON;
}
else
{
LED1_OFF;
}
reset |= (1<<0);
}
else if( temp==2 ) //EINT2
{
if(GPFDAT&(1<<2)) //GPF2
{
LED2_ON;
}
else
{
LED2_OFF;
}
reset |= (1<<2);
}
else if( temp==5 ) //EINT8_23
{
if( EINTPEND&(1<<11) )
{
if(GPGDAT&(1<<3)) //GPG3
{
LED3_ON;
}
else
{
LED3_OFF;
}
}
else if( EINTPEND&(1<<19) )
{
if(GPGDAT&(1<<11)) //GPG11
{
LED1_ON;
LED2_ON;
LED3_ON;
}
else
{
LED1_OFF;
LED2_OFF;
LED3_OFF;
}
}
reset |= (1<<5);
}
/* 清除SRCPND、INTPND標志位 */
EINTPEND |= ( (1<<11)|(1<19) );//寫1清除
SRCPND |= ( (1<<0)|(1<2)|(1<<5) );
INTPND |= ( (1<<0)|(1<2)|(1<<5) );
}
最后還要清除EINTPEND、SRCPND、INTPND中的標志位,不然程序會一直處于中斷中。
六. 匯編IRQ異常處理程序
當觸發外部中斷時,CPU會去中斷向量表中IRQ的指令位置,跳轉到IRQ的處理程序中,所以首先得在_start之后的0X18處寫一個跳轉指令,跳轉至IRQ處理程序,如下:
_start:
/* 異常向量表 */
bl reset /* 0X0 Reset 上電復位,從0地址開始執行程序,依次:關閉看門狗、配置時鐘系統、初始化sdram、拷貝代碼到sdram(重定位)、清除.bcc段、進入mian函數 */
bl halt /* 0X4 Undefined instruction */
bl halt /* 0X8 Software Interrupt */
bl halt /* 0XC Abort(prefetch) */
bl halt /* 0X10 Abort(data) */
bl halt /* 0X14 Reserved */
ldr pc, =irq_addr /* 0X18 IRQ */
bl halt /* 0X1C FIQ */
.align 4
irq_addr:
.word do_irq
在do_irq程序中,首先指定棧指針,然后保存現場(注意返回的值是lr-4),然后調用C處理函數,然后恢復現場,如下:
.align 4
do_irq:
ldr sp, =0x33d00000
sub lr, lr, #4 /* 發生中斷時,返回值是lr-4 */
stmdb sp!, {r0-r12,lr}
bl Exti_ProcessingInterrupt
ldmia sp!, {r0-r12,pc}^
七. 源碼
start.S
.text
.global _start
_start:
/* 異常向量表 */
bl reset /* 0X0 Reset 上電復位,從0地址開始執行程序,依次:關閉看門狗、配置時鐘系統、初始化sdram、拷貝代碼到sdram(重定位)、清除.bcc段、進入mian函數 */
bl halt /* 0X4 Undefined instruction */
bl halt /* 0X8 Software Interrupt */
bl halt /* 0XC Abort(prefetch) */
bl halt /* 0X10 Abort(data) */
bl halt /* 0X14 Reserved */
ldr pc, =irq_addr /* 0X18 IRQ */
bl halt /* 0X1C FIQ */
.align 4
irq_addr:
.word do_irq
.align 4
do_irq:
ldr sp, =0x33d00000
sub lr, lr, #4 /* 發生中斷時,返回值是lr-4 */
stmdb sp!, {r0-r12,lr}
bl Exti_ProcessingInterrupt
ldmia sp!, {r0-r12,pc}^
.align 4
reset:
/* 關閉看門狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 設置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 設置CPU工作于異步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 設置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦設置PLL, 就會鎖定lock time直到PLL輸出穩定
* 然后CPU工作于新的頻率FCLK
*/
/* 設置內存: sp 棧 */
/* 分辨是nor/nand啟動
* 寫0到0地址, 再讀出來
* 如果得到0, 表示0地址上的內容被修改了, 它對應ram, 這就是nand啟動
* 否則就是nor啟動
*/
mov r1, #0
ldr r0, [r1] /* 讀出原來的值備份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND啟動 */
ldr sp, =0x40000000+4096 /* 先假設是nor啟動 */
moveq sp, #4096 /* nand啟動 */
streq r0, [r1] /* 恢復原來的值 */
/* 代碼重定位的時候,首先初始化SDRAM,然后拷貝代碼,然后清除.bss段(防止內存訪問出錯),最后執行main函數,main就在重定位的SDRAM中去執行 */
bl sdram_init
/* 重定位text, rodata, data段整個程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
mrs r0, cpsr /* 讀出cpsr */
bic r0, r0, #0xf /* 修改M4-M0為0b10000, 進入usr模式 */
bic r0, r0, #(1<<7) /* 清除I位, 使能中斷 */
msr cpsr, r0
/* 設置 sp_usr */
ldr sp, =0x33f00000
//bl main /* 使用BL命令相對跳轉, 程序仍然在NOR/sram執行 */
ldr pc, =main /* 絕對跳轉, 跳到SDRAM */
.align 4
halt:
b halt
exti.c
#include "s3c2440_soc.h"
#include "uart.h"
/* LED引腳低電平點亮 */
#define LED1_ON GPFDAT &= ~(1<<4)
#define LED2_ON GPFDAT &= ~(1<<5)
#define LED3_ON GPFDAT &= ~(1<<6)
#define LED1_OFF GPFDAT |= (1<<4)
#define LED2_OFF GPFDAT |= (1<<5)
#define LED3_OFF GPFDAT |= (1<<6)
/* 中斷控制器初始化
* 說明:開啟中斷使能,在EINTMASK中,只有EINT0~3是默認不屏蔽的
*/
void Exti_InterruptControlInit( void )
{
/* 開啟中斷使能 */
INTMSK &= ~( (1<<0)|(1<<2)|(1<<5) );
}
/* 中斷源(按鍵引腳)初始化
* EINT0 EINT2 EINT11 EINT19
* GPF0 GPF2 GPG3 GPG11
上一篇:LPC2000系列學習筆記4--存儲器映射控制
下一篇:S3C2440—11.und異常
- 熱門資源推薦
- 熱門放大器推薦