中断处理流程

中断是一种通知信号,用于告诉CPU发生了某件事,一般由外部设备产生,也可以由软件产生,称为软中断。中断采用异步工作方式,CPU提前注册中断处理,中断一旦发生,CPU就会去执行指定的工作。

中断产生后,并不会直接传递给CPU,而是先传递给中断控制器,中断控制器进行相应的过滤和优先级比较之后,才会传递给CPU进行处理。

2440支持60个中断源,有些中断源包含子中断源,比如串口中断就包含错误中断、接收中断、发送中断三个。
6410支持64个中断源,210支持93个中断源。

中断产生后首先要传递给中断控制器进行过滤,以2440的过滤流程为例:
Alt text
中断的处理过程如下,带子断的中断源需要先经过子中断保留寄存器SUBSRCPND和子中断屏蔽寄存器SUBMASK,经过过滤后进入中断保留寄存器SRCPND,不带子中断的中断源直接进入SRCPND。位于SRCPND中保存的中断可以分为普通中断IRQ和快速中断FIQ,通过MODE寄存器过滤出FIQ并送往FIQ线,通过中断屏蔽寄存器MASK和MODE寄存器过滤出IRQ再输入Priority进行优先级判断后再送往IRQ线。

CPU处理中断信号时有两种方式:

  1. 非向量方式(2440)
  2. 向量方式(6410/210)

非向量方式:

  1. 中断发生,PC指针跳转到固定的地址处
  2. 保存环境,进入中断工作模式后需要保存相关寄存器,以便于中断结之后恢复环境
  3. 读取中断源,判断中断类型
  4. 调用对应该中断的中断处理程序
  5. 恢复环境

以2440为例,其中断处理的部分代码如下:

irq:
    sub lr, lr ,#4 
        stmfd sp! , {r0-r12,lr} /*保存环境*/
        bl handle_int   /*跳转到中断处理程序*/
        ldmfd sp!, {r0-r12, pc}^ /*恢复环境*/

向量方式:

  1. 中断发生,CPU直接跳转到用户设置好的中断处理程序
  2. 保存环境
  3. 处理中断
  4. 恢复环境

使用向量方式的中断处理需要提前设置好各个中断的处理函数。

要完成中断的处理,需要配置以下各项:

  1. 初始中断源,比如要想使用按键中断,就必须把按键配置成中断模式
  2. 初始化中断控制器
  3. 编写中断处理函数

下面通过按键中断,完成在开发板上按下按键后,点亮LED的目的。

2440按键中断编程

TQ2440开发板使用GPF1管脚作为按键引脚,GPF组寄存器描述如下:
Alt text

把管脚设置为中断模式的代码如下:

#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将被打开。
Alt text
Alt text
Alt text

除了初始化中断寄存器外,还需要将系统中断使能,这需要操作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"            
    : 
    : 
  );
}

下面开始编写中断处理程序。根据前面的分析可知,中断处理包含以下几个步骤:

  1. 保存环境
  2. 判断中断源
  3. 调用中断处理程序
  4. 恢复环境

中断异常处理如下:

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的按键电路如下图所示:
Alt text
Alt text

由此可知,6个按键的引脚分别是GPN0-GPN5。参考中断部分的描述,6个按键对外部中断0-5,但是这些按键对应的中断号只有两个,分别是EINT0和EINT1,芯片手册部分的描述如下:
Alt text

下面将这些引脚配置成中断模式,相关寄存器的描述如下:
Alt text

我们将第1个和第6个按键配置成中断模式,原因在注释中有描述,代码如下:

#define GPNCON *((volatile unsigned long*)0x7f008830)
void button_init()
{
    GPNCON = (0x2) | (0x2 << 10);  //使用第1个和第6个按键,对应EINT0和EINT1
    
    /*不能使用第1个和第2个按键来测试中断,
    因为它们对应的中断号是相同的,都是EINT0*/
}

下面对中断控制器进行初始化。
第一步要配置中断的产生时机,这里将中断配置成下降沿产生中断,相关寄存器是EINT0CON0,以下是该寄存器的描述:
Alt text

第二步要取消EINT0和EIN1的屏蔽,相关寄存器是EINT0MASK,以下是该寄存器的描述:
Alt text

第三步是使能中断,EINT0和EINT1属于第0组向量中断控制器VIC0,其寄存器是
Alt text

第四步是修改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个步骤:

  1. 保存环境
  2. 中断处理
  3. 清除中断
  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"
        :
        :
    );
}

  • 无标签