weiqi7777

基于S3C6410的ARM11学习(三) 核心初始化之设置中断向量表

0
阅读(3413)

    前面将流程搞清楚后,下面就开始进行按照顺序来编写程序了。

           第一步就是进行中断向量表的设置。在ARM11中,中断向量表叫做异常向量表。

           ARM11共有10种异常,这个在ARM11datasheet中有。

           clip_image002

这里说明一下:

异常

说明

详细说明

Reset

复位异常

当系统刚上电,或者按下复位键时候,触发这个异常,这个时候,程序跳转到这个地址处执行程序

undefined_instruction

未定义指令异常

当程序执行发现有一条指令是未定义的指令,会触发这个异常,这个时候,程序跳转到这个地址处执行程序

software_interrupt

软中断异常

当软件设置软中断时,会触发这个异常,这个时候,程序跳转到这个地址处执行程序

prefetch_abort

取指异常

CPU取指令发生问题时,会触发这个异常,这个时候,程序跳转到这个地址处执行程序

data_abort

数据异常

这个就包括内部取数据和外部取数据,当取数据发生问题时,会触发这个异常,这个时候,程序跳转到这个地址处执行程序

irq

中断异常

当有中断触发后,会触发这个异常,这个时候,程序会跳转到这个地址出执行程序

fiq

快中断异常

当快中断触发后,会触发这个异常,这个时候,程序会跳转到这个地址出执行程序


 最后一个目前不知道是什么意思。现在也用不上,先就不管了。

 异常,也都写得比较清楚,都知道这些异常大致是干什么的。这里,要注意,异常发生的时候,是跳转到异常地址去执行程序的,但是每个异常地址的大小是4个字节,4个字节大小肯定是放不下程序的。所以,肯定会有第二级跳转。所以这个异常地址的指令,就是一个跳转指令,跳转到对应的程序去执行。

 这些异常,现在不用清楚这些异常怎么用,用的时候再来学习就可以了。只要知道有这么些异常就可以了。

 这些异常的地址是固定的,这个和STM32是不一样的。所以,我们设置中断向量表的时候,要将这些异常写在固定的地址上。这样,程序才能正常访问这些异常。

异常

地址

Reset

0x0000_0000

undefined_instruction

0x0000_0004

software_interrupt

0x0000_0008

prefetch_abort

0x0000_000c

data_abort

0x0000_0010

irq

0x0000_0018

fiq

0x0000_001c

从表中,会发现,数据异常和中断异常之间怎么相隔了8个字节大小,其他都是相隔的4个字节大小。这里是保留了一个异常,但是目前没有定义这个异常是什么,所以把地址给空出来了。所以,我们写程序的时候,要注意把这个地址给空出来。


下面就是我们的程序:


.text
.global _start
_start:
    b reset
    ldr  pc, _undefined_instructions
    ldr  pc, _software_interrupt
    ldr  pc, _prefetch_abort
    ldr  pc, _data_abort
    ldr  pc, _no_use
    ldr  pc, _irq
    ldr  pc, _fiq
_undefined_instructions:
    .word undefined_instructions
_software_interrupt:
    .word software_interrupt
_prefetch_abort:
    .word prefetch_abort
_data_abort:
    .word data_abort
_no_use:
    .word no_use
_irq:
    .word irq
_fiq:
    .word fiq
   
undefined_instructions:
    nop
software_interrupt:
    nop
prefetch_abort:
    nop 
data_abort:
    nop
no_use:
    nop 
irq:
    nop 
fiq:
    nop
reset:


简单说明下

.text :  表示是代码段,说明下面的程序是代码

.global _start : 定义全局标号_start

_start的代码,就是设置中断向量表了。可以看出,其实都是跳转指令。不同的异常,跳转到不同的地方去执行程序,这样就实现了异常的处理。

这里

                         ldr  pc, _undefined_instructions     1

_undefined_instructions:           

    .word undefined_instructions

undefined_instructions:

    nop


  1的指令,就是将标号_undefined_instructions地址处的值赋值给pc这样pc的值就是undefined_instructions的值,所以就跳转到undefined_instructions程序地方去执行。这里,undefined_instructions程序就只有一个nop指令。因为目前没有用到这些异常,所以这里,除了Reset异常我们是编写代码外,其他的异常我们都是写的nop

  其他异常的分析,和以上的分析是一样的。只是要注意,我们中间定义了一个_no_use异常。可是这个异常是ARM异常里面没有的。这里定义这个异常,就是为了占一个字节大小,这样的话,irq的地址才会是0x0000_0018,否则的话就是0x0000_0014,这样就不对了。

   这里有个问题,只有Reset的跳转指令是b指令,其他的指令都是ldr指令。这个是为什么了?

   b指令是相对跳转指令,ldr是绝对跳转指令。在上电或者复位的时候,程序在内部的stepping stone中执行,地址从0x0000_0000开始。但是我们在编译代码的时候,用的链接脚本的链接地址是0x5000_0000,如果使用ldr决定跳转指令的话,就会跳到内存去执行程序了,这个时候,我们还没有把程序拷贝到内存中,所以执行就会出错了,所以这里使用b指令。复位结束后,我们已经把代码拷贝到内存中去了,所以这个时候,就要用ldr绝对跳转指令了。

   以上,就将我们的异常向量表就设置好了,接下来,我们就对Reset函数编写代码就好了。因为这里写的代码,就是上电执行的代码。


对比一下STM32的中断向量表的建立:

    这个应该很多学STM32的人,都很少去分析这个了,我以前也没有分析,现在是学习比较的时候,才去分析了一下这个东西,发现了很多好玩的东西。

    STM32不像ARM一样,分为几个异常,而是将各个异常都分开成独立的中断(在STM32中将异常称为中断了)。在ARM11中,只有一个irq异常,这样的话,不管是什么中断发生,都会跳转到irq的代码去执行。但是STM32就不是了,他将各个中断给独立开,比如外部有外部中断,串口有串口中断。。。当外部中断产生时,就执行外部中断的代码,不会执行串口中断的代码。所以STM32的中断向量表就相对比较大。

          

           下面是中断向量表的一部分截图

clip_image004

   前面几个是系统的一些中断,后面是外设的一些中断。我们发现第一个Reset中断的地址竟然不是0x0000_0000,而是0x0000_0004。这里先记下来,后面分析。

clip_image005

    上面就是外设的一些中断,可以看出,不同的外设对应不同的中断,而不同的中断有不同的地址。

     这里,要说明一下,STM32的中断向量表和ARM11的中断向量表有什么区别,最大的区别就是

STM32的中断向量表的内容保存的是中断的入口地址,即当中断发生时,PC需要跳转的地址

ARM11的中断向量表的内容是异常发生时需执行的指令,即当异常发生时,PC应该执行什么指令。

    所以,从上面两个区别可以得出:

       ARM11的中断向量表的位置是绝对唯一的,即每个异常的地址是固定的,Reset就是0x0000_0000, irq就是0x0000_0018

       STM32的中断向量表的位置不是唯一的。即每个中断的地址是可以随意放置的。因为他存的是中断的入口地址,而不是存的指令。


还有一个区别,STM32Reset的地址是0x0000_0004,而不是0x0000_0000。那是因为,STM32规定中断向量表的第一项内容,存的是栈的地址。这个和ARM11也不一样,ARM11的中断向量表的第一项内容就是Reset的指令。

clip_image007

STM32的启动文件分析,在代码的前面,就定义了这样一个向量表,这个就是中断向量表,里面保存的每个中断的入口地址。第一项内容就是栈的地址。依次是定义各个中断的入口地址。使用DCD    0是定义一个数据,用来占位的。


      中断向量表定义之后,就是定义各个中断函数了。

clip_image009

   第一个定义的是复位中断,也是就系统上电或者复位有效的时候,执行的程序。后面的依次定义各个中断函数。每个函数的后面都带有[WEAK]属性,表示这里定义的函数是弱函数,外部程序是可以改写这个中断函数的。

   到这里,我们就可以知道,因为这里定义了中断函数,所以,当你要使用某个外设的中断的时候,中断函数名字可是不能随便取的,而是要和这里取的名字要一样。不然的话,就跳转不到正确的中断地址去了。


   在复位中断函数中,会跳转到SystemInit去执行,这个函数是对时钟和中断向量表进行设置。我们这里就看中断向量表的设置。

clip_image011

clip_image013

clip_image015

这里将设置中断向量表的代码和代码中宏定义定义的值给截图出来。

   首先是判断是否定义了VECR_TAB_SRAM这个宏定义,

定义的话,那么中断向量表的基地址就为SRAM_BASE | VECT_TAB_OFFSET

否则的话,中断向量表的基地址就为FLASH_BASE | VECT_TAB_OFFSET

要理解这个代码,就得要说说STM32的启动方式。之前知道ARM11的启动方式有多种,从NANDFLASH启动,从SD卡启动。。。。不管怎么启动,ARM11都是从外部的存储器设备启动的。但是我们的STM32可不是这样的噢。

           首先贴上STM32的内部存储器的图。

clip_image017

   这个图,没有画出内部闪存FLASH的区域。可以看到内部是有SRAM的,地址从0x2000_00000x3FFFF_FFFF。而且空间还挺大的,有0.5G空间,但是不是所有的STM32都使用了这0.5G空间,像我用的STM32F103ZET6,只有64KB的SRAM,不过已经很大了。不像ARM11,只有8K大小。所以一般STM32是不需要外加SRAM的。还有一个闪存区,图上没有画,从0x0800_0000开始,至于结束地址由芯片的FLASH大小决定的。我用的是STM32F103ZET6系列,FLASH大小是512K。看出来,这大小也不算特别大,但是一般的程序还是够了,毕竟,我们都是用STM32写裸机程序的。

   所以说,程序是可以从内部FLASH启动,或者是从SRAM启动的。

   这个就是由芯片的两个管脚来决定的。

clip_image019

  我用过是主闪存和内置SRAM模式,这两个比较常见。内置SRAM模式启动一般是调试的时候用的。因为FLASH的擦除次数是有限的,调试的话会一直擦除FLASH。会影响FLASH寿命。

  有了上面的知识后,理解设置中断向量表的程序就不难了。

  定义了VECT_TAB_SRAM这个宏定义,就说明程序是从SRAM启动了,所以就要将中断向量表的基地址给映射到SRAM的起始地址中。SRAM的起始地址是0x2000_0000

  如果没有定义这个宏的话,就说明程序从FLASH启动,所以就要将中断向量表的基地址给映射到FLASH的起始地址中。FLASH的起始地址是0x0800_0000

  因为之前说过,STM32的中断向量表的位置是可以随意放的,可以就可以映射到内存或者是FLASH中。但是ARM11可就不行了,就必须得是0x0000_0000处。


  以上,就分析了核心初始化的第一步,设置中断向量表,接着,就是进行设置处理器的模式了。