基于S5PV210的中断处理的过程和代码分析
--参考朱有鹏ARM裸机教程
1、中断流程梳理:
第一部分:我们为中断响应做的预备功能:
1、初始化中断控制器
2、绑定好写的ISR到中断控制器
3、相应的中断所有的条件使能
第二部分:当硬件产生中断后是如何执行ISR的?
1、第一步:经过异常向量表跳转入IRQ入口
2、第二步:做中断的现场保护,然后跳入ISR_handler
3、第三步:在上述的函数中先去搞清楚是哪一个VIC中断
4、然后直接去这个VIC的ADDR寄存器中取ISR来执行即可
5、ISR执行完,中断现场恢复,直接返回做常规任务。
2、经过异常向量表跳入IRQ入口。
// 主要功能:绑定第一阶段异常向量表;禁止所有中断;选择所有中断类型为IRQ;
// 清除VICnADDR为0
void system_init_exception(void)
{
// 第一阶段处理,绑定异常向量表
r_exception_reset = (unsigned int)reset_exception;
r_exception_undef = (unsigned int)undef_exception;
r_exception_sotf_int = (unsigned int)sotf_int_exception;
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)IRQ_handle;
// 初始化中断控制器的基本寄存器
intc_init();
}
3、做中断的现场保护工作,在汇编文件中进行处理:
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}^
4、然后再在IRQ_Handler函数里面判断中断源在哪里?
// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场
void irq_handler(void)
{
//printf("irq_handler.\n");
// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断
// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来
// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断
// 对应的isr。
// 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须
// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
void (*isr)(void) = NULL;
for(i=0; i<4; i++)
{
// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0
if(intc_getvicirqstatus(i) != 0)
{
isr = (void (*)(void)) vicaddr[i];
break;
}
}
(*isr)(); // 通过函数指针来调用函数
}
5、以外部中断为例,写出ISR的中断服务函数
// 以中断方式来处理按键的初始化
void key_init_interrupt(void)
{
// 1. 外部中断对应的GPIO模式设置
rGPH0CON |= 0xFF<<8; // GPH0_2 GPH0_3设置为外部中断模式
rGPH2CON |= 0xFFFF<<0; // GPH2_0123共4个引脚设置为外部中断模式
// 2. 中断触发模式设置
rEXT_INT_0_CON &= ~(0xFF<<8); // bit8~bit15全部清零
rEXT_INT_0_CON |= ((2<<8)|(2<<12)); // EXT_INT2和EXT_INT3设置为下降沿触发
rEXT_INT_2_CON &= ~(0xFFFF<<0);
rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));
// 3. 中断允许
rEXT_INT_0_MASK &= ~(3<<2); // 外部中断允许
rEXT_INT_2_MASK &= ~(0x0f<<0);
// 4. 清挂起,清除是写1,不是写0
rEXT_INT_0_PEND |= (3<<2);
rEXT_INT_2_PEND |= (0x0F<<0);
}
// EINT2通道对应的按键,就是GPH0_2引脚对应的按键,就是开发板上标了LEFT的那个按键
void isr_eint2(void)
{
// 真正的isr应该做2件事情。
// 第一,中断处理代码,就是真正干活的代码
printf("isr_eint2_LEFT.\n");
// 第二,清除中断挂起
rEXT_INT_0_PEND |= (1<<2);
intc_clearvectaddr();
}
void isr_eint3(void)
{
// 真正的isr应该做2件事情。
// 第一,中断处理代码,就是真正干活的代码
printf("isr_eint3_DOWN.\n");
// 第二,清除中断挂起
rEXT_INT_0_PEND |= (1<<3);
intc_clearvectaddr();
}
void isr_eint16171819(void)
{
// 真正的isr应该做2件事情。
// 第一,中断处理代码,就是真正干活的代码
// 因为EINT16~31是共享中断,所以要在这里再次去区分具体是哪个子中断
if (rEXT_INT_2_PEND & (1<<0))
{
printf("eint16\n");
}
if (rEXT_INT_2_PEND & (1<<1))
{
printf("eint17\n");
}
if (rEXT_INT_2_PEND & (1<<2))
{
printf("eint18\n");
}
if (rEXT_INT_2_PEND & (1<<3))
{
printf("eint19\n");
}
// 第二,清除中断挂起
rEXT_INT_2_PEND |= (0x0f<<0);
intc_clearvectaddr();
}
6、主函数的调用:
int main(void)
{
uart_init();
//key_init();
key_init_interrupt();
// 如果程序中要使用中断,就要调用中断初始化来初步初始化中断控制器
system_init_exception();
printf("-------------key interrypt test--------------");
// 绑定isr到中断控制器硬件
intc_setvectaddr(KEY_EINT2, isr_eint2);
intc_setvectaddr(KEY_EINT3, isr_eint3);
intc_setvectaddr(KEY_EINT16_19, isr_eint16171819);
// 使能中断
intc_enable(KEY_EINT2);
intc_enable(KEY_EINT3);
intc_enable(KEY_EINT16_19);
return 0;
}
7、绑定中断号和ISR函数的函数intc_setvectaddr();
相当于设置了一个函数指针的数组,
这个函数的目的是,把对应的寄存器地址和相应的ISR的函数指针对应起来,这样就可以形成一种映射关系。
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
//VIC0
if(intnum<32)
{
*( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
}
//VIC1
else if(intnum<64)
{
*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
}
//VIC2
else if(intnum<96)
{
*( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
}
//VIC3
else
{
*( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
}
return;
}