美文网首页ARM 单片开发
ARM异常与中断体系详解

ARM异常与中断体系详解

作者: ZebraWei | 来源:发表于2018-11-07 18:57 被阅读67次

    版权声明:本文为小斑马学习总结文章,技术来源于韦东山著作,转载请注明出处!

    一、概念引入与处理流程

    取个场景解释中断。
    假设有个大房间里面有小房间,婴儿正在睡觉,他的妈妈在外面看书。
    问:这个母亲怎么才能知道这个小孩醒?
    过一会打开一次房门,看婴儿是否睡醒,让后接着看书
    一直等到婴儿发出声音以后再过去查看,期间都在读书
    第一种 叫做查询方式:

    • 优点:简单
    • 缺点: 累

    写程序如何:

    while(1)
    {
       1 read book(读书)
       2 open door(开门)
       if(睡)
          return(read book)
       else
          照顾小孩
    
    }
    

    第二种叫中断方式:

    • 优点:不累
    • 缺点:复杂
    while(1)
    {
        read book
        中断服务程序()//如何被调用?
       {
        处理照顾小孩
       }
    }
    

    母亲的处理过程:

    • 1 平时看书
    • 2 发生了各种声音,如何处理这些声音
      有远处的猫叫(听而不闻,忽略)
      门铃声有快递(开门收快递)
      小孩哭声(打开房门,照顾小孩)
    • 3 母亲的处理
      只会处理门铃声和小孩哭声
      a 现在书中放入书签,合上书(保存现场)
      b 去处理 (调用对应的中断服务程序)
      c 继续看书(恢复现场)
      不同情况,不同处理:
      a 对于门铃:开门取快件
      b 对于哭声:照顾小孩

    我们将母亲的处理过程抽象化——母亲的头脑相当于CPU
    耳朵听到声音会发送信号给脑袋,声音来源有很多种,有远处的猫叫,门铃声,小孩哭声。这些声音传入耳朵,再由耳朵传给大脑,除了这些可以中断母亲的看书,还有其他情况,比如身体不舒服,有只蜘蛛掉下来,对于特殊情况无法回避,必须立即处理

    对比我们的arm系统

    图片来源于白问网 有CPU,有中断控制器。
    中断控制器可以发信号给CPU告诉它发生了那些紧急情况
    中断源有按键、定时器、有其它的(比如网络数据)
    这些信号都可以发送信号给中断控制器,再由中断控制器发送信号给CPU表明有这些中断产生了,这些成为中断(属于一种异常)
    还有什么可以中断CPU运行?
    指令不对,数据访问有问题
    reset信号,这些都可以中断CPU 这些成为异常中断
    重点在于保存现场以及恢复现场
    处理过程
    • a 保存现场(各种寄存器)
    • b 处理异常(中断属于一种异常)
    • c 恢复现场

    arm对异常(中断)处理过程
    -1 初始化:

    • a 设置中断源,让它可以产生中断
    • b 设置中断控制器(可以屏蔽某个中断,优先级)
    • c 设置CPU总开关,(使能中断)

    2 执行其他程序:正常程序
    3 产生中断:按下按键--->中断控制器--->CPU
    4 cpu每执行完一条指令都会检查有无中断/异常产生
    5 发现有中断/异常产生,开始处理。对于不同的异常,跳去不同的地址执行程序。这地址上,只是一条跳转指令,跳去执行某个函数(地址),这个就是异常向量。如下就是异常向量表,对于不同的异常都有一条跳转指令。

    .globl _start
    _start: b   reset
    ldr pc, _undefined_instruction
    ldr pc, _software_interrupt
    ldr pc, _prefetch_abort
    ldr pc, _data_abort
    ldr pc, _not_used
    ldr pc, _irq //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18**
    ldr pc, _fiq
    //我们先在0x18这里放 ldr pc ,__irq,于是cpu最终会跳去执行__irq代码
    //保护现场,调用处理函数,恢复现场
    

    (3-5都是硬件强制做的)
    6 这些函数做什么事情?
    软件做的:

    • a 保存现场(各种寄存器)
    • b 处理异常(中断):
      分辨中断源
      再调用不同的处理函数
    • c 恢复现场

    对比母亲的处理过程来比较arm中断的处理过程。
    中断处理程序怎么被调用?
    CPU--->0x18 --跳转到其他函数->

    • 做保护现场
    • 调用函数
    • 分辨中断源
    • 调用对应函数
    • 恢复现场
      cpu到0x18是由硬件决定的,跳去执行更加复杂函数(由软件决定)

    二、CPU模式(Mode)_状态(State)与寄存器

    CPU的工作模式(Mode) 状态(State)寄存器
    7种Mode:

    usr/sys
    undefined(und)
    Supervisor(svc)
    Abort(abt)
    IRQ(irq)
    FIQ(fiq)
    

    2种State:

    ARM state
    Thumb state
    

    寄存器:
    通用寄存器
    备份寄存器(banked register)
    当前程序状态寄存器(Current Program Status Register);CPSR
    CPSR的备份寄存器:SPSR(Save Program Status Register)
    我们仍然以这个母亲为例讲解这个CPU模式
    这个母亲无压力看书 -->(正常模式)
    要考试,看书--->(兴奋模式)
    生病---->(异常模式)

    可以参考书籍 《ARM体系结构与编程》作者:杜春雷
    对于ARM CPU有7种模式:

    • 1 usr :类比 正常模式
    • 2 sys :类比的话兴奋模式
      3 5种异常模式:(2440用户手册72页)
      3.1 und :未定义模式
      3.2 svc :管理模式
      3.3 abt :终止模式
      a 指令预取终止(读写某条错误的指令导致终止运行)
      b 数据访问终止 (读写某个地址,这个过程出错)
      都会进入终止模式
      3.4 IRQ: 中断模式
      3.5 FIQ: 快中断模式

    我们可以称以下6种为特权模式
    und :未定义模式
    svc :管理模式
    abt :终止模式
    IRQ :中断模式
    FIQ :快中断模式
    sys :系统模式
    usr用户模式(不可直接进入其他模式) 可以编程操作CPSR直接进入其他模式


    这个图是有关各个模式下能访问寄存器的,再讲这个图之前我们先引入 2种state
    CPU有两种state:
    • 1 ARM state:使用ARM指令集,每个指令4byte
    • 2 Thumb state:使用的是Thumb指令集,每个指令2byte

    比如同样是:
    mov R0, R1 编译后
    对于ARM指令集要占据4个字节:机器码
    对于Thumb指令集占据2个字节:机器码
    引入Thumb减少存储空间
    ARM指令集与Thumb指令集的区别:
    Thumb 指令可以看作是 ARM 指令压缩形式的子集,是针对代码密度的问题而提出的,它具有 16 位的代码密度但是它不如ARM指令的效率高 .
    Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集.
    因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集,比如,所有异常自动进入 ARM 状态.在编写 Thumb 指令时,先要使用伪指令 CODE16 声明,而且在 ARM 指令中要使用 BX指令跳转到 Thumb 指令,以切换处理器状态.编写 ARM 指令时,则可使用伪指令 CODE32声明.
    使用Thumb指令集编译,看是否生成的bin文件会变小很多


    在每种模式下都有R0 ~ R15
    在这张图注意到有些寄存器画有灰色的三角形,表示访问该模式下访问的专属寄存器
    比如
    mov R0, R8
    mov R0, R8
    在System 模式下访问的是R0 ~ R8,在所有模式下访问R0都是同一个寄存器
    mov R0,R8_fiq
    但是在FIQ模式下,访问R8是访问的FIQ模式专属的R8寄存器,不是同一个物理上的寄存器
    在这五种异常模式中每个模式都有自己专属的R13 R14寄存器,R13用作SP(栈) R14用作LR(返回地址)
    LR是用来保存发生异常时的指令地址
    为什么快中断(FIQ)有那么多专属寄存器,这些寄存器称为备份寄存器
    回顾一下中断的处理过程
    • 1 保存现场(保存被中断模式的寄存器)
      就比如说我们的程序正在系统模式/用户模式下运行,当你发生中断时,需要把R0 ~ R14这些寄存器全部保存下来,让后处理异常,最后恢复这些寄存器
      但如果是快中断,那么我就不需要保存 系统/用户模式下的R8 ~ R12这几个寄存器,在FIQ模式下有自己专属的R8 ~ R12寄存器,省略保存寄存器的时间,加快处理速度
      但是在Linux中并不会使用FIQ模式
    • 2 处理
    • 3 恢复现场
      CRSR当前程序状态寄存器,这是一个特别重要的寄存器
      SPSR保存的程序状态寄存器,他们格式如下: 首先 M4 ~ M0 表示当前CPU处于哪一种模式(Mode);
      我们可以读取这5位来判断CPU处于哪一种模式,也可以修改这一种模式位,让其修改这种模式;
      假如你当前处于用户模式下,是没有权限修改这些位的;
      M4 ~ M0对应什么值,会有说明:
      查看其他位
      Bit5 State bits表示CPU工作与Thumb State还是ARM State用的指令集是什么
      Bit6 FIQ disable当bit6等于1时,FIQ是不工作的
      Bit7 IRQ disable当bit5等于1时,禁止所有的IRQ中断,这个位是IRQ的总开关
      Bit8 ~ Bit27是保留位
      Bite28 ~ Bit31是状态位,
      什么是状态位,比如说执行一条指令
      cmp R0, R1
      如果R0 等于 R1 那么zero位等于1,这条指令影响 Z 位,如果R0 == R1,则Z = 1
      beq跳转到xxx这条指令会判断Bit30是否为1,是1的话则跳转,不是1的话则不会跳转
      使用 Z 位,如果 Z 位等于1 则跳转,这些指令是借助状态位实现的
      SPSR保存的程序状态寄存器:
      表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR
      就比如我我的程序在系统模式下运行 CPSR是某个值,当发生中断时会进入irq模式,这个CPSR_irq就保存系统模式下的CPSR
      我们来看看发生异常时CPU是如何协同工作的:
      进入异常的处理流程(硬件) 我们来翻译一下:
      发生异常时,我们的CPU会做什么事情
    • 1把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址)
      它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况
    • 2 把CPSR保存在SPSR里面(某一种异常模式下SPSR里面的值等于CPSR)
    • 3 修改CPSR的模式为进入异常模式(修改CPSR的M4 ~ M0进入异常模式)
    • 4 跳到向量表
      退出异常怎么做?
      让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset)
      减去什么值呢?
      也就是我们怎么返回去继续执行原来的程序,根据下面这个表来取值 如果发生的是SWI可以把 R14_svc复制给PC
      如果发生的是IRQ可以把R14_irq的值减去4赋值给PC
    • 2 把CPSR的值恢复(CPSR 值等于 某一个一场模式下的SPSR)
    • 3 清中断(如果是中断的话,对于其他异常不用设置)

    三、不重要_Thumb指令集程序示例

    ARM State 每条指令会占据4byte
    Thumb State 每条指令占据2byte
    打开Makefile和Start.S

    all:
    arm-linux-gcc -c -o led.o led.c
    arm-linux-gcc -c -o uart.o uart.c
    arm-linux-gcc -c -o init.o init.c
    arm-linux-gcc -c -o main.o main.c
    arm-linux-gcc -c -o start.o start.S
    #arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-objcopy -O binary -S sdram.elf sdram.bin
    arm-linux-objdump -D sdram.elf > sdram.dis
    clean:
    rm *.bin *.o *.elf *.dis
    

    对于使用Thumb指令集

    all:
    arm-linux-gcc -mthumb -c -o led.o led.c//只需要在arm-linux-gcc加上 mthumb命令即可
    arm-linux-gcc -c -o uart.o uart.c
    arm-linux-gcc -c -o init.o init.c
    arm-linux-gcc -c -o main.o main.c
    arm-linux-gcc -c -o start.o start.S
    #arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-objcopy -O binary -S sdram.elf sdram.bin
    arm-linux-objdump -D sdram.elf > sdram.dis
    clean:
    rm *.bin *.o *.elf *.dis
    

    改进

    all: led.o uart.o init.o main.o start.o //all依赖led.o uart.o init.o main.o start.o
    #arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -
    o sdram.elf
    arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
    arm-linux-objcopy -O binary -S sdram.elf sdram.bin
    arm-linux-objdump -D sdram.elf > sdram.dis
    clean:
    rm .bin .o .elf .dis
    
     %.o : %.c
     arm-linux-gcc -mthumb -c -o $@ $< //对于所有的.c文件使用规则就可以使用thumb指令集编译 $@表示目标 $<表示第一个依赖
    
    %.o : %.S
    arm-linux-gcc -c -o $@ $<
    

    对start.S需要修改代码
    原重定位章节Start.S文件

    .text
    .global _start
    
    _start:
    
    /* 关闭看门狗 */
    ldr r0, =0x53000000
    ldr r1, =0
    str r1, [r0]
    
    /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]
    
    /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    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(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
     *  m = MDIV+8 = 92+8=100
     *  p = PDIV+2 = 1+2 = 3
     *  s = SDIV = 1
     *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
     */
    ldr r0, =0x4C000004
    ldr r1, =(92<<12)|(1<<4)|(1<<0)
    str r1, [r0]
    
    /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
     * 然后CPU工作于新的频率FCLK
     */
    
    
    
    /* 设置内存: sp 栈 */
    /* 分辨是nor/nand启动
     * 写0到0地址, 再读出来
     * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
     * 否则就是nor启动
     */
    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]   /* 恢复原来的值 */
    
    bl sdram_init
    //bl sdram_init2     /* 用到有初始值的数组, 不是位置无关码 */
    
    /* 重定位text, rodata, data段整个程序 */
    bl copy2sdram
    
    /* 清除BSS段 */
    bl clean_bss
    
    //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    

    使用thumb指令集的Start.S文件

    .text
    .global _start
    .code 32 //表示后续的指令使用ARM指令集
    _start:
    
    /* 关闭看门狗 */
    ldr r0, =0x53000000
    ldr r1, =0
    str r1, [r0]
    
    /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]
    
    /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    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(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
     *  m = MDIV+8 = 92+8=100
     *  p = PDIV+2 = 1+2 = 3
     *  s = SDIV = 1
     *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
     */
    ldr r0, =0x4C000004
    ldr r1, =(92<<12)|(1<<4)|(1<<0)
    str r1, [r0]
    
    /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
     * 然后CPU工作于新的频率FCLK
     */
    
    
    
    /* 设置内存: sp 栈 */
    /* 分辨是nor/nand启动
     * 写0到0地址, 再读出来
     * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
     * 否则就是nor启动
     */
    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]   /* 恢复原来的值 */
    
    /* 怎么从ARM State切换到Thumb State? */
    adr r0, thumb_func //定义此标号的地址
    add r0, r0, #1  /* bit0=1时, bx就会切换CPU State到thumb state */
    bx r0
    
    .code 16 //下面都使用thumb指令集    
    thumb_func: //需要得到这个标号的地址
    /*下面就是使用thumb指令来执行程序*/
    bl sdram_init
    //bl sdram_init2     /* 用到有初始值的数组, 不是位置无关码 */
    
    /* 重定位text, rodata, data段整个程序 */
    bl copy2sdram
    
    /* 清除BSS段 */
    bl clean_bss
    
    //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    ldr r0, =main  /* 绝对跳转, 跳到SDRAM ,先把main的地址赋值给R0 */
    mov pc, r0  /*让后再移动到PC*/
    
    halt:
         b halt
    

    上传代码编译测试
    出现错误,如下
    init.o(.text+0x6c):In function 'sdram_init2';
    undefined reference to 'memcpy'
    发现是init,o里sdram_init2使用的了memcpy函数
    查看init.c

    #include "s3c2440_soc.h"
    
    void sdram_init(void)
    {
    BWSCON = 0x22000000;
    
    BANKCON6 = 0x18001;
    BANKCON7 = 0x18001;
    
    REFRESH  = 0x8404f5;
    
    BANKSIZE = 0xb1;
    
    MRSRB6   = 0x20;
    MRSRB7   = 0x20;
    }
    
    #if 0
    
    
    /**************************************************************************   
    * 设置控制SDRAM的13个寄存器
    * 使用位置无关代码
    **************************************************************************/   
    void memsetup(void)
    {
    unsigned long *p = (unsigned long *)MEM_CTL_BASE;   
    p[0] = 0x22111110;      //BWSCON
    p[1] = 0x00000700;      //BANKCON0
    p[2] = 0x00000700;      //BANKCON1
    p[3] = 0x00000700;      //BANKCON2
    p[4] = 0x00000700;      //BANKCON3  
    p[5] = 0x00000700;      //BANKCON4
    p[6] = 0x00000700;      //BANKCON5
    p[7] = 0x00018005;      //BANKCON6
    p[8] = 0x00018005;      //BANKCON7
    p[9] = 0x008e07a3;      //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
    p[10] = 0x000000b2;     //BANKSIZE
    p[11] = 0x00000030;     //MRSRB6
    p[12] = 0x00000030;     //MRSRB7
    }
    #endif
    /*下面函数使用了memcpy函数,显然是编译器的操作,使用了memcpy把数
     组里的值从代码段拷贝到了arr局部变量里
     是否可以禁用掉memcpy*/
     void sdram_init2(void)
    {
    unsigned int arr[] = {
        0x22000000,     //BWSCON
        0x00000700,     //BANKCON0
        0x00000700,     //BANKCON1
        0x00000700,     //BANKCON2
        0x00000700,     //BANKCON3  
        0x00000700,     //BANKCON4
        0x00000700,     //BANKCON5
        0x18001,    //BANKCON6
        0x18001,    //BANKCON7
        0x8404f5,   //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
         0xb1,  //BANKSIZE
         0x20,  //MRSRB6
         0x20,  //MRSRB7
    
        };
    volatile unsigned int * p = (volatile unsigned int *)0x48000000;
    int i;
    
    for (i = 0; i < 13; i++)
    {
        *p = arr[i];
        p++;
    }
    
    }
    

    章说没有什么方法禁用memecpy但是可以修改这些变量
    比如说将其修改为静态变量,这些数据就会放在数据段中,最终重定位时会把数据类拷贝到对应的arr地址里面去

    void sdram_init2(void)
    {
    const static unsigned int arr[] = {  //加上const 和static
        0x22000000,     //BWSCON
        0x00000700,     //BANKCON0
        0x00000700,     //BANKCON1
        0x00000700,     //BANKCON2
        0x00000700,     //BANKCON3  
        0x00000700,     //BANKCON4
        0x00000700,     //BANKCON5
        0x18001,    //BANKCON6
        0x18001,    //BANKCON7
        0x8404f5,   //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
         0xb1,  //BANKSIZE
         0x20,  //MRSRB6
         0x20,  //MRSRB7
    
        };
    volatile unsigned int * p = (volatile unsigned int *)0x48000000;
    int i;
    
    for (i = 0; i < 13; i++)
    {
        *p = arr[i];
        p++;
    }
    
    }
    

    拷贝进行实验
    得出bin文件有1.4k左右
    查看反汇编代码

    sdram.elf:     file format elf32-littlearm
    
    Disassembly of section .text:
    
    /*前面这些ARM指令还是占用4个字节*/
    30000000 <_start>:
    30000000:   e3a00453    mov r0, #1392508928 ; 0x53000000
    30000004:   e3a01000    mov r1, #0  ; 0x0
    30000008:   e5801000    str r1, [r0]
    3000000c:   e3a00313    mov r0, #1275068416 ; 0x4c000000
    30000010:   e3e01000    mvn r1, #0  ; 0x0
    30000014:   e5801000    str r1, [r0]
    30000018:   e59f005c    ldr r0, [pc, #92]   ; 3000007c <.text+0x7c>
    3000001c:   e3a01005    mov r1, #5  ; 0x5
    30000020:   e5801000    str r1, [r0]
    30000024:   ee110f10    mrc 15, 0, r0, cr1, cr0, {0}
    30000028:   e3800103    orr r0, r0, #-1073741824    ; 0xc0000000
    3000002c:   ee010f10    mcr 15, 0, r0, cr1, cr0, {0}
    30000030:   e59f0048    ldr r0, [pc, #72]   ; 30000080 <.text+0x80>
    30000034:   e59f1048    ldr r1, [pc, #72]   ; 30000084 <.text+0x84>
    30000038:   e5801000    str r1, [r0]
    3000003c:   e3a01000    mov r1, #0  ; 0x0
    30000040:   e5910000    ldr r0, [r1]
    30000044:   e5811000    str r1, [r1]
    30000048:   e5912000    ldr r2, [r1]
    3000004c:   e1510002    cmp r1, r2
    30000050:   e59fd030    ldr sp, [pc, #48]   ; 30000088 <.text+0x88>
    30000054:   03a0da01    moveq   sp, #4096   ; 0x1000
    30000058:   05810000    streq   r0, [r1]
    3000005c:   e28f0004    add r0, pc, #4  ; 0x4
    30000060:   e2800001    add r0, r0, #1  ; 0x1
    30000064:   e12fff10    bx  r0
    
    30000068 <thumb_func>:
    30000068:   f94ef000    bl  30000308 <sdram_init>
    3000006c:   f9fef000    bl  3000046c <copy2sdram>
    30000070:   fa24f000    bl  300004bc <clean_bss>
     /**下面的thumb指令占据2个字节**/
    30000074:   4805        ldr r0, [pc, #20]   (3000008c <.text+0x8c>)
    30000076:   4687        mov pc, r0
    
    30000078 <halt>:
    30000078:   e7fe        b   30000078 <halt>
    3000007a:   0000        lsl r0, r0, #0
    3000007c:   0014        lsl r4, r2, #0
    3000007e:   4c00        ldr r4, [pc, #0]    (30000080 <.text+0x80>)
    30000080:   0004        lsl r4, r0, #0
    30000082:   4c00        ldr r4, [pc, #0]    (30000084 <.text+0x84>)
    30000084:   c011        stmia   r0!,{r0, r4}
    30000086:   0005        lsl r5, r0, #0
    30000088:   1000        asr r0, r0, #0
    3000008a:   4000        and r0, r0
    3000008c:   04fd        lsl r5, r7, #19
    3000008e:   3000        add r0, #0
    

    如果你的flash很小的话可以考虑使用Thumb指令集
    烧写进去看是否可以运行
    测试结果没有任何问题
    Thumb指令集后面没有任何作用,只是简单作为介绍

    四、und异常模示程序示例

    写一个程序故意让其发生未定义异常,让后处理这个异常
    查看uboot中源码uboot\u-boot-1.1.6\cpu\arm920t
    打开start.S

    /*code: 28 -- 72*/
    #include <config.h>
    #include <version.h>
    
    
    /*
     *************************************************************************
     *
     * Jump vector table as in table 3.1 in [1]
     *
     *************************************************************************
     */
    #define GSTATUS2   (0x560000B4)
    #define GSTATUS3   (0x560000B8)
    #define GSTATUS4   (0x560000BC)
    
    #define REFRESH(0x48000024)
    #define MISCCR (0x56000080)
    
    #define LOCKTIME    0x4C000000  /* R/W, PLL lock time count register */
    #define MPLLCON     0x4C000004  /* R/W, MPLL configuration register */
    #define UPLLCON     0x4C000008  /* R/W, UPLL configuration register */
    #define CLKCON      0x4C00000C  /* R/W, Clock generator control reg. */
    #define CLKSLOW     0x4C000010  /* R/W, Slow clock control register */
    #define CLKDIVN     0x4C000014  /* R/W, Clock divider control */
    
    /******下面这些就是异常向量表*****/
    .globl _start
    _start: b   reset
        ldr pc, _undefined_instruction
        ldr pc, _software_interrupt
        ldr pc, _prefetch_abort
        ldr pc, _data_abort
        ldr pc, _not_used
        ldr pc, _irq
        ldr pc, _fiq
    
    _undefined_instruction: .word undefined_instruction
    _software_interrupt:    .word software_interrupt
    _prefetch_abort:    .word prefetch_abort
    _data_abort:        .word data_abort
    _not_used:      .word not_used
    _irq:           .word irq
    _fiq:           .word fiq
    
        .balignl 16,0xdeadbeef
    
    手册异常向量表定义

    接下来我们写程序

    .text
    .global _start
    
    _start:
        b reset  /* vector 0 : reset */  //一上电复位,是从0地址开始执行,跳到reset处
        b do_und /* vector 4 : und */ //如果发生未定义指令异常,就会跳到0x04地址未定义指令异常处,执行do_und程序
    
    /*假设一上电从0地址开始执行,reset,做一系列初始化之后
    *故意加入一条未定义指令
        
    und_code:
        .word 0xdeadc0de  /* 未定义指令 */
    当CPU发现无法执行此条指令时,就会发生未定义指令异常,就会执行do_und
        bl print2,
    */
    do_und:
        /* 执行到这里之前:
         * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
         * 2. SPSR_und保存有被中断模式的CPSR
         * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
         * 4. 跳到0x4的地方执行程序 
         */
    //需要从新设置sp栈,指向某一块没有使用的地址    
        /* sp_und未设置, 先设置它 */
        ldr sp, =0x34000000
    
        /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
        /* 发生异常时,当前被中断的地址会保存在lr寄存器中 先减后存*/
        /* lr是异常处理完后的返回地址, 也要保存 */
        stmdb sp!, {r0-r12, lr}
        
        /* 保存现场 */
        /* 处理und异常 */
        mrs r0, cpsr//把cpsr的值读入r0
        ldr r1, =und_string//把下面的字符串地址赋值给r1
    
        bl printException
        
        /* 这些寄存器保存在栈中,把他读取出来就可以了*/
        /* 恢复现场 */
        /* 先读后加*/
        /* 把r0 ~ r12的值从栈中都取出来,并且把原来保存的lr值,赋值到pc中去*/
        ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    
    /*
     *如何定义字符串,可以百度搜索 arm-linux-gcc 汇编 定义字符串
     *
    *官方的说明文档
    *http://web.mit.edu/gnu/doc/html/as_7.html
    .string "str"
    
     Copy the characters in str to the object file. You may specify more than one string to copy, separated by commas. Unless otherwise specified for a particular machine, the assembler marks the end of each string with a 0 byte. You can use any of the escape sequences described in section Strings. 
    
     我们使用.str会自动加上结束符
     */  
    und_string:
        .string "undefined instruction exception"
    
    
    reset:
        /* 关闭看门狗 */
        ldr r0, =0x53000000
        ldr r1, =0
        str r1, [r0]
    
        /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
        /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
        ldr r0, =0x4C000000
        ldr r1, =0xFFFFFFFF
        str r1, [r0]
    
        /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
        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(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
         *  m = MDIV+8 = 92+8=100
         *  p = PDIV+2 = 1+2 = 3
         *  s = SDIV = 1
         *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
         */
        ldr r0, =0x4C000004
        ldr r1, =(92<<12)|(1<<4)|(1<<0)
        str r1, [r0]
    
        /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
         * 然后CPU工作于新的频率FCLK
         */
        
    
        /* 设置内存: sp 栈 */
        /* 分辨是nor/nand启动
         * 写0到0地址, 再读出来
         * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
         * 否则就是nor启动
         */
        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]   /* 恢复原来的值 */
    
        bl sdram_init
        //bl sdram_init2     /* 用到有初始值的数组, 不是位置无关码 */
    
        /* 重定位text, rodata, data段整个程序 */
        bl copy2sdram
    
        /* 清除BSS段 */
        bl clean_bss
    
        bl uart0_init
    
        bl print1
        /* 故意加入一条未定义指令 */
    und_code:
        .word 0xff123456  /* 未定义指令 */
        bl print2
    
        //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
        ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    

    如何处理这个异常呢?
    直接print打印一句话,新建一个exception.c文件

    #include "uart.h"
    
    void printException(unsigned int cpsr, char *str) //cpsr打印相应的寄存器,str打印一个字符串
    {
        puts("Exception! cpsr = ");\\打印cpsr
        printHex(cpsr);//输出cpsr的值
        puts(" ");//输出空格
        puts(str);//输出str值
        puts("\n\r");//回车,换行
    }
    

    我们打开之前编译过的程序的反汇编文件
    里面一定包含了保存恢复

    30000084 <delay>:
    30000084:   e1a0c00d    mov ip, sp
    30000088:   e92dd800    stmdb   sp!, {fp, ip, lr, pc} //保存 d是减 b是存
    3000008c:   e24cb004    sub fp, ip, #4  ; 0x4
    30000090:   e24dd004    sub sp, sp, #4  ; 0x4
    30000094:   e50b0010    str r0, [fp, #-16]
    30000098:   e51b3010    ldr r3, [fp, #-16]
    3000009c:   e2433001    sub r3, r3, #1  ; 0x1
    300000a0:   e50b3010    str r3, [fp, #-16]
    300000a4:   e51b3010    ldr r3, [fp, #-16]
    300000a8:   e3730001    cmn r3, #1  ; 0x1
    300000ac:   0a000000    beq 300000b4 <delay+0x30>
    300000b0:   eafffff8    b   30000098 <delay+0x14>
    300000b4:   e89da808    ldmia   sp, {r3, fp, sp, pc}//恢复,先读后加
    

    上传编译
    修改makefile添加文件

    all: start.o led.o uart.o init.o main.o exception.o
        #arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf
        arm-linux-ld -T sdram.lds $^ -o sdram.elf 
        #用$ ^来包含所有的依赖
        arm-linux-objcopy -O binary -S sdram.elf sdram.bin
        arm-linux-objdump -D sdram.elf > sdram.dis
    clean:
        rm *.bin *.o *.elf *.dis
        
    %.o : %.c
        arm-linux-gcc -c -o $@ $<
    
    %.o : %.S
        arm-linux-gcc -c -o $@ $<
        *.dis
    

    编译成功烧写
    没有输出我们想要的字符串
    很多同学想学会如何调试程序
    这里我们演示

    sdram:
        bl print1 //添加print1
        /* 故意加入一条未定义指令 */
    und_code:
        .word 0xdeadc0de  /* 未定义指令 */
        bl print2 //添加print2,实现这两个函数,来打印
    
        //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
        ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    

    实现print1 print2这两个打印函数,在uart.c这个文件里

    void print1(void)
    {
        puts("abc\n\r");
    }
    
    void print2(void)
    {
        puts("123\n\r");
    }
    

    上传代码烧写,发现print1、print2并未执行成功
    发现在start.S并未初始化 uart0_init(),删除main.c中的uart0_init()初始化函数

    ldr pc, =sdram
    sdram:
        bl uart0_init
    
        bl print1
        /* 故意加入一条未定义指令 */
    und_code:
        .word 0xff123456  /* 未定义指令 */
        bl print2
    
        //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
        ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    

    加上uart0_init,再次编译烧写
    程序正常运行,print1 print2全部打印,表明未定义指令并未运行,难道这个地址是一个已经定义的地址

    打开2440芯片手册,找到ARM指令集
    发现竟然是SWI指令,CPU可以识别出来,他不是一条未定义指令
    我们得找到一条CPU不能识别的指令,定义为0x03000000
    ldr pc, =sdram
    sdram:
        bl uart0_init
    
        bl print1
        /* 故意加入一条未定义指令 */
    und_code:
        .word 0x03000000  /* 未定义指令 */
        bl print2
    
        //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
        ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    

    编译烧写执行
    打印了未定义指令异常CPSR地址,打印了字符串,最后执行main函数
    .word 0xdeadcode /* 也是一条未定义指令 只要指令地址对不上上表就是未定义指令*/
    我们查看下cpsr是否处于未定义模式
    bit[4:0]表示CPU模式 11011,果然处于und模式
    我们看看这个程序做了什么事情

    .text
    .global _start
    /*一上电复位,从0地址开始执行
     跳到   reset:
     做了一系列初始化
     当执行到0xdeadc0de这条指令时候,CPU根本就不知道这条指令什么意思
    und_code:
        .word 0xdeadc0de  /* 未定义指令 */
        bl print2
    让后就发生未定义指令异常,他会把下一条指令的地址保存到异常模式的LR寄存器
    
        /* 执行到这里之前已经发生了很多事情
         * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
         * 2. SPSR_und保存有被中断模式的CPSR
         * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
         * 4. 跳到0x4的地方执行程序 
         * 
         * 设置栈 sp是指und的地址
         * sp_und未设置, 先设置它
         *  /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
         * lr是异常处理完后的返回地址, 也要保存 */
         * 保存现场 */
         * 处理und异常 */
         * 恢复sp
         * cpu就会切换到之前的模式
         */
    */    
    .text
    .global _start
    
    _start:
    b reset  /* vector 0 : reset */
    b do_und /* vector 4 : und */
    
    und_addr:
        .word do_und
    
    do_und:
        /* 执行到这里之前:
         * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
         * 2. SPSR_und保存有被中断模式的CPSR
         * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
         * 4. 跳到0x4的地方执行程序 
         */
    
        /* sp_und未设置, 先设置它 */
        ldr sp, =0x34000000
    
        /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
        /* lr是异常处理完后的返回地址, 也要保存 */
        stmdb sp!, {r0-r12, lr}  
        
        /* 保存现场 */
        /* 处理und异常 */
        mrs r0, cpsr
        ldr r1, =und_string
        bl printException
        
        /* 恢复现场 */
        ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
        
    und_string:
        .string "undefined instruction exception"
    

    程序改进
    源程序

    .text
    .global _start
    
    _start:
    b reset  /* vector 0 : reset */
    /*使用b命令跳转  相对跳转*/
    b do_und /* vector 4 : und */
    
    do_und:
    /* 执行到这里之前:
     * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_und保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
     * 4. 跳到0x4的地方执行程序 
     */
    
    /* sp_und未设置, 先设置它 */
    ldr sp, =0x34000000
    
    /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    
    /* 保存现场 */
    /* 处理und异常 */
    mrs r0, cpsr
    ldr r1, =und_string
    
    /*这里又使用bl指令跳转,如果是nand启动,这个函数在4k之外,这个函数必定出错 为了保险,跳转到sdram中执行程序*/
    bl printException
    
    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    
    und_string:
        .string "undefined instruction exception"
    
    
    reset:
    /* 关闭看门狗 */
    ldr r0, =0x53000000
    ldr r1, =0
    str r1, [r0]
    
    /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    ldr r0, =0x4C000000
    ldr r1, =0xFFFFFFFF
    str r1, [r0]
    
    /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    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(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
     *  m = MDIV+8 = 92+8=100
     *  p = PDIV+2 = 1+2 = 3
     *  s = SDIV = 1
     *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
     */
    ldr r0, =0x4C000004
    ldr r1, =(92<<12)|(1<<4)|(1<<0)
    str r1, [r0]
    
    /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
     * 然后CPU工作于新的频率FCLK
     */
    
    
    
    /* 设置内存: sp 栈 */
    /* 分辨是nor/nand启动
     * 写0到0地址, 再读出来
     * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
     * 否则就是nor启动
     */
    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]   /* 恢复原来的值 */
    
    bl sdram_init
    //bl sdram_init2     /* 用到有初始值的数组, 不是位置无关码 */
    
    /* 重定位text, rodata, data段整个程序 */
    bl copy2sdram
    
    /* 清除BSS段 */
    bl clean_bss
    
    bl uart0_init
    
    bl print1
    /* 故意加入一条未定义指令 */
    und_code:
    .word 0xdeadc0de  /* 未定义指令 */
    bl print2
    
    //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    

    改进后代码

    .text
    .global _start
    
    _start:
        b reset  /* vector 0 : reset */
    /*跳转到sdram执行这个函数,那么这个函数一定在sdram中
     我们需要指定让他去前面这块内存去读这个值,担心如果这个文件很大,超过4Knand就没法去读这个文件*/
        ldr pc, und_addr /* vector 4 : und */
    
    /*增加如下 查看反汇编,在08的地址读让后跳到3c*/    
    und_addr:
        .word do_und
    
    do_und:
        /* 执行到这里之前:
         * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
         * 2. SPSR_und保存有被中断模式的CPSR
         * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
         * 4. 跳到0x4的地方执行程序 
         */
    
        /* sp_und未设置, 先设置它 */
        ldr sp, =0x34000000
    
        /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
        /* lr是异常处理完后的返回地址, 也要保存 */
        stmdb sp!, {r0-r12, lr}  
        
        /* 保存现场 */
        /* 处理und异常 */
        mrs r0, cpsr
        ldr r1, =und_string
        bl printException
        
        /* 恢复现场 */
        ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
        
    und_string:
        .string "undefined instruction exception"
    /**如果你的程序长度稍有变化,就不能保证运行
    加上 .align 4才能保证后面的程序以4字节对齐,保证程序运行
    **/
    .align 4
    
    reset:
        /* 关闭看门狗 */
        ldr r0, =0x53000000
        ldr r1, =0
        str r1, [r0]
    
        /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
        /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
        ldr r0, =0x4C000000
        ldr r1, =0xFFFFFFFF
        str r1, [r0]
    
        /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
        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(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
         *  m = MDIV+8 = 92+8=100
         *  p = PDIV+2 = 1+2 = 3
         *  s = SDIV = 1
         *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
         */
        ldr r0, =0x4C000004
        ldr r1, =(92<<12)|(1<<4)|(1<<0)
        str r1, [r0]
    
        /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
         * 然后CPU工作于新的频率FCLK
         */
        
        
    
        /* 设置内存: sp 栈 */
        /* 分辨是nor/nand启动
         * 写0到0地址, 再读出来
         * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
         * 否则就是nor启动
         */
        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]   /* 恢复原来的值 */
    
        bl sdram_init
        //bl sdram_init2     /* 用到有初始值的数组, 不是位置无关码 */
    
        /* 重定位text, rodata, data段整个程序 */
        bl copy2sdram
    
        /* 清除BSS段 */
        bl clean_bss
    
    /*把链接地址赋值给pc 直接就跳转到sdram中*/   
        ldr pc, =sdram
    sdram:
        bl uart0_init
    
        bl print1
        /* 故意加入一条未定义指令 */
    und_code:
        .word 0xdeadc0de  /* 未定义指令 */
        bl print2
    
        //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
        ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    
    看一下整个程序的执行过程

    五、swi异常模示程序示例

    swi的处理流程
    swi软件中断:software interrupt
    在ARMCPU有7中模式,除了用户模式以外,其他6种都是特权模式,这些特权模式可以直接修改CPSR进入其他模式
    usr用户模式不能修改CPSR进入其他模式
    Linux应用程序一般运行于用户模式
    APP运行于usermode,(受限模式,不可访问硬件)
    APP想访问硬件,必须切换模式,怎么切换?
    发生异常3种模式

    • 中断是一种异常
    • und也是
    • swi + 某个值(使用软中断切换模式)

    现在start.S把要做的事情列出来

    /*1
    /* 复位之后, cpu处于svc模式
     * 现在, 切换到usr模式
     * 设置栈
     * 跳转执行
     */
    
    /*2 故意引入一条swi指令*/
    
    /*3 需在_start这里放一条swi指令*/
    
    查看异常向量表swi异常的向量地址是0x8 我们先切换到usr模式下
    usr模式下的 M0 ~ M4是10000
    /**5 先进入usr模式*/
    mrs r0, cpsr      /* 读出cpsr 读到r0 */
    /使用bic命令 bitclean 把低4位清零/
    bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
    msr cpsr, r0
    
    /*6 设置栈*/
    /* 设置 sp_usr */
    ldr sp, =0x33f00000
    

    编译运行
    发现可以处理und指令
    添加 swi异常,仿照未定义指令做

    .text
    .global _start
    
    _start:
    b reset          /* vector 0 : reset */
    ldr pc, und_addr /* vector 4 : und */
    /*1 添加swi指令*/
    ldr pc, swi_addr /* vector 8 : swi */
    
    und_addr:
        .word do_und
    
    /*2 仿照und未定义添加指令*/
    swi_addr:
        .word do_swi
    
    do_und:
    /* 执行到这里之前:
     * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_und保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
     * 4. 跳到0x4的地方执行程序 
     */
    
    /* sp_und未设置, 先设置它 */
    ldr sp, =0x34000000
    
    /* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    
    /* 保存现场 */
    /* 处理und异常 */
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException
    
    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    
    und_string:
         .string "undefined instruction exception"
    
    
    /*3 复制do_und修改为swi */
    do_swi:
    /* 执行到这里之前:
     * 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 3.2. SPSR_svc保存有被中断模式的CPSR
     * 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
     * 3.4. 跳到0x08的地方执行程序 
     */
    
    /* 3.5 sp_svc未设置, 先设置它 */
    ldr sp, =0x33e00000
    
    /* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* 3.7 lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    
    /* 3.8 保存现场 */
    /* 3.9 处理swi异常 只是打印 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException
    
    /*3.10  恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    
    /*swi处理函数*/ 
    swi_string:
        .string "swi exception"
    

    上传代码实验
    烧写 发现没有执行
    先把下面这些代码注释掉

    /*3 复制do_und 修改为swi */
    /* 执行到这里之前:
     * 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 3.2. SPSR_svc保存有被中断模式的CPSR
     * 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
     * 3.4. 跳到0x08的地方执行程序 
     */
    
    /* 3.5 sp_svc未设置, 先设置它 */
    ldr sp, =0x33e00000
    
    /* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* 3.7 lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    
    /* 3.8 保存现场 */
    /* 3.9 处理swi异常 只是打印 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException
    
    /*3.10  恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    
    /* swi处理函数 */   
    swi_string:
         .string "swi exception"
    

    上传编译
    烧写执行 可以正常运行
    循环打印
    swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 /
    执行后继续执行
    ldr pc, swi_addr /
    vector 8 : swi */
    表明问题出现在 do_swi:函数中
    先把下面这句话注释掉
    .string "swi exception"
    编译烧写运行
    程序可以正常运行
    显然程序问题出现在.string "swi exception" 这句话,为什么加上这句话程序就无法执行,查看一下反汇编:

    30000064 <swi_string>: //这里地址是64
    30000064:   20697773    rsbcs   r7, r9, r3, ror r7
    30000068:   65637865    strvsb  r7, [r3, #-2149]!
    3000007c:   6f697470    swivs   0x00697470
    30000070:   0000006e    andeq   r0, r0, lr, rrx
    
    30000082 <reset>: //我们使用的是ARM指令集,应该是4字节对齐,发现这里并不是,问题就在这里
    30000082:   e3a00453    mov r0, #1392508928 ; 0x53000000
    30000086:   e3a01000    mov r1, #0  ; 0x0
    3000008a:   e5801000    str r1, [r0]
    3000008e:   e3a00313    mov r0, #1275068416 ; 0x4c000000
    30000092:   e3e01000    mvn r1, #0  ; 0x0
    

    因为这个字符串长度有问题
    前面und_string 那里的字符串长度刚刚好
    我们不能把问题放在运气上面
    添加:

    /*******
      以4字节对齐
    */
    .align 4
    
    
    do_swi:
    /* 执行到这里之前:
     * 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 3.2. SPSR_svc保存有被中断模式的CPSR
     * 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
     * 3.4. 跳到0x08的地方执行程序 
     */
    
    /* 3.5 sp_svc未设置, 先设置它 */
    ldr sp, =0x33e00000
    
    /* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* 3.7 lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    
    /* 3.8 保存现场 */
    /* 3.9 处理swi异常 只是打印 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException
    
    /*3.10  恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    /*****
     swi处理函数
    */  
    swi_string:
        .string "swi exception"
    
    
    .align 4
    /**************
     表明下面的标号要放在4字节对齐的地方
    */
    

    上传代码编译运行查看反汇编:

    30000068 <swi_string>:
    30000068:   20697773    rsbcs   r7, r9, r3, ror r7
    3000006c:   65637865    strvsb  r7, [r3, #-2149]!
    30000070:   6f697470    swivs   0x00697470
    30000074:   0000006e    andeq   r0, r0, lr, rrx
    ...
    
    30000080 <reset>: //现在reset放在4自己对齐的地方
    30000080:   e3a00453    mov r0, #1392508928 ; 0x53000000
    30000084:   e3a01000    mov r1, #0  ; 0x0
    30000088:   e5801000    str r1, [r0]
    3000008c:   e3a00313    mov r0, #1275068416 ; 0x4c000000
    30000090:   e3e01000    mvn r1, #0  ; 0x0
    30000094:   e5801000    str r1, [r0]
    30000098:   e59f0084    ldr r0, [pc, #132]  ; 30000124 <.text+0x124>
    3000009c:   e3a01005    mov r1, #5  ; 0x5
    300000a0:   e5801000    str r1, [r0]
    300000a4:   ee110f10    mrc 15, 0, r0, cr1, cr0, {0}
    300000a8:   e3700103    orr r0, r0, #-1073741824    ; 0xc0000000
    300000ac:   ee010f10    mcr 15, 0, r0, cr1, cr0, {0}
    300000b0:   e59f0070    ldr r0, [pc, #112]  ; 30000128 <.text+0x128>
    300000b4:   e59f1070    ldr r1, [pc, #112]  ; 3000012c <.text+0x12c>
    300000b8:   e5801000    str r1, [r0]
    300000bc:   e3a01000    mov r1, #0  ; 0x0
    300000c0:   e5910000    ldr r0, [r1]
    

    下载烧写
    程序执行完全没有问题
    程序备份修改代码
    swi可以根据应用程序传入的val来判断为什么调用swi指令,我们的异常处理函数能不能把这个val值读出来

    do_swi:
    /* 执行到这里之前:
     * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_svc保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
     * 4. 跳到0x08的地方执行程序
     */
    
    /* sp_svc未设置, 先设置它 */
    ldr sp, =0x33e00000
    
    /* 保存现场 */
    /* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    

    我们要把lr拿出来保存
    因为bl printException会破坏lr
    mov rX, lr
    把lr保存在那个寄存器?
    这个函数 bl printException 可能会修改某些寄存器,但是又会恢复这些寄存器,我得知道他会保护那些寄存器
    ATPCS规则
    对于 r4 ~ r11在C函数里他都会保存这几个寄存器,如果用到的话就把他保存起来,执行完C函数再把它释放掉
    我们把lr 保存在r4寄存器里,r4寄存器不会被C语言破坏
    mov r4, lr

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException
    

    跳转到printSWIVal
    如何才能知道swi的值呢?
    我们得读出swi 0x123指令,这条指令保存在内存中,我们得找到他的内存地址
    执行完0x123指令以后,会发生一次异常,那个异常模式里的lr寄存器会保存下一条指令的地址
    我们把lr寄存器的地址减去4就是swi 0x123这条指令的地址
    我再把r4的寄存器赋给r0让后打印
    我们得写出打印函数
    mov r0, r4
    指令地址减4才可以
    swi 0x123
    下一条指令bl main 减4就是指令本身
    sub r0, r4, #4
    bl printSWIVal

    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    

    swi_string:
    .string "swi exception"
    在uart.c添加printSWIVal打印函数

    void printSWIVal(unsigned int *pSWI)
    {
    
    puts("SWI val = ");
    printHEx(*pSWI & ~0xff000000); //高8位忽略掉  
    puts("\n\r");
    
    }
    

    再来看看这个程序是怎么跳转的

     /*1
      发生swi异常,他是在sdram中,CPU就会跳到0x8的地方
      swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */
    
     */
    
    /* 2
    
    _start:
    b reset          /* vector 0 : reset */
    ldr pc, und_addr /* vector 4 : und */
    执行这条读内存指令
    ldr pc, swi_addr /* vector 8 : swi */
    
    读到swi_addr地址跳转到sdram执行代码 do_swi那段代码
    swi_addr:
        .word do_swi
    
     */
    
    /* 3
     这段代码被设置栈保存现场 调用处理函数恢复现场,让后就会跳到sdram执行 swi 0x123的下一条指令
    do_swi:
    /* 执行到这里之前:
     * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_svc保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
     * 4. 跳到0x08的地方执行程序 
     */
    
    /* sp_svc未设置, 先设置它 */
    ldr sp, =0x33e00000
    
    /* 保存现场 */
    /* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
    /* lr是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}  
    
    mov r4, lr
    
    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException
    
    sub r0, r4, #4
    bl printSWIVal
    
    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
    
    swi_string:
       .string "swi exception"
    */
    

    六、按键中断程序示例_概述与初始

    在前面的视频里我们举了一个例子,母亲看书被声音打断,远处的声音来源有多种多样,声音传入耳朵,再由耳朵传入大脑,整个过程涉及声音来源耳朵大脑,为了确保这个母亲看书的过程能够被声音打断,我们必须保证声音来源可以发出声音,耳朵没有聋,脑袋没有傻。

    类比嵌入式系统我们可以设置中断源,让他发出中断信号,还需要设置中断控制器,让他把这些信号发送给CPU,还需要设置CPU让他能够处理中断。
    中断的处理流程:

    • 1 中断初始化:
      :: 1.1 我们需要设置中断源,让它能够发出中断喜好
      :: 1.2 设置中断控制器,让它能发出中断给CPU
      :: 1.3 设置CPU,CPSR有I位,是总开关
      :: 我们需要这样设置,中断源才能发送给CPU

    • 2处理完要清中断

    • 3处理时,要分辨中断源,对于不同的中断源要执行不同的处理函数

    下面开始写代码
    打开start.S 先做初始化工作,先做第 3 设置CPU,CPSR有I位,是总开关

    我们需要把CPSR寄存器 bit7给清零,这是中断的总开关,如果bit7设置为1,CPU无法响应任何中断
    /* 清除BSS段 */
    bl clean_bss
    
    /* 复位之后, cpu处于svc模式
     * 现在, 切换到usr模式
     */
    mrs r0, cpsr         /* 读出cpsr */
    bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
    
    /*1
    把bit7这一位清零
    */
    bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
    msr cpsr, r0
    
    /* 设置 sp_usr */
    ldr sp, =0x33f00000
    
    ldr pc, =sdram
    sdram:
    bl uart0_init
    
    bl print1
    /* 故意加入一条未定义指令 */
    und_code:
    .word 0xdeadc0de  /* 未定义指令 */
    bl print2
    
    swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */
    
    /*2 调用两个中断 */
    bl interrupt_init /*初始化中断控制器*/
    bl eint_init /*初始化按键,设为中断源*/
    
    /*需要初始化上面这两个函数*/
    //bl main  /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
    ldr pc, =main  /* 绝对跳转, 跳到SDRAM */
    
    halt:
        b halt
    
    添加一个 interrupt.c文件,这个程序稍微有些复杂,我们先画个流程图 图片来源于白问网
    我们想达到按下按键灯亮松开按键灯灭,把下面四个按键全部配置为外部中断按键
    打开芯片手册找到第九章 IO ports,直接搜索“'EINT0号中断和EINT2号中断”',找配置寄存器 GPFCON
    为了简单操作
    /* 初始化按键, 设为中断源 */
    void key_eint_init(void)
    {
        /*1 配置GPIO为中断引脚 */
        //先把eint0和eint2这两个引脚清零
        GPFCON &= ~((3<<0) | (3<<4));
        GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置为中断引脚 */ 
    
    通过电路图得知 S4 S5按键为EINT11号中断引脚和EINT19号中断引脚

    GPGCON &= ~((3<<6) | (3<<11));
    GPGCON |= ((2<<6) | (2<<11)); /* S4,S5被配置为中断引脚 */

    2 设置中断触发方式: (按下松开,从低电源变为高电源,或者从)双边沿触发
      设置<code>EINT0 EINT2</code>为双边沿触发
    
     EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */
    
    设置EINT11为双边沿触发
    EXTINT1 |= (7<<12); /* S4 /
    设置EINT19为双边沿触发 EXTINT2 |= (7<<12); /
    S5 */
    外部中断屏蔽寄存器EINTMASK,设置为1的话就禁止向外部发出中断信号,只有EINTMASK相应的位设置为0外部中断才能给中断控制器发信号.
    我们需要设置这个寄存器
    把EINT11设置为0 把EINT19设置为0对于EINT0 和EINT2显示为保留,默认时使能的,可以直接发送给中断控制器,无需设置
    设置EINTMASK使能eint11,19
    EINTMASK &= ~((1<<11) | (1<<19));
    读EINTPEND分辨率哪个EINT产生(eint4~23)(并且要清除它)
    清除中断时, 写EINTPEND的相应位

    我们接下来需要阅读'第14章 Interrupt Controller章节设置中断控制器我们只需要按照下面这张流程图设置就可以了

    相关文章

      网友评论

        本文标题:ARM异常与中断体系详解

        本文链接:https://www.haomeiwen.com/subject/dmavxqtx.html