周立功NXPLPC21xx22xx系列ARM芯片的启动程序分解.docx
《周立功NXPLPC21xx22xx系列ARM芯片的启动程序分解.docx》由会员分享,可在线阅读,更多相关《周立功NXPLPC21xx22xx系列ARM芯片的启动程序分解.docx(62页珍藏版)》请在冰点文库上搜索。
![周立功NXPLPC21xx22xx系列ARM芯片的启动程序分解.docx](https://file1.bingdoc.com/fileroot1/2023-6/26/95e02708-162d-458d-9062-a9f21512245b/95e02708-162d-458d-9062-a9f21512245b1.gif)
周立功NXPLPC21xx22xx系列ARM芯片的启动程序分解
周立功NXPLPC21xx/22xx系列ARM芯片的启动程序分解2008.11.25
zhongcw1002@作者:
钟常慰关于LPC2200启动程序分散加载描述文件的叙述在ADSLPC2200的启动模板中有一个scf文件夹,其中有mem_a.scf、mem_b.scf、mem_c.scf这3
个文件,这3个文件是ADS的分散加载机制,其目的是将代码段和数据段分别定位到指定地址上。
可以在ArmLinker中选择加载路径。
分散装载技术概述:
分散装载技术可以把用户的应用程序分割成多个RO(只读)运行域和RW(可读写)运行域(一个存储区域块),并且给它们制定不同的地址。
一个嵌入式系统中,Flash、16位RAM、32位RAM都可以存在于系统中,所以,将不同功能的代码定位在特定的位置会大大地提高系统的运行效率。
下面是最为常用的2种情况:
1、32位的RAM运行速度很快,因此就把中断服务程序作为一个单独的运行域,放在32位的RAM,使它的响应时间达到最快。
2、程序在RAM中运行,其效率要远远高于在ROM中运行,所以将启动代码(Bootloader)以外的所有代码都复制在RAM中运行,可以提高运行效率。
分散装载技术主要完成了2个基本的功能:
如何分散。
就是如何将输入段组成输出段和域。
如何装载。
就是确定装载域和运行域在存储空间里的地址是多少。
域可以分为装载域和运行域
装载域描述运行前输出段和域在ROM/RAM里的分布状态,运行域描述了运行时输出段和域在ROM/RAM里的分布状态。
大多数情况下,映像文件在执行前把它装载到ROM里,而当运行时,RWRAM里,程序才能正常运行,所以,在装载和运行时,RW类的输出段处在不同的位置(地址空间)。
Scatterfile分散加载文件:
RO:
只读的代码段和常量
RW:
可以读写的全局变量和静态变量
ZI:
RW段中要被初始化为零的变量。
Scatterfile中的定义要按照系统冲定向后的存储器分布情况进行,在引导程序完成初始化任务后,应该把主程序转移到RAM中运行以加快系统的运行速度。
分(消)散
2008.11.25
zhongcw1002@
LPC2200的分散加载文件分析:
ROM_LOAD0x80000000
{
ROM_EXEC0x80000000
(2)第一个运行时域描述
(3)
(4)
(5)
(6)
(7)将栈底放入堆栈的后面(+0)不进行初始化(UNINIT),
(1)加载时域描述{Startup.o(vectors,+First)*(+RO)}IRAM0x40000000放置向量表将MyStacks堆栈段装载到片内静态RAM中。
{Startup.o(MyStacks)}STACKS_BOTTOM+0UNINIT
{Startup.o(StackBottom)}
STACKS0x40004000UNINIT
栈底为Startup中的StackBottom。
(8)(9)将栈放入地址为0x40004000并且不进行初始化(UNINIT)。
(10)(11)将所有的RW和ZI段放入外部存储器中以0x80040000(13)在RWZI段后放入堆底(Startup.o(Heap))(14){Startup.o(Stacks)}ERAM0x80040000{*(+RW,+ZI)}HEAP+0UNINIT(12)为开头的地址中。
并且全部清零(+ZI)外部RAM中指定的区域。
{Startup.o(Heap)}并且不进行初始化。
HEAP_BOTTOM0x80080000UNINIT{Startup.o(HeapTop)}
}
FLASH_LOAD0x810000000x1000
{FLASH_EXEC0x81000000
{main.o(+RO)}
}(15)将堆定放入外部RAM中(0x80080000)。
(16)(17)自己添加的加载代码,把main.c的目标文件加载到(18)片外Flash中并且占用了0x1000的大小。
(19)
(1)加载时域描述,名称位ROM_LODA它的地址为0x80000000;
0x80000000为LPC片外RAM地址,即将以下的加载的段和域都在RAM中。
(2)第一个运行时域描述。
ROM_EXEC描述了执行区的地址,放在第一块定义,其起始地址、空间大小域加载区起始地址、空间大小要一样。
(2)-(4)从起始地址开始放置向量表。
Startup.o是Startup.s的目标文件。
Vectors为中断向量表。
模块Startup位于该加载域的开
头(+First),vectors作为入口点,包含全部的RO代码。
ARM在芯片复位之后,系统进入管理模式、ARM状态,PC(R15)寄存器的值为0x00000000,所以必须保证用户的向量表代码定位在0x00000000处,或者映射到0x00000000处(例如向量表代码在0x80000000处,通过存储器映射,访问0x0000000就是访问(0x80000000)。
(5)-(6)第二运行时域描述。
将MyStacks堆栈段装载到片内静态RAM中。
(7)-(8)将栈底放入堆栈的后面(+0)不进行初始化(UNINIT),栈底为Startup中的StackBottom。
(9)-(10)将栈放入地址为0x40004000并且不进行初始化(UNINIT)。
(11)-(12将所有的RW和ZI段放入外部存储器中以0x80040000为开头的地址中。
并且全部清零(+ZI)外部RAM中指定的区域。
(13)-(14)在RWZI段后放入堆底(Startup.o(Heap))并且不进行初始化。
(15)-(16)将堆定放入外部RAM中(0x80080000)。
(17)-(19)自己添加的加载代码,把main.c的目标文件加载到片外Flash中并且占用了0x1000的大小。
;********************************************************************************************************/;**FileName:
men_a.scf
ROM_LOAD0x80000000//ROM加载{
ROM_EXEC0x80000000//ROM执行(起始地址)程序0x80000000外部存储区
{Startup.o(vectors,+First)//Startup.o文件(向量,程序入口)*(+RO)}//只读
IRAM0x40000000//IndexedRandomAccessMethod,索引随机存取方法0x40000000{Startup.o(MyStacks)}//Startup(堆栈)装载到片内SRAM中
STACKS_BOTTOM+0UNINIT//堆栈栈底,将栈底放入堆栈的后面(+0),不初始化
{Startup.o(StackBottom)}//将栈放入地址为0x40004000并且不进行初始化(UNINIT)
STACKS0x40004000UNINIT//堆栈0x4000400016kb的SRAM片内存储区{Startup.o(Stacks)}//Startup.o文件(堆栈)
ERAM0x81000000//将所有的RW和ZI段放入外部存储器中0x81000000为开头的地址中{*(+RW,+ZI)}//并且全部清零(+ZI)外部RAM中指定的区域读写(无输入ZI)HEAP+0UNINIT//在RWZI段后放入堆底(Startup.o(Heap))并且不进行初始化{Startup.o(Heap)}//Startup.o文件(堆栈)
HEAP_BOTTOM0x81080000UNINIT//将堆定放入外部RAM中0x81080000
{Startup.o(HeapTop)}//Startup.o文件(堆栈栈顶)
}
;********************************************************************************************************/;**FileName:
men_b.scf
ROM_LOAD0x80000000
{
ROM_EXEC0x80000000
{Startup.o(vectors,+First)
*(+RO)}
IRAM0x40000000
{Startup.o(MyStacks)}
STACKS_BOTTOM+0UNINIT
{Startup.o(StackBottom)}
STACKS0x40004000UNINIT
{Startup.o(Stacks)}
ERAM0x80040000
{*(+RW,+ZI)}
HEAP+0UNINIT
{Startup.o(Heap)}
HEAP_BOTTOM0x80080000UNINIT
{Startup.o(HeapTop)}
}
;********************************************************************************************************/;**FileName:
men_c.scf
ROM_LOAD0x0
{
ROM_EXEC0x00000000
{Startup.o(vectors,+First)
*(+RO)}
IRAM0x40000000
{Startup.o(MyStacks)}
STACKS_BOTTOM+0UNINIT
{Startup.o(StackBottom)}
STACKS0x40004000UNINIT
{Startup.o(Stacks)}
ERAM0x80000000
{*(+RW,+ZI)}
HEAP+0UNINIT
{Startup.o(Heap)}
HEAP_BOTTOM0x80080000UNINIT
{Startup.o(HeapTop)}
}
;/********************************************************************************************************
加载文件的更多分解说明,网上了解资料。
只作参考
有如下分散加载文件:
ROM_LOAD0x00000000//程序起始点(程序在Flash中)OriginationPointofCode(CodeinFlash){
ROM_EXEC0x00000000//起始程序执行OriginationPointofExecuting
{Startup.o(vectors,+First)
*(+RO)}
IRAM0x40000040//内部SRAM起始点OriginationPointofInternalSRAM
{Startup.o(MyStacks)}//0x40000000~0x4000003FforVector
STACKS_BOTTOM+0UNINIT
{Startup.o(StackBottom)}
STACKS0x40004000UNINIT//EndPointofInternalSRAM
{Startup.o(Stacks)}
ERAM0x81000000//外部RAM起始点OriginationPointofExternalSRAM
{*(+RW,+ZI)}
HEAP+0UNINIT
{Startup.o(Heap)}
HEAP_BOTTOM0x81800000UNINIT//外部RAM结束点EndPointofExternalSRAM
{Startup.o(HeapTop)}
}
其中,ROM_LOAD为加载区的名称,其后面的0x00000000表示加载区的起始地址(存放程序代码的起始地址),也可以在后面添加其空间大小,如“ROM_LOAD0x000000000x20000”表示加载区起始地址为0x00000000,大小为128K字节;ROM_EXEC描述了执行区的地址,放在第一块位置定义,其起始地址、空间大小与加载区起始地址、空间大小要一致。
从起始地址开始放置向量表(即Startup.o(vectors,+First),其中Startup.o为Startup.s的目标文件),接着放置其他代码(即映像文件)(即*(RO));变量区IRAM的起始地址为0x4000000040,放置Startup.o(MyStacks);变量区ERAM的起始地址为0x80000000,放置出Startup.o文件之外的其他文件的变量(即*(+RW,+ZI));紧靠ERAM变量区之后的是系统堆空间(HEAP),放置描述为Startup.o(Heap);堆栈区STACKS使用片内RAM,由于ARM的堆栈一般采用满递减堆栈,所以堆栈区的起始地址设置为0x40004000,放置描述为Startup.o(Stacks)
2.使用地址不连续的内存(LPC2368)
Lpc2368一共有56K的RAM,其中通用Ram32K,地址为0x40000000~0x40007fff;8KB的USB专用RAM,地址0x7fd00000~0x7fd01ffff;16KBEthernet专用RAM,地址为0x7fe00000~0x7fe03fff;以上的USB和Ethernet专用RAM也可用做通用RAM,需要做如下设置:
(1)target.c中将USB和Ethernet功能打开,需要设置PCONP寄存器,详见Datasheet。
(2)设置分散加载文件,分配这两段内存。
在DebugInRam模式下,有如下分散加载文件:
ROM_LOAD0x40000000
{
ROM_EXEC0x40000000//加载映像文件(通用RAM首地址)
{Startup.o(vectors,+First)
*(+RO)}
IRAM0x40007000//用户堆栈
{Startup.o(MyStacks)}
STACKS0x40008000UNINIT//系统堆栈
{Startup.o(Stacks)}
ERAM0x7fe00000/*变量,放置与Ethernet专用RAM首地址*/
{*(+RW,+ZI)}
HEAP+0UNINIT
{Startup.o(Heap)}
}
3.分散使用Flash地址(LPC2368)
项目中,要求将片内Flash起始几个扇区空出来留作他用,或者当用到的Flash地址不连续的时候,都可用以下方法来编写分散加载文件:
ROM_LOAD0x00000000
{
ROM_EXEC0x00000000/*中断向量表*/
{Startup.o(vectors,+First)}
……
ROM_LOAD10x00004000//加载映像文件,从第四个扇区开始
{ROM_EXEC10x00004000
{
*(+RO)
}
}
值得注意的是,中断向量表必须放在flash起始地址处,否则无法启动。
根据以上分散加载文件编译生成的Hex文件会有两个,分别如下:
Hex1:
:
020000040000FA
:
1000000018F09FE518F09FE518F09FE518F09FE5C0
……
Hex2:
:
020000040000FA
:
1040000090808FE20F0098E8080080E0081081E0BF
。
。
。
可以看出,生成的两段Hex文件的起始地址是不同的,其中一段为中断向量表;另一段为用户映像文件。
4.固定变量内存地址
嵌入式开发中,有时会需要在同一片内的不同段程序(比如Bootloader和主程序间)间传递数据,这时候往往需要固定变量地址。
一般来言,C语言编写的程序,变量地址是由C编译器来分配内存的,程序员无法实现知道变量地址。
而ADS中的分散加载文件可以告知编译器,固定某些变量的地址,如下:
ROM_LOAD0x00000000
{
ROM_EXEC0x00000000
{Startup.o(vectors,+First)
*(+RO)}
RAM0x40000000UNINIT//Mfile.c中的所有变量地址从0x40000000开始
{Mfile.O(+RW,+ZI)}
IRAM0x40000010
{Startup.o(MyStacks)
*(+RW,+ZI)}
HEAP+0UNINIT
{Startup.o(Heap)}
STACKS0x40004000UNINIT
{Startup.o(Stacks)}
}
上述分散加载文件固定了Mfile.c中变量的起始地址,以这种方法,可以固定任何全局变量的地址,以便其被其他系统访问。
;/********************************************************************************************************;**Filename:
Startup.s//起始文件
;定义堆栈的大小
SVC_STACK_LEGTHEQU0//管理堆栈长度
FIQ_STACK_LEGTHEQU0//快速中断堆栈长度
IRQ_STACK_LEGTHEQU256//通用中断堆栈长度
ABT_STACK_LEGTHEQU0//中止堆栈长度
UND_STACK_LEGTHEQU0//未定义堆栈长度
NoIntEQU0x80
USR32ModeEQU0x10//用户模式
SVC32ModeEQU0x13//管理模式
SYS32ModeEQU0x1f//系统模式
IRQ32ModeEQU0x12//通用中断模式
FIQ32ModeEQU0x11//快速中断模式
//以下几个定义是确定寄存器所在地址(保证前期初始化作业)书本191-193页PINSEL2EQU0xE002C014//定义引脚功能选择寄存器2在芯片中所在的地址(0xE002C014)(不管内容)
BCFG0EQU0xFFE00000//存储器组0所在芯片位置(0xFFE00000)设定与外设的读写及间隔速度设定BCFG1EQU0xFFE00004//存储器组1所在芯片位置(0xFFE00004)设定与外设的读写及间隔速度设定BCFG2EQU0xFFE00008//存储器组2所在芯片位置(0xFFE00008)设定与外设的读写及间隔速度设定BCFG3EQU0xFFE0000C//存储器组3所在芯片位置(0xFFE0000C)设定与外设的读写及间隔速度设定
IMPORT__use_no_semihosting_swi
IMPORT__use_two_region_memory
;引入的外部标号在这声明
IMPORTFIQ_Exception//快速中断异常处理程序
IMPORT__main//C语言主程序入口
IMPORTTargetResetInit//目标板基本初始化
;给外部使用的标号在这声明//这些标号都是代表一段字节的首地址,可参见后面的部分设定
EXPORTbottom_of_heap//heap的底部
EXPORTbottom_of_Stacks//stack的底部
EXPORTtop_of_heap//heap的顶部
EXPORTStackUsr
EXPORTReset//复位
EXPORT__user_initial_stackheap//库函数初始化堆和栈
CODE32//32位ARM代码
AREAvectors,CODE,READONLY//只读程序入口
ENTRY//进入
;中断向量表
Reset
LDRPC,ResetAddr//复位起始地址
LDRPC,UndefinedAddr//未定义异常向量地址
LDRPC,SWI_Addr//软件复位向量地址
LDRPC,PrefetchAddr//预指令中止
LDRPC,DataAbortAddr//数据异常向量地址
DCD0xb9205f80//将所有向量表合计为0的一个指定数据
LDRPC,[PC,#-0xff0]//IRQ(该指令会读取VICVectAddr寄存器的值,然后放入PC指针)
LDRPC,FIQ_Addr//快速中断地址
;给每一个向量分配连续的字存储单元
ResetAddrDCDResetInit//将ResetInit入口地址赋予ResetAddr复位初始化
UndefinedAddrDCDUndefined//将Undefined入口地址赋予UndefinedAddr未定义
SWI_AddrDCDSoftwareInterrupt//将SoftwareInterrupt入口地址赋予SWI_Addr软件复位
PrefetchAddrDCDPrefetchAbort//将PrefetchAbort入口地址赋予PrefetchAddr预指令中止
DataAbortAddrDCDDataAbort//将DataAbort入口地址赋予DataAbortAddr数据异常
NouseDCD0//将Nouse赋予0
IRQ_AddrDCD0//将IRQ_Addr赋予0通用中断
FIQ_AddrDCDFIQ_Handler//将FIQ_Handler入口地址赋予FIQ_Addr快速中断
;未定义指令
Undefined
BUndefined//原地循环
;软中断
SoftwareInterrupt
BSoftwareInterrupt//原地循环
;取指令中止
PrefetchAbort
BPrefetchAbort//原地循环
;取数据中止
DataAbort
BDataAbort//原地循环
;快速中断
FIQ_Handler
STMFDSP!
{R0-R3,LR}//入栈
BLFIQ_Exception//调快速中断程序
LDMFDSP!
{R0-R3,LR}//出栈
SUBSPC,LR,#4//返回当初的地址
;/*****************