裸机——中断2

1. 中断是什么?

  中断是一种异常,特点是CPU被打断后执行中断处理程序,然后再恢复执行原来的程序。

  中断是软件依托硬件提供的机制实现的,理解异常,必须先理解硬件的机制。

2. 硬件提供的机制——异常向量表 

  当发生异常时,硬件会自动让pc跳到异常向量表对应位置执行,

  异常向量表如下:

  可以看出异常向量表就是数组,数组元素是函数指针,按照访问内存的方式访问。

  板子刚启动时,处于SVC模式,查阅 iROM_Application_note 可以知道异常向量表的基地址。

我们可以根据上面知识写出下面代码:

#define    EXCEPTION_BASE    0xD0037400


#define rFIQ_EXCEPTION            (*(volatile unsigned int *)(EXCEPTION_BASE + 0x1C))
#define rIRQ_EXCEPTION            (*(volatile unsigned int *)(EXCEPTION_BASE + 0x18))
#define rDATA_ABORT_EXCEPTION        (*(volatile unsigned int *)(EXCEPTION_BASE + 0x10))
#define rPREFECTH_ABORT_EXCEPTION    (*(volatile unsigned int *)(EXCEPTION_BASE + 0x0C))
#define rSOFTWARE__EXCEPTION        (*(volatile unsigned int *)(EXCEPTION_BASE + 0x08))
#define rUNDEFINED_EXCEPTION        (*(volatile unsigned int *)(EXCEPTION_BASE + 0x04))
#define rRESET_EXCEPTION        (*(volatile unsigned int *)(EXCEPTION_BASE))



#include "stdio.h"


void fiq_exception(void)
{
    printf("fiq_exception\n");
}
void irq_exception(void)
{
    printf("irq_exception\n");
}
void data_abort_exception(void)
{
    printf("data_abort_exception\n");
}
void prefetch_exception(void)
{
    printf("prefetch_exception\n");
}
void software_exception(void)
{
    printf("software_exception\n");
}
void undefined_exception(void)
{
    printf("undefined_exception\n");
}
void reset_exception(void)
{
    printf("reset_exception\n");
}

void system_exception_init()
{
    rFIQ_EXCEPTION                =        (unsigned int)fiq_exception;
    rIRQ_EXCEPTION                =        (unsigned int)irq_exception;
    rDATA_ABORT_EXCEPTION        =       (unsigned int)data_abort_exception;
    rPREFECTH_ABORT_EXCEPTION    =       (unsigned int)prefetch_exception;
    rSOFTWARE__EXCEPTION        =       (unsigned int)software_exception;
    rUNDEFINED_EXCEPTION        =       (unsigned int)undefined_exception;
    rRESET_EXCEPTION            =       (unsigned int)reset_exception;
}

这样将中断处理程序绑定到异常向量表,如果发生了异常就会跳转执行对应打印语句。

但上面的代码不能实现中断,因为中断是停下手上的工作,去完成中断服务,然后返回继续原先的工作,这要求保护现场,处理中断,恢复现场。

所以异常向量表绑定的程序应该这样写:

irq_exception:
    ldr sp, =IRQ_STACK  @由于异常发生时,CPU模式切换,IRQ的栈需要设置
    sub lr, lr, #4    @ 由于ARM的流水线技术,所以 返回地址需要计算
    stmfd sp!, {r0-r12, lr} @ 将返回地址和寄存器值保持到栈中
    bl irq_handler      @ 处理中断
    ldmfd sp!, {r0-r12, pc}^ @ 先将栈中保持的值恢复到寄存器中,然后pc跳转到返回地址,最后 cpsr 恢复

上面还有一个细节,代码中没有体现,即异常发生时 硬件自动将 cpsr 的值保存到 spsr 中,所以再保护现场中我们没有手动完成这步。

上面就完成了中断处理的框架,下面讨论 irq_handler

3.硬件对异常的支持

   irq_handler的目标是找到中断处理程序,并执行。

  这就需要了解SoC对中断提供的其他支持了。

  以S5PV210为例,210有120种左右的中断,每个中断都有一个自己的寄存器用来绑定自己的处理程序入口地址。

  并且210将这120种中断分为4组,每组有一个状态寄存器,当发生中断时,状态 寄存器的对应为置一,

                每组还有一个地址寄存器,当发生中断时,硬件自动将中断的入口地址写入那个地址寄存器。

下面给出与中断相关寄存器的总结:

中断最基本寄存器大致分为三类:
(1)中断查看
    IRQStatus
    FIQStatus
    RawInterrupt

(2)中断设置
     禁止和使能
   VICINTENABLE
   VICINTENCLEAR
     模式选择
   VICINTSELECT

(3)ISR vector address
    地址绑定
   VICVECTADDR[0-31]
    地址获取
   VICADDRESS

  这样我们就知道了编程思路:

  

  中断发生前

  (1)初始化中断:

    先禁止所有中断(防止跑飞),选择中断模式,中断地址寄存器清零。

    将要使用的中断的程序入口地址绑定到对应的寄存器中。

    开启要使用的中断。

  中断发生后

  (2)寻找中断服务程序,并执行

    通过查看4组中断状态寄存器,如果非0,即为该组发生了中断,中断服务程序即在该组的地址寄存器中。

    从地址寄存器中取出程序入口地址并执行。

  (3)清中断

    当中断处理完后,将4组中断状态寄存器置0。

那么(1)初始化中断就应该这样:

void intc_clearvectaddr(void)
{
    VIC0ADDR = 0;
    VIC1ADDR = 0;
    VIC2ADDR = 0;
    VIC3ADDR = 0;
}

void int_init()
{
    // 禁止所有中断
    VIC0INTENCLEAR = 0xffffffff;
    VIC1INTENCLEAR = 0xffffffff;
    VIC2INTENCLEAR = 0xffffffff;
    VIC3INTENCLEAR = 0xffffffff;

    // 选择中断类型为IRQ
    VIC0INTSELECT = 0x0;
    VIC1INTSELECT = 0x0;
    VIC2INTSELECT = 0x0;
    VIC3INTSELECT = 0x0;
    
    // 清零地址寄存器
    intc_clearvectaddr();
}

void system_exception_init()
{
    // 设置异常向量表
    rFIQ_EXCEPTION                =        (unsigned int)fiq_exception;
    rIRQ_EXCEPTION                =        (unsigned int)irq_exception;
    rDATA_ABORT_EXCEPTION        =       (unsigned int)data_abort_exception;
    rPREFECTH_ABORT_EXCEPTION    =       (unsigned int)prefetch_exception;
    rSOFTWARE__EXCEPTION        =       (unsigned int)software_exception;
    rUNDEFINED_EXCEPTION        =       (unsigned int)undefined_exception;
    rRESET_EXCEPTION            =       (unsigned int)reset_exception;

    // 初始化中断
    int_init();
}

main.c中绑定要用的中断服务程序和开启中断

void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
    //VIC0
    if(intnum<32)
    {
        *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (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;
}


// 使能中断
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;
    }

}
int main()
{

    uart_init();
    
    system_exception_init();
    // 绑定isr到中断控制器硬件
    intc_setvectaddr(KEY_EINT2, isr_eint2);
    intc_setvectaddr(KEY_EINT3, isr_eint3);
    
    // 使能中断
    intc_enable(KEY_EINT2);
    intc_enable(KEY_EINT3);
    
    
    printf("hello world\n");

    return 0;
}

第二步这样做

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)
{
    printf("irq_handler.\n");

    unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
    int i=0;
    void (*isr)(void) = NULL;

    for(i=0; i<4; i++)
    {
        if(intc_getvicirqstatus(i) != 0)
        {
            isr = (void (*)(void)) vicaddr[i];
            break;
        }
    }
    (*isr)();        
}

第三步

void isr_eint2(void)
{// 第一,中断处理代码
    printf("isr_eint2_LEFT.\n");
    // 第二,清除中断挂起
    intc_clearvectaddr();
}

这样就可以处理SoC内部外设产生的中断,但是对于外部设备的中断还不能处理。

4.外部中断

  中断分为SoC内部中断,如定时器,外部中断,如按键。

  对于外部中断是通过GPIO传输的,所以需要将GPIO设置为外部中断模式。

  外部中断重要的寄存器如下:

EXT_INT_CON:
     设置触发模式为电平触发还是边沿触发。
EXT_INT_MASK:
    禁止或使能
EXT_INT_PEND:
    外部中断挂起,但中断发生后,PEND的对应位会置1,就会不停的向SoC发中断请求。

  那么对于按键的中断初始化就如下:

// 以中断方式来处理按键的初始化
void key_init_interrupt(void)
{
    // 1. 外部中断对应的GPIO模式设置
    rGPH0CON |= 0xFF<<8;        // GPH0_2 GPH0_3设置为外部中断模式
    rGPH2CON |= 0xFFFF<<0;        // GPH2_0123共4个引脚设置为外部中断模式
    
    // 2. 中断触发模式设置
    rEXT_INT_0_CON &= ~(0xFF<<8);    // bit8~bit15全部清零
    rEXT_INT_0_CON |= ((2<<8)|(2<<12));        // EXT_INT2和EXT_INT3设置为下降沿触发
    rEXT_INT_2_CON &= ~(0xFFFF<<0);
    rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));    
    
    // 3. 中断允许
    rEXT_INT_0_MASK &= ~(3<<2);            // 外部中断允许
    rEXT_INT_2_MASK &= ~(0x0f<<0);
    
    // 4. 清挂起,清除是写1,不是写0
    rEXT_INT_0_PEND |= (3<<2);
    rEXT_INT_2_PEND |= (0x0F<<0);
}

  对于外部中断,中断处理完后还需要清中断挂起

void isr_eint2(void)
{
    printf("isr_eint2_LEFT.\n");
    // 第二,清除中断挂起
    rEXT_INT_0_PEND |= (1<<2);
    intc_clearvectaddr();
}

  以上就完成中断。

猜你喜欢

转载自www.cnblogs.com/yangxinrui/p/9951538.html