BMS引脚接地,系统上电后,NorFlash重映射到0x0,此时除了可以通过它的固有存储器映射0x1000,0000访问它,还可以通过它的重映射地址0x0访问它。如果之后将片内SRAM重映射到地址0x0,此时再访问地址0x0,那么实际访问的是片内SRAM。
图1 重映射 总之,从地址空间的角度看,这个特殊的重映射区域对应的存储器映射具有多重性和临时性,但是在任意时刻它的存储器映射具有唯一性。除了这个特殊的重映射区域,其它地址空间和存储器之间是一一对应的。反之从存储器的角度看,无论何时总是可以通过它的固有地址空间访问它,但是只有当满足某些条件时才能从重映射区域访问它。
2 存储器布局RealView工具链利用分散加载机制创建复杂的映像文件。分散加载文件能够精确的描述映像中每个区的加载和执行视图。加载视图描述映像开始执行前的存储器布局,执行视图描述映像执行时存储器布局。分散加载文件可以描述的最小单元,对于汇编语言来说为一个.s文件中的AREA,例如board_cstartup_keil.s中的cstartup和VECTOR;对于C语言来说为一个.c文件,例如board_lowlevel.c和board_memories.c。
本文使用的分散加载文件norflash.sct如下所示。从中可以看出:这里只有一个名字为Load_region的加载区,即映像文件存储在地址为0x10000000的NorFlash中;执行区一共有四个,分别为Fixed_region(包含cstartup区域和大部分具有RO属性的节),Relocate_region(包含异常向量、板级初始化代码编译后生成的对象文件以及属性为RW和ZI的节),ARM_LIB_HEAP(堆区)和ARM_LIB_STACK(自片内SRAM顶部开始向下增长的堆栈区)。
Load_region 0x10000000 0x400000 {
Fixed_region 0x10000000 {
*(cstartup +First)
.ANY (+RO)
}
Relocate_region 0x300000 0x28000 {
*.o (VECTOR, +First)
board_lowlevel.o (+RO)
board_memories.o (+RO)
.ANY (+RW +ZI)
}
ARM_LIB_HEAP 0x326000 EMPTY 0x1000 {
}
ARM_LIB_STACK 0x328000 EMPTY -0x1000 {
}
}
3 系统初始化3.1 第一条指令ARM核总是从地址0x0取出第一条指令开始引导过程。在本文中映像文件烧写到NorFlash中,为了从NorFlash引导,BMS引脚接地,这样复位时,NorFlash重映射到地址0x0。cstartup区域部分代码如下所示:
AREA cstartup, CODE
ENTRY
resetHandler
; The first instruction Set pc to actual code location (i.e. not in remap zone)
LDR pc, =label
label
LDR r0, = |Image$$ARM_LIB_STACK$$ZI$$Limit|
MOV sp, r0
; other codes
由于cstartup区域定位于NorFlash的起始位置,因此执行的第一条指令是LDR pc, =label。这是一条伪指令,汇编器将label的值放在一个文字池(literal pool)中,并生成一个相对PC寻址的LDR指令从文字池中装载该值。这可以从IDA-The Interactive Disassembler反汇编生成的.axf文件的输出得到确认:
0x10000000 ldr pc, [pc, #0x34] ;34 F0 9F E5
…
0x1000003C DCD 0x10000004
可以看到label的值等于0x10000004,它存储在地址0x1000003C处。首条指令的编码为34 F0 9F E5,即ldr pc, [pc, #0x34],执行此指令后,PC=PC+0x8+0x34=PC+0x3C,取出当前指令地址偏移0x3C处的值(即0x10000004),赋给PC寄存器,也就是跳转到绝对地址0x10000004处继续执行。其后的两条汇编语句设置临时堆栈,为调用C语言编写的函数LowLevelInit做准备。
3.2 关键外围设备系统引导时必须首先初始化一些关键的外围设备,例如晶振和锁相环,高级中断控制器,看门狗等,它们都是在LowLevelInit函数中完成的。由于不同的应用程序的外设初始化代码相差较大,因此此处不做详细说明。
3.3 重映射异常向量和异常处理器所有的ARM系统在地址0x0都有一个向量表。向量表虽然不是初始化序列的一部分,但是必须存在,它是转到各个异常处理器的跳转指令表。当发生异常时(例如,数据中止,未定义指令,IRQ等),ARM核立即取出位于地址0x0和0x1C之间的8条指令中的一条,并执行它。如果程序不需要处理这个异常,那么可以放置一个无限循环,例如:
undefVector
b undefVector
如果必须处理这个异常,例如复位异常,那么可以放置一个相对PC的跳转指令,例如:
LDR pc, =resetHandler
很明显,当前的存储器布局不能满足这种要求,因为地址0x0-0xF,FFFF此时映射到NorFlash,而NorFlash的起始位置存储的是cstartup区域,而不是异常向量。为了满足ARM核对异常向量的位置要求,引导过程的下一步就是将片内SRAM重映射到0x0-0xF,FFFF,这是通过调用BOARD_RemapRam函数完成的。注意,此时异常向量并未真正就绪,只有当随后使用分散加载机制将VECTOR区域从加载区拷贝到片内SRAM的起始处后,才能访问异常向量。
最后调用BOARD_ConfigureNorFlash函数配置外部总线接口片选0(EBI CS0),因为默认配置参数并不是最优的,所以需要根据存储器特性进行重新配置。
3.4 设置各种模式的堆栈ARM处理器的堆栈主要有两个特点,其一每种模式都有自己的堆栈指针SP,其二堆栈是递减的。设置方法是依次进入各种模式,赋给SP正确的值。一般Supervisor和User模式的堆栈最大,IRQ和FIQ模式次之,其它模式经常只需要几个字节。在本文中,首先设置IRQ模式堆栈设置到片内SRAM顶部,然后设置Supervisor模式的堆栈,同时使能IRQ和FIQ。
3.5 从加载视图到执行视图链接器生成可执行映像的同时也定义了映像中各个符合的绝对地址,只有将它们移动到对应的地址,应用程序才能正常运行。以可读写的RW节为例,如果在改写它的值之前,没有将其从Flash存储器移动到可读写的RAM存储器,那么必然会发生异常。
ARM库中的__main例程负责将加载视图转变为执行视图,最后调用C语言编写的main函数。__main例程的主要功能有三个:拷贝需要移动的区、零初始化ZI区和初始化堆栈和堆,分别对应于图2中的①②③。
大多数RO属性的节都位于NorFlash中,它的加载视图和执行视图的起始地址相同,因此不需要移动。
Relocate_region区的加载视图和执行视图的起始地址不同,因此需要移动。将VECTOR节定位到片内SRAM主要是为了满足ARM架构对异常向量的位置要求,在执行完初始化序列中的SRAM重映射之后,就可以在地址0x0访问异常向量。board_lowlevel.o和board_memories.o紧随VECTOR之后,之所以将它们定位到片内SRAM主要是为了提高系统性能。
最后放置属性为ZI的节。 ZI节在映像中不占据空间,它们在SRAM中创建和零初始化。
ARM_LIB_STACK执行区用来为各种模式分配堆栈空间,它自SRAM顶部,即地址0x328000,开始向下增长。ARM_LIB_HEAP执行区,即堆空间,它自地址0x326000开始向上增长。
图2 从加载视图到执行视图 3.5 main登场__main例程在完成任务之后最后调用C语言编写的main函数,引导过程结束,用户程序开始执行。
参考文献[1] Andrew N.Sloss, Dominic Symes, Chris Wright. ARM嵌入式系统开发:软件设计与优化[M].沈建华, 译. 北京: 北京航空航天大学出版社, 2005.
[2] Randal E.Bryant, DavidO’Hallaron. 深入理解计算机系统[M]. 龚奕利, 雷迎春, 译. 修订版. 北京:中国电力出版社, 2004.
[3] David Seal. ARM Architecture Reference Manual(2nd Edition)[M]. Addison-Wesley, 2001.
[4] Getting Started with the AT91SAM9261 Microcontroller[EB/OL]. http://www.atmel.com/dyn/resources/prod_documents/doc6298.pdf
[5] John R. Levine. Linkers and Loaders[M]. Morgan Kaufmann, 1999. |