s5pv210 中断学习笔记

来源:互联网 发布:挂号网软件 编辑:程序博客网 时间:2024/06/05 17:09

声明:本文为本人学习朱有鹏老师讲解的《ARM裸机编程》中“按键与中断”章节的学习总结
如有任何侵权行为,请联系本人删除—-QQ:534116982

近期在学习按键与中断这个章节,在此做一个梳理与总结,便于以后的查阅由于本人个人的能力水平有限,不保证所写内容的正确性,仅作为当前学习的笔记记录,也欢迎各位朋友一起讨论,衷心感谢!

正文:
cpu为了支持处理异常的功能,通常需要解决的几个问题:

1–CPU如何支持异常处理
目前大部分CPU采用异常向量表机制。异常向量表在CPU设计时被确定,当异常发生时,CPU会自动跳转到指定的位置去执行代码,来实现对异常的处理;异常向量表中各个向量的相对位置是固定的

举例:s5pv210的异常向量表      0x1c------fiq      0x18------irq      0x14------(Reserved)      0x10------data_abort      0x0c------prefetch_abort      0x08------software interrupt      0x04------undefined instruction      0x00------Reset

2–多种中断源可能会导致产生相同的异常,如何区分是哪种中断源
各个CPU对支持的中断源都有一个唯一的编号,得到这个编号就得知了产生中断的中断源
如何得到这个编号?多数的CPU通过特定的寄存器中的不同的位来表示每个中断源
举例:
s3c2440 采用的方法:

        用一个32位寄存器和一个子中断寄存器;32位寄存器中的每一位对应一种或两种中断源        因为32个中断源不够用,所以有些只好共用一位,1就表示发生了该种中断,0表示没有        对于共用的中断源,再通过子中断寄存器去区分

s5pv210采用的方法:

        用了4个32位的寄存器(VIC0----VIC3),这4个32位寄存器上的每一位对应了一种中断源        理论上S5PV210支持128个中断源,但中间有的位没有使用;这组寄存器中的某位被置1时,则表示发生了该种中断。

3–得知中断编号后,如何找到其对应的中断处理程序(isr)
s3c2440 采用的方法:

      需要用软件来实现,将各中断处理子函数的地址,做成一张表(即函数指针数组)      然后通过软件查询寄存器得到中断源的编号,再去这个表中找到对应的元素(子函数地址),即找到了isr

s5pv210采用的方法:

      通过硬件来实现,程序员在配置时,将中断处理子函数的地址写到对应的寄存器中      当中断发生时,由硬件来完成isr的查找,通过读VICnADDR寄存器就可以得到这个isr

s5pv210中断处理的几个主要寄存器概述

1.VICnINTENABLE    中断源使能寄存器: 写1有效    写0不影响2.VICNINTENCLEAR    中断源禁止寄存器: 写1有效   写0不影响3.VICnINTSELECT    设置中断源模式寄存器:1--FIQ模式   0--IRQ模式4.VICnIRQSTATUS    中断状态寄存器:0--无    1--产生中断    查询这组寄存器可以得到中断源编号5.VICnVECTPRIORITY0 ----  VICnVECTPRIORITY31    中断优先级配置寄存器6.VICnVECTADDR0 --- VICnVECTADDR31    这4 * 32个寄存器 分别用来存放每个中断源对应的isr的函数地址7.VICnADDR    这组寄存器只需要读;当发生了中断,硬件会根据中断编号自动找到其VECTADDR对应的地址,并复制到该寄存器中

s5pv210的中断配置步骤:

STEP1. 异常向量表的初始化
将异常发生后需要执行的程序入口,根据异常向量表的格式位置,填到对应的地址中

  //结构体代表各个异常在异常向量表的相对位置  typedef struct  {    uint32 Reset;    uint32 Undef;    uint32 SWI;    uint32 Prefetch;    uint32 Data;    uint32 Reserved;    uint32 IRQ;    uint32 FIQ;   }VECTOR_Typedef;  //异常向量表的基地址  #define VECTOR_BASE  0xD0037400  //使用VECTOR即可以访问这个地址  #define VECTOR      ((INT_Typedef *)VECTOR_BASE)  //在初始化程序中 填好指定的服务程序入口  void IRQ_HANDLE(void);  void interrupt_init(VECTOR_Typedef *vector)  {       vector->IRQ   = (uint32)IRQ_HANDLE;//将IRQ_HANDLE函数的地址放入中断向量表的IRQ位置       vector->FIQ   = (uint32)IRQ_HANDLE;       vector->Data  = (uint32)exceptiondabort;       vector->Undef = (uint32)exceptionundef;        ...        // 初始化中断控制器       intc_init(VIC0);  }   这个IRQ_HANDLE的具体实现放在start.S中   这是因为中断产生,进入中断服务程序后,要做的第一步是保存现场。   保存现场是为了中断返回后,程序能够回到中断时的位置继续执行下去   而保存现场及回复现场的代码需要用汇编语言实现,所以写在start.S中   而intc_init()函数是配置中断寄存器,这两个函数在下面会讲到。

STEP2. 中断源寄存器的初始化

// 初始化中断源控制器void intc_init(void){    // 禁止所有中断源    VIC0INTENCLEAR = 0xffffffff;    VIC1INTENCLEAR = 0xffffffff;    VIC2INTENCLEAR = 0xffffffff;    VIC3INTENCLEAR = 0xffffffff;    // 选择中断类型为IRQ    VIC0INTSELECT = 0x0;    VIC1INTSELECT = 0x0;    VIC2INTSELECT = 0x0;    VIC3INTSELECT = 0x0;    // 清VICxADDR    intc_clearvectaddr();}// 清除需要处理的中断的中断处理函数的地址void intc_clearvectaddr(void){    // VICxADDR:当前正在处理的中断的中断处理函数的地址    VIC0ADDR = 0;    VIC1ADDR = 0;    VIC2ADDR = 0;    VIC3ADDR = 0;}

STEP3.完善做好现场保护工作
为了中断返回后程序能回到刚才中断的地方继续向下执行,需要在汇编中实现现场的保存/恢复工作

start.S文件中有如下定义.global IRQ_HANDLEIRQ_HANDLE:        //设置IRQ模式下的栈         ldr sp, =IRQ_STACK //#define  IRQ_STACK  0xD0037F80 这个地址是手册规定的地址        //保护现场        //lr减4得到下条指令地址        sub lr, lr, #4; //因为流水线的关系,lr中的值是当前执行的指令地址的下两条指令的地址(+8 byte)        //保存r0-r12,lr        stmfd sp!,{r0-r12, lr}        //跳转至中断服务程序         bl irq_handler//这个irq_handler是真正的中断服务程序,用C语言实现        //现场恢复        ldmfd sp!,{r0-r12, pc}^ 

STEP4.完善irq_handler函数
目前假设中断产生了,并假设已经绑定好了中断处理函数的地址(实际上这两步还没有完成)
那么:产生异常–>通过异常向量表跳到irq入口–>进入汇编现场保护部分–>跳到irq_handler中之后,仍有个问题就是得到ISR地址并执行中断处理函数的问题。
因为有四个VICADDR寄存器,而正确的地址只在其中一个寄存器中;所以要找到正确的寄存器
在irq_handler中通过调用intc_getvivirqstatus()函数来读取正确的寄存器并执行中断处理函数

//查找中断源寄存器unsigned long intc_getvicirqstatus(unsigned long ucontroller){    if(ucontroller == 0)        return  VIC0IRQSTATUS;    else if(ucontroller == 1)        return  VIC1IRQSTATUS;    else if(ucontroller == 2)        return  VIC2IRQSTATUS;    else if(ucontroller == 3)        return  VIC3IRQSTATUS;    else    {}    return 0;}// 通用中断处理函数 void irq_handler(void){    unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};    int i=0;    void (*isr)(void) = NULL;    for(; i<4; i++)    {        //通过查找中断源寄存器来确定该中断源使用的寄存器组(0-3)        if(intc_getvicirqstatus(i) != 0)        {            isr = (void (*)(void)) vicaddr[i];            break;        }    }    (*isr)();}

解决了ISR查找问题后,就可以继续完成余下的工作–配置中断源和绑定ISR

STEP5.配置外部中断,使得外部中断可以产生中断源

// 配置成外部中断模式 GPH2CON |= 0xF;                         // 010 = Falling edge triggered下降沿触发EXT_INT_2_CON |= 1<<1;          // unmasked 外部中断使能EXT_INT_2_MASK &= ~(1<<0);

STEP6.编写中断服务程序
注意:在中断服务程序中,要注意清对应的中断挂起寄存器的位,并且是写1清除; 同时还要把取出后的地址清理掉

void isr_key(void){    printf("we get company:EINT16_31\r\n");    //清地址    intc_clearvectaddr();                   // clear pending bit        EXT_INT_2_PEND |= 1<<0;                 }

STEP7.将上一步编写好的中断服务程序与使用的中断进行绑定
绑定之后,如果发生该中断,则硬件会将绑定好的isr 自动放入VICnADDR寄存器中

在main.c中  intc_setvectaddr(NUM_EINT16_31, isr_key);来绑定中断源编号为NUM_EINT16_31的中断源与isr_key中断函数这样,在发生中断编号为NUM_EINT16_31的时候,硬件会自动将绑定好的isr_key函数地址复制到VICnADDR寄存器中,如果不绑定,发生该中断时,就不知道执行什么绑定函数的实现在int.c中//因为有4组绑定地址的寄存器,具体要将地址写入到哪个寄存器中,要通过编号来选择void intc_setvectaddr(unsigned long intnum, void (*handler)(void)){    //VIC0    if(intnum<32)    {        *( (volatile unsigned long *)(VIC0VECTADDR + 4*intnum) ) = (unsigned)handler;    }    //VIC1    else if(intnum<64)    {        *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;    }    //VIC2    else if(intnum<96)    {        *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;    }    //VIC3    else    {        *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;    }    return;}

STEP8.相应的中断源使能

在main.c中intc_enable(NUM_EINT16_31);调用intc_enable(NUM_EINT16_31);来使能NUM_EINT16_31号中断源特别提醒:这里使能的是中断源,可以理解为打开了这个编号的中断源的接收开关。在使用具体的中断的时候,还需要设置具体中断的寄存器来使能具体的中断使能中断源的实现部分:// 根据中断源编号使能中断源 如果数大于NUM_ALL 中断源会全部使能void intc_enable(unsigned long intnum){    unsigned long temp;    if(intnum<32)    {        temp = VIC0INTENABLE;        temp |= (1<<intnum);        VIC0INTENABLE = temp;    }    else if(intnum<64)    {        temp = VIC1INTENABLE;        temp |= (1<<(intnum-32));        VIC1INTENABLE = temp;    }    else if(intnum<96)    {        temp = VIC2INTENABLE;        temp |= (1<<(intnum-64));        VIC2INTENABLE = temp;    }    else if(intnum<NUM_ALL)    {        temp = VIC3INTENABLE;        temp |= (1<<(intnum-96));        VIC3INTENABLE = temp;    }    // NUM_ALL : enable all interrupt    else    {        VIC0INTENABLE = 0xFFFFFFFF;        VIC1INTENABLE = 0xFFFFFFFF;        VIC2INTENABLE = 0xFFFFFFFF;        VIC3INTENABLE = 0xFFFFFFFF;    }}

通过以上步骤,s5pv210的外部中断配置完成;

扩展:S5pv210是如何保证fiq比irq更快的?

1--fiq模式下的专用寄存器   s5pv210在fiq模式下,r8--r12是专用的,因此在 fiq的isr中,可以直接使用R8-R12而不用保存2--异常向量表中的位置   fiq是异常向量表中最后一个向量,而其他向量由于它们之间只有4字节大小的空间,所以只能放一条跳转指令   而fiq就不必进行跳转,可以直接将中断处理函数写在这里,省去了执行跳转指令的时间基于以上两种原因,fiq可以比irq更快