ARM9的中断体系结构

时间:2021-03-19 19:08:19
实验的目的:
当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的异常向量表:

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呢?具体解释如下:
ARM9的中断体系结构

/***************************************************************************
我们知道在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寄存器中的。所以当执行完这条指令之后,就又跳回来了。



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

ARM9的中断体系结构