Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1741975
  • 博文数量: 1493
  • 博客积分: 38
  • 博客等级: 民兵
  • 技术积分: 5834
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-19 17:28
文章分类

全部博文(1493)

文章存档

2016年(11)

2015年(38)

2014年(137)

2013年(253)

2012年(1054)

2011年(1)

分类: 嵌入式

2016-04-25 16:14:40

原文地址:S3C6410裸机中断程序 作者:stuyou

【版权归stuyou@126.com所有,转载请注明出处】
主机环境: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模式


  1. //开启S3C6410的中断VIC_port功能
  2.     //开启此功能,发生中断时,CPU会自动
  3.     //跳到ISR,执行ISR。这种跳转操作
  4.     //是由硬件自动执行的
  5.      mrc p15,0,r0,c1,c0,0
  6.     orr r0,r0,#(1<<24)
  7.     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,代码如下:

  1. #define GPNCON             (*((volatile unsigned long *)0x7F008830))
  2.  GPNCON &= ~(0xff);
  3.     GPNCON |= 0xaa;
这里先对GPNCON的[7:0]清零,然后再对其配置为10101010

B.配置中断触发方式

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

图3 EINT0CON0寄存器

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

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

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


图4 EINT0MASK寄存器

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

  1. #define EINT0MASK             (*((volatile unsigned long *)0x7F008920))
  2. /* 开启中断 */
  3.     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 VIC0INTCLEAR寄存器配置表


图7 VIC0INTSELECT寄存器的配置表

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

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


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


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

图8 VIC0VECTADDR寄存器

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

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

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

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

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

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

【3.CPSR开中断

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

  1. //开启中断总开关,设置CPSR的I位为0
  2.     mrs r0,cpsr
  3.     bic r0,r0,#0x80
  4.     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汇编):

  1. //do_irq函数是中断服务函数,首先保存现场
  2. //然后跳转到key_press按键判断程序
  3. //最后恢复现场
  4. do_irq:
  5.     ldr sp, =0x54000000    //发生IRQ中断后,CPU自动切换到IRQ模式下
  6.                     //sp是分组寄存器,因此关看门狗后设置的
  7.                     //栈不能使用,这里要重新设置栈,供保存、
  8.                     //恢复现场使用
  9.                     
  10.     sub lr, lr, #4        //lr存放的是发生中断时那条指令的下一条指令
  11.                     //恢复现场时,保证能正常返回到主程序,
  12.                     //这里lr应减去4
  13.                     
  14.     stmdb {r0-r12, lr} //保存现场
  15.     bl key_isr             //跳到key_isr程序,
  16.     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.中断处理


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

  5.     switch(key_press)
  6.     {    //key1按下
  7.         case 1:
  8.         {
  9.             led_display(LED_ALL_OFF);
  10.             led_display(LED1_ON);
  11.             break;
  12.         }
  13.         //key2按下
  14.         case 2:
  15.         {
  16.             led_display(LED_ALL_OFF);
  17.             led_display(LED2_ON);
  18.             break;
  19.         }
  20.         //key3按下
  21.         case 4:
  22.         {
  23.             led_display(LED_ALL_OFF);
  24.             led_display(LED3_ON);
  25.             break;
  26.         }
  27.         //key4按下
  28.         case 8:
  29.         {
  30.             led_display(LED_ALL_OFF);
  31.             led_display(LED4_ON);
  32.             break;
  33.         }

  34.         default:
  35.             break;

  36.     }
  37.     
  38.     //清中断
  39.     EINT0PEND = 0xf;
  40.     VIC0ADDRESS = 0;
  41. }
中断处理时,先根据EINT0PEND寄存器的值判断是哪个按键按下,然后再点亮相应的LED灯。最后清中断。

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



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

  1. //清中断
  2.     EINT0PEND = 0xf;
  3.     VIC0ADDRESS = 0;

【Makefile】

  1. irq.bin: start.o main.o led.o irq.o
  2. arm-linux-ld -Ttext 0x50000000 -o irq.elf $^
  3. arm-linux-objcopy -O binary irq.elf irq.bin
  4. arm-linux-objdump -D irq.elf > irq_elf.dis
  5. %.o : %.S
  6. arm-linux-gcc -o $@ $< -c
  7. %.o : %.c
  8. arm-linux-gcc -o $@ $< -c
  9. clean:
  10. rm *.o *.elf *.bin *.dis -rf

【编译、运行】

     执行make,编译生成二进制文件irq.bin,使用友善之臂的裸机程序烧写工具minitools,把irq.bin下载到
开发板内存运行。当分别按下key1~key4时,LED1~LED4分别点亮。


源代码:

阅读(1031) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~