第一节 关于S5PV210的中断体系结构
S5PV210的中断控制器是由4个向量中断控制器(VIC)、ARM PrimeCell PL192 和 4个 TrustZone Interrupt Controller (TZIC)共同组成。 S5PV210共支持93个中断源,待会我们将使能其中的一个外部中断,让大家了解中断处理的完整过程。
第二节 程序相关讲解
完整代码见目录详细代码下载链接。
1. start.S
共4个步骤,其中第2、4步和中断相关:
第一步 清bss;
第二步 开中断,设置CPSR 寄存器,允许中断发生,代码如下:
mov r0, #0x53
msr CPSR_cxsf, r0
第三步 跳转到main;
第四步 中断处理;程序正常执行时,只会运行到第三步就跳转到main而不会执行该部分代码。当有中断发生时,PC才会跳转到该部分代码,进行中断相关的处理。别急,后面会详细解释该段代码。
2. main.c
共4个步骤,其中第2、3、4步和中断相关:
第一步 初始化串口;
第二步 中断相关初始化,调用了system_initexception();
代码如下:
void system_initexception( void)
{
// 设置中断向量表
pExceptionUNDEF = (unsigned long)exceptionundef;
pExceptionSWI = (unsigned long)exceptionswi;
pExceptionPABORT = (unsigned long)exceptionpabort;
pExceptionDABORT = (unsigned long)exceptiondabort;
pExceptionIRQ = (unsigned long)IRQ_handle;
pExceptionFIQ = (unsigned long)IRQ_handle;
// 初始化中断控制器
intc_init();
}
这段代码主要做了下面两件事:
1) 设置中断向量表
当发生各种异常时,PC会自动跳转到相应的异常向量;S5PV210的异常向量表的起始地址是0xD0037400,我们需要特别注意的是,当发生IRQ中断异常时,对应的处理函数是IRQ_handle(),即我们在start.S中第四步所设置的代码如下:
IRQ_handle:
// 设置中断模式的栈
ldr sp, =0xD0037F80
// 保存现场
sub lr, lr, #4
stmfd sp!, {r0-r12, lr}
// 跳转到中断处理函数
bl irq_handler
// 恢复现场
ldmfd sp!, {r0-r12, PC}
注释已经写得很清楚了,当发生IRQ中断异常时,会根据中断向量表里的设置,跳转到该部分代码,然后设置中断模式下的栈,保存现场,再调用中断处理函数irq_handler(),处理完中断后再恢复现场。irq_handler()里会怎么做,后面再解释。
2) 初始化中断控制器
调用了函数intc_init(),其代码如下:
void intc_init(void)
{
// 禁止所有中断
VIC0INTENCLEAR = 0xffffffff;
VIC1INTENCLEAR = 0xffffffff;
VIC2INTENCLEAR = 0xffffffff;
VIC3INTENCLEAR = 0xffffffff;
// 选择中断类型为IRQ
VIC0INTSELECT = 0x0;
VIC1INTSELECT = 0x0;
VIC2INTSELECT = 0x0;
VIC3INTSELECT = 0x0;
// 清VICxADDR
intc_clearvectaddr();
}
首先先禁止所有中断,然后选择中断类型为IRQ,最后清寄存器VICxARRD,VICxADDr是用来保存当前发生的中断的处理函数的
第三步 设置外部中断相关寄存器
代码如下:
// 1111 = EXT_INT[16]
GPH2CON |= 0xF;
// 010 = Falling edge triggered
EXT_INT_2_CON |= 1<<1;
// unmasked
EXT_INT_2_MASK &= ~(1<<0);
首先配置GPH2_0引脚为中断功能; 然后设置外部中断EINT16_31为下降沿触发;
最后是不屏蔽该中断;
// 设置中断EINT16_31的处理函数
intc_setvectaddr(NUM_EINT16_31, isr_key);
// 使能中断EINT16_31
intc_enable(NUM_EINT16_31);
这里调用了两个函数:
1) intc_setvectaddr()
它的作用是设置VICVECTADDR这一类寄存器,作用是保存各个中断的处理函数,这里我们的外部中断EINT16_31的中断处理函数是isr_key()。
前面提到,在start.S的第四步中,会调用函数irq_handler(),其代码如下:
void irq_handler(void)
{
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
void (*isr)(void);
for(; i<4; i++)
{
if(intc_getvicirqstatus(i) != 0)
{ isr = vicaddr[i]; break; }
}
(*isr)();
}
当有中断发生时,硬件上会将当前中断的中断处理函数从寄存器VICVECTADDR自动拷贝到寄存器VICDDR中,所以我们在irq_handler()函数里会调用保存在寄存器VICDDR里的中断处理函数即可。 再来看看外部中断EINT16_31的处理函数isr_key(),代码如下:
void isr_key(void)
{
printf("we get company\r\n");
beep();
// clear VIC0ADDR intc_clearvectaddr();
// clear pending
bit EXT_INT_2_PEND |= 1<<0;
}
首先打印一句"we get company",然后蜂鸣器会响一下,最后是清VIC相关寄存器VIC0ADDR和外部中断相关寄存器EXT_INT_2_PEND。
2) intc_enable()
在VIC里通过设置寄存器VICINTENABLE使能外部中断EINT16_31。
第五步 死循环
打印数字1、2、3、4…,等待外部中断EINT16_31的发生。
第三节 实验现象
首先会不断的打印数字1、2、3、4...,当我们按下KEY1时会产生外部中断EINT16_31时,会跳转到IRQ_handler,然后调用irq_handler(),最后调用对应的中断处理函数isr_key()。该函数首先打印"we get company:EINT16_31",然后清中断。