关于ARM loader的一些心得Word格式文档下载.docx
《关于ARM loader的一些心得Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《关于ARM loader的一些心得Word格式文档下载.docx(11页珍藏版)》请在冰豆网上搜索。
![关于ARM loader的一些心得Word格式文档下载.docx](https://file1.bdocx.com/fileroot1/2022-11/30/75879fa5-786d-4e5b-83ce-21f9b187d403/75879fa5-786d-4e5b-83ce-21f9b187d4031.gif)
变量的初始化.
变量的初始化是个什么样的概念呢?
举个简单的例子,如果你程序里有个变量这样定义(这是不可能避免的吧):
uchari=5;
main()
{
}
就是说,i这个变量,初始值是5,具体反映到硬件上面,就是说,i这个变量所对应的内存单元,它在相应程序要执行之前,这个内存单元里保存的数值应该是5.否则,这个内存单元保存的是上电时硬件随机产生的不确定数值.
自己来做变量初始化的工作,其实不是必要的,而仅仅是当使用的编译器是ADS,才是必要的.如果使用的是其他编译器,有可能不用做这步.譬如Keil的编译器在链接成最终代码时,会自动添加一段变量初始化的代码,而GCC编译器记得也是这样.现在用的编译器是ADS。
因此,很遗憾,loader又得复杂一点点了,新的列表如下:
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了。
最后步骤归纳如下:
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
reservedforPC
{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
获取相应的中断程序入口地址
strr8,[sp,#8]
{r8-r9,pc};
把中断程序入口地址置入pc,程序跳入对应的中断服务程序
isrquit
{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
LDRR1,=0x3ffe204;
禁止所有中断,免得中断对初始化工作造成影响,此步不做也行,默认上电后是禁止所有中断的
LDRR1,=0x3ff4000;
LDRR0,=0xD7F20008
LDRR1,=0x3ff4008;
LDRR0,=0x0
第二步初始化,先把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:
12)+0x70);
末地址10位+首地址10位+其他参数
LDRR1,=0x3ff4038;
SDRAMRASCAS参数
LDRR0,=0x0000000a
LDRR1,=0x3ff403c;
SDRAM缓冲控制寄存器
LDRR0,=0x20033
LDRR1,=0x3ff4040;
SDRAM刷新参数
LDRR0,=0x00000168
LDRR1,=0x3ff4030;
SDRAMBANK0控制器寄存器,映射RAM地址为0x02000000到0x027fffff,2banks,32BIT,8M
LDRR0,=((0x27f:
22)+(0x200:
12)+0x0e);
第三步,把所有代码拷贝到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!
每次依次写入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
FLASHBANK0控制寄存器,映射FLASH地址为0x0100,0000到0x011f,ffff,共2M大小,普通ROM模式
LDRR0,=((0x11f:
22)+(0x100:
12)+0x70)
同理,必须跳回刚remap好的flash里继续执行代码,然后才能对SDRAM进行remap
movpc,r4;
这指令还是在SDRAM里执行,这指令的后面指令,已经是在FLASH里了
下面这段代码其实已经是在FLASH里执行
remap_ram
SDRAMBANK0控制器寄存器,映射RAM地址为0x0000,0000到0x007f,ffff,2banks,32BIT,8M
LDRR0,=((0x7f:
22)+(0x0:
12)+0x0e)
STRR0,[R1]
做完SDRAMremap后,就直接跳回低端地址执行,低端地址现在已经是SDRAM区了
movpc,r2;
这指令还是在flash里执行,这指令的后一指令,已经是在SDRAM里了
上面代码执行完后,下面的代码已经在RAM里执行了
因为上面的代码已经把SDRAM映射到0地址开始的地方,而此时的PC指针还是指向偏移0地址不远的地方,因此下一执行的指令地址还是在0地址不远的一个地方
此时,要执行的指令已经是在SDRAM的区域里了
continue
第五步,初始化各种模式所需要的堆栈,别忘了,经过上面几步映射后,我们的SDRAM有效范围是0~0x007f,ffff,定义的堆栈指针可不能超过这范围
而且也不能占据低地址0开始的一