开发环境

主机环境:UBUNTU1004 32bit + arm-linux-gcc(v4.5.1)

开发板:友善之臂tiny6410,S3C6410 CPU

任务描述:基于VIC_Port模式,编写中断程序,当按下开发板上的key1~key4时,分别点亮核心板上的LED1~LED4。

原理

S3C6410支持两种中断处理方式,一种是非向量模式,另一种是向量模式。其中非向量模式,当中断产生时,跳转到中断异常去,然后这个中断异常中,编写程序,判断是哪一个中断产生,然后去执行对应的中断处理程序。又称system_bus模式,在整个程序的最开始的位置根据异常向量表,安排异常向量,S3C6410及之前处理器采用这种中断处理模式。向量模式设定每个中断对应的中断服务程序的入口地址(设置相关寄存器),这样当中断产生的时候,就不用跳转到中断异常去了,直接跳转到对应的中断程序去了(由硬件自动完成)。这样中断处理的效率就提高了。又称VIC_port模式。这里采用VIC_port模式编写S3C6410中断处理裸机程序。

采用VIC_port模式编写S3C6410中断处理程序一共分为三个步骤:一.使能VIC_port模式、二.中断初始化、三.中断服务程序ISR。

【一.使能VIC_port模式】

使用如下ARM汇编代码,开启VIC_port模式

//开启S3C6410的中断VIC_port功能
    //开启此功能,发生中断时,CPU会自动
    //跳到ISR,执行ISR。这种跳转操作
    //是由硬件自动执行的
     mrc p15,0,r0,c1,c0,0
    orr r0,r0,#(1<<24)
    mcr p15,0,r0,c1,c0,0

【二.中断初始化】

中断初始化包括三个方面,1.中断源初始化;2.中断控制器初始化;3.CPSR开中断

【1.中断源初始化】

这里的中断源就是按键,先看按键硬件原理图,如图1所示。

图1 按键硬件图

从图1看出,key1~key4用作外部中断XENT0~3,分别连接至S3C6410的GPN0~3口。正常情况下,XENT0~3处于高电平,当按键key1~key4按下时,XENT0~3为低电平。

中断源的初始化包括三个方面:A.配置GPN0~3为中断功能;B.配置中断触发方式;C.使能中断

A.配置GPN0~3为中断功能

使用GPNCON寄存器,配置GPN0~3为中断功能。GPNCON寄存器配置表如图2所示。

图2 GPNCON寄存器

从图2可以看出,若要配置GPN0~3为中断功能,则需使GPNCON[7:0]=10101010,代码如下:

#define GPNCON  (*((volatile unsigned long *)0x7F008830))
GPNCON &= ~(0xff);
GPNCON |= 0xaa;

这里先对GPNCON的[7:0]清零,然后再对其配置为10101010

B.配置中断触发方式

配置中断触发方式需要对EINT0CON0寄存器进行配置。图3是该寄存器的配置表

图3 EINT0CON0寄存器

这里,中断触发类型配置为下降沿触发,因此需配置EINT0CON0[7:0]=00100010,代码如下:

#define EINT0CON0  (*((volatile unsigned long *)0x7F008900))
/* 设置中断触发方式为: 下降沿触发 */
EINT0CON0 &= ~(0xff);
EINT0CON0 |= 0x22;

C.使能中断

使能中断,需要对EINT0MASK寄存器进行配置。图4是EINT0MASK的配置表

图4 EINT0MASK寄存器

从图4可以看出,若要使能中断ENT0~3,只需要把EINT0MASK寄存器的[3:0]设置为0000即可。代码如下

#define EINT0MASK  (*((volatile unsigned long *)0x7F008920))
/* 开启中断 */
EINT0MASK &= ~(0xf);

通过以上步骤,对中断源进行了初始化。此时按下按键时,中断源已经有能力向CPU发送中断了。下一步要对中断控制器进行初始化,使得中断控制器有能力把中断源产生的中断发送给CPU。

【2.中断控制器初始化】

S3C6410的中断管理机制如图5所示。

图5 S3C6410中断管理

从图5可以看出,S3C6410一共两个矢量中断控制VIC0/VIC1,一共管理64个中断源,其中VIC0管理0~31号中断源,VIC1管理32~63号中断源。这里才用了复用中断号的方法。外部中断EINT0~3就共用了0号中断源,也就是说当key1~key4的任何一个键按下时,都会触发0号中断。

中断控制器的初始化的主要工作包括:A.设定中断类型;B.设定中断服务程序的入口地址;C.使能中断

A.设定中断类型:

设定中断类型使用VICxINTSELECT(x=0 or 1)寄存器。但是在配置之前呢,为了预防不可知的中断发生,应先把中断关闭,使用VICxINTCLEAR(x=0 or 1)寄存器可以关闭相应中断,图6是VIC0INTCLEAR寄存器配置表。图7是VIC0INTSELECT寄存器的配置表。

图6 S3C6410中断管理

图7 VIC0INTSELECT寄存器的配置表

图6可以看出,VIC0INTCLEAR寄存器的每一位对应一个中断源。因此要关闭0号中断源(EINT0~3),只需要往VIC0INTCLEAR[0]写入1

图7可以看出,VIC0INTSELECT寄存器的每一位对应一个中断源的类型。因此要设定0号中断源(EINT0~3)为IRQ类型,只需要往VIC0INTSELECT[0]写入0。代码如下

VIC0INTENCLEAR |= (0x1);//中断控制器关闭中断
VIC0INTSELECT &= (~(0x1));//设定0号中断源的中断类型为IRQ

B.设定中断服务程序的入口地址 设定中断服务程序的入口地址使用VICxVECTADDR寄存器。VIC0VECTADDR寄存器配置表如图8所示

图8 VIC0VECTADDR寄存器

从图8可以看出,VIC0VECTADDR是一组寄存器,共32个,分别对应VIC0管理的32个中断源的中断服务程序的入口地址。由于key1~key3对应的中断号为0,因此要把中断服务程序的入口地址赋值给VIC0VECTADDR[0]寄存器。代码如下

//中断服务函数入口地址赋值给VIC0VECTADDR0
//中断发生时,CPU会自动到VIC0VECTADDR0读取
//中断服务程序的入口地址,执行中断服务函数
//配置VIC_PORT使能模式下,中断发生不会进入
//异常向量,避免使用sys_bus模式处理中断
VIC0VECTADDR0 = (unsigned long)&do_irq;

这里的do_irq是中断服务函数。

C.使能中断

中断控制器中使能中断,使用VICxINTENABLE寄存器。VIC0INTENABLE寄存器的配置表如图9所示。

图9 VIC0INTENABLE寄存器

从图9可以看出,VIC0INTENABLE的每一位对应一个中断源。若要使能0号中断源,则把VIC0INTENABLE[0]=1即可。

/* 在中断控制器里使能这些中断 */
VIC0INTENABLE |= (0x1); /* bit0: EINT0~3 */

【3.CPSR开中断】

ARM处理器中有个IRQ中断总开关,就是CPSR寄存器的bit7,需要将该位设置为0,CPU才能响应中断。ARM汇编代码如下:

//开启中断总开关,设置CPSR的I位为0
    mrs r0,cpsr
    bic r0,r0,#0x80
    msr cpsr_c,r0

通过以上步骤,中断初始化步骤完成,从中断源到中断控制器再到CPU的中断通道已经打通。此时如果有按键按下,GPIO模块就会产生一个中断给中断控制器,中断控制器中已经使能了中断,发出一个信号给CPU,CPU执行每一条指令之前,都会先判断有无中断发生,如果有:

  1. CPU进入irq模式;
  2. 之前的CPSR保存到SPSR_irq
  3. 使用irq模式下的R13_irq和R14_irq
  4. 把下一条指令的地址存入R14_irq
  5. 硬件读取VIC0VECTADDR中设定的ISR地址,跳入ISR执行

以上5个步骤是由硬件自动完成,接下来就要设计中断服务程序ISR,来处理中断了。

【三.中断服务程序ISR】

任何一个中断服务程序的设计一般都由以下三个部分组成:1.保护现场;2.中断处理;3.恢复现场。

先看保护现场和恢复现场。所谓的保护现场是指在中断处理之前,把相应的寄存器入栈;所谓的回复现场,是指把入栈的寄存器重新恢复。保护现场和恢复现场的程序代码如下(ARM汇编):

//do_irq函数是中断服务函数,首先保存现场
//然后跳转到key_press按键判断程序
//最后恢复现场
do_irq:
    ldr sp, =0x54000000    //发生IRQ中断后,CPU自动切换到IRQ模式下
                    //sp是分组寄存器,因此关看门狗后设置的
                    //栈不能使用,这里要重新设置栈,供保存、
                    //恢复现场使用
                    
    sub lr, lr, #4        //lr存放的是发生中断时那条指令的下一条指令
                    //恢复现场时,保证能正常返回到主程序,
                    //这里lr应减去4
                    
    stmdb {r0-r12, lr} //保存现场
    bl key_isr             //跳到key_isr程序,
    ldmia {r0-r12, pc}^ //恢复现场,^表示把SPSR恢复到CPSR

由于中断发生时,硬件动作中把下一条指令的地址存入LR,而上一条指令尚未执行,在恢复现场后,程序应从中断发生时的上一条指令开始执行,因此这里LR需减去4。

保护完现场之后,跳到中断处理程序key_isr来处理中断。中断的处理一般有三个步骤构成:A.分辨是哪个中断;B.进入相应的中断处理程序;C.清中断

A.分辨是哪个中断

当中断发生时,EINT0PEND寄存器的相应位会被置1,通过判断相应位,即可知道是哪个中断发生了。图10是EINT0PEND寄存器配置表。

图10 EINT0PEND寄存器

从图10可以看出,当EINT0~3发生中断时,EINT0PEND[3:0]分别被置1。因此用过判断EINT0PEND寄存器中哪一位为1,即可知道是哪个中断发生了。

此外,该寄存器还用来作为清除中断使用。往相应的位写入1时,即可清除该中断。

B.中断处理

void key_isr(void)
{
    unsigned long key_press=0;
    key_press=(EINT0PEND & 0xf);//读EINT0PEND[3:0],判断哪个键按下

    switch(key_press)
    {    //key1按下
        case 1:
        {
            led_display(LED_ALL_OFF);
            led_display(LED1_ON);
            break;
        }
        //key2按下
        case 2:
        {
            led_display(LED_ALL_OFF);
            led_display(LED2_ON);
            break;
        }
        //key3按下
        case 4:
        {
            led_display(LED_ALL_OFF);
            led_display(LED3_ON);
            break;
        }
        //key4按下
        case 8:
        {
            led_display(LED_ALL_OFF);
            led_display(LED4_ON);
            break;
        }

        default:
            break;

    }
    
    //清中断
    EINT0PEND = 0xf;
    VIC0ADDRESS = 0;
}

中断处理时,先根据EINT0PEND寄存器的值判断是哪个按键按下,然后再点亮相应的LED灯。最后清中断。

C.清中断

清除中断分为两个步骤,第1步,先清除源头,即往EINT0PEND寄存器的相应位写入1,即EINT0PEND[3:0]=1111;第2步,再清中断控制器。清除中断控制器时,需要使用VICxADDRESS,VIC0ADDRESS寄存器的配置表如图11所示。

图11 VIC0ADDRESS寄存器

从图11可以看出,VIC0ADDRESS寄存器的每一位对应一个中断源,往该位写入任意值,即可清除该中断,即VIC0ADDRESS[0]=0。清中断代码如下

//清中断
EINT0PEND = 0xf;
VIC0ADDRESS = 0;

【Makefile】

irq.bin: start.o main.o led.o irq.o
arm-linux-ld -Ttext 0x50000000 -o irq.elf $^
arm-linux-objcopy -O binary irq.elf irq.bin
arm-linux-objdump -D irq.elf > irq_elf.dis
%.o : %.S
arm-linux-gcc -o $@ $< -c
%.o : %.c
arm-linux-gcc -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis -rf

【编译、运行】

执行make,编译生成二进制文件irq.bin,使用友善之臂的裸机程序烧写工具minitools,把irq.bin下载到

开发板内存运行。当分别按下key1~key4时,LED1~LED4分别点亮。

源代码:http://download.csdn.net/detail/stuyou/9501350