S5PV210的中断实现机制
SOC中断机制的实现靠的是异常向量表。ARM处理器有7种工作模式,除了用户模式和系统模式外的五种模式都为异常模式,其中就包括中断模式(irq)和快速中断模式(firq),也就是说中断时异常的一种。异常向量表就是一个地址表,表项是异常的地址。当异常发生时,PC跳转到对应异常的地址执行相应的处理。中断作为一种异常,当中断发生时,PC跳转到中断模式对应的地址处,执行相应的处理。
中断处理可以分为两个阶段。异常向量表跳转为第一阶段,第二阶段为进入中断处理程序之后的部分。
第一个阶段的实现依赖于CPU提供的异常向量表机制。异常发生后响应异常,保存/恢复现场,跳转的相应的异常服务程序。
中断源众多,包括外部中断,定时器中断,ADC中断,DMA中断等等。在第二阶段识别时哪一个中断触发的中断,然后执行相应的中断服务程序,这部分工作主要是中断控制器完成的。
中断源
以外部中断为例,配置GPIO为中断模式
void KeyInitInterrupt(void)
{
//对应外部gpio模式设置
GPH0CON = (0xF << 8); //GPH0_2 设置为外部中断模式
//外部中断出发模式 双
EXT_INT_0_CON &= ~(0xF << 8);
EXT_INT_0_CON |= (2<<8); //下降沿触发
//允许外部中断
EXT_INT_0_MASK &= ~(1 << 2); //允许外部中断 2
//清外部中断挂起位 清除写1, 这有点反数据手册~
EXT_INT_0_PEND |= (1 << 2);
}
为中断响应做的一些准备
绑定异常向量表并初始化中断控制器
之前说到,异常发生时,CPU基于异常向量表进行跳转至相应的异常位置处执行。裸机程序在IRAM执行,IRAM为异常向量表划分了一片空间,可以在IROM_APPLICATION里查看,起始地址为0xD0037400。
绑定异常向量表就是向异常向量表内写对应处理的函数名。函数名的本质就是函数执行首地址。这样异常发生时就回去执行对应的函数。
#define exception_vector_table_base 0xD0037400
#define exception_reset (exception_vector_table_base + 0x00)
#define exception_undef (exception_vector_table_base + 0x04)
#define exception_sotf_int (exception_vector_table_base + 0x08)
#define exception_prefetch (exception_vector_table_base + 0x0C)
#define exception_data (exception_vector_table_base + 0x10)
#define exception_irq (exception_vector_table_base + 0x18)
#define exception_fiq (exception_vector_table_base + 0x1C)
void system_init_exception(void)
{
//绑定异常向量表
r_exception_reset = (unsigned int)reset_exception;
r_exception_undef = (unsigned int)udef_exception;
r_exception_sotf_int = (unsigned int)soft_int; //软中断
r_exception_prefetch = (unsigned int)prefetch_exception;
r_exception_data = (unsigned int)data_exception;
r_exception_irq = (unsigned int)IRQ_handle;
r_exception_fiq = (unsigned int)FIRQ_handle;
//初始化中断控制器的基本寄存器
intc_init();
}
绑定异常向量表后,初始化中断控制器。
void intc_init(void)
{
//失能中断
VIC0INTENCLEAR = 0xFFFFFFFF;
VIC1INTENCLEAR = 0xFFFFFFFF;
VIC2INTENCLEAR= 0xFFFFFFFF;
VIC3INTENCLEAR= 0xFFFFFFFF;
// 选择中断类型为IRQ
VIC0INTSELECT = 0x00;
VIC1INTSELECT = 0x00;
VIC2INTSELECT = 0x00;
VIC2INTSELECT = 0x00;
//清VICxADDR
VIC0ADDR = 0;
VIC1ADDR = 0;
VIC2ADDR = 0;
VIC3ADDR = 0;
}
中断控制器的配置还剩下最重要的一步,就是如何识别哪个中断发生了并跳转到相应的中断服务程序。210给出的策略如下:
210支持的中断个数为128个(包括复用)。由四个中断控制器分别控制 VIC0~VIC3。210为每个中断控制器提供了32个中断服务程序地址寄存器VICnVECTADDRm(n(0-3), m(0-31));用户预先把对应的中断服务程序地址(即中断服务函数的函数名)写到对应的地址寄存器。当中断发生时,硬件会自动选择发生了哪个中断,并把对应的中断服务程序地址返回到VICnADDR寄存器。用户只需要读这个寄存器的内容然后执行就可以了。配置如下:
/**
* @brief 注册中断服务程序到VICnVECTADDDR
* @param intnum: 中断号
* hnadler: isr 地址
*/
void intc_setVcctAddr(unsigned long intnum, void (*handler)(void))
{
//VIC 0
if(intnum<32)
{
*((volatile unsigned int *)(VIC0VECTADDR+4*(intnum-0))) = (unsigned long)handler;
}
//VIC 1
else if(intnum<64)
{
*((volatile unsigned int *)(VIC1VECTADDR+4*(intnum-32))) = (unsigned long)handler;
}
//VIC 2
else if(intnum<96)
{
*((volatile unsigned int *)(VIC2VECTADDR+4*(intnum-64))) = (unsigned long)handler;
}
//VIC 3
else if(intnum<128)
{
*((volatile unsigned int *)(VIC3VECTADDR+4*(intnum-96))) = (unsigned long)handler;
}
else
{
// 中断号error
}
return;
}
使能中断
/**
* @brief 使能中断
* @param intnum - 中断号
*/
void intc_enable(unsigned long intnum)
{
unsigned long temp;
if (intnum < 32)
{
temp = VIC0INTENABLE;
temp |= (1 << intnum);
VIC0INTENABLE = temp;
}
else if (intnum < 64)
{
temp = VIC0INTENABLE;
temp |= (1 << (intnum-32));
VIC1INTENABLE = temp;
}
else if (intnum < 96)
{
temp = VIC0INTENABLE;
temp |= (1 << (intnum-64));
VIC2INTENABLE = temp;
}
else if (intnum < NUM_ALL)
{
temp = VIC0INTENABLE;
temp |= (1 << (intnum-96));
VIC3INTENABLE = temp;
}
// num_all :使能所有中断 这为啥要关闭所有中断?
else
{
VIC0INTENCLEAR = 0xFFFFFFFF;
VIC1INTENCLEAR = 0xFFFFFFFF;
VIC2INTENCLEAR = 0xFFFFFFFF;
VIC3INTENCLEAR = 0xFFFFFFFF;
}
return;
}
到这中断控制寄存器的配置基本配置完了。到这已经完成的功能包括:
1.中断发生时,CPU会跳转到中断处理。这是由我们绑定的异常向量表决定的。
2.中断发生时,硬件会自动识别哪个中断发生了,并自动返回相应的中断服务程序入口地址,这是通过中断控制器的配置实现的。
现在中断发生前的准备工作已基本完成,下面开始中断发生后,如何处理。
中断发生后的处理
中断发生后,CPU会从SVC模式跳转到IRQ模式,所以首先要设置IRQ模式的栈;中断发生后会根据异常向量表跳转到IRQ_handle,在IRQ_handle里要保存现场,跳转到中断服务程序irq_handler,然后恢现场。
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80
#define IRQ_STACK 0xd0037f80
.global _start
.global IRQ_handle
_start:
// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:初始化时钟
bl clock_init
// 第3步:设置SVC栈
ldr sp, =SVC_STACK
// 第4步:开/关icache
mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中
//bic r0, r0, #(1<<12) // bit12 置0 关icache
orr r0, r0, #(1<<12) // bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
clean_bss:
ldr r0, = __bss_start
ldr r1, = _end
mov r2, #0
cmp r0, r1
bne clean_loop
clean_loop:
str r2, [r0], #4
cmp r0, r1
bne clean_loop
bl main
// 汇编最后的这个死循环不能丢
b .
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}^
在中断服务程序中,去读取VICnADDR的值,其值就是中断源对应服务程序的首地址,然后执行
void irq_handler(void)
{
unsigned long vicaddr[4] = {VIC0ADDR, VIC1ADDR, VIC2ADDR, VIC3ADDR};
int i = 0;
void (*isr)(void) = NULL;
for(i = 0; i < 4; i++)
{
if(intc_getVicIrqQuestion(i) != 0)
{
isr = (void (*)(void)) vicaddr[i];
break;
}
}
(*isr)();
}
//中断处理函数
void isrEint2(void)
{
printf("isr is left");
EXT_INT_0_PEND |= (1 << 2); //中断发生后,清挂起
intc_clearvectaddr();
}
void main(void)
{
//clock_init();
uart0_init();
KeyInitInterrupt();
//初始化中断控制器
system_init_exception();
printf("----------------int--------------------");
//绑定isr到中断控制器硬件
intc_setVcctAddr(KEY_EINT2, isrEint2);
//使能中断
intc_enable(KEY_EINT2);
while(1)
{
printf("a ");
delay();
}
}
现象如下