s5pv210 中断学习笔记

声明:本文为本人学习朱有鹏老师讲解的《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_HANDLE
IRQ_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更快
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值