中断处理流程
中断是一种通知信号,用于告诉CPU发生了某件事,一般由外部设备产生,也可以由软件产生,称为软中断。中断采用异步工作方式,CPU提前注册中断处理,中断一旦发生,CPU就会去执行指定的工作。
中断产生后,并不会直接传递给CPU,而是先传递给中断控制器,中断控制器进行相应的过滤和优先级比较之后,才会传递给CPU进行处理。
2440支持60个中断源,有些中断源包含子中断源,比如串口中断就包含错误中断、接收中断、发送中断三个。
6410支持64个中断源,210支持93个中断源。
中断产生后首先要传递给中断控制器进行过滤,以2440的过滤流程为例:
中断的处理过程如下,带子断的中断源需要先经过子中断保留寄存器SUBSRCPND和子中断屏蔽寄存器SUBMASK,经过过滤后进入中断保留寄存器SRCPND,不带子中断的中断源直接进入SRCPND。位于SRCPND中保存的中断可以分为普通中断IRQ和快速中断FIQ,通过MODE寄存器过滤出FIQ并送往FIQ线,通过中断屏蔽寄存器MASK和MODE寄存器过滤出IRQ再输入Priority进行优先级判断后再送往IRQ线。
CPU处理中断信号时有两种方式:
- 非向量方式(2440)
- 向量方式(6410/210)
非向量方式:
- 中断发生,PC指针跳转到固定的地址处
- 保存环境,进入中断工作模式后需要保存相关寄存器,以便于中断结之后恢复环境
- 读取中断源,判断中断类型
- 调用对应该中断的中断处理程序
- 恢复环境
以2440为例,其中断处理的部分代码如下:
irq:
sub lr, lr ,#4
stmfd sp! , {r0-r12,lr} /*保存环境*/
bl handle_int /*跳转到中断处理程序*/
ldmfd sp!, {r0-r12, pc}^ /*恢复环境*/
向量方式:
- 中断发生,CPU直接跳转到用户设置好的中断处理程序
- 保存环境
- 处理中断
- 恢复环境
使用向量方式的中断处理需要提前设置好各个中断的处理函数。
要完成中断的处理,需要配置以下各项:
- 初始中断源,比如要想使用按键中断,就必须把按键配置成中断模式
- 初始化中断控制器
- 编写中断处理函数
下面通过按键中断,完成在开发板上按下按键后,点亮LED的目的。
2440按键中断编程
TQ2440开发板使用GPF1管脚作为按键引脚,GPF组寄存器描述如下:
把管脚设置为中断模式的代码如下:
#define GPGCON (volatile unsigned long *)0x56000060
/*
* K1,K2,K3,K4对应GPG0、GPG3、GPG5、GPG6
*/
#define GPG0_int (0x2<<(0*2))
#define GPG3_int (0x2<<(3*2))
#define GPG5_int (0x2<<(5*2))
#define GPG6_int (0x2<<(6*2))
#define GPG0_msk (3<<(0*2))
#define GPG3_msk (3<<(3*2))
#define GPG5_msk (3<<(5*2))
#define GPG6_msk (3<<(6*2))
void button_init()
{
*(GPGCON) &= ~(GPG0_msk | GPG3_msk | GPG5_msk | GPG6_msk);
*(GPGCON) |= GPG0_int | GPG3_int | GPG5_int | GPG6_int;
}
下面对2440的中断控制器进行初始化。
参考芯片手册,第一个与中断控制有关的寄存器是INTMSK,它表示中断屏蔽位,每一位表示一个中断,只有将对应位设置为0,对应的中断才能使用。其中,EINT4_7比较特殊,它还有一个寄存器需要设置,即EXTINT0,用来设置具体哪个EINT将被打开。


除了初始化中断寄存器外,还需要将系统中断使能,这需要操作CPSR寄存器的第7位打开。
综上,2440中断初始化的代码如下:
#define INTMSK (volatile unsigned long *)0x4A000008
#define EINTMASK (volatile unsigned long *)0x560000a4
void init_irq()
{
// 对于EINT4,需要在EINTMASK寄存器中使能它
*(EINTMASK) &= ~(1<<4);
// EINT0、EINT1、EINT2、EINT4_7使能
*(INTMSK) &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4));
__asm__(
/*开中断*/
"mrs r0,cpsr\n"
"bic r0, r0, #0x80\n"
"msr cpsr_c, r0\n"
:
:
);
}
下面开始编写中断处理程序。根据前面的分析可知,中断处理包含以下几个步骤:
- 保存环境
- 判断中断源
- 调用中断处理程序
- 恢复环境
中断异常处理如下:
irq:
sub lr, lr, #4
stmfd sp!, {r0-r12, lr} /* 保护现场 */
bl handle_int
ldmfd sp!, {r0-r12, pc}^ /* 恢复现场,^表示把spsr恢复到cpsr */
中断处理程序如下:
#define SRCPND (volatile unsigned long *)0x4A000000
#define INTMOD (volatile unsigned long *)0x4A000004
#define PRIORITY (volatile unsigned long *)0x4A00000c
#define INTPND (volatile unsigned long *)0x4A000010
#define INTOFFSET (volatile unsigned long *)0x4A000014
#define SUBSRCPND (volatile unsigned long *)0x4A000018
#define INTSUBMSK (volatile unsigned long *)0x4A00001c
#define EINTPEND (volatile unsigned long *)0x560000a8
void handle_int()
{
/*读取产生中断的源*/
unsigned long value = *(INTOFFSET);
switch(value)
{
case 0: //EINT0~K4
led_on();
break;
case 1: //EINT1~K1
led_off();
break;
case 2: //EINT2~K3
led_on();
break;
case 4: //EINT4~K2
led_off();
break;
default:
break;
}
/* 中断清除 */
if(value == 4)
*(EINTPEND) = (1 << 4);
*(SRCPND) = 1 << value;
*(INTPND) = 1 << value;
}
注意,中断产生后处理器的工作模式会发生切换,由于中断模式和svc模式使用不能的sp指针,导致在中断模式下栈其实是没有初始化过的,这需要在初始化栈的时候再对中断模式下的sp指针也进行一次初始化:
init_stack:
msr cpsr_c, #0xd2
ldr sp, =0x33000000 @此处实际设置的是r13_irq
msr cpsr_c, #0xd3
ldr sp, =0x34000000 @此处实际设置的是r13_svc
mov pc, lr
6410按键中断编程
OK6410的按键电路如下图所示:

由此可知,6个按键的引脚分别是GPN0-GPN5。参考中断部分的描述,6个按键对外部中断0-5,但是这些按键对应的中断号只有两个,分别是EINT0和EINT1,芯片手册部分的描述如下:
下面将这些引脚配置成中断模式,相关寄存器的描述如下:
我们将第1个和第6个按键配置成中断模式,原因在注释中有描述,代码如下:
#define GPNCON *((volatile unsigned long*)0x7f008830)
void button_init()
{
GPNCON = (0x2) | (0x2 << 10); //使用第1个和第6个按键,对应EINT0和EINT1
/*不能使用第1个和第2个按键来测试中断,
因为它们对应的中断号是相同的,都是EINT0*/
}
下面对中断控制器进行初始化。
第一步要配置中断的产生时机,这里将中断配置成下降沿产生中断,相关寄存器是EINT0CON0,以下是该寄存器的描述:
第二步要取消EINT0和EIN1的屏蔽,相关寄存器是EINT0MASK,以下是该寄存器的描述:
第三步是使能中断,EINT0和EINT1属于第0组向量中断控制器VIC0,其寄存器是
第四步是修改CPSR寄存器,将总中断开打。
除了以上步骤,在6410中,由于使用了向量中断,还需要对向量中断进行配置。中断向量的配置是填充相关中断对应的跳转地址,每个中断都有一个固定的地址,用于填充该中断跳转地址。对于EINT0和EINT1的中断向量地址配置寄存器是VIC0VECTADDR0和VIC0VECTADDR1。
6410既可以使用向量中断,也可以使用非向量中断,由cp15中的c1寄存器来配置。
以上步骤对应的代码如下:
void irq_init()
{
//1.配置按键中断在下降沿产生,使用第1和第6个按键
//EINT0CON0 = (0x2) | (0x2 << 8);
EINT0CON0 = 0x2;
//2.取消EINT0和EIN1的屏蔽
EINT0MASK = 0x0;
//3.使能中断
VIC0INTENABLE |= 0x3;
//5.设置EINT0和EINT1的中断向量地址
EINT0_VICADDR = (int)key1_isr;
EINT1_VICADDR = (int)key2_isr;
//4.设置CPSR,使能向量中断,打开总的中断
__asm__(
"mrc p15, 0, r0, c1, c0, 0\n"
"orr r0, r0, #(1<<24)\n"
"mcr p15, 0, r0, c1, c0, 0\n"
"mrs r0, cpsr\n"
"bic r0, r0, #0x80\n"
"msr cpsr_c, r0\n"
:
:
);
}
除此之外,同样还要设置一下irq模式下sp的地址,以便于在进入中断模式后能顺利地使用栈。
stack_init:
msr cpsr_c, #0xd2
ldr sp, =0x53000000 @初始化r13_irq
msr cpsr_c, #0xd3
ldr sp, =0x54000000 @初始化r13_svc
mov pc, lr
最后是编写中断处理程序,同样包含以下4个步骤:
- 保存环境
- 中断处理
- 清除中断
- 恢复环境
代码如下:
void key1_isr()
{
//1.保存环境
__asm__(
"sub lr, lr, #4\n"
"stmfd sp!, {r0-r12, lr}\n"
:
:
);
//2.中断处理
led_on();
//3.清除中断
EINT0PEND |= 0x1;
VIC0ADDRESS = 0x0;
//4.恢复环境
__asm__(
"ldmfd sp!, {r0-r12, pc}^\n"
);
}
void key2_isr()
{
//1.保存环境
__asm__(
"sub lr, lr, #4\n"
"stmfd sp!, {r0-r12, lr}\n"
:
:
);
//2.中断处理
led_off();
//3.清除中断
EINT0PEND |= 0x2;
VIC0ADDRESS = 0x0;
//4.恢复环境
__asm__(
"ldmfd sp!, {r0-r12, pc}^\n"
:
:
);
}
- 无标签