b Reset;
b Undef;
b SWI;
b PreAbort;
b DataAbort;
b . ;保留
b IRQ;
b FIQ;
建立异常向量表的过程,其中第一个指令通常都是存放在主存的零地址的。
异常向量表存放的全是汇编跳转指令,这些指令从主存的零地址(0x0)开始连续存储在内存中(每条指令4B)。
当发生对应的异常时,PC将通过硬件机制跳转到相应异常向量对应的地址开始执行,因为是硬件机制实现的,所以当发生异常时,所有跳转的地址都是在CPU芯片生产时就确定且无法更改的,并且这些跳转指令都是单条指令连续在一起的,所以无法在原地实现中断服务程序(中断服务程序需一系列指令来完成),这也就是异常向量表中的代码全是跳转指令的原因。
当产生异常时,通过硬件跳转到一个确定地址,再通过跳转指令跳转到一个异常处理程序的起始地址。
硬件的初始化是千变万化的,因为不同嵌入式处理器集成的硬件设备不一样。
在Mini2440开发板上,如果设置从NAND Flash启动,则最开始的启动代码不能超过4KB的大小,所以在这个阶段的硬件初始化最好只对CPU和主存进行初始化。使CPU能运行,将更为完整的硬件初始化代码从外存复制到主存中执行。
在硬件初始化阶段,是不希望有中断的打扰,此外,硬件都还没有设置完毕,中断的处理又怎能正常进行呢?
WTCON EQU 0x53000000
LDR R0, =WTCON
MOV R1, 0x00000000
STR R1, [R0]
INTMSK EQU 0x4A000008
INTSUBMSK EQU 0x4A00001C
ldr r0,=INTMSK
ldr r1,=0xffffffff
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff
str r1,[r0]
值得注意的是:在启动代码屏蔽中断后,如果在启动完了所加载的应用程序,并需要使用中断处理或实现一些中断服务程序时,一定要重新开启中断,否则用户的中断将不会得到响应。
在设置MPLL和UPLL时,必须先设定UPLL,然后才能设定MPLL,而且中间需要若干空指令(NOP)的间隔。
设定设置时钟与PLL时,需要用到的寄存器主要有锁定时间计数寄存器LOCKTIME、MPLL配置寄存器MPLLCON、UPLL配置寄存器UPLLCON、时钟分频器控制寄存器CLKDIVN。
a. LOCKTIME寄存器。LOCKTIME寄存器使用与设置的原因是与PLL的硬件特性相关的。因为当PLL启动后需要一定的时间才能够稳定的工作。所以在向CPU与USB外部总线提供时钟频率之前,需要锁定一段时间以等待PLL能正常工作。MPLL与UPLL所需的等待时间应该大于300us。
b. MPLLCON与UPLLCON寄存器。这两个寄存器是用来设置CPU频率与USB频率对外部晶体振荡器频率的倍数参数的。
c. CLKDIV寄存器。该寄存器设置CPU、AHB和APB频率的比值。
SMRDATA
DCD
对BANK进行设置,参数存放在一段连续的内存空间中,其起始地位为SMRDATA,大小为52字节(一共包括了13条记录,每条4个字节,分别是“DCD…”)。
设置BANK时,逐一从SMRDATA地址开始,读出每一条记录,将其中的参数写入BANK的寄存器BWSCON,从而完成BANK的设置。
因为ARM有七种不同的运行模式,而各模式都共同享有公用的通用寄存器,所以在模式切换后,有必要将前一种模式的通用寄存器上的数据保存以便模式切换回后能正常运行。
这时,不同模式下的堆栈就发挥了保护现场的作用了。因为ARM在不同模式下都有专用的堆栈指针。
所以每个模式的堆栈初始化只需将堆栈指针赋值为预先确定好的一个固定的、与各模式相对应的地址(该地址可由用户指定)。
在开发板复位和上电时,芯片处在的工作模式都是管理模式(Supervisor Mode),又因为在进行各模式的系统堆栈初始化时,需要分别进入各个工作模式分别初始化,将管理模式的系统堆栈初始化放在最后,这样做是为了保证前后模式运行的一致。
在ARM中用户模式与系统模式使用的是相同的寄存器,系统模式与用户模式共用堆栈。
这一小节的内容其实已不属于严格意义的启动代码的范围了,可以归属应用程序级别的内容。
但是,将应用程序从Flash加载到RAM的实现代码是一定在启动代码中实现的。(若引用程序是纯二进制可执行代码,加载过程仅仅是简单的复制;若是ELF格式的可执行代码,则加载就要复杂很多,但这种加载已不属于班级初始化阶段的工作了)。
计算机系统的运行其实是CPU到相应的内存地址去取回指令,然后译码并执行指令,再依次从下一个地址取指、执行,而程序就是指令与数据的集合。
程序的运行就是CPU从程序中取出指令,执行指令,当需要时,再从程序中取得需要的数据。
在PC环境下,用户使用的PC都是安装了操作系统的,应用程序的执行无非就是打开一个应用程序的快捷方式,然后程序就自动地运行,并且正常无误。
但是,对于没有任何操作系统或裸板上的应用程序,应用程序的执行就会涉及许多步骤,这就是裸板启动代码在启动一个应用程序时所要完成的任务。
为了完成这个任务,编写启动代码的人员需要了解:在给定的CPU体系下,裸板应用程序的镜像文件的组成方式,这里镜像文件就是可执行文件。
由ADS编译链接器生成的ARM镜像文件,当都由一个或多个域组成,域有两种:加载域与运行域。
加载域指程序被加载到内存的地方。
运行域则是程序在内存中具体运行时所占有的地方。
一个域由一个或多个输出段组成,而一个输出段由一个或多个输入段组成。
输入段的属性有三种:RO、RW和ZI(可读写但是未被初始化且需要初始化为0)。
输出段是由一个和多个属性相同的输入段组成的。
一个简单裸板ARM镜像文件在外部存储器中的大致结构如图。
在该镜像文件结构中,ZI段没有占应有的数据空间,只有一些必要的信息。
RW输出段通常是紧跟在RO输出段之后的,加载域的RO起始位置与运行时的RO起始位置相同。
但是当ARM镜像文件在执行时,RW段可以不与RO段连续,ZI段与RW段连续的。这样设计的原因在于让应用程序能够充分使用系统有限的内存。
在用ADS编译裸板ARM启动程序的时候,会设置镜像文件的参数:
这些参数是由ADS编译器声明的,可在代码中通过IMPORT引入使用。
需要做的就是将加载域的ARM镜像文件中对应的输出段搬运到参数所设定的值,并将ZI段清零。
进行完应用程序运行域的生成后,程序就可以开始运行了。
有个问题:为什么一定要进行运行域的生成?难道将程序镜像直接复制到RAM后开始运行不行吗?参数设置来做什么呢?
首先是|Image
Z
I
ZI
ZIBASE|等参数设置的理由。这些参数的设置是将应用程序不同属性的输出段放在RAM的相应位置,这样镜像不必要连续存放,能够更充分地利用有限的存储资源。
这一点突出体现在裸板启动程序上,这时尚未启动任何硬件与软件支持,如MMU(Memory Management Unit),RAM是没有在虚拟内存模式下工作的,也没有分页或分段等高级内存管理机制的支持,所以在这样的条件下调用应用程序的加载就要涉及刚才提及的问题。另一个原因是:裸板环境下,用户的镜像文件是由裸板汇编编译链接的,连接后生成的代码与数据地址都需要绝对物理地址,而这些确切的地址就需要依靠参数来生成。所以,为了使应用程序代码与数据访问能正常进行,运行域的生成是必不可少的。
在由虚拟内存、分页管理或操作系统支持的环境下,应用程序编写是另一回事了,因为涉及的具体运行域的生成规则会因不同的机制而不同,特别是在有操作系统的环境下,应用程序运行建立在操作系统之上,因此,所有的加载与运行过程都由操作系统完成。
不同操作系统要求的应用程序的格式不一样,这也是为什么同是二进制的可执行代码,而Windows的应用程序无法在Linux下运行的原因。
本节内容还包括:将应用程序从Flash复制到RAM的代码,这个代码可自己用汇编语言编写,在这里就不做描述。
跳转到主程序可通过一条bl指令完成
bl my_MainLoop
在轮询系统的应用程序中,为了响应某一外部事件,CPU通过循环访问某一外设,以便得知某一事件的发生并作相应处理。
虽然这样的方式也能实现对外部事件的响应,但是,与中断不同是:CPU需要循环地访问外设以便得知事件的发生,这样,即便在没有事件发生时,CPU也无法作其它工作,这也是在没有中断机制的情况下实现外部事件响应的方法。