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能运行,将更为完整的硬件初始化代码从外存复制到主存中执行。
在硬件初始化阶段,是不希望有中断的打扰,此外,硬件都还没有设置完毕,中断的处理又怎能正常进行呢?
- 关闭看门狗。看门狗(Watch Dog Timer)是S3C2440A提供的一个计数器。看门狗是一个防止程序跑飞的一种监测机制。它需要在一个设定的时间(脉冲)内向其发送一个“喂狗”的脉冲信号,如果超出了该设定值,看门狗就会发出一个复位信号,让程序复位。
如果不关闭看门狗的话,启动程序在一定的时间内就需要去“喂狗”,这样不仅增加代码量,还浪费了系统的宝贵资源,因为在启动代码中是不会涉及大量数据运算和长时间循环的。
WTCON EQU 0x53000000
LDR R0, =WTCON
MOV R1, 0x00000000
STR R1, [R0]
- 屏蔽中断。在启动代码的开始阶段,中断屏蔽是显而易见的,因为CPU等硬件初始化是一切服务的基础,在没有初始化完成以前,中断是没有意义的。起始,在复位异常发生后,ARM920T已经通过硬件机制将CPSR中的I位与F位置1,屏蔽了中断。在这里,为了代码更具通用性、严整性与逻辑性,采用了通过设置中断控制器相关寄存器来屏蔽中断的方式。
INTMSK EQU 0x4A000008
INTSUBMSK EQU 0x4A00001C
ldr r0,=INTMSK
ldr r1,=0xffffffff
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff
str r1,[r0]
值得注意的是:在启动代码屏蔽中断后,如果在启动完了所加载的应用程序,并需要使用中断处理或实现一些中断服务程序时,一定要重新开启中断,否则用户的中断将不会得到响应。
- 设置时钟与PLL。S3C2440A芯片为用户提供了多个CPU频率与多个相应的AHB总线频率和APB总线频率。在启动代码中,选择所需的频率来配置CPU是必要的。
在S3C2440A芯片中,所有的时钟源均来自一个12MHz的外部晶体振荡器。CPU的高频率是通过锁相环PLL(Phase Locked Loop)电路模块将12MHz频率提升后得到的,AHB与APB的时钟频率是通过设置与CPU时钟的比值得到的。
S3C2440A芯片提供了两个PLL:MPLL和UPLL。
MPLL是用于CPU及其他外围器件的,UPLL是用于USB的,MPLL产生三种频率:FCLK、HCLK、PCLK,其用途分别为:
(1)FCLK为CPU提供时钟信号,S3C2440最大支持400MHz的主频,可以通过设定MPLL与UPLL寄存器来设定CPU的工作频率。
(2)HCLK为AHB总线提供时钟信号,主要用于高速外设,如内存控制器、中断控制器、LCD控制器、DMA等。
(3)PCLK为APB总线提供的时钟信号,主要用于低速外设,如看门狗,UART控制器,IIS、IIC、SDI/MMS、GPIO、RTC和SPI等。
UPLL专门用于驱动USB host/device,并且驱动USB host/device的频率必须为48MHz。
在设置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频率的比值。
- 初始化BANK。S3C2440A芯片配置了8个BANK,每一个BANK大小最大为128MB,每一个BANK的访问宽度可调整。
BANK0到BANK5可以挂载ROM与SRAM,BANK6和BANK7还可以挂载SDRAM,也就是人们常说的内存。
BANK0到BANK6的起始地址是固定的,BANK7的起始地址是可变的。
BANK6的起始地址为0x30000000,开发板的内存挂载在BANK6,所以开发板的内存起始地址为0x30000000,这也是CPU启动后需要将应用程序加载到的地方。在加载并运行前,必须对BANK进行初始化,确定8个BANK的内存分布,完成内存刷屏频率等设置。
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)。
- RO代表的是一个源文件中指令代码或常量,这些在程序中不能改变。
- RW代表一个源文件中已经被初始化的变量(全局变量),这些变量是可以修改与读取的,并且已经被初始化为一个确定的值。
- ZI代表未被初始化且初始化为0的变量。(ZI段在镜像文件中并不占用空间,因为这些变量的初始值为零,无需在镜像文件中记录这些变量的初始值,只需要在后面应用程序运行环境的创建中将这一段的值全部清零即可)。
输出段是由一个和多个属性相同的输入段组成的。
一个简单裸板ARM镜像文件在外部存储器中的大致结构如图。
在该镜像文件结构中,ZI段没有占应有的数据空间,只有一些必要的信息。
RW输出段通常是紧跟在RO输出段之后的,加载域的RO起始位置与运行时的RO起始位置相同。
但是当ARM镜像文件在执行时,RW段可以不与RO段连续,ZI段与RW段连续的。这样设计的原因在于让应用程序能够充分使用系统有限的内存。
在用ADS编译裸板ARM启动程序的时候,会设置镜像文件的参数:
- |Image R O RO ROBase|:RO输出段运行时的起始地址
- |Image R O RO ROLimit|:RO输出段运行时的结束地址+1
- |Image R W RW RWBase|:RW输出段运行时的起始地址
- |Image R W RW RWLimit|:RW输出段运行时的结束地址+1
- |Image Z I ZI ZIBase|:ZI输出段运行时的起始地址
- |Image Z I ZI ZILimit|:ZI输出段运行时的结束地址+1
这些参数是由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也无法作其它工作,这也是在没有中断机制的情况下实现外部事件响应的方法。