本来以为自己可以很快就把中断程序写好的,但是没想到知道昨天才有了点眉目,虽然还不知道对不对,但是写出来给大家批评指正。
笔者自从上次写了一个轮询式的按键驱动LED灯之后就一直在弄中断这一部分,可是弄来弄去都没什么起色,只好也中断一段时间去配置自己的linux系统,写写应用程序,果然停了几天后突然就成了,真是不知道怎么回事。下面说说笔者的思路。
笔者从《ARM体系结构与编程》这本书中知道了ARM中有七种中断,中断需要中断向量表,而且中断向量表需要放在最低端从地址空间0开始的连续32字节内,为什么七个中断要32字节呢?因为倒数第三个四字节的空间需要空出来。然后是ARM中的中断处理体系,想必阅者都知道x86的中断过程吧,外设通过外设控制器发出中断信号,被中断控制器拦截并进行一定的处理然后发送给CPU,那么ARM肯定也是有中断控制器的,是什么呢?根据s3c6410的用户手册,笔者知道了叫作VIC(全称的中文翻译叫作中断向量控制器)。这个VIC就相当于PC机中的APIC,下面是出事换VIC的代码:
/* 先清除所有中断*/
rVIC0INTENCLEAR = ~(0x0);
rVIC1INTENCLEAR = ~(0x0);
/* 置中断类型, 全部为IRQ中断*/
rVIC0INTSELECT = 0x0;
rVIC1INTSELECT = 0x0;
rVIC0INTENABLE |= 1 << 0;
rVIC0INTENABLE |= 1 << 1;
这个其实很简单,就是让所有中断无效,然后使能按键中断。初始化了VIC之后呢?中断需要三方面协调才可以发生,中断控制器是一个方面,除此之外cpu要能够接受中断,还有就是外设要能够发送中断信号。下面初始化按键:
tmp = rGPNCON;
tmp &= ~0xFFF;
tmp |= 0xAAA; // 六个按键全部为设置为中断引脚
rGPNCON = tmp;
rGPNPUD &= ~0xFFF;
tmp = rEINT0PEND;
tmp |= 0x1;
tmp |= 0x1 << 1;
rEINT0PEND = tmp;
tmp = rEINT0MASK;
tmp &= ~(0x3F);
rEINT0MASK = tmp;
初始化按键除了要把引脚设置为中断模式外,还有比较重要的就是要清除中断状态寄存器(向对应位写’1‘)和清除中断屏蔽寄存器(对应位清0)。
到了这里似乎就万事俱备,只差中断程序了,但是笔者上次就是在这里遇到了问题,中断向量表要求在地址空间的最低端,但是SDRAM是从0x50000000开始的,程序肯定要下载到这个地址之后的空间,那么笔者自己写的中断向量表怎么办呢?于是笔者想到了使用MMU,把0地址映射到物理地址的0x50200000(程序下载到这个地方),其它部分做相同映射:
start
Interrupt_Vec ; 中断向量表
ldr PC, ResetAddr
ldr PC, UndefAddr
ldr PC, SWIAddr
ldr PC, PrefetchAddr
ldr PC, DataAbortAddr
DCD 0x0
ldr PC, IRQAddr
ldr PC, FIQAddr
ResetAddr DCD ResetInit
UndefAddr DCD UndefHandler
SWIAddr DCD SWIHandler
PrefetchAddr DCD PrefetchHandler
DataAbortAddr DCD DataAbortHandler
DCD 0x0
IRQAddr DCD IRQHandler
FIQAddr DCD FIQHandler
ReservedSpace SPACE 16384 - 64 ; 页表只能16KB对齐
PageTable SPACE 16384 ; 页表,只实现一级映射,1MSection
; 以下是用不到的中断
UndefHandler
subs pc, r14, #4
SWIHandler
subs pc, r14, #4
PrefetchHandler
subs pc, r14, #4
DataAbortHandler
subs pc, r14, #4
FIQHandler
subs pc, r14, #4
EnableMMU
; Invalidate entire Cache
mov r0, #0
mcr p15, 0, r0, c7, c7, 0
; Load PageTableBase
ldr r0, =PageTable
mcr p15, 0, r0, c2, c0, 0
;Field control
ldr r0, =0xFFFFFFFF
mcr p15, 0, r0, c3, c0, 0
; Enable MMU, Enable I Cache, Disable AP bits
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #0x1000
orr r0, r0, #0x800000
orr r0, r0, #0x1
mcr p15, 0, r0, c1, c0, 0
mov pc, r14
LTORG
ResetInit ; 复位,设置栈
LDR r13, =0x50400000
bl _SetPageTable
bl EnableMMU
b Main
LTORG
IRQHandler ; 中断处理
stmfd sp!, {r0-r3, r12, r14}
ldr r14, =IRQRet
b _HandleIRQ
IRQRet
ldmfd sp!, {r0-r3, r12, r14}
subs pc, r14, #4
LTORG
至于_SetPageTable这个程序使用c语言实现的:
extern int PageTable[];
#define L1_DESCRIPTOR 2
int _SetPageTable(void)
{
int PhyBase;
int i;
PhyBase = 0x50200000 + L1_DESCRIPTOR;
PageTable[0] = PhyBase;
for (i = 1; i < 4096; i++)
{
PhyBase = 0x0 + L1_DESCRIPTOR + i * 0x00100000;
PageTable[i] = PhyBase;
}
return 0;
}
这样以后,MMU就算是开启了,笔者编译仿真了一下,发现EnableMMU这个程序运行顺利,而且这之后的代码还能正确运行,然后笔者写了中断程序,仍然是按键驱动LED,发现竟然正确运行了。
主函数如下:
void Main(void)
{
InitVIC();
InitGPIO();
EnableIRQ();
while (1);
}
这就是主流程,可以看出来笔者确实用的中断实现了按键驱动LED。
虽然到这里为止笔者的第一个中断程序算是写出来了,但是上述的程序很容易在中断内部或者InitGPIO函数内死循环,不知道为什么,只好以后解决了,先这样吧!