首页 > 技术文章 > 韦东山嵌入式Linux学习笔记08--中断体系结构

cheyihaosky 2019-12-02 23:23 原文

中断是什么?   

举个栗子, 系统怎么知道你什么时候插入鼠标这个设备? 可以有两种处理方式:

1. 查询方式:

  轮询去检测是否有设备插入;

2. 中断的方式

  当鼠标插入这个事件发生时, 置位某个寄存器,告诉CPU去处理这个事件.

对于查询方式, 我们需要一直去监控想要知道的状态, 而中断的处理方式, 使得CPU有空去处理其他的事情, 当插入鼠标这个时间发生时才来处理这个事件, 这样下来,

处理效率会高很多,中断的作用也可见一斑.

 

下面介绍s3c2440的中断控制器

s3c2440有60个中断源, 这么多中断源, 怎么处理? 下面是中断处理的一个框图:

 上面框图中寄存器的介绍:

SUBSRCPND: (0x4A00 0018)

  它包含的中断源如下, 当这些中断发生时,对应的位会被自动置1.我们可以往某位写入1来令此位为0, 清中断.写入0无效果,数据保持不变.

 SUBMASK: (0x4A00 001C)

SUBSRCPND寄存器后面是SUBMASK寄存器, 这个寄存器用作屏蔽中断, 这个寄存器的对应的位与SUBSRCPND对应. 当这里相应的

位被置一之后,即使相应中断发生了,也会被屏蔽掉. 这就是这个寄存器的作用.

 

SRCPND: (0x4A00 0000)

SRCPND寄存器, 这里包含了SUBSRCPND中没有的一些中断源, 同样地,当中断被相应之后,相应的位会被置一,如果需要清除中断,往相应的位写入1就可以实现清0的效果.

 

 

 

 INTMOD: (0x4A00 0004)

这个寄存器同样由32位组成,每一位对应于一种中断源,如果某一位被设置为一,那么这个中断源就会被当做是快速中断来处理.且只有一个位可以被设置成快速中断,其他都为普通中断.

 

 INTMASK: (0x4A00 0008)

INTMASK寄存器, 这个寄存器也是用于屏蔽某一种中断源.与SRCPND对应.

 

 

 PRIORITY: (0x4A00 000C)

出现多个普通中断时有可能同时出现的,这时候就需要告诉CPU那种中断源优先级更高,CPU根据优先级高低先后处理中断.

 优先级仲裁:

上图中的ARB_SELN(n:0-6)时什么来的?原来CPU有一个仲裁器,用来对中断源进行仲裁,.

如下图, 有6个一级仲裁器和1个二级仲裁器

  1.REQ0总是有最高的优先级, REQ5总是有最低的优先级.

  2.REQ1-REQ4可以根据上面的寄存器进行设置. 

     当ARB_MODEn被设置为0时,当这个中断被处理之后,REQn的优先级不会发生变化.

    当ARB_MODEn被设置为1时,当这个中断被处理之后,REQn优先级会被置为最低,

INTPND: (0x4A00 0010)

INTPND寄存器, 经过中断优先级仲裁器选出优先级最高的中断后,这个中断的相应的位会被置1, 如果想要清除中断,往这个位写1

插播以下知识点, 因为下面代码会用到:

下面的代码多了一些陌生的知识,比如系统的模式:

ARM体系CPU有7中工作模式:

1.用户模式: ARM处理器正常的程序执行状态. (usr)

2.快速中断模式: 用于高速数据传输或通道处理; (fiq)

3.中断模式: 用于通用的中断处理; (irq)

4.管理模式: 操作系统使用的保护模式; (svc)

5.数据访问终止模式: 当数据或指令预取终止时进入该模式,用于虚拟存储及存储保护; (abt)

6.系统模式:运行具有特权的操作系统任务; (sys)

7.未定义指令终止模式: 当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真. (und)

 

这些模式的切换可以通过软件设置来进行切换, 或者当CPU发生中断或者异常时自动进入相应的模式. 除用户模式外,其他6种模式都属于特权模式.大多数程序运行与用户模式,进入特权模式是为了处理中断, 异常或者访问被保护的系统资源.

比如, CPU复位上电也属于一种异常, 会自动跳转到0x00地址执行指令, 当CPU发生通用中断时, 会自动进入中断模式并且跳转到0x18地址处执行指令.

 如上图, ARM中有16个寄存器, 每种模式下都有这些寄存器, 但是每种模式都有一些"备份寄存器", 这些寄存器是每种模式下与其他模式不同的寄存器,是独立的. 其中

R13被称为栈指针寄存器,又叫SP

R14被称为程序链接寄存器

R15被称为程序计数器

比如你在fiq模式下设置了SP的地址, 又可以切换到irq模式下设置SP的地址,这两个SP地址是互相独立的,在两种模式下设置后都是不同的.

关于切换CPU模式,如果通过软件方式设置, 我们可以更改寄存器CPSR(Current Program Status Register)的值, 这个寄存器的每个位的含义如下, 你可以初步看到一些mode设置位, 快速中断去使能位和普通中断去使能位.

CPSR各个bit的详细解析如下:

 

 

 进入异常模式时应该按照以下步骤执行:(CPU核自动执行)

 1.  在异常工作模式的R14中保存前一个工作模式的下一条指令. 对于ARM状态,这个值是当前PC值加4或者加8,参考下表

  2. 将CPSR的值复制到异常模式的SPSR(Saved Program Status Register)

 3. 将CPSR设置为相应的异常对应的工作模式.

4.将PC值等于这个异常在异常向量表中的地址(这里的异常向量表指的上面代码0x00-0x1c地址的指令

退出异常模式的操作步骤:(软件完成)

1.将前面保存在R14中的值,将它减去一个值,参考上表

2.将SPSR的值复制回CPSR

 

 

下面来看下如何实现代码测试中断:

文件如下:

Makefile:(关于编译选项可以参考: https://www.cnblogs.com/cheyihaosky/p/11562330.html)

 

objs := head.o init.o interrupt.o main.o        

int.bin: $(objs)   #编译的目标文件是int.bin, 要生成这个文件需要依赖${objs} ->> head.o init.o interrupt.o main.o这几个OBJ文件
	arm-linux-ld -Ttext 0x00000000 -o int_elf $^ #$^表示所有依赖文件
	arm-linux-objcopy -O binary -S int_elf $@ #$@目标文件的名称, 表示int.bin
	arm-linux-objdump -D -m arm int_elf > int.dis #生成反汇编文件
	
%.o:%.c
	arm-linux-gcc -Wall -O2 -c -o $@ $< #$<表示第一个.c文件

%.o:%.S
	arm-linux-gcc -Wall -O2 -c -o $@ $<

clean:
	rm -f int.bin int_elf int.dis *.o

 

测试代码: head.S

.extern     main
.text                                         @表明这里为代码段
.global _start 
_start:                                       @系统一上电先从这里开始跑

    b   Reset                                 @这条语句的地址为0x00,上电也算是一种异常,跳到这个地址执行第一条指令
HandleUndef:                 
    b   HandleUndef                         @ 0x04: 这条语句在地址0x04处, 标号就在它上方,实际这里什么都不干
HandleSWI:                   
    b   HandleSWI                      @ 0x08:同0x04
HandlePrefetchAbort:       
    b   HandlePrefetchAbort                @ 0x0c:同0x04
HandleDataAbort:            
    b   HandleDataAbort                    @ 0x10:同0x04
HandleNotUsed:              
    b   HandleNotUsed                       @ 0x14:同0x04
    b   HandleIRQ                            @ 0x18: 这里实际是有相关的代码实现的
HandleFIQ:                     
    b   HandleFIQ                           @ 0x1c:同0x04

Reset:                                        @系统复位,会自动执行0x00地址的b reset跳转到这里
    ldr sp, =4096                            @下面是调用c函数,所以要先设置栈指针,由于CPU的内部RAM只有4k,所以是指成4096
    bl  disable_watch_dog                   @这里只是往控制看门够的一个寄存器写值关闭看门狗, 跳转到init.c执行这个函数
    
    msr cpsr_c, #0xd2                       @ 进入中断模式
    ldr sp, =3072                            @ 然后设置中断模式的栈指针

    msr cpsr_c, #0xd5                       @进入系统模式
    ldr sp, =4096                             @设置系统模式栈指针
                                         @ 其实系统复位就处于系统模式
    bl  init_led                              @初始化LED相关的GPIO引脚
    bl  init_irq                              @ 中断初始化函数
    msr cpsr_c, #0x5f                      @ 开IRQ中断
    
    ldr lr, =halt_loop                       @ 设置返回地址
    ldr pc, =main                           @ 调用main函数
halt_loop:
    b   halt_loop

HandleIRQ:
    sub lr, lr, #4                            @计算返回地址?
    stmdb   sp!,    { r0-r12,lr }            @保存使用到的寄存器
                                               @
                                               @
    
    ldr lr, =int_return                       @ 设置函数的返回地址
    ldr pc, =EINT_Handle                    @ 调用EINT_Handle函数
int_return:
    ldmia   sp!,    { r0-r12,pc }^          @ 中断返回, ^表示将SPSR的值复制到CPSR中
                    

 init.c 

#include "s3c24xx.h"

#define	GPF4_out	(1<<(4*2))
#define	GPF5_out	(1<<(5*2))
#define	GPF6_out	(1<<(6*2))

#define	GPF4_msk	(3<<(4*2))
#define	GPF5_msk	(3<<(5*2))
#define	GPF6_msk	(3<<(6*2))

#define GPF0_eint     (0x2<<(0*2))
#define GPF2_eint     (0x2<<(2*2))
#define GPG3_eint     (0x2<<(3*2))

#define GPF0_msk    (3<<(0*2))
#define GPF2_msk    (3<<(2*2))
#define GPG3_msk    (3<<(3*2))

void disable_watch_dog(void)
{
    WTCON = 0; 
}

void init_led(void)
{
    GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk);
    GPFCON |= GPF4_out | GPF5_out | GPF6_out;
}
void init_irq( )
{
    GPFCON &= ~(GPF0_msk | GPF2_msk);
    GPFCON |= GPF0_eint | GPF2_eint;

    GPGCON &= ~GPG3_msk;
    GPGCON |= GPG3_eint;
   
    EINTMASK &= ~(1<<11);
     
    PRIORITY = (PRIORITY & ((~0x01) | (0x3<<7))) | (0x0 << 7) ;

    INTMSK   &= (~(1<<0)) & (~(1<<2)) & (~(1<<5));

 s3c24xx.h

/* WOTCH DOG register */
#define     WTCON           (*(volatile unsigned long *)0x53000000)

/* SDRAM regisers */
#define     MEM_CTL_BASE    0x48000000
#define     SDRAM_BASE      0x30000000

/* NAND Flash registers */
#define NFCONF              (*(volatile unsigned int  *)0x4e000000)
#define NFCMD               (*(volatile unsigned char *)0x4e000004)
#define NFADDR              (*(volatile unsigned char *)0x4e000008)
#define NFDATA              (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT              (*(volatile unsigned char *)0x4e000010)

/*GPIO registers*/
#define GPBCON              (*(volatile unsigned long *)0x56000010)
#define GPBDAT              (*(volatile unsigned long *)0x56000014)

#define GPFCON              (*(volatile unsigned long *)0x56000050)
#define GPFDAT              (*(volatile unsigned long *)0x56000054)
#define GPFUP               (*(volatile unsigned long *)0x56000058)

#define GPGCON              (*(volatile unsigned long *)0x56000060)
#define GPGDAT              (*(volatile unsigned long *)0x56000064)
#define GPGUP               (*(volatile unsigned long *)0x56000068)

#define GPHCON              (*(volatile unsigned long *)0x56000070)
#define GPHDAT              (*(volatile unsigned long *)0x56000074)
#define GPHUP               (*(volatile unsigned long *)0x56000078)



/*UART registers*/
#define ULCON0              (*(volatile unsigned long *)0x50000000)
#define UCON0               (*(volatile unsigned long *)0x50000004)
#define UFCON0              (*(volatile unsigned long *)0x50000008)
#define UMCON0              (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0            (*(volatile unsigned long *)0x50000010)
#define UTXH0               (*(volatile unsigned char *)0x50000020)
#define URXH0               (*(volatile unsigned char *)0x50000024)
#define UBRDIV0             (*(volatile unsigned long *)0x50000028)


/*interrupt registes*/
#define SRCPND              (*(volatile unsigned long *)0x4A000000)
#define INTMOD              (*(volatile unsigned long *)0x4A000004)
#define INTMSK              (*(volatile unsigned long *)0x4A000008)
#define PRIORITY            (*(volatile unsigned long *)0x4A00000c)
#define INTPND              (*(volatile unsigned long *)0x4A000010)
#define INTOFFSET           (*(volatile unsigned long *)0x4A000014)
#define SUBSRCPND           (*(volatile unsigned long *)0x4A000018)
#define INTSUBMSK           (*(volatile unsigned long *)0x4A00001c)

/*external interrupt registers*/
#define EINTMASK            (*(volatile unsigned long *)0x560000a4)
#define EINTPEND            (*(volatile unsigned long *)0x560000a8)

 interrupt.c

#include "s3c24xx.h"

void EINT_Handle()
{
    unsigned long oft = INTOFFSET;
    unsigned long val;
    
    switch( oft )
    {
        case 0: 
        {   
            GPFDAT |= (0x7<<4); 
            GPFDAT &= ~(1<<4); 
            break;
        }
        
        // S3±»°ŽÏÂ
        case 2:
        {   
            GPFDAT |= (0x7<<4); 
            GPFDAT &= ~(1<<5);  
            break;
        }

        // K4±»°ŽÏÂ
        case 5:
        {   
            GPFDAT |= (0x7<<4); 
            GPFDAT &= ~(1<<6);                
            break;
        }

        default:
            break;
    }

    if( oft == 5 ) 
        EINTPEND = (1<<11);
    SRCPND = 1<<oft;
    INTPND = 1<<oft;
}

  main.c

int main()
	{
	    while(1);
	    return 0;
	}

 

 

 

推荐阅读