中断是一种通知信号,用于告诉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为例,其中断处理的部分代码如下:
irq: sub lr, lr ,#4 stmfd sp! , {r0-r12,lr} /*保存环境*/ bl handle_int /*跳转到中断处理程序*/ ldmfd sp!, {r0-r12, pc}^ /*恢复环境*/ |
向量方式:
使用向量方式的中断处理需要提前设置好各个中断的处理函数。
要完成中断的处理,需要配置以下各项:
下面通过按键中断,完成在开发板上按下按键后,点亮LED的目的。
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 |
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" : : ); } |