CPU工作模式

  1. 用户模式(usr)
  2. 系统模式(sys)
  3. 异常模式

    1. 未定义模式(und)
    2. 管理模式(svc)
    3. 终止模式(abt)
    4. 中断模式(irq)
    5. 快速中断模式(fiq)

系统模式和异常模式一起称为特权模式,可以随意切换,用户模式时无法切换模式。

ARM状态下寄存器

通用寄存器

clipboard.png
带有灰色三角的寄存器表示当前工作模式下专属的寄存器。
R13寄存器用作sp栈,R14寄存器用作lr返回地址(保存发生异常时候运行到的地址,异常处理完返回继续执行主程序)。

程序状态寄存器

clipboard.png

clipboard.png

  • CPSR:当前程序状态寄存器,SPSR:保存被中断模式下的CPSR寄存器。
  • M4-M0:表示工作模式位,写入表示修改工作模式,读表示判断当前工作模式。

clipboard.png

  • T:表示是ARM状态还是Thumb状态
  • F:1时关闭所有FIQ中断
  • I:1时关闭所有IRQ中断

中断处理流程

进入异常时cpu的操作(硬件完成)

clipboard.png
1.异常模式下的LR寄存器(R14)保存被中断模式的中断位置的下一条指令位置(PC+4/PC+8)
2.保存被中断模式的CPSR到异常模式的SPSR寄存器(因为CPSR寄存器是公用的)
3.保存CPSR之后修改模式位进入异常模式
4.跳转到vector向量表

退出异常时的操作

clipboard.png
1.把异常模式下的LR寄存器值(保存被中断模式下一条指令位置)减去某个值赋值给PC返回执行(见下表)
clipboard.png
2.把SPSR中的值拷贝给CPSR
3.清中断(对于中断才需要)

异常向量表

clipboard.png
当系统发生异常时硬件会按照异常向量表的说明跳到对应地址去执行,所以对应的0x0~0x1c必须让出给相应的处理函数

未定义指令异常

在start.S中定义一条未定义指令,CPU运行到这里时触发未定义指令异常

und_code:
    .word 0xdeadc0de//故意加入一条未定义指令

clipboard.png

硬件跳到0x0000_0004地址处执行相应命令

.text
.global _start
_start:
    b reset
    b do_und //0x0000_0004
do_und函数需要做以下三件事

0.设置und模式下专属的sp栈才可以调用c函数
ldr sp, =0x34000000随意指定一个地址

1.保存现场
stmdb sp!, {r0-r12, lr}保存r0-r12寄存器和lr寄存器(und模式的lr寄存器,因为目前的lr_und寄存器保存有被中断模式的下一条指令PC地址而之后会调用bl PrintException会覆盖lr_und寄存器)到栈中,因为这些寄存器是公用的有可能被修改,stmdb表示sp先减4再存。
cpsr保存到spsr是硬件完成的。

2.处理(以下都是自己随意添加的操作)
mrs r0, cpsr:表示读cpsr寄存器
定义字符串

und_string:
    .string "undefined exception!"

3.恢复现场
ldmia sp!, {r0-r12, pc}^:ldmia表示先读后加把栈中的值读取到{r0-r12, pc}对应寄存器,^表示把spsr恢复到cpsr

start.S
.text
.global _start
_start:
    b reset
    ldr pc, =do_und

do_und:
    //设置栈
    ldr sp, =0x34000000
    //保存现场
    stmdb sp!, {r0-r12, lr}
    
    //处理异常
    mrs r0, cpsr
    mrs r1, spsr
    ldr r2, =und_string
    bl PrintException
    
    //恢复现场
    ldmia sp!, {r0-r12, pc}^ 
    
und_string:
    .string "undefined instruction exception"
.align 4 //保证4字节对齐
    
reset:
    //****关闭看门狗****
    ldr r0, =0x53000000
    mov r1, #0
    str r1, [r0]

    //****设置MPLL时钟****
    //设置LOCKTIME
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]

    /*设置CLKDIVN*/
    ldr r0, =0x4C000014
    ldr r1, =0x5
    str r1, [r0]

    /*设置CPU异步模式*/
    mrc p15,0,r0,c1,c0,0 
    orr r0,r0,#0xc0000000 /*R1_nF:OR:R1_iA */
    mcr p15,0,r0,c1,c0,0

    /*设置MPLLCON*/
    ldr r0, =0x4C000004
    ldr r1, =(92 << 12) | (1 << 4) | (1<<0)
    str r1, [r0]    


    //****设置栈****
    mov r1, #0
    ldr r0, [r1] /* 读出原来的值备份 */
    str r1, [r1] /* 0->[0] */ 
    ldr r2, [r1] /* r2=[0] */
    cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
    ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
    moveq sp, #4096  /* nand启动 */
    streq r0, [r1]   /* 恢复原来的值 */
    
    //初始化sdram
    bl Init_SDRAM
        
    //****重定位****
    mov r1, #0
    ldr r2, =_start //第一条指令地址
    ldr r3, =__bss_start //bss段起始地址
cpy:
    ldr r4, [r1] //从data_load_addr拷贝1个字节 效率低
    str r4, [r2]
    add r1, r1, #4
    add r2, r2, #4
    cmp r2, r3
    ble cpy //小于
    
    //清除bss段
    ldr r1, =__bss_start 
    ldr r2, =_end
    mov r3, #0
clean:    
    str r3, [r1]
    add r1, r1, #4
    cmp r1, r2
    ble clean
    
    ldr pc, =sdram //绝对跳转,到sdram中
sdram:
    //初始化串口
    bl Init_UART0
    
    bl print1
    
und_code:
    .word 0x03000000//故意加入一条未定义指令
    bl print2
    
    //****调用main****
    ldr pc, =main
halt:
    b halt

ldr pc, =do_und防止处理函数在4k之外,这里跳到sdram执行。
30000004: e59ff0f0 ldr pc, [pc, #240] ; 300000fc <.text+0xfc>表示把第一列为300000fc地址上的数据赋值给pc,300000fc: 30000008 andcc r0, r0, r8,这里就是把0x30000008赋值给pc,pc跳到sdram执行
.word之后存放的数据直接保存
输出cpsr = 0x600000DB spsr = 0x600000D3 undefined instruction exception可以看出系统上电是处于10011管理模式

跳转流程图

clipboard.png

swi(软中断)异常模式程序分析

IRQ中断

mini2440按键原理图

clipboard.png


配置流程图
st=>start: Start
e=>end: End
op1=>operation: cpsr寄存器
op2=>operation: 中断向量表
op3=>operation: GPxCON
op4=>operation: EXTINTx
op5=>operation: EINTMASK
op6=>operation: INTMSK

st->op1->op2
op2->op3->op4
op4->op5->op6
GPIO部分寄存器配置

1.设置cpsr寄存器的I位开中断总开关,清除bit7

    //切换到usr模式
    mrs r0, cpsr         /* 读出cpsr */
    bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
    bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
    msr cpsr, r0

    /* 设置 sp_usr */
    ldr sp, =0x33f00000

2.设置中断向量表0x18

.text
.global _start
_start:
    b reset //0x00
    ldr pc, und_addr //0x04
    b halt //0x08
    b halt //0x0c
    b halt //0x10
    b halt //0x14
    ldr pc, irq_addr //0x18
    
und_addr:
    .word do_und
    
irq_addr:
    .word do_irq

3.设置GPGCON寄存器把引脚配置为中断模式

    //GPG0 -> EINT8
    //GPG3 -> EINT11
    //GPG5 -> EINT13
    GPGCON &= ~((3 << 0) | (3 << 6) | (3 << 10))
    GPGCON |= ((2 << 0) | (2 << 6) | (2 << 10))

4.配置EXTINTx寄存器设置引脚触发方式

    //设置为下降沿触发
    EXTINT1 &= ~((7 << 0) | (7 << 12) | (7 << 20));
    EXTINT1 |= ((3 << 0) | (3 << 12) | (3 << 20));

5.配置EINTMASK需要设置外部中断的位清0

    //使能中断
    EINTMASK &= ~((1 << 8) | (1 << 11) | (1 << 13));
中断控制器部分寄存器配置

clipboard.png
系统默认配置为IRQ模式,需要设置为FIQ则配置INTMOD寄存器。
这里使用的外部中断EINT都属于SOURCES所以这里暂时不关心SUB SOURCES类中断。
1.设置INTMSK对应位清0关闭屏蔽

INTMSK &= ~(1 << 5);
外部中断发生判断

1.SRCPND相应位置1表示发生中断(需要清0)
2.如果是外部中断EINT4~23需要继续读EINTPEND判断具体引脚(需要清0)
3.INTPND寄存器显示当前优先级最高的中断(需要清0)
4.INTOFFSET寄存器里的值判断哪个中断正在被处理,指示了INTPND寄存器中哪位值被设置成了1

可以直接读INTOFFSET寄存器判断中断,读EINTPEND判断具体引脚,然后清EINTPEND、清SRCPND、清INTPND

中断处理函数
//中断处理函数
void handle_irq_c(){
    int bit;
    
    bit = INTOFFSET;//读当前发生中断的值
    
    if(bit == 5){ //eint8 ~23
        if(EINTPEND & (1 << 8)){
            SendString("EINT8!\n\r");
            EINTPEND |= (1 << 8);//清中断
        }else if(EINTPEND & (1 << 11)){
            SendString("EINT11!\n\r");
            EINTPEND |= (1 << 11);//清中断
        }else if(EINTPEND & (1 << 13)){
            SendString("EINT13!\n\r");
            EINTPEND |= (1 << 13);//清中断
        }
    }
    
    //清中断 写1清除
    SRCPND |= (1 << bit);
    INTPND |= (1 << bit);
}

完整代码


Kyseng
1 声望3 粉丝

电子爱好者一枚,利用工作空余时间记录一下学习过程