ARM的启动过程.docx
- 文档编号:30660008
- 上传时间:2023-08-19
- 格式:DOCX
- 页数:32
- 大小:56.88KB
ARM的启动过程.docx
《ARM的启动过程.docx》由会员分享,可在线阅读,更多相关《ARM的启动过程.docx(32页珍藏版)》请在冰豆网上搜索。
ARM的启动过程
ARM芯片的启动程序的分析和总结
1、综述:
目前大多基于ARM芯片的系统都是一个比较复杂的片上系统,多数硬件模块都是可配置的,可以通过软件来设置其需要的工作状态。
因此在运行用户的应用程序之前,需要由专门的一段代码来完成对系统的初始化。
这一段代码就称为启动程序。
由于这类代码直接面对处理器内核和硬件控制器进行编程,一般都是用汇编语言。
在ARM系统上电复位后,需要设置中断向量表、初始化各模式堆栈、设置系统时钟频率等,需要用ARM的汇编语言编写启动代码,由启动代码完成系统初始化以及跳转到用户C程序。
在ARM设计开发中,启动代码的编写是一个极重要的过程。
然而启动代码随具体的目标系统和开发系统有所区别,但通常包含以下部分:
·向量表定义
·地址重映射及中断向量表的转移
·堆栈初始化
·设置系统时钟频率
·中断寄存器的初始化
·进入C应用程序
下面就结合PHILIPS的LPC2119的启动代码来分析与说明ARM7处理器的启动代码的编写。
1.1向量表定义
ARM芯片上电或复位后,系统进入管理模式、ARM状态、PC(R15寄存器)指向0x00000000地址处。
中断向量表为每一个中断设置1个字的存储空间,存放一条跳转指令,通过这条指令使PC指针指向相应的中断服务程序入口,继而执行相应的中断处理程序。
LPC2119的中断向量表和其它基于ARM核的芯片中断向量表较类似,只要注意LPC2119要使向量表所有数据32位累加和为零(0x00000000-0x0000001C的8个字的机器码累加),才能使用户的程序脱机运行。
LPC2119的中断向量表如图1所示。
1.2地址重映射及中断向量表的转移
ARM7处理器在复位后从地址0读取第一条指令并执行,因此系统上电后地址0必须是非易失的ROM/FLASH,这样才能保证处理器有正确可用的指令。
为了加快对中断的处理以及实现在不同操作系统模式下对中断的处理,这就需要重新映射中断向量表、Bootblock和SRAM空间的一小部分。
ARM具有非常灵活的存储器地址分配特性。
ARM处理器的地址重映射机制有两种情况:
①由专门的寄存器完成重映射(Remap),只需对相应的Remap寄存器相应位设置即可。
②没有专门的Remap控制寄存器需要重新改写用于控制存储器起始地址的块(Bank)寄存器来实现Remap。
在LPC2119上的重映射,可以通过存储器映射控制器来实现。
实现REMAP操作的程序实现如下:
MOVR8,#0x40000000; /设置新向量表起始地址/
LDRR9,=Interrupt_Vector_Table; /读原向量表源地址/
LDMIAR9!
(R0-R7); /复制中断向量表及中断处理程序的入口地址到RAM中(64字节)/
STMIAR8!
(R0-R7)
LDMIAR9!
(R0-R7)
STMIAR8!
(R0-R7)
LDRR8,=MEMMAP; /REMMAP操作/
MOVR9,#0x02
STRR9,[R8]
1.3堆栈初始化
启动代码中各模式堆栈空间的设置是为中断处理和程序跳转时服务的。
当系统响应中断或程序跳转时,需要将当前处理器的状态和部分重要参数保存在一段存储空间中,所以对每个模式都要进行堆栈初始化工作,给每个模式的SP定义一个堆栈基地址和堆栈的容量。
堆栈的初始化有两种方法:
第一种方法是结合ADS开发套件中的分散加载文件来定义堆栈。
第二种方法是最简单也是最常用的一种就是直接进入对应的处理器模式,为SP寄存器指定相应的值。
下面给出了用第二种方法初始化管理模式和中断模式堆栈的程序:
MSRCPSR_c,#0xD3; /切换到管理模式,并初始化管理模式的堆栈/
LDRSP,Stack_Svc
MSRCPSR_c,#0xD2; /切换到IRQ模式,并初始化IRQ模式的堆栈/
LDRSP,Stack_Irq
…
1.4系统部分时钟初始化
时钟是芯片各部分正常工作的基础,应该在进入main()函数前设置。
部分ARM7片子内部集成有PLL(锁相环)电路,用户可以用低频率的晶振通过PLL电路获得一个较高频率的时钟。
LPC2119内部的PLL电路接受的输入时钟频率范围为10~25MHz,输入频率通过一个电流控制振荡器(CCO)倍增到范围10~60MHz。
同时为了使高速的ARM处理器与低速的外设正常通讯和降低功耗(降低外设运行速度使功耗降低),LPC2119又集成了一个额外的分频器。
PLL的激活是由PLLCON寄存器控制。
PLL倍频器和分频器的值由PLLCFG寄存器控制。
对PLLCON或PLLCFG寄存器的更改必须遵循严格的顺序,否则所作更改是无法生效的(在连续的VPB周期内向PLLFEED寄存器写入0xAA、0x55,在此期间中断必须是被禁止的。
)
1.5中断初始化
ARM7的向量中断控制器(VectoredInterruptController)可以将中断编程为3类:
FIQ、向量IRQ、非向量IRQ。
FIQ中断请求的优先级最高,其次是IRQ中断请求,非向量IRQ的优先级最低。
VIC具有32个中断请求输入,但在LPC2219中只占用了17个中断输入。
对于这17个中断源的IRQ/FIQ选择,由VICIntSelect寄存器控制,当对应位设置位1时,则此中断为FIQ中断,否则为IRQ中断。
若再将IRQ中断设置到向量控制寄存器(VICVectCntIn)中,则此中断为向量IRQ中断,否则为非向量IRQ中断。
FIQ中断是专门用来处理那些需要及时响应的特殊事件,尽可能地只给FIQ分配一个中断源。
1.6进入C应用程序
至此,系统各部分的初始化基本完成,可以直接从启动代码转入到应用程序的main()函数入口。
从启动代码转入到应用程序的实例代码如下:
IMPORTmain
LDRR0,=main
BXR0
2、总结
一个优秀的启动代码将给应用程序的开发提供一个良好的开发平台。
本文中较详细的讨论了启动代码的编写及难点。
其中在堆栈初始化过程中要特别的注意两点:
①要尽量给堆栈分配快速和高带宽的存储器。
②尽量避免过早将处理器切换到用户模式,一般在系统初始化的最后阶段才切换到用户模式(用户模式没有权限通过修改CPSR来进行模式切换)。
嵌入式系统的迅猛发展,使启动代码的编写成为嵌入式系统开发人员应该具备的能力。
本文有助于正在从事嵌入式ARM开发的读者理解启动代码的内涵与编写出适合自己的启动代码。
启动文件分析(试验总结)
(以下所有完全是我自己在今晚对arm启动代码的所有理解,可能有很大的问题,但目前为止,我还是能自圆其说,还是另自己信服的,如果高手看出什么低级错误,欢迎指出,共同学习!
)
玩arm底层编程也有一个星期,感觉机会成熟,晚上认真把周立功的启动底层代码认真看了一遍,感觉豁然开朗,呵呵,赶快把学到的写在这里,怕以后忘记。
完全理解这些基础的代码是学习移至uc/os或者uclinux的第一步,所有arm的核,甚至可以说x86的架构都是很相似的。
这里所说的启动文件是没有任何操作系统,没有任何软件的基础上,去实现arm的编程,让程序运行于arm之上。
简单的说就是arm上电复位,首先执行的代码,相当与x86的BIOS。
如果想透彻了解,下面知识是必备的:
c语言,汇编基础,arm的架构(对于arm底层开发,以及中断处理,假中断避免,调度算法,实时性分析都有很重要的帮助),arm常用指令。
这些东西分别可以从谭浩强、杜春雷的书上找到解决方法。
最好还要知道一些可执行文件的知识(hex和bin,ads产生的axf文件的差别,用utla还是很容易看出ro,rw,zi上的区别,二进制文件头还是有些不同的)。
因为有操作系统和计算机组成原理的知识,所以进入下面一段,直入主题:
拿周立功lpc2100开发板带的启动文件说事,其全部引导相关文件如下:
config.h****************************配置文件
lpc2XXX.h**************************此款芯片的头文件
target.h***************************目标头文件
heap.s****************************堆配置
stack.s***************************栈配置
IRQ.s*****************************中断配置
startup.s***************************启动文件配置(VIC等)
mem_b.scf*************************分散和加载文件(mem_a.scf类似,以b开刀)
下面分别分析每个文件:
打开文件config.h,其中关于数据类型的定义不用多说,是为了完成数据类型与平台无关,另一个重要的配置是系统晶振频率,VOC,VPB的配置,这个根据手册要求配置(比如说CCO频率,必须为Fcclk的2、4、8、16倍,范围为156MHz~320MHz),并且一定要和实际硬件相一致!
这个文件,重要的就这一点点
接着是lpc2XXX.h,里面是本处理器控制寄存器的地址宏定义,(*((volatileunsignedchar*)0xE01FC140))第一个*表示把后面的数字强制转换成地址,前面的*表示把宏定义的名称标为指向这个地址的指针。
Target.h,启动定义了一些“空”函数给用户填写,用于处理特殊情况
heap.s堆配置,仅仅分配了一个空间
stack.s栈配置,与heap.s类似,作为异常向量入口及异常向量与c语言代码的接口
irp.s这个文件值得研究,我把代码贴在下面,我把我懂的地方都注释了:
NoIntEQU0x80//实际存储的地方
USR32ModeEQU0x10//定义arm各个模式
SVC32ModeEQU0x13
SYS32ModeEQU0x1f
IRQ32ModeEQU0x12
FIQ32ModeEQU0x11
CODE32
AREAIRQ,CODE,READONLY
MACRO
$IRQ_LabelHANDLER$IRQ_Exception_Function
EXPORT$IRQ_Label;输出的标号
IMPORT$IRQ_Exception_Function;引用的外部标号
$IRQ_Label
SUBLR,LR,#4;计算返回地址
STMFDSP!
{R0-R3,R12,LR};把寄存器压栈保存任务环境
MRSR3,SPSR;把spsR保存到R3中
STMFDSP,{R3,LR}^;保存SPSR和用户状态的SP,注意不能回写
;如果回写的是用户的SP,所以后面要调整SP
^表示在压栈同时,保存SPSR
NOP
SUBSP,SP,#4*2//此句不理解
MSRCPSR_c,#(NoInt|SYS32Mode);切换到系统模式
BL$IRQ_Exception_Function;调用c语言的中断处理程序
(函数返回)
MSRCPSR_c,#(NoInt|IRQ32Mode);切换回irq模式
LDMFDSP,{R3,LR}^;恢复SPSR和用户状态的SP,注意不能回写
;如果回写的是用户的SP,所以后面要调整SP,此处^功能相似(参考uc/osarm移至要点详解)
MSRSPSR_cxsf,R3;装载之前的SPSR
ADDSP,SP,#4*2;此句不理解
LDMFDSP!
{R0-R3,R12,PC}^;恢复所有的调用之前寄存器状态
MEND
startup.s文件:
首先是定义每个栈的大小,然后是定义了一个PINSEL2根据芯片的pdf个人理解是设置pin的调试功能。
接着引入一系列外部声明的函数,然后是非常重要的中断向量表,是要放在逻辑0x0的地址(所有向量相加应该为0!
!
),然后是初始化各个堆栈,接着进入了复位中断函数,ResetInit
BLInitStack;初始化堆栈
BLTargetResetInit;目标板基本初始化
B__main;跳转到c语言入口
下面还有些加密函数等等,重要的是堆栈的地址定义,因为有:
MSRCPSR_c,#0xd2
LDRSP,StackIrq,这里会调用下面的地址定义,这里注意,使用的是ldr而不是B,因为ldr支持全范围的跳转,可以在flash内,而b不可以
StackSvcDCDSvcStackSpace+(SVC_STACK_LEGTH-1)*4
StackIrqDCDIrqStackSpace+(IRQ_STACK_LEGTH-1)*4
StackFiqDCDFiqStackSpace+(FIQ_STACK_LEGTH-1)*4
StackAbtDCDAbtStackSpace+(ABT_STACK_LEGTH-1)*4
StackUndDCDUndtStackSpace+(UND_STACK_LEGTH-1)*4
堆栈的空间分配根本上是在这儿:
AREAMyStacks,DATA,NOINIT,ALIGN=2
SvcStackSpaceSPACESVC_STACK_LEGTH*4;管理模式堆栈空间
IrqStackSpaceSPACEIRQ_STACK_LEGTH*4;中断模式堆栈空间
FiqStackSpaceSPACEFIQ_STACK_LEGTH*4;快速中断模式堆栈空间
AbtStackSpaceSPACEABT_STACK_LEGTH*4;中止义模式堆栈空间
UndtStackSpaceSPACEUND_STACK_LEGTH*4;未定义模式堆栈
请注意这里的NOINT,他指的是0x80因为有NoIntEQU0x80在先
最后一个很重要的文件是mem_b.scf,他是用来分散加载文件的,为什么要这样,这在最后来讲,先看他具体内容:
ROM_LOAD0x40000000//定义加载时域的名称为ROM_LOAD起始地址为0x400000,什么是时域,上网查吧!
{
ROM_EXEC0x40000000第一个运行时域的名称和加载地址
{
Startup.o(vectors,+First)将startup.o的文件的vectors加载到本域的起始地址,以下顺序加载其他只读代码
*(+RO)
}
HEAP+0UNINIT这段的分析和上面分析类似,就不重复了
{
heap.o(+ZI)
}
当我们改变加载地址时候,当编译完成后,通过cmd的fromelf-d命令我们可以很清楚的看到,每段时域的基地址根据我们的设置是变化的,说明我的猜想是基本正确的。
下面分析具体的启动过程,首先arm加电后,pc指的是0x0的地址,但是这个0x0的地址是逻辑地址,他在这个系统里对应的是0x4000000000这个地址,然后设置中断向量表,初始化堆栈,初始化硬件包括锁相环的设置和连接(上面忘记把这段代码拿出来分析了,其实是很简单的注意些0xaa,0x55(好像是这两个值)是不允许被打断的,否则无法连接锁相环),最后完成配置后跳转到c语言的main函数(此话在startup.s中,内容如下:
)。
ResetInit
BLInitStack;初始化堆栈调用相关语句
BLTargetResetInit;目标板基本初始化,调用相关函数
B__main;跳转到c语言入口,进入c环境
注:
因为二进制代码的RO,RW,zi是按顺序存放的,但是在arm的地址空间分配中,并不是按照这种格式,有时要把其他文件的ro放在本文件的后面,且地址也有严格的规定,这时候,就是分散文件加载和存放上场的时候了,就看到上面ROM_EXEC0x40000000等语句了
s3c2410(ARM9)启动代码分析转贴
发布:
2009-12-3109:
57|作者:
bing8687|来源:
本站|查看:
53次|字号:
小中大
from:
ADS下C语言的入口方式和ROM镜像文件的生成
这部分介绍下ADS下如何生成可以运行的ROM镜像文件,我们知道当程序下载到flash中运行的时候,对于RW、ZI数据就存在着两个环境,一个load环境,一个是exec环境,有时候由于速度的需要RO数据也要重新加载,那么对RO数据也是有两个环境。
编译器产生ROM镜像文件时候,这三块数据的存放依次为RO、RW、ZI,并且地址空间时连续的。
但是到了运行的时候,RW数据必须被拷贝到SDRAM(SRAM)中以支持读写,这就是我们所谓的运行环境。
那么就要有一段代码去完成这个任务,在本章中我们介绍如何生成这段代码。
玩过2410的朋友都知道2410初始化代码中有一段搬运RW和ZI初始化的代码,没错,它确实能够在一定程度上完成上面所说的任务,只要我们在生成二进制可执行代码的时候在编译器链接项的地方填写正确的RO&RW地址,(比如RO=0,RW=0x30000000),那么将程序下到NORflash的零地址并从norflash启动,启动代码会将RW&ZI数据弄到0x30000000,程序就能跑起来了。
但是各位有没有想过,怎么把RO代码弄到SDRAM中(有时候这是必须的,比方后面我将提到用norflash的bootloader烧写norflash)?
如果直接设RO=0x30000000,那么这段代码下载到0地址肯定跑不起来,除非是ROPI,这个要求就高了。
这里我们有必要从介绍ADS中规定的C语言入口开始,ADS中从初始化汇编代码跳到main函数有两种方式,main和__main:
1,在__main入口的模式下,汇编代码的指令为b __main,编译器在跳转到main之前还要作一系列的工作,这其中就包括对运行环境的初始化,在
copiesnonroot(RO&RW)executionregionsfromloadaddrtoexecaddr,andZerosZIregion.借助编译器,我们就可以定义更为复杂的运行环境,这里要用到scatter文件(.scf),比如我们要的目标运行环境是:
将启动代码以外的所有代码都拷贝到SDRAM的初始地址中运行,比且把RW段设在0x30800000,那么对应的scf文件如下:
FLASH0x0 0x200000
{
EXEC10x00x200000
{
2410init.o(Init,+First)
__main.o(+RO) ;copycode
*(Region$$Table) ;RO/RWaddressestocopy
*(ZISection$$Table) ;ZIaddressestozero
}
EXEC20x300000000x00800000
{
*(+RO)
}
SDRAM0x308000000x00800000
{
*(+RW,+ZI)
}
}
;SectionsnamedRegion$$TableandZISection$$Tablewhichcontaintheaddressesofthecode/datatobecopied.
当然,在这种模式下,有些入口函数必须自己重定义,比如__user_initial_stackheap,具体参见ADS文档。
2,main入口模式即简单的跳转,这里起始不用“main”这个名字也无所谓。
那么编译器不会作任何的初始化,所有运行环境的建立都要靠我们自己,这就是大家看到的那段搬运代码存在的理由。
但是它实现一些简单的运行环境是可取的,如果用scf定义的复杂环境,虽然我相信是可以做到的,但是可能会比较麻烦。
我还没深究。
另外,这里提一下semihost,因为我们在看ADS的东西的时候经常出现这个词,我也一直受其困扰。
这里我简单说一下自己的见解,semihost仅仅是一种调试手段,它的机理就是利用MULTI_IDE等工具捕捉目标环境运行过程中产生的值为0x123456的SWI中断,然后向上位机的ADS软件发送对应的调试信息。
对于我们最后的应用代码来说,都是nonsemihost类型的。
如果我们在调试中使用semihost,那么只要在最后重定义ADS中的一些使用到的库函数(比如fputc),代码就可以从semihost向nonsemihost的类型转变。
不过到目前为止,我还没体会到semihost的威力。
2410启动代码分析
这一章主要对目前广泛流行的2410启动代码进行分析:
S3C2410的初始化代码主要涉及到对系统主要模块的配置、运行环境的建立、系统时钟、MMU等模块的配置,下面按执行顺序依次都各个部分进行分析:
程序入口:
(ResetHandler)
在程序一开始,首先进行的一些操作主要保证初始化程序能够顺利的运行,因此主要包括关闭WDT、中断,配置锁相环等。
配置memory接口
memory接口是确保数据访问正确的基本保障,此处主要配置SFR寄存器中0x48000000开始的memory接口寄存器组, 确保每个bank的位宽、访问类型(waitable)以及时序参数正确。
如果没有特别的要求,一般来说时序参数使用默认值即可。
初始化堆栈
ARM有6种运行模式,必须为每一种模式提供独立的堆栈空间,在堆栈设置之前是不能进行C函数的调用的。
ARM的堆栈模式是从高地址递减的,我的所有代码统一将堆栈的首地址设在0x33ff8000处,往低依次为FIQ、IRQ、Abort、Undef、SVC,其中
SVC和User模式不予区分。
堆栈大小一般可在头文件或者当前文件中修改。
运行空间的初始化
这段代码主要完成两个功能,一是将RW数据搬运到RW空间(我们生成ROM镜像时,RW数据是跟在RO数据之后的),二是初始化ZI数据段。
当然,这段代码存在的前提是代码的运行环境只是标准的两段式:
一段RO空间和一段RW空间;并且在C程序
入口时没有调用编译器的链接库(__ma
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ARM 启动 过程