ARM9 (2440A) 从启动代码到应用程序(Main) 1

来源:互联网 发布:java cas是什么 编辑:程序博客网 时间:2024/06/05 17:52

ARM9(2440A) 从启动代码到应用程序

说一下从启动代码到Main函数的过程,以及到了Main还需要设置些什么,才算是一个完整的应用程序。

启动代码

我们知道,uboot的第一阶段的功能是:(1)定义入口;(2)设置异常向量(exception vector);(3)设置CPU的速度、时钟频率及中断控制寄存器;(4)初始化内存控制器 ;(5)将rom中的程序复制到ram中;(6)初始化堆栈;(7)转到ram中执行;

其实我们要实现的启动代码功能也就是实现这些功能,最后跳转到我们自己的应用程序入口。只是这里的启动代码,我们不用uboot来实现,可以说是根据自己的需求来实现,毕竟uboot代码量不小。

keil下创建工程时自动添加的启动文件实现的功能不多,网上大多人采用的是ADS下面的启动文件2440init.s,这里有关于这个文件的详细注释:2​4​4​0​i​n​i​t​详​细​注​释​.​s(http://wenku.baidu.com/view/de704e4f767f5acfa1c7cd6d.html?re=view)

 

这个文件实现的功能大致有:

建立异常向量表;

关闭看门狗,设置时钟频率;

初始化各个模式下的堆栈;

拷贝RO代码拷贝到特定地址处;

初始化RW、 ZI数据;

跳转到Main函数;

 

我们知道,当系统重启时,CPU是从0地址处运行,0x00到0x1c地址处存放的内容为:

地址

指令

异常名称

进入时处理器模式

0x0000,0000

b ResetHandler

复位异常

管理模式

0x0000,0004

b HandlerUndef

未定义指令异常

未定义模式

0x0000,0008

b HandlerSWI

软中断异常

管理模式

0x0000,000c

b HandlerPabort

指令预取异常

中止模式

0x0000,0010

b HandlerDabort

数据中止异常

中止模式

0x0000,0014

b .

保留

----

0x0000,0018

b HandlerIRQ

IRQ中断异常

中断模式

0x0000,001c

b HandlerFIQ

FIQ中断异常

快速中断模式

可以看到,0x00地址处存放的是ResetHandler,系统重启时,CPU从0地址处取指令,然后跳转到ResetHandler处执行,此处就开始了关闭看门狗,初始化时钟,初始化SDRAM,初始化堆栈,拷贝代码到SDRAM中。当然还有最重要的就是建立异常中断向量表。下面以IRQ中断为例简要叙述中断过程,结合的文件是前面提到的2440init.s。从网上看了些这方面的资料,觉得这个网址:庖丁解牛 ARM9 中断处理过程http://xinyingdachl.blog.163.com/blog/static/2327600452014229104931186/解释的比较周全,可以深入的看看。下面的理解也来源于这片文章。还有一篇文章不得不推荐,《Mini2440启动代码的编写(第三版)》,这个对启动代码的一些分析很到位。

当发生IRQ中断时,CPU将会从0x18地址处取指令,从上面的表中可知,0x18地址处存的是HandlerIRQ。HandlerIRQ只是一个标号,后有宏定义

HandlerIRQ HANDLER HandleIRQ。HandleIRQ可以认为它是一个函数指针,这个宏定义之后,程序就跳转到了HandleIRQ所指向的函数地址。而HandleIRQ本身所在的地址在哪呢?在_ISR_STARTADDRESS后面的偏移处。

_ISR_STARTADDRESS

实际地址

HandleReset # 4

_ISR_STARTADDRESS+0x00

HandleUndef # 4

_ISR_STARTADDRESS+0x04

HandleSWI # 4

_ISR_STARTADDRESS+0x08

HandlePabort # 4

_ISR_STARTADDRESS+0x0c

HandleDabort # 4

_ISR_STARTADDRESS+0x10

HandleReserved # 4

_ISR_STARTADDRESS+0x14

HandleIRQ # 4

_ISR_STARTADDRESS+0x18

HandleFIQ           #   4

_ISR_STARTADDRESS+0x1c


(注:虽然这里面将HandleReset也写了进去,但是HandleReset并没有执行前面的宏定义,为什么呢?想一下,如果系统复位的时候去_ISR_STARTADDRESS+0x00地址处取复位函数,能正确执行么?肯定不行,因为这个时候,_ISR_STARTADDRESS+0x00地址所在的RAM区有可能还没有被初始化。)

即:HandleIRQ在_ISR_STARTADDRESS+0x18地址处。所以HandleIRQ里面存的是要跳转函数的地址。ARM9有好多中断都挂靠在IRQ这个中断号上,那如果IRQ中断发生了,程序怎么知道是哪个中断源呢?因此又加了一级判断,来判断是哪个中断源发生的。这个时候要将判断IRQ中断源的函数放在HandleIRQ地址上。从2440init.s可知,IsrIRQ中断函数被放到了HandleIRQ处。在IsrIRQ函数里实现判断是哪个中断源发生的中断。下表中的变量仍然是指向函数指针的地址。比如说是HandleEINT0产生了中断,那么在IsrIRQ函数中就会去调用HandleEINT0所指向的函数(这个中断函数需要后续添加进去),从而实现中断调用。

;@0x33FF_FF20

实际地址

;@0x33FF_FF60

实际地址

HandleEINT0

_ISR_STARTADDRESS+0x20

HandleLCD

_ISR_STARTADDRESS+0x40

HandleEINT1

_ISR_STARTADDRESS+0x24

HandleDMA0

_ISR_STARTADDRESS+0x44

HandleEINT2

_ISR_STARTADDRESS+0x28

HandleDMA1

_ISR_STARTADDRESS+0x48

HandleEINT3

_ISR_STARTADDRESS+0x2c

HandleDMA2

_ISR_STARTADDRESS+0x4c

HandleEINT4_7

 ...................................................

HandleDMA3

 ...................................................

HandleEINT8_23

 ...................................................

HandleMMC

 ...................................................

HandleCAM

 ...................................................

HandleSPI0

 ...................................................

HandleBATFLT

 ...................................................

HandleUART1

 ...................................................

HandleTICK

 ...................................................

HandleNFCON

 ...................................................

HandleWDT

 ...................................................

HandleUSBD

 ...................................................

HandleTIMER0

 ...................................................

HandleUSBH

 ...................................................

HandleTIMER1

 ...................................................

HandleIIC

 ...................................................

HandleTIMER2

 ...................................................

HandleUART0

 ...................................................

HandleTIMER3

 ...................................................

HandleSPI1

 ...................................................

HandleTIMER4

 ...................................................

HandleRTC

 ...................................................

HandleUART2 

 ...................................................

HandleADC

 ...................................................


从上面的简要分析可知,当中断发生时,除了第一级中断是由CPU自己跳转过去的,后面的全由程序设计者自己实现。但是像HandleReset,HandleUndef,HandleSWI,HandlePabort,HandleDabort,HandleFIQ这些中断用不到第二级跳转。

到这里还有一个疑问,就是下面这段宏被放到了哪个位置?

              MACRO

$HandlerLabel HANDLER $HandleLabel

 

$HandlerLabel

       sub  sp,sp,#4  ;decrementsp(to store jump address)

       stmfd      sp!,{r0}  ;PUSHthe work register to stack(lr does''t push because it return to originaladdress)

       ldr     r0,=$HandleLabel;load the address ofHandleXXX to r0

       ldr     r0,[r0]      ;load the contents(service routine start address) of HandleXXX

       str     r0,[sp,#4]      ;store the contents(ISR) of HandleXXX tostack

       ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)

       MEND

通过调试可以发现,这些

HandlerFIQ      HANDLER HandleFIQ

HandlerIRQ      HANDLER HandleIRQ

HandlerUndef    HANDLER HandleUndef

HandlerSWI      HANDLER HandleSWI

HandlerDabort   HANDLERHandleDabort

HandlerPabort   HANDLERHandlePabort

被放到了0x00地址和ResetHandler之间的位置上。

根据上面可以绘出一个简单的中断对应关系图,如下:



应用程序

当一切就绪好后,就可以跳转到主(应用)程序的入口处了。在主程序中填充需要的中断函数,以及有一点很重要的就是初始化MMU和CACHE。关于CACHE也可以不用初始化,但初始化CACHE后速度会提升不少。MMU和CACHE是什么?这里也没有找到一个较好的网址参考,关于这方面的知识网络上非常的多,多看几篇就能明白个大概了。总的来说,MMU是位于CPU和主存SDRAM之间的一个器件,用来将CPU发出的地址或者叫虚拟地址转成主存SDRAM这边真正的物理地址。CACHE是当CPU要访问某一地址的数据或指令时优先去Cache里面找,如果找到了,直接取走,这叫命中;如果没有找到,这叫未命中。然后就将主存对应位置里的数据或指令拷贝到Cache里。Cache分为指令Cache和数据Cache,同样还有逻辑Cache和物理Cache。可参考这个网址看一下:http://www.elecfans.com/emb/197050_2.html

为什么要初始MMU?

如果从Nor Flash启动,系统上电时从Nor Flash第一条指令开始运行,之后将Nor Flash代码拷贝到SDRAM内运行。比如说当IRQ中断发生时,CPU跳转到0x0000,0018地址处读取数据。此时0x0000,0018地址对应的是Nor Flash,因此取数据的速度变慢,但也可以运行。由此可知,在Nor Flash启动模式下,不初始化MMU是可以运行的。

如果从Nand Flash启动,Nand Flash前4kB被CPU读取到SteppingStone内,然后开始运行,之后在SDRAM内运行。比如说当IRQ中断发生时,CPU跳转到0x0000,0018地址处读取数据。此时0x0000,0018地址对应(Nand Flash启动,Nand Flash前4kB映射到片内SRAM内)的是内部的SRAM,因此从SRAM中读取数据,然后继续运行。由此可知,在Nand Flash启动模式下,不初始化MMU是可以运行的。

由上可知,当发生中断时,一个是跳转到Nor Flash内取数据,一个是跳转到片内SRAM取数据,都可以正常的中断跳转。如果我们在仿真调试时,那么在中断函数内打断点是无法停住的,因为调试时代码是在片外SDRAM内的。而中断时跳转的地址要么是在Nor Flash里,要么是在片内SRAM里。虽然不开MMU可以正常运行,但却给调试带来了麻烦。因此如果使用MMU,将0x0000,0000地址附近的地址空间映射到程序代码的运行域地址,比如说将0x0000,0000映射到SDRAM地址0x3000,0000处,那当发生中断时跳转的地址就是片内SDRAM的地址,这样就可以在SDRAM内断点调试。

可以参考网址:S3C2440无MMU_Init不能进中断的原因http://hi.baidu.com/1066033011/item/7f3dea48ac06102410ee1e80

问题:初始化MMU后,无法进入中断,程序仍然跑飞。

一开始,一个串口程序,接收是中断方式,在仿真调试时可以正常触发串口接收中断,进入接收中断函数。后来将ucos程序加入,串口接收是中断方式,并没有运行到ucos相关的代码,串口接收中断无法进入。连2440init.s文件中IsrIRQ标签后的程序都无法运行到。前后对比代码没有发现异样的地方。最后在查看map文件时发现,ZI数据的base和limit相同,但我申请了好几个初始化为0的数组,base和limit不应该相等。最后发现在我的scatter文件中有

RW_RAM1 0x31000000 0x01000000  {  ; RW data
   .ANY (+RW +ZI)
  }

ZI +0  {  ; ZI data
   .ANY (+ZI)
  }

而在启动的初始化文件2440init.s中引用ZI的base和limit时采用|Image$$ZI$$Base|、IMPORT  |Image$$ZI$$Limit|,而变量都被放到了RW_RAM1内的ZI区。从而导致没有正确初始化ZI区的数据。后将|Image$$ZI$$Base|、 |Image$$ZI$$Limit|改为|Image$$RW_RAM1$$ZI$$Base| 、|Image$$RW_RAM1$$ZI$$Limit|,串口接收中断函数可以正常进入。

Scatter文件详解可参考http://www.cnblogs.com/Ilmen/p/3453248.html

应用程序运行时应处于哪种模式?

下面内容大部分摘自:《Mini2440启动代码的编写(第三版)》Page15。

ARM9中CPU运行程序所在的模式有:用户模式,快速中断模式,外部中断模式,管理模式,中止模式,未定义模式和系统模式。除用户模式之外的其它模式被称为特权模式,特权模式中除系统模式外的其它模式被称为异常模式。管理、中止、未定义、外部中断和快速中断这5种异常模式由相应的异常进入,用户程序不适合在这些模式下运行。这样留给我们选择的只有用户模式和系统模式,系统模式可以访问所有的系统资源并可以切换到其它模式,而用户模式却无法切换到其它模式,因此程序运行在系统模式下。
0 0
原创粉丝点击