ARM 的分散加载

来源:互联网 发布:为知笔记导出到有道 编辑:程序博客网 时间:2024/04/28 08:10

引自http://hi.baidu.com/liudefang888/blog/item/6192fd45620ecc3b86947378.html

对于刚学习ARM的人来说,如果分析它的启动代码,往往不明白下面几个变量的含义:|Image$$RO$$Limit||Image$$RW$$Base||Image$$ZI$$Base|

首先申明我使用的调试软件为ADS1.2,当我们把程序编写好以后,就要进行编译和链接了,在ADS1.2中选择MAKE按钮,会出现一个Errors and Warnings 的对话框,在该栏中显示编译和链接的结果,如果没有错误,在文件的最后应该能看到Image component sizes,后面紧跟的依次是CodeRO Data RW DataZI DataDebug各个项目的字节数,最后会有他们的一个统计数据:

Code 163632RO Data 20939RW Data 53ZI Data 17028

Tatal RO size (Code+ RO Data)             184571 (180.25kB)

Tatal RW size(RW Data+ ZI Data)           17081(16.68 kB)

Tatal ROM size(Code+ RO Data+ RW Data)   184624(180.30 kB)

后面的字节数是根据用户不同的程序而来的,下面就以上面的数据为例来介绍那几个变量的计算。

ADSDebug Settings中有一栏是Linker/ARM Linker,在output选项中有一个RO base选项,下面应该有一个地址,我这里是0x0c100000,后面的RW base 地址是0x0c200000,然后在Options选项中有Image entry point ,是一个初始程序的入口地址,我这里是0x0c100000

有了上面这些信息我们就可以完全知道这几个变量是怎么来的了:

|Image$$RO$$Base| = Image entry point = 0x0c100000;表示程序代码存放的起始地址

|Image$$RO$$Limit|=程序代码起始地址+代码长度+1=0x0c100000+Tatal RO size+1

= 0x0c100000 + 184571 + 1 = 0x0c100000 +0x2D0FB + 1

= 0x0c12d0fc

|Image$$RW$$Base| = 0x0c200000;RW base地址指定

|Image$$RW$$Limit| =|Image$$RW$$Base|+ RW Data 53 = 0x0c200000+0x374的倍数,055,共56个单元)

=0x0c200037

|Image$$ZI$$Base| = |Image$$RW$$Limit| + 1 =0x0c200038

|Image$$ZI$$Limit| = |Image$$ZI$$Base| + ZI Data 17028

                            =0x0c200038 + 0x4284

                            =0x0c2042bc

也可以由此计算:

|Image$$ZI$$Limit| = |Image$$RW$$Base| +TatalRWsize(RWData+ZIData) 17081

                            =0x0c200000+0x42b9+3(要满足4的倍数)

                            =0x0c2042bc

 

原文地址http://blog.csdn.net/yyt7529/archive/2009/06/05/4245604.aspx

 

简单应用时可以不写.scf文件。而在"Output"页中选择"Simple".然后填写"RO Base""RW Base"的起始地址。在"Lay Out"页中,填写Object/Symble: Startup.o, Section: Start.编写启动文件:Startup.s.

 

"Option"页里的"Image Entry Point"填入起始地址。

--------------------------------------------------------------------------------

 

Scatter-Load Description File的结构:

 

".scf"文件中的"+RW"对应".s"源文件中的"READWRITE".

".scf"文件中的"+ZI"对应".s"源文件中的"NOINIT".

".scf"文件中的"+RO"对应".s"源文件中的"READONLY".

 

".s"源文件中有:

AREA area_name CODE/DATA,READONLY/NOINIT/READWRITE

END

 

 

".scf"的例子

 

内容 注解

ROM_LOAD 0x80000000

{ Name of Load Region, Start Address for Load Region and Maximum size of Load Region(省略了)

  ROM_EXEC 0x80000000 0x20000

{                        片外存储区,从0x80000000开始,最多0x20000字节。

  Startup.o(Vector,+First)            Startup模块的Vector段放在最前面。注1

  *(+RO);其他所有模块中的所有代码和只读的数据放在这里。

  } 

  IRAM 0x40000000 0x00004000

{;片内RAM区,从0x40000000开始,最多0x4000字节

  Startup.o(MyStacks,+first);指定Startup.oMyStacks放在最前面。

  Startup.o(+RW,+ZI) Startup.o中的其他+RW/+ZI段。注1

  os_cpu_a.o(+RW,+ZI) 

  } 

 STACKS 0x40004000 UNINIT

{;片内16K RAM的顶端,存放不需要被"C library"初始化的段。

  Stack.o(+ZI)2

  } 

  ERAM 0x80040000

{ 

  *(+RW,+ZI) 

  } 

  HEAP +0 UNINIT

{"+0"表示接着上一段"ERAM"的结尾,继续安排存储区。

  Heap.o(+ZI)3

  } 

}  

 

下面是在scf文件中引用过的源文件示意: "Startup.s"

code 32

area Vectors,CODE,READONLY

entry

...

end1:在"Startup.o"里面会生成名为"Vectors"的段,段的属性为"READONLY"

"Stack.s"

area Stacks, DATA, NOINIT

export StackUsr

StackUsr SPACE 1

end2:"Stack.o"里面会生成名为"Stacks"的段,段的属性为"NOINIT",该属性对应scf文件中的"+ZI".该段不需要初始化或者可以被初始化为"0".

"Heap.s"

area Heap,DATA,NOINIT

export bottom_of_heap

bottom_of_heap SPACE 1

end3: "Heap.o"里面名为"Heap"的段。

 

 

Scatter文件中最好每一个Region都加一个Maximum参数,这样当编译时如果实际使用的空间大于Maximum Size,会有Error:16220E: Excution region xxx size (xxx bytes) exceeds limit (xx bytes)。如果地址有重复,会有Error: 16221E: Excution region xxx overlaps with excution region xxx。前一个Region的首地址 + Maximum > 后一个Region的首地址时不一定有Error。只有当一分配的内存出现覆盖时才会有Error

 

Region"UNINIT"之类的参数要放在"Maximum size"参数之前。

 

在一个Region中,RAM的分配不是按照罗列的顺序来的。要想让汇编中使用的变量有固定的位置,可以把所有汇编文件产生的".o"放在同一个Region中。如:

IRAM1 0x40000000

{

startup.o(+RW,+ZI)

ASMSourceCode1.o(+RW,+ZI)

ASMSourceCode2.o(+RW,+ZI)

}

IRAM2 +0

{

CSourceCode1.o(+RW,+ZI)

CSourceCode2.o(+RW,+ZI)

}

这样,所有汇编中定义的变量地址就相对集中了。

如果只有一个汇编文件如startup.s,也可以这样:

IRAM 0x40002000 0x1000

{

startup.o (Mystack,+first)

*(+RW,+ZI)

}

用一个"+first"强行将startup.s中的Mystack放在0x40002000位置。

 

"Edit -> DebugRel Settings...->ARM Linker"中选中"Image map"。编译后在Error & Warnings窗口会显示出详细的内存分配情况。如果在"List file name"中指定一个输出文件名,该祥单会直接存在制定文件中以供多次研究。

 

 

--------------------------------------------------------------------------------

 

关于JTAG接口:

P1.20/TRACESYNC应该加上拉电阻以禁止TRACE功能。PINSEL2一定要在程序开始时初始化一下。 LPC2210

 JTAG

 

 

  1,2/VDD3.3V 

P1.31/nTRST, input 3/nTRST, output EasyJTAG中有上拉电阻。

P1.28/TDI, input 5/TDI, output EasyJTAG中有上拉电阻。

P1.30/TMS, input 7/TMS,output EasyJTAG中有上拉电阻。

P1.29/TCK, input/output 9/TCK, input/output EasyJTAG中有上拉电阻。

P1.26/RTCK, input 11/RTCK, output P1.26外接下拉电阻。

P1.26有内部上拉电阻,故测量时该引脚会呈现高电平。但是在复位时,它的上拉电阻不起作用,只有外部的下拉电阻起作用,P1.26 = 0V, 所以上电后PINSEL2D3~D0会是0x04(B0100)JTAG有效。

若将P1.26接到3.3V再复位,此时PINSEL2D3~D0将会是0x00JTAG无效。

P1.27/TDO, output 13/TDO, input EasyJTAG中有上拉电阻。

nRESET, input 15/nRST, output EasyJTAG中有上拉电阻。

  4,6,8,10,12,14,16,18,20/GND 

  17,19/NC 

 

 

G18控制板采用LPC2114,每次运行Axd都不会正确调入程序。原因如下:

 

有一次是因为已经有一个Axd在运行了,打开第二个Axd,当然不会正确调入程序。

还有一次是重新编译了一下,就好了。

以上两次都不奇怪,奇怪的是下面几次:

"Config Target -> Config -> Easy JTag Setup"随便点两下"Halt Mode"中的选项,然后一路点击"OK",会出现"Reload the last Image?",点击"Yes"。有时会有正确的程序被调入,但有时候不成功。要检验是不是已经成功调入了,只要按下"Ctrl-D"显示Disassembly窗口,即可看到芯片中的程序是否正确。

"Option -> Config Interface -> Session File -> Session file Options"中选择"Reload Images",之后每次启动Axd都会提示"The processor ARM_1 already has image(s) loaded. Continue the operation will replace the currently loaded images(s).... Do you wish to continue?"选择"Yes",有时候也可以成功调入程序。

当然,在"Easy JTag Setup -> Aux Option"中要选中"Erase Flash when need".

固化的程序中有禁止JTag调试端口的语句(操作PINSEL2的语句),连不上时用LPC2000 Flash Utility擦除了Flash。偶尔可行。

注意使用LPC2000 Flash Utility时要先将电路复位,再点"OK".

 

当然最根本的解决办法是将计算机并口设置为"EPP"模式。其他地方都按照"Default"就可以了。

 

 

 

--------------------------------------------------------------------------------

 

 

有效用户代码:

ARM把“向量表所有32位数据累加和为0作为有效用户代码的条件,只适用于使用片内程序存储器的时候,片外程序存储器无此限制。

 

C语言程序通常需要一段用于初始化的汇编代码,通常存储为"Startup.s",它实现的任务通常是:

1、做好中断向量表

2、初始化外部总线控制器/堆栈/目标板基本模块。

3、给库函数使用的"__user_inital_stackheap"

4、除数为零时的死循环"__rt_div0: B __rt_div0"

5、支持加密功能的语句。

6、定义堆栈空间:AREA MyStacks, DATA,NOINIT,ALIGN = 2

要定义栈的起始地址:IrqStackSpace SPACE ...

和栈的头部:StackIrq DCD IrqStackSpace + Length*4。因为栈是向下生长的。

 

与目标板有关的初始化程序可以放在一个名为"Target.c"的文件里。

1、定义中断处理入口:void IRQ_Exception(void), void FIQ_Exception(void), void Timer0_Exception(void)

2、向量中断控制器初始化。

3remap,系统时钟,实时时钟,存储器加速。

 

 

 

--------------------------------------------------------------------------------

 

 

C语言中的延时:

__asm{

nop;

nop;}

即可。

 

关注C编译器:"=="的优先级确实比"&"的高,所以,凡牵扯到逻辑的东西,用"()"确认优先级,以避免出现低级错误。

 

 

 

--------------------------------------------------------------------------------

 

 

对定时器的操作:

void Timer0Init(uint8 VICSlot, uint32 fdiv)

{

T0PR = 0; //Prescaling = 0

T0PC = 0; //Prescalar Counter

T0TC = 0; //T0 Counter

T0MR0 = Fpclk / fdiv; //计数周期

T0MCR = 0x03; //计数达到T0MR0则置位中断,计数器复位并继续运行。

T0CCR = 0x00; //不用捕获模式

T0TR = 0xffffffff; //清中断

T0TCR = 0x01; //运行

 

if(VICSlot <= 15){

   *((uint32*)(&VICVectAddr0 + VICSlot)) = (uint32)Timer0_Exception;

   *((uint32*)(&VICVectCntl0 + VICSlot)) = 0x20 | 0x04;

   VICIntEnable = 1 << 0x04;

}

}

 

 

注意:

1"*((uint32*)(&VICVectAddr0 + VICSlot)) = ..."中,&VICVectAddr0作为基址,VICSlot作为偏移量。由于前面已经有(uint32*)声明这是一个指向uint32的指针,故偏移量每变化一个数字代表地址变化了4个字节,在基址与偏移量相加的时候,系统自动将VICSlot乘以4。如果程序中写成"... + 4 * VICSlot"就错了。

2、一定要用"Fpclk / fdiv"设置,以延时1/fdiv秒。该参数不可以以uS为单位。若"Fpclk * us / 1000000"在计算中会乘法溢出,不易避免,又无警告,故不可用。

 

 

 

--------------------------------------------------------------------------------

 

 

I2C占空比的设置:

I2SCLH = (Fpclk / fi2c + 1) / 2;

I2SCLL = (Fpclk / fi2c) / 2;

妙哉!无论"Fpclk / fi2c"是奇是偶,单方面的"Fpclk / fi2c + 1"使得I2C总周期"Fpclk / fi2c = I2SCLH + I2SCLL"在方法上没有误差。

 

I2C必须工作在中断模式。因为:"When the "SI" flag is reset, no serial interrupt is requested, and there is no stretching of the serial clock on the SCL line."

 

I2C的资料在http://www.semiconductors.philips.com/acrobat/various/8xC552_562OVERVIEW_2.pdf.

 

 

 

--------------------------------------------------------------------------------

 

 

宏的应用:

 

在片内外设如I2C,UART,T0,T1,SPI的设置过程中,都需要根据Fpclk计算出一些设定值。我讨厌用ARM做除法,所以就用宏来实现,除法在编译时就可以完成。

 

首先,所有片内外设的初始化程序都名为:"void _xxxInit();"。之所以在正式函数名之前加一个"_",是为了与宏区别开,不至于误写函数。因为宏的名字与函数名相同,只是全部大写,并且前面没有"_"。如:

 

#define TIMER0INIT(VICSlot,ms) _Timer0Init(VICSlot,Fpclk/100*ms/10);

 

void _Timer0Init(uint8 VICSlot,uint32 ClockCycle);

在函数中,直接"T0MR0 = ClockCycle"即可。

 

注意宏里面的表达式,不可写成"Fpclk*ms/1000",因为如果这样写,当mS太大时,比如mS=1000, Fpclk*mS=(11059200/4)*1000=0xA4CB8000,算到这一步,编译器认为是溢出(它把计算结果看作是有符号数),只要有溢出的警告出现,设置就不正确。

也不可以先做除法,以防止吃掉精度,使计算结果为"0"而令定时器死掉。

总之,既要保证计算精度,又不可以出现溢出警告。

 

 

 

--------------------------------------------------------------------------------

 

 

关于C编译器使用的堆栈设置:

1、在Startup.s中有一句:

MSR CPSR_C,#0x5f   //系统模式

LDR SP, =UsrStack //用户栈

2、在Scatter文件中,有

STACKS 0x40004000 UNINT

{

   UsrStack.o(+ZI)

}

3、在UsrStack.s中有

AREA Stacks, DATA, NOINT

EXPORT UsrStack

UsrStack SPACE 1

END

定义一个UsrStack,大小都无所谓,把它放在可用物理内存的最顶端。C编译器在编译子程序调用时,会将要保护的寄存器压栈,如:

stmfd r13!,{r3-r7,r14}

其中,r13的别名是SP

这是一个满递减堆栈。即SP指向的单元内的数据是有效的,入栈时先减SP再存数据。