您的位置:首页 > 其它

ARM9的中断体系结构

2015-10-10 10:53 204 查看
实验的目的:
当TQ2440开发板的按键按下的时候,触发中断,点亮LED灯。

实验的源程序:
/*************************************************************************************
*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)

@******************************************************************************

@ File:head.S

@ 功能:初始化,设置中断模式、管理模式的栈,设置好中断处理函数

@******************************************************************************       

   

.extern     main

.text 

.global _start 

_start:

@******************************************************************************       

@ 异常向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用

@******************************************************************************       

    b   Reset

@ 0x04: 未定义指令中止模式的向量地址

HandleUndef:

    b   HandleUndef 

 

@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式

HandleSWI:

    b   HandleSWI

@ 0x0c: 指令预取终止导致的异常的向量地址

HandlePrefetchAbort:

    b   HandlePrefetchAbort

@ 0x10: 数据访问终止导致的异常的向量地址

HandleDataAbort:

    b   HandleDataAbort

@ 0x14: 保留

HandleNotUsed:

    b   HandleNotUsed

@ 0x18: 中断模式的向量地址

    b   HandleIRQ

@ 0x1c: 快中断模式的向量地址

HandleFIQ:

    b   HandleFIQ

Reset:                  

    ldr sp, =4096           @ 设置栈指针,以下都是C函数,调用前需要设好栈

    bl  disable_watch_dog   @ 关闭WATCHDOG,否则CPU会不断重启

    

    msr cpsr_c, #0xd2       @ 进入中断模式

    ldr sp, =3072           @ 设置中断模式栈指针

    msr cpsr_c, #0xd3       @ 进入管理模式

    ldr sp, =4096           @ 设置管理模式栈指针,

                            @ 其实复位之后,CPU就处于管理模式,

                            @ 前面的“ldr sp, =4096”完成同样的功能,此句可省略

    bl  init_led            @ 初始化LED的GPIO管脚

    bl  init_irq            @ 调用中断初始化函数,在init.c中

    msr cpsr_c, #0x53       @ 设置I-bit=0,开IRQ中断

    

    ldr lr, =halt_loop      @ 设置返回地址

    ldr pc, =main           @ 调用main函数

halt_loop:

    b   halt_loop

HandleIRQ:

    sub lr, lr, #4                  @ 计算返回地址

    stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器

                                    @ 注意,此时的sp是中断模式的sp

                                    @ 初始值是上面设置的3072

    

    ldr lr, =int_return             @ 设置调用ISR即EINT_Handle函数后的返回地址  

    ldr pc, =EINT_Handle            @ 调用中断服务函数,在interrupt.c中

int_return:

    ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将spsr的值复制到cpsr

    

======================================================

注:ARM9的异常向量表:



======================================================

/**************************************************************************************************
 * init.c: 进行一些初始化
 *************************************************************************************************/ 

#include "s3c24xx.h"

/*
 * LED1,LED2,LED4对应GPB5、GPB6、GPB7、GPB8
 */
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))

#define GPB5_msk (3<<(5*2))
#define GPB6_msk (3<<(6*2))
#define GPB7_msk (3<<(7*2))
#define GPB8_msk (3<<(8*2))

/*
 * K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
 */
#define GPF0_int     (0x2<<(0*2))
#define GPF1_int     (0x2<<(1*2))
#define GPF2_int     (0x2<<(2*2))
#define GPF4_int     (0x2<<(4*2))

#define GPF0_msk    (3<<(0*2))
#define GPF1_msk    (3<<(1*2))
#define GPF2_msk    (3<<(2*2))
#define GPF4_msk    (3<<(4*2))

/*
 * 关闭WATCHDOG,否则CPU会不断重启
 */
void disable_watch_dog(void)
{
    WTCON = 0;  // 关闭WATCHDOG很简单,往这个寄存器写0即可
}

void init_led(void)
{
// LED1,LED2,LED3,LED4对应的4根引脚设为输出
GPBCON &= ~(GPB5_msk | GPB6_msk | GPB7_msk | GPB8_msk);
GPBCON |= GPB5_out | GPB6_out | GPB7_out | GPB8_out;
}

/*
 * 初始化GPIO引脚为外部中断
 * GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
 */ 
void init_irq( )
{
// K1,K2,K3,K4对应的4根引脚设为中断功能
GPFCON &= ~(GPF0_msk | GPF1_msk | GPF2_msk | GPF4_msk);
GPFCON |= GPF0_int | GPF1_int | GPF2_int | GPF4_int;

    // 对于EINT4,需要在EINTMASK寄存器中使能它
    EINTMASK &= ~(1<<4);
            
    /*
     * 设定优先级:
     * ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ2 > REQ3,即EINT0 > EINT1 > EINT2
     * 仲裁器1、6无需设置
     * 最终:
     * EINT0 > EINT1> EINT2 > EINT4 即K4 > K1 > K3 > K2
     */
    PRIORITY = (PRIORITY & ((~0x01) | ~(0x3<<7)));

    // EINT0、EINT1、EINT2、EINT4_7使能
    INTMSK   &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4));
}

/*******************************************************************************************
*interrupt.c
*******************************************************************************************/
#include "s3c24xx.h"

void EINT_Handle()
{
    unsigned long oft = INTOFFSET;
    unsigned long val;

/*
* K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
*            即 EINT1, ETIN4, EINT2, EINT0
*            oft为 1, 4, 2, 0 (对应INTMSK寄存器)
*/
    
    switch( oft )
    {
        // K1被按下
        case 1: 
        {   
            GPBDAT |= (0xF<<5);   // 所有LED熄灭
            GPBDAT &= ~(1<<5);      // LED1点亮
            break;
        }
        
        // K2被按下
        case 4:
        {   
            GPBDAT |= (0xF<<5);   // 所有LED熄灭
            GPBDAT &= ~(1<<6);      // LED2点亮
            break;
        }

        // K3被按下
        case 2:
        {   
            GPBDAT |= (0xF<<5);   // 所有LED熄灭
            GPBDAT &= ~(1<<7);      // LED3点亮
            break;
        }

        // K4被按下
        case 0:
        {   
            GPBDAT |= (0xF<<5);   // 所有LED熄灭
            GPBDAT &= ~(1<<8);      // LED4点亮
            break;
        }

        default:
            break;
    }

    //清中断
    if( oft == 4 ) 
        EINTPEND = (1<<4);   // EINT4_7合用IRQ4
    SRCPND = 1<<oft;

     INTPND = 1<<oft;

 }

/*****************************************************************************

*main.c

*****************************************************************************/

int main()

{

    while(1);

    return 0;

}

/************************************************************************************

*Makefile

************************************************************************************/

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

int.bin: $(objs)

arm-linux-ld -Ttext 0x00000000 -o int_elf $^

arm-linux-objcopy -O binary -S int_elf $@

arm-linux-objdump -D -m arm int_elf > int.dis

%.o:%.c

arm-linux-gcc -Wall -O2 -c -o $@ $<

%.o:%.S

arm-linux-gcc -Wall -O2 -c -o $@ $<

clean:

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

实验的问题总结:

I.刚开始程序会执行head.S文件,第一条指令是b reset指令。在reset中,我们可以知道:

设置了reset模式下的栈,因为我们之后需要调用C语言函数,而C语言函数的局部变量是需要栈来保存的。

然后我们可以看到,执行了这样一条指令:msr cpsr_c, #0xd2

其原因可以看一下解释:

/***************************MRS 和 MSR*********************************

     MRS R0, CPSR               读取CPSR的内容写入R0

     MSR CPSR, R0               读取R0的内容写入CPSR

     MSR CPSR_f, R0             读取R0的24-31的内容写入CPSR的24-31bit

     MSR CPSR_c, R0             读取R0的0-7的内容写入CPSR的0-7bit

     MSR CPSR_fc, R0  

     MSR CPSR_f, #0xf0000000    将0xf写入CPSR的24-31bit

     所有的CPSR可以替换成SPSR

***************************MRS 和 MSR*********************************/

/****************************CPSR***********************************

CPSR格式如下所示。SPSR和CPSR格式相同。

31     30     29    28     27     26               7     6     5     4     3     2     1     0

N      Z      C     V      Q     DNM(RAZ)          I     F     T     M4    M3    M2    M1    M0

***条件标志位***

N——本位设置成当前指令运算结果的bit[31]的值。当两个表示的有符号整数运算时,n=1表示运算结果为负数,n=0表示结果为正书或零。

Z——z=1表示运算的结果为零;z=0表示运算的结果不为零。对于CMP指令,Z=1表示进行比较的两个数大小相等。

C——下面分四种情况讨论C的设置方法:

在加法指令中(包括比较指令CMP),当结果产生了进位,则C=1,表示无符号运算发生上溢出;其他情况C=0。

在减法指令中(包括减法指令CMP),当运算中发生错位,则C=0,表示无符号运算数发生下溢出;其他情况下C=1。

对于包含移位操作的非加减运算指令,C中包含最后一次溢出的的位的数值。对于其他非加减运算指令,C位的值通常不受影响。

V——对于加减运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号为溢出;通常其他指令不影响V位。

***Q标识位***

在ARM V5的E系列处理器中,CPSR的bit[27]称为q标识位,主要用于指示增强的dsp指令是否发生了溢出。同样的spsr的bit[27]位也称为q标识位,用于在异常中断发生时保存和恢复CPSR中的Q标识位。

在ARM V5以前的版本及ARM V5的非E系列的处理器中,Q标识位没有被定义。

***CPSR中的控制位***

CPSR的低八位I、F、T、M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下,软件可以修改这些控制位。

**中断禁止位I,F:当I=1时禁止IRQ中断,当F=1时禁止FIQ中断

**T控制位:T控制位用于控制指令执行的状态,即说明本指令是ARM指令还是Thumb指令。对于ARM V4以更高版本的T系列ARM处理器,T控制位含义如下:

T=0表示执行ARM指令

T=1表示执行Thumb指令

对于ARM V5以及更高版本的非T系列处理器,T控制位的含义如下

T=0表示执行ARM指令

T=1表示强制下一条执行的指令产生未定指令中断

***M控制位***

M控制位控制处理器模式,具体含义如下:

M[4:0] 处理器模式    可访问的寄存器

ob10000 user     pc,r14~r0,CPSR

0b10001 FIQ      PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ

0b10010 IRQ      PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ

0B10011 SUPERVISOR    PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC

0b10111 ABORT     PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT

0b11011 UNDEFINEED    PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND

0b11111 SYSTEM     PC,R14-R0,CPSR(ARM V4以及更高版本)

*****************************CPSR*************************************/

看完这两点,你就知道为什么要设置的值为#0xd2。接下来是设置中断模式下的栈指针。同上,因为每种模式下的栈寄存器是不共用的。

II.接下来,我们重点看一下 ldr lr, =halt_loop 这条指令,为什么在调用main函数之前需要设置lr寄存器呢?不设置难道不可以么?

具体解释如下:

在跳转的时候,PC的值会赋给lr寄存器。

在汇编调用C语言的时候,不一定非得配置lr寄存器,这要看你调用完C语言后想再往下执行哪条指令,

若接下来执行的指令刚好是C语言完了之后的这条指令,那么这种情况下可以不用设置,因为CPU会自动将lr = lr - 4,

也就是接下来该执行的一条指令,若你想再调用C语言之后,是跳转到某处的一标号,此时需要设置lr。

III.接下来,若发生中断的时候,CPU会自动让PC跳转到 HandleIRQ 去执行对应的指令。

然后,我们可以看到:sub lr, lr, #4

为什么要将lr的值减4呢?具体解释如下:



/***************************************************************************

我们知道在ARM 架构里,PC值指向当前执行指令的地址加8处,也就是说, 当执行指令A(地址0x8000)时,PC 等于指令C 的地址(0x8008)。

假如指令A 是“BL”指令,则当执行该指令时,会把PC(=0x8008)保存到LR 寄存器里面,但是接下去处理器会马上对LR 进行一个自动的调整动作:

LR=LR-0x4。这样,最终保存在 LR 里面的是 B 指令的地址,所以当从 BL 返回时,LR 里面正好是正确的返回地址。同样的调整机制在所有LR

自动保存操作中都存在,比如进入中断响应时,处理器所做的LR 保存中,也进行了一次自动调整,并且调整动作都是LR=LR-0x4。

下面,我们对不同类型的异常的返回地址依次进行说明:

假设在指令A 处(地址0x8000)发生了异常,进入异常响应后,LR 上经过调整保存的地址值应该是B 的地址0x8004。

1、 如果发生的是软件中断,即A 是“SWI”指令

异常是由指令本身引起的,从 SWI 中断返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。

MOVS pc, lr

2、 发生的是Undefined instruction异常

异常是由指令本身引起的,从异常返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。

MOVS pc, lr

3、 发生的是IRQ或FIQ中断

因为指令不可能被中断打断,所以A指令执行完以后才能响应中断,此时PC已更新,指向指令D的地址(地址0x800C),

LR 上经过调整保存的地址值是C 的地址0x8008。中断返回后应该执行B指令,所以返回操作是:

SUBS pc, lr, #4

4、 发生的是Prefetch Abort异常

该异常并不是处理器试图从一个非法地址取指令时触发,取出的指令只是被标记为非法,按正常处理流程放在流水线上,

在执行阶段触发Prefetch Abort异常,此时LR 上经过调整保存的地址值是B 的地址0x8004。异常返回应该返回到A指令,

尝试重新取指令,所以返回操作是:

SUBS pc, lr, #4

5、 发生的是“Data Abort”

CPU访问存储器时触发该异常,此时PC指向指令D的地址(地址0x800C),LR 上经过调整保存的地址值是C 的地址0x8008。

异常返回后,应回到指令A,尝试重新操作存储器,所以返回操作是:

SUBS pc, lr, #8

****************************************************************************/

这就是为什么我们需要将lr的值减4,具体可以参考我转载的博客。

IV.然后我们可以看得这样一条指令:stmdb   sp!,    { r0-r12,lr },它是和 ldmia   sp!,    { r0-r12,pc }^ 指令对应起来的。

在跳转到中断服务程序之前,先保存用到的寄存器,在完成中断服务程序之后,又恢复寄存器。其中,lr和pc是对应起来的。

也就是说,恢复寄存器后,pc中保存的值是lr寄存器中的。所以当执行完这条指令之后,就又跳回来了。

关注微信公众号获取更多资讯



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: