关于ARM loader的一些心得.docx
- 文档编号:4422092
- 上传时间:2022-12-01
- 格式:DOCX
- 页数:11
- 大小:25.04KB
关于ARM loader的一些心得.docx
《关于ARM loader的一些心得.docx》由会员分享,可在线阅读,更多相关《关于ARM loader的一些心得.docx(11页珍藏版)》请在冰豆网上搜索。
关于ARMloader的一些心得
关于ARMloader的一些心得
最近的项目里需要用KS8695这块MPU,它是ARM922T内核的,有cache,也有MMU,RAM和ROM需要外扩,最大都支持到64M,足以跑起一个完整功能的linux核.虽然这MPU主频最高只有166Mhz,相比三星的S3C2410等性能不是很强,但有内置的硬件WAN口和LAN口,构建网络路由器非常方便.
考虑到各种原因,最主要是实时性响应和速度响应的原因,决定不上linux操作系统,当作普通单片机以单一任务不断循环的方式实现,这就涉及到了loader的问题.由于专用于网络,普及率不大,这块MPU的网上资料不多,随开发板赠送的loader是uboot,uboot是引导linux内核的,功能多,但要在linux环境下编译和调试,比较麻烦.我习惯用的工具是ADS,因此决定用ADS的汇编实现一个简单功能的loader(以下提到的代码语法全部是针对ADS环境的,而且仅是KS8695这块芯片,对于KeilforARM或GCC环境或其他ARM芯片需要做别的考虑,但可以参考下面的作法和思路).
玩ARM的朋友,少不免要接触loader,但因为loader涉及到很多跟硬件有关的很抽象的东西,看起来比较复杂和难理解。
希望下面说的这些能帮助有兴趣的朋友更好理解。
下面由简单到复杂来说说这个loader。
先想想,这个loader该有哪些功能呢?
很明显,最重要的是,它必须最后能调用C语言里的Main函数,因为我不想整个项目都用汇编写,毕竟我不是自虐狂.而很明显,如果你要调用函数,必须进行入栈,出栈这些保护现场的工作,否则程序就会乱套.那保护现场工作需要什么?
当然就是需要RAM来保存现场.对于KS8695,它没有内置可随意使用的RAM(实际上,它有特殊的小容量的RAM,但那是给网卡这些做硬件数据缓冲,用户是不能随意调用的,顺便提一下,ARM芯片很多,不同的厂家有不同的硬件配置,譬如很多低端应用的ARM芯片其实有内置的SRAM,譬如菲利浦的LPC系列),因此,就得使用外扩的RAM了.开发板上用的是SDRAM,SDRAM的本身的特性决定,需要定时刷新的支持,KS8695有内置的SDRAM控制器,只要往某个专用的寄存器写入一些与硬件参数有关的数值,该SDRAM控制器就能自动产生各种读写SDRAM的时序信号来驱动SDRAM。
然后用户程序只需用str和ldr指令就能读写SDRAM的内容.
OK,现在有点头绪了,loader至少要完成这两个功能:
1.初始化SDRAM控制器,让SDRAM可以读写(而且我想没有谁的程序会不打算使用RAM吧?
?
)
2.建立堆栈区,设置好堆栈指针,把堆栈指针设到SDRAM里面,以便进出函数时的参数能保存到SDRAM里.
只要这两部做完,其实就可以调用C里的main函数,然后由main函数再调用各种功能的子函数,以后就爱咋滴就咋滴了.
咋一看,似乎这两步做完,就天下太平了,真的就这么简单吗?
未必,别忘了,几乎所有的CPU都带有中断触发机制.一个稍能完成点实用功能的项目,你想完全避开中断处理这一部分是完全不可能的,也不现实,你必须按你的需要来编写各种各样的中断服务程序,处理各种各样的中断触发(至于中断触发对CPU来说是必需的吗?
中断有什么好处?
中断时CPU的硬件会做什么动作?
...这些就不想罗嗦,有兴趣的可以参考51系列的中断原理作为入门理解).编写中断服务程序,可以在C语言里实现,不要在loader里实现,loader里要实现的,是要建立中断向量表.中断向量表是一个有点专业的名词,请自行查入门资料.
这里说说为什么要建立中断向量表.在51单片机里,是不用自己建立中断向量表的,这是因为标准51核的中断,本身就是一个向量中断(自行查资料),自身硬件里就已经有内置的中断向量表,没必要自己去建立.就是说,当某个中断触发时,51核的硬件会自动把PC置成相应的固定的地址入口来执行相应的中断处理程序,不同的中断触发时,会有不同的地址入口,一一对应,俗称向量中断.但KS8695不是这样(很多arm芯片没中断向量表,但有些arm芯片同时支持向量中断和非向量中断两种模式,可以按需要设定),它没内置的中断向量表,当中断触发时,无论什么中断,入口地址只有一个,你必须在这个入口程序里读取某些中断寄存器的内容,来判断究竟是发生了哪个中断,然后通过查表,查自己建立的中断向量表,来调用相应的中断处理程序.
现在看来,loader复杂一点点了,归纳一下,现在变成有三个功能需要而且是必须要完成了:
1.初始化SDRAM控制器,让SDRAM可以读写(而且我想没有谁的程序会不打算使用RAM吧?
?
)
2.建立堆栈区,设置好堆栈指针,把堆栈指针设到SDRAM里面,以便进出函数时的参数能保存到SDRAM里.
3.在内存里建立异常中断向量表,并在loader实现普通中断的查表功能
完成了这三步,似乎又天下太平可以一马平川了,真的是这样吗?
不是的.下面还有一个功能必须要实现:
变量的初始化.
变量的初始化是个什么样的概念呢?
举个简单的例子,如果你程序里有个变量这样定义(这是不可能避免的吧):
uchari=5;
main()
{
}
就是说,i这个变量,初始值是5,具体反映到硬件上面,就是说,i这个变量所对应的内存单元,它在相应程序要执行之前,这个内存单元里保存的数值应该是5.否则,这个内存单元保存的是上电时硬件随机产生的不确定数值.
自己来做变量初始化的工作,其实不是必要的,而仅仅是当使用的编译器是ADS,才是必要的.如果使用的是其他编译器,有可能不用做这步.譬如Keil的编译器在链接成最终代码时,会自动添加一段变量初始化的代码,而GCC编译器记得也是这样.现在用的编译器是ADS。
因此,很遗憾,loader又得复杂一点点了,新的列表如下:
1.初始化SDRAM控制器,让SDRAM可以读写(而且我想没有谁的程序会不打算使用RAM吧?
?
)
2.建立堆栈区,设置好堆栈指针,把堆栈指针设到SDRAM里面,以便进出函数时的参数能保存到SDRAM里.
3.在内存里建立异常中断向量表,并在loader实现查表功能
4.初始化变量
这四步做完,确实可以一马平川了,但如果你想独步天下,还需要做得更好.作为一个编程人员,通常都是完美主义者,偏执狂,所追求的是无止境的效率和自由,我想很少有人会拒绝耍一些提高程序效率的手段吧?
于是下面可以再多了一个步骤,但这个功能对KS8695来说其实不是必需的(因为KS8695用的是NORflash,允许单字节随机访问,代码可以在NORflash里执行,但对于别的ARM芯片,如果用的是NANDflash,由于不支持单字节读以及随机地址访问,则必须把代码拷贝到RAM里,从RAM里执行),只是让我们的代码跑得更快.这个功能是:
把代码在放在RAM里执行.
对于51核这些微控制器(题外话,广义嵌入式有两种,一种是MCU,Microcontrolunit,叫微控制器,特点是运算速度比较慢,偏重于对运算速度要求不高而用于逻辑控制的场合,另外的就是MPU,Microprocessunit,叫微处理器,特点是处理数据能力非常强,专用于对数据处理要求较高的场合)来说,由于它的应用场合往往对速度要求不高,因此,并没有设计成代码可以在RAM执行的结构,也就是说,对标准的51核来说,它的代码全部都是存放在ROM里,每个指令要执行时,先从ROM里取指(读取指令),然后解释执行.而ARM是微处理器,往往用于数据处理场合,对程序效率要求很敏感,因此ARM核都设计成可以从ROM里读取指令运行也可以从RAM里读取指令运行的结构,它的PC指针如果指向的地址是ROM范围内的地址,那么读取的指令就是ROM里的指令,如果指向的是RAM范围,那么读取的指令就是存放在RAM里的.众所周知,RAM的读取速度比FLASH快很多。
KS8695包括很多ARM核的MPU,他们的中断入口都是放在前端靠近0地址的地方,而中断处理往往是非常频繁的,这里的代码被访问率非常高,因此,如果能把中断处理甚至整个执行程序的代码都拷贝到RAM里并在RAM里执行,无疑会大大提高代码的执行效率.在通常的loader设计里,强烈建议至少把中断向量表相关拷贝到内存里,然后使用MMU或其他类型的映射功能,把内存里的中断向量表映射到0地址开头的地址。
ARM里要完成这项工作通常有两个步骤:
第一,把ROM里的代码拷贝到RAM里去,第二,把PC指针指向RAM里的相应地址,让代码从RAM里读取执行.第一步几乎所有的ARMloader都差不多,但读取的是norflash还是nandflash,还是有分别的,如果存放代码的是NORFLASH,可以以字节为单位拷贝代码放到RAM里,如果是nandflash,就要按flash的参数,以block为单位读取放到ram里去.而对于第二步,在上述做完拷贝后,直接把PC置成RAM里存放执行代码的所在地址就OK了。
最后步骤归纳如下:
1.初始化SDRAM控制器,让SDRAM可以读写(而且我想没有谁的程序会不打算使用RAM吧?
?
)
2.建立堆栈区,设置好堆栈指针,把堆栈指针设到SDRAM里面,以便进出函数时的参数能保存到SDRAM里.
3.在内存里建立异常中断向量表,并在loader实现查表功能
4.初始化变量
5.拷贝代码到RAM里,让代码在RAM里执行
下面是loader的代码,已经包括了上面提到的步骤,另外还掺加了一些额外的硬件初始化步骤,注释已经够详细,不想再多说:
;************************************************************************************************
;Entry:
0x0000,0000
;
;************************************************************************************************
INCLUDEmem.inc
INCLUDEcasia.inc
IMPORTMain
IMPORT|Image$$RO$$Base|;/*RO代码段起始地址*/
IMPORT|Image$$RO$$Limit|;/*RO代码段结束地址*/
IMPORT|Image$$RW$$Base|;/*RW代码段起始地址*/
IMPORT|Image$$RW$$Limit|;/*RW代码段结束地址*/
IMPORT|Image$$ZI$$Base|;/*ZI代码段起始地址*/
IMPORT|Image$$ZI$$Limit|;/*ZI代码段结束地址*/
;关于各种模式处理的宏定义,因为进入各种模式时必须保存原现场(pc值及会被影响的寄存器),这保护工作对于所有模式都是一样的代码,因此用一宏定义代替。
MACRO
$HandlerLabelHANDLER$HandleLabel
$HandlerLabel
subsp,sp,#4;decrementsp(tostorejumpaddress)
stmfdsp!
{r0};PUSHtheworkregistertostack(lrdoesnotpushbecauseitreturntooriginaladdress)
ldrr0,=$HandleLabel;loadtheaddressofHandleXXXtor0
ldrr0,[r0];loadthecontents(serviceroutinestartaddress)ofHandleXXX
strr0,[sp,#4];storethecontents(ISR)ofHandleXXXtostack
ldmfdsp!
{r0,pc};POPtheworkregisterandpc(jumptoISR)
MEND
AREAInit,CODE,READONLY
ENTRY
;Image$$RO$$BaseDCD|Image$$RO$$Base|
;Image$$RO$$LimitDCD|Image$$RO$$Limit|
;Image$$RW$$BaseDCD|Image$$RW$$Base|
;Image$$RW$$LimitDCD|Image$$RW$$Limit|
;Image$$ZI$$BaseDCD|Image$$ZI$$Base|
;Image$$ZI$$LimitDCD|Image$$ZI$$Limit|
;=======
;ENTRY
;=======
;0地址开始的执行代码,应该是各种模式的跳转代码,包括中断跳转
bResetHandler
bHandlerUndef;handlerforUndefinedmode
bHandlerSWI;handlerforSWIinterrupt
bHandlerPabort;handlerforPAbort
bHandlerDabort;handlerforDAbort
b.;reserved
bHandlerIRQ;handlerforIRQinterrupt
bHandlerFIQ;handlerforFIQinterrupt
HandlerFIQHANDLERHandleFIQ;这里的HANDLER为前面的MICRO宏定义
HandlerIRQHANDLERHandleIRQ
HandlerUndefHANDLERHandleUndef
HandlerSWIHANDLERHandleSWI
HandlerDabortHANDLERHandleDabort
HandlerPabortHANDLERHandlePabort
;中断向量表查表程序,下面的中断服务里会调用到。
ARM922T核的不是向量中断,也无法设置成向量中断模式,只能自己建立中断向量表,自己处理中断跳转
IsrIRQ
subsp,sp,#4;reservedforPC
stmfdsp!
{r8-r9}
ldrr9,=REG_IRQ_PEND_PRIORITY
ldrr9,[r9];读取优先级最高的中断标志,看现在最需要处理的中断是哪个
;ldrr8,=REG_INT_STATUS;
;ldrr8,[r8];读取所有的中断标志
;andr9,r8,r9;相与,结果存放在r9
ldrr8,=HandleCCR;最开始的中断服务程序入口存放变量,其他的中断服务程序入口存放变量都是以这个为开始递增的
isrloop;通过中断标志,计算该中断的服务程序入口
cmpr9,#0
beqisrquit;如果为零表示中断已经撤销,退出
cmpr9,#1;是否为1
beqisrcontinue;是1则退出循环右移
movr9,r9,lsr#1;r9右移1
addr8,r8,#4;r8+4,每个中断服务程序入口地址占4个字节,因此每次要递增4个
bisrloop
isrcontinue
ldrr8,[r8];获取相应的中断程序入口地址
strr8,[sp,#8]
ldmfdsp!
{r8-r9,pc};把中断程序入口地址置入pc,程序跳入对应的中断服务程序
isrquit
ldmfdsp!
{r8-r9};恢复r8跟r9
addsp,sp,#4;恢复sp指针
subspc,r14,#4;读取原先的pc值(保存在此模式下的r14)并返回,包括返回原先模式,这里必须要用subs指令,否则不能返回原工作模式
ResetHandler
;第一步,最初的初始化程序开始,首先应该是关中断,关看门狗,因为看门狗跟中断在现阶段会给你带来不可预知的错误,完全没必要打开
;特别留意的是,某些ARM核默认是开看门狗的,下面提到的上电后默认情况仅指KS8695这个CPU
;ks8695的所有系统寄存器,都是位于0x03ffxxxx地址
LDRR1,=0x3ffe408;关闭看门狗,默认已经关闭,可不做此步
LDRR0,=0xffffff00
STRR0,[R1]
LDRR1,=0x3ffe200;定义中断模式,所有中断为IRQ中断,非FIQ类型,上电后默认为全IRQ,可不做此步
LDRR0,=0x00000000
STRR0,[R1]
LDRR1,=0x3ffe204;禁止所有中断,免得中断对初始化工作造成影响,此步不做也行,默认上电后是禁止所有中断的
LDRR0,=0x00000000
STRR0,[R1]
LDRR1,=0x3ff4000;
LDRR0,=0xD7F20008
STRR0,[R1]
LDRR1,=0x3ff4008;
LDRR0,=0x0
STRR0,[R1]
;第二步初始化,先把flash映射到0~0x001fffff,初始化SDRAM,把SDRAM映射到0x02000000到0x027fffff
;ks8695上电后默认仅有flash地址,无SDRAM,也没有给SDRAM分配地址,因此必须得初始化SDRAM并映射好,否则就没有RAM用
;映射时映射的地址别覆盖掉系统寄存器区,也就是0x03ffxxxx区
LDRR1,=0x3ff4010;FLASHBANK0控制寄存器,映射FLASH地址为0X00000000到0X001fffff,共2M大小,普通ROM模式
LDRR0,=((0x1f:
SHL:
22)+(0:
SHL:
12)+0x70);末地址10位+首地址10位+其他参数
STRR0,[R1]
LDRR1,=0x3ff4038;SDRAMRASCAS参数
LDRR0,=0x0000000a
STRR0,[R1]
LDRR1,=0x3ff403c;SDRAM缓冲控制寄存器
LDRR0,=0x20033
STRR0,[R1]
LDRR1,=0x3ff4040;SDRAM刷新参数
LDRR0,=0x00000168
STRR0,[R1]
LDRR1,=0x3ff4030;SDRAMBANK0控制器寄存器,映射RAM地址为0x02000000到0x027fffff,2banks,32BIT,8M
LDRR0,=((0x27f:
SHL:
22)+(0x200:
SHL:
12)+0x0e);末地址10位+首地址10位+其他参数
STRR0,[R1]
;第三步,把所有代码拷贝到SDRAM里去,注意,现阶段的SDRAM的访问地址是从0x02000000开始的
;r0=sourceaddress
;r1=targetaddress
;r2=sourceendaddress
ldrr1,=0x02000000
ldrr0,=0x00000000
ldrr2,=|Image$$ZI$$Base|;计算flash区里需要拷贝到RAM的0地址开始的代码长度,也就是ro+rw那段
ldrr3,=|Image$$RW$$Base|
subr2,r2,r3;rw段长度=zibase-rwbase
ldrr3,=|Image$$RO$$Limit|
addr2,r2,r3
copy_ro_loop
ldmiar0!
{r3-r10};每次拷贝8个byte,依次放入r3到r10寄存器
stmiar1!
{r3-r10};每次依次写入8个byte到目的地址
cmpr0,r2
blecopy_ro_loop;少于或等于时都跳,大于时继续
;第四步,下面准备重新把SDRAM映射到0~0x007f,ffff,8m,把flash映射到0x01000000~0x011f,ffff,2m
;这步工作的好处是,映射内存到0地址后,这样无需使用MMU的映射
;因为整个代码已经拷贝到SDRAM里,即使是发生中断,中断入口也是在SDRAM里,直接从SDRAM里执行代码,执行效率非常高
;先把各个入口地址保存好,必须先把地址先存好在寄存器里,由于remap容易造成地址失效,如果不预先保存在寄存器
;而是保存在flash存储或者sdram存储里,remap后地址变了,从存储器读取到的返回地址就不是计划中的地址了
ldrr3,=(remap_rom+0x02000000)
ldrr4,=(remap_ram+0x01000000)
ldrr2,=continue
;把pc指向高端地址,也就是现在的SDRAM内存,跳到内存里,由于内存里已经有一份FLASH的完整拷贝,因此得以继续执行程序
;这是因为下面要做flash的remap,必须先跳到内存里执行后面的代码,否则一remap,pc指向的下一指令就不存在,跑飞了
movpc,r3
;下面这段代码其实已经是在SDRAM里执行,把FLASH地址映射到高端上去,好把低端地址腾出来给SDRAM用
remap_rom
LDRR1,=0x3ff4010;FLASHBANK0控制寄存器,映射FLASH地址为0x0100,0000到0x011f,ffff,共2M大小,普通ROM模式
LDRR0,=((0x11f:
SHL:
22)+(0x100:
SHL:
12)+0x70)
STRR0,[R1]
;同理,必须跳回刚remap好的flash里继续执行代码,然后才能对SDRAM进行remap
movpc,r4;这指令还是在SDRAM里执行,这指令的后面指令,已经是在FLASH里了
;下面这段代码其实已经是在FLASH里执行
remap_ram
LDRR1,=0x3ff4030;SDRAMBANK0控制器寄存器,映射RAM地址为0x0000,0000到0x007f,ffff,2banks,32BIT,8M
LDRR0,=((0x7f:
SHL:
22)+(0x0:
SHL:
12)+0x0e)
STRR0,[R1]
;做完SDRAMremap后,就直接跳回低端地址执行,低端地址现在已经是SDRAM区了
movpc,r2;这指令还是在flash里执行,这指令的后一指令,已经是在SDRAM里了
;上面代码执行完后,下面的代码已经在RAM里执行了
;因为上面的代码已经把SDRAM映射到0地址开始的地方,而此时的PC指针还是指向偏移0地址不远的地方,因此下一执行的指令地址还是在0地址不远的一个地方
;此时,要执行的指令已经是在SDRAM的区域里了
continue
;第五步,初始化各种模式所需要的堆栈,别忘了,经过上面几步映射后,我们的SDRAM有效范围是0~0x007f,ffff,定义的堆栈指针可不能超过这范围
;而且也不能占据低地址0开始的一
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 关于ARM loader的一些心得 关于 ARM loader 一些 心得