本文使用的处理器为PhilipsLPC2210,由于它的片内只集成了一块位于0x40000000~0x40004000,大小为16K的SRAM,因此扩展了一块位于0x80000000~0x801FFFFF,大小为2M字节的Flash。在实验中,程序将首先从外部Flash启动,然后将异常向量表从Flash存储器复制到片内SRAM中,并进行存储器重映射将异常向量表映射到片内SRAM。虽然本文的内容都是以LPC2210处理器为例来进行说明的,但对其他ARM系列的处理器来说也是适用的。
l 复位和初始化
ARM处理器在复位后处于SVC模式,中断是禁止的,并且处于ARM状态。必须设置好各个异常模式堆栈的位置和大小,应用程序运行时所在的处理器模式和状态,并为它分配合适的堆栈和空间,以及使能中断和启用缓存(如果有的话)的时机。一般来说,总是需要按照一个合理的顺序来初始化系统,图1从总体上描述了一个适用于ARM嵌入式系统的可能的初始化序列。
可以看出,整个初始化序列可以分为2大的部分:用户代码和C库。C库初始化代码是由集成开发环境(这里使用的是ADS1.2)自动生成的,在完成任务之后,它最终将控制权转交给用户代码。
__scatterload的主要作用是复制代码和数据,它会根据应用程序的设置(比如,可以通过分散加载文件精确地指定各部分代码或者数据在加载时和运行时的位置)自动生成代码将程序装载到用户希望的地址。
rt_entry部分的作用是执行运行时库的初始化工作。
需要编码的是用户代码部分:
首先设置各个模式的堆栈指针(由于LPC2210中没有MMU/MPU、缓存和TCM,因此这里只需要设置各个模式的堆栈指针即可,程序如list1所示);
//list 1:设置各种模式的堆栈指针值
Reset_Handler
IMPORT stack_base //stack_base的值在分散加载文件中设置
LDR r0,=stackbase
MSR CPSR_c, #Mode_FIQ|I_Bit|F_Bit
SUB sp,r0, #offset_FIQ_Stack
MSR CPSR_c, #Mode_IRQ|I_Bit|F_Bit
SUB sp,r0, #offset_IRQ_Stack
//⋯设置其它模式的堆栈指针
其次,如果使用了分散加载描述文件(由于实际系统中存储器类型的多样性),那么必须实现函数user_initial_stackheap();最后是启用中断。
设置完各个模式的堆栈指针之后,要对关键的I/O设备进行初始化,对处理器时钟、存储器加速模块,以及外部存储器模块进行设置。然后跳转到C库的main,首先由C库代码按照分散加载文件的描述将代码和数据从加载地址拷贝到运行地址并对未初始化数据进行清零,然后跳转到rt_entry对C运行时库进行初始化,最后将控制权转交给用户代码(即主程序main)。
2 存储器重映射
2.1存储器映射与分散加载文件
嵌入式系统中经常同时存在多种不同类型的存储介质,从访问速度比较快的SRAM、DRAM到比较慢的Flash ROM、EEPROM等。
嵌入式系统通常对实时性的要求很高,因此希望合理地分配存储空间。在ADS1.2中,是通过编写分散加载文件进行描述的。分散加载文件描述了源文件编译后生成的目标文件在存储器中的位置,它同时指定了目标文件的加载地址和执行地址。每个目标文件都有一个唯一的加载地址和一个唯一的运行地址。如果一个目标文件的加载地址和执行地址不同,那么C库的scatterload部分负责将它从加载地址处复制到执行地址处。
2.2为什么引入重映射机制
重映射与处理器的异常处理机制密切相关。
当异常产生时,CPU会从预先设定好的内存地址中取出异常处理程序的入口地址,并跳转到异常处理程序。不同型号的处理器的异常处理机制一般也会不同,ARM系列处理器将所有异常处理程序的地址都保存在一个连续的存储器空间中,位于这个存储器空间上的异常入口的集合就称为“异常向量表”。由于普通的跳转指令B label的跳转范围只有32M,为了实现4G范围内的任意跳转,可以采用指令LDR PC,target_addr,它将target_addr的值看作是一个地址值装载到PC中,于是跳转到那个地址处继续执行。
这样异常向量表通常包括32字节的相对PC的跳转指令和32字节的异常处理程序地址。
为了在系统掉电后能够重新建立异常向量表,必须将其存储在非易失性存储器中,但是非易失性存储器的访问速度非常慢,使得处理器只能插入多个空闲周期来等待它,特别是在一些需要频繁调用异常处理程序的场合,这种负面影响很容易成为系统的瓶颈,于是引入了重映射机制来提高系统对异常的实时响应能力。
重映射一般是在系统初始化过程中完成的。由于处理器对片内RAM的访问速度最快,因此片内RAM成为存放异常向量表的首选位置。整个重映射的过程非常简单,首先将异常向量从非易失性存储器中拷贝到片内RAM中,然后执行重映射命令,将位于片内RAM中的异常向量块映射到异常向量地址空间中。此后,系统若产生异常,处理器将从已映射到异常向量地址空间的片内RAM中读取异常向量。
2.3 LPC2210处理器的重映射机制
在LPC2210中,重映射是通过寄存器MEMMAP来控制的,其用法如表1所示。
在LPC2000中,存储器重映射的部分包括异常向量区(32Byte)和额外的32Byte,一共是64Byte,重新映射的代码位置与地址0x0~0x0000003F重叠。也就是说,当处于用户RAM模式时,如果访问0x0~0x3F的数据,实际上是在对0x40000000~0x4000003F进行访问。同样如果切换到外部存储器模式,并且同样对0x0~0x3F进行访问,就变成访问0x80000000~0x8000003F中的数据/指令了。
2.4 存储器重映射的实现
为了实现对系统启动过程的精确控制,最好把与异常向量和启动代码相关的程序分别放在vectors.s和init.s这2个文件中,这样可以使用分散加载机制精确的控制代码在内存中的布局。为了在系统复位或掉电重启后,程序能够正常运行,必须将异常向量表加载到非易失性存储器Flash中,而且必须位于从0开始的地址处。由于LPC2210处理器没有片内Flash存储器,必须正确的配置引脚13和16使系统从外部存储器启动。启动代码如list2所示,系统启动后首先从标号Instruct_2开始执行,然后修改MEMMAP寄存器的内容(其中CM_ctl_reg等于MEMMAP寄存器的地址),于是异常向量表从片内RAM重新映射,最后将异常向量表从片外Flash拷贝到片内Flash。此后在发生异常后,处理器就自动从片内RAM取出异常处理程序的地址,从而能够显著提高程序的性能。
//list2:部分启动代码
CM_ctl_reg EQU 0xE01FCO4O
RAM EQU 0x2
Reset_Handler
LDR pc,=Instruct_2
Instruct_2
LDR rl,=CM_ctl_reg
LDR r0,[r1]
AND r0,r0,#RemapMask
0RR r0,r0,#RAM
STR rO,[r1]
MOV r9,#0x80000000
MOV r10,#0x40000000
LDMIA r9!,{r0-r7}
STMIA r10!,{r0-r7}
LDMIA r9!,{r0-r7}
STMIA r10!,{r0-r7}
在上面的复制完成之后,异常向量表就会同时出现在地址0x0、0x40000000以及0x80000000。注意执行区IRAM的基址不是0x40000000,而是0x40000040,因为起始的64Byte是为异常向量表预留的。整个过程如果图2所示。
(略)
备注:这篇文章其实讲的是异常向量表的重映射。。。
======================================================================================================
地址映射
把芯片里和芯片外的FLASH、RAM、BootBlock和外设进行统一编址。一般来说,芯片厂商都把这些地址分好了,搞清楚在哪里,使用就行了。外扩FLASH和RAM的时候,需要进行地址映射。使用ADS编译器的时候,有一个scf文件,用来描述地址映射的。而比较新的Keil MDK 采用的可视化的方法。
ARM7TDMI的地址映射位置:ARM7TDMI采用32位寻址,4G的地址空间。一般而言。在0x0000000放FLASH、0x40000000放RAM、0x80000000以上是外部存储器、0xE0000000向上是APB(低速外设,如GPIO、UART等)、0xFFFFFFFF向下是APB(高速设备,如USB,向量中断控制器)。一个比较怪异的地址是BootBlock的,单独来说。
BootBlock:
ARM芯片厂商提供的芯片引导程序,判断用户代码是否有效、芯片是否加密,芯片是否工作在ISP状态、芯片是否工作在IAP状态。这段代码在芯片启动的时候用,生产芯片的时候,厂商已经固化进去了,用户不用管。比较难搞的事情是这段芯片的地址,这段代码一般是放在FLASH中的,放在地址0x00000000似乎最合理,但是这个地址,中断向量表已经用了,于是干脆放到FLASH的尾部了。另一个问题出来了,不同的芯片,内部的FLASH不一样大,这段代码的地址该怎么定哪?办法就是重映射。
BootBlock的重映射:
BootBlock的实际位置在片内FLASH的尾部,但是编地址的时候,重新映射到地址0x80000000向下的地方。这样以来,编程的时候采用的地址是固定在接近0x80000000地方的,而实际的位置是在FLASH的尾部。地址重映射:通过芯片的存储器管理部件,把不同的程序可见地址映射到同一个物理位置。还有一种更加实用的地址重映射叫异常向量表重映射。
异常向量表重映射:
异常向量表在0x00000000的位置,BootBlock启动完之后,就到这里开始程序的运行,分别对应ARM的7种异常状态进行处理。
存储器映射:
为存储器分配地址的过程称为存储器映射,那么什么叫存储器重映射呢?为了增加系统的灵活性,系统中有部分地址可以同时出现在不同的地址上,这就叫做存储器重映射。重映射主要包括引导块“Boot Block”重映射和异常向量表的重映射。
1.引导块“Boot Block”及其重映射
Boot Block是芯片设计厂商在LPC2000系列ARM内部固化的一段代码,用户无法对其进行修改或者删除。这段代码在复位时被首先运行,主要用来判断运行哪个存储器上面的程序,检查用户代码是否有效,判断芯片是否被加密,系统的在应用编程(IAP)以及在系统编程功能(ISP)等。
Boot Block存在于内部Flash,LPC2200系列大小为8kb,它占用了用户的Flash空间,但也有其他的LPC系列不占用FLash空间的,而部分没有内部Flash空间的ARM处理器仍然存在Boot Block。
重映射的原因:
Boot Block中有些程序可被用户调用,如擦写片内Flash的IAP代码。为了增加用户代码的可移植性,所以最好把Boot Block的代码固定的某个地址上。但由于各芯片的片内Flash大小不尽相同,如果把Boot Block的地址安排在内部Flash结束的位置上,那就无法固定Boot Block的地址。
为了解决上面的问题,于是芯片厂家将Boot Block的地址重映射到片内存储器空间的最高端,即接近2Gb的地方,这样无论片内存储器的大小如何,都不会影响Boot Block的地址。因此当Boot Block中包含可被用户调用的IAP操作的代码时,不用修改IAP的操作地址就可以在不同的LPC系列的ARM上运行了。
2.异常向量表及其重映射
ARM内核在发生异常后,会使程序跳转到位于0x0000~0x001C的异常向量表处,再经过向量跳转到异常服务程序。但ARM单条指令的寻址范围有限,无法用一条指令实现4G范围的跳转,所以应在其后面的0x0020~0x003F地址上放置跳转目标,这样就可以实现4G范围内的任意跳转,因此一个异常向量表实际上占用了16个字的存储单元。以下为一张中断向量表:
LDR PC, ResetAddr
LDR PC, UndefinedAddr
LDR PC, SWI_Addr
LDR PC, PrefetchAddr
LDR PC, DataAbortAddr
DCD 0xb9205f80
LDR PC, [PC, #-0xff0]
LDR PC, FIQ_Addr
ResetAddr DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr DCD SoftwareInterrupt
PrefetchAddr DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse DCD 0
IRQ_Addr DCD 0
FIQ_Addr DCD FIQ_Handler
重映射的原因:
由于ARM处理器的存储器结构比较复杂,可能同时存在片内存储器和片外存储器等,他们在存储器映射上的起始地址都不一样,因此ARM内核要访问的中断向量表可能不在0x0000~0x003F地址上,因此采用了存储器重映射来实现将存在与不同地方的中断向量表都映射到0x0000~0x003F地址上。
注意:Boot Block 也存在中断向量表,而且复位后这段代码首先映射到 0x0000~0x003F地址上,也就是说复位后首先运行的是Boot Block程序。各个存储区域的中断向量表也不尽相同。