最近,一直在研究ucosii在arm2410-s上的移植问题。一开始看的头都有点大了。不过,现在针对arm板子的初始化有了一定的认识。现总结一下。呵呵,方便查看。
先从整体说一下有关嵌入式开发的问题,我们要用嵌入式开发工具编译出相应的烧录文件(*.bin),还有我们必须有把*.bin文件烧录进板子flah的手段或方法。
我用的是博创公司产的arm2410-s的板子,用的编译工具是ads1.2。嵌入式编译器一般需要有 编译、链接、定位。而ads1.2 就可以混合编译c c++ 汇编等语言。而且它最终生成的代码的定位还可以由用户指定。(即我们在编译的时候一般指定编译生成的文件的各个不同属性的排放位置)。这需要ads里的一种扩展名为.csf的文件来指定。其语法比较简单。其简介如下:
ADS分散加载文件语法:
;加载域书写格式:加载域名称 起始地址 属性(可省略) 最大长度(可省略)
;
;加载域起始地址:可以使用绝对地址和偏移地址
;加载域属性:ABSOLUTE:绝对地址,默认属性,不允许加载域互相重叠
; PI:位置无关,允许加载域互相重叠
; RELOG:重定位,允许加载域互相重叠,执行域没有本属性但可以继承加载域的属性
; OVERLAY:覆盖,允许加载域互相重叠,可以在相同地址上建立多个执行域,ADS不支持本属性
;加载域包含1个或多个执行域
;执行域书写格式:执行域名称 起始地址 属性(可省略) 最大长度(可省略)
;
;执行域起始地址:可以使用绝对地址和偏移地址,使用偏移地址(要求可以被4整除的数)的执行域不能指定属性
;执行域属性:ABSOLUTE:绝对地址,默认属性,不允许执行域互相重叠
; FIXED:固定地址,加载和执行都是同一地址,必须使用绝对地址或0偏移地址
; PI:位置无关,允许执行域互相重叠
; OVERLAY:覆盖,允许执行域互相重叠
; UNINIT:未初始化,ZI段将不会被初始化为0,仅仅保留了内存单元,而没有将各初始写入内存单元,或者将内存单元初始化为0
;执行域包含1个或多个输入段
;输入段书写格式:包括模块描述和段描述
;
;模块描述:指定包含模块的文件(包括目标文件.o和库文件.LIB)搜索范围,可以使用通配符*和?
; *.o(所有目标文件)
; *(所有目标文件和库文件)
; .ANY(所有文件)不论放在文件哪个位置,本描述被最后解析,相当于“剩下的所有文件”
;模块:汇编用AREA声明的段,例如AREA StackBottom, DATA, NOINIT
; C中指定段:#pragma arm section rwdata = "SRAM",zidata = "SRAM"
;
;段描述:包括属性描述和段名描述
;
;属性描述:(+ 属性)
; RO:只读代码段+数据段,也可表达为TEXT
; RO-CODE:只读代码段
; RO-DATA:只读数据段
; RW:读写代码段+数据段,也可表达为DATA
; RW-CODE:读写代码段
; RW-DATA:读写数据段
; ZI:初始化为0的数据段,也可表达为BSS
; ENTRY:包含入口点的输入段
;伪属性:FIRST:放在最前
; LAST:放在最后
;
;段名描述:(输入段名)只能用在汇编语言中
;周立功的例程总共三个分散加载文件mem_a.scf,mem_b.scf,mem_c.scf,区别是加载地址不一样
;具体加载哪个,在DebugInExram->ARM Linker->Scatter定义,链接类型选择Scattered
ROM_LOAD 0x0 ;ROM_LOAD 为加载域的名称,其后面的0x0 表示加载域的起始地址(存放程序代码的起始地址)
;image entry point一定要跟ROM_LOAD值一样
{
ROM_EXEC 0x00000000 ;ROM_EXEC 描述了执行域的地址,放在第一块位置定义
{
Startup.o (vectors, +First) ;从起始地址开始放置向量表(Startup.o 为Startup.s 的目标文件)
;+First表示Vector段放在最前面
* (+RO) ;接着放置其它代码(* 是通配符,类似WINDOW下搜索用的通配符)
}
IRAM 0x40000000 ;变量域IRAM ,内部RAM的起始地址为0x40000000
{
Startup.o (MyStacks) ;放置Startup.o (MyStacks)
}
STACKS_BOTTOM +0 UNINIT ;+0表示接着上一段,UNINIT 表示不初始化
{
Startup.o (StackBottom) ;放置AREA StackBottom, DATA, NOINIT
}
STACKS 0x40004000 UNINIT ;接着从0x40004000 开始,放置 AREA Stacks, DATA, NOINIT UNINIT 表示不初始化
{
Startup.o (Stacks)
}
ERAM 0x80000000 ;外部RAM从0x80000000开始为变量域
;如果片外RAM起始地址不为0x8000 0000,则需要修改mem_.scf文件
{
* (+RW,+ZI)
}
HEAP +0 UNINIT ;+0表示接着上一段,UNINIT 表示不初始化
{
Startup.o (Heap) ;放置堆底, AREA Heap, DATA, NOINIT
}
HEAP_BOTTOM 0x80080000 UNINIT ;接着在外部0x80080000 放置堆顶
;这个地址是片外RAM 的结束地址,根据实际情况修改
{
Startup.o (HeapTop)
}
}
“PI” 属性使用示例:
LR_1 0x010000 PI ; 加载域起始地址0x010000.
{
ER_RO +0 ; 执行域从加载区继承PI属性
; 默认执行域起始地址是0x010000, 在此处可以被移动
{
*(+RO) ; 所有RO段.
}
ER_RW +0 ABSOLUTE ; PI属性被ABSOLUTE取代
{
*(+RW) ; RW被紧接着放置,不能移动
}
ER_ZI +0 ; ER_ZI执行域在ER_RW执行域后面
{
*(+ZI) ; 所有ZI段被连续放置
}
}
LR_1 0x010000 ; 加载域起始地址0x010000.
{
ER_RO +0 ; 执行域从加载区继承ABSOLUTE属性
; 默认执行域起始地址是0x010000, 在此处不能移动
{
*(+RO) ; 所有RO段.
}
ER_RW 0x018000 PI ; PI属性取代ABSOLUTE
{
*(+RW) ; RW被放置在0x018000,在此处可以被移动
}
ER_ZI +0 ; ER_ZI执行域在ER_RW执行域后面
{
*(+ZI) ; 所有ZI段被连续放置
}
}
其实每个编译器在生成代码是都是这样,把不同类型的代码放分开放置。只不过,在我们平时用的编译器把这些东西都对大家屏蔽了,我们不用更改而已,但是嵌入式产品有着很强的软件和硬件的结合性。所以有些细节需要自己去做。
然后,我们说一下怎么把编译好的代码load到目标机上。其实方法还是很多的,比如在arm2410-s上我们可以用他们提供的JTAG接口用他们写的烧录程序来把程序烧到flash中。也可以用vivi(板子自己带的一个bootloader 程序)利用串口来把程序烧录到flash中的。
下面,我们说一下arm板子的初始化的:
首先是硬件初始化阶段,
此阶段包括:
复位向量、最小硬件初始化、其余硬件初始化
AREA Init,CODE,READONLY
IMPORT __use_no_semihosting_swi
IMPORT Enter_UNDEF
IMPORT Enter_SWI
IMPORT Enter_PABORT
IMPORT Enter_DABORT
IMPORT Enter_FIQ
ENTRY
b ColdReset
b Enter_UNDEF ;UndefinedInstruction
b Enter_SWI ;syscall_handler or SWI
b Enter_PABORT ;PrefetchAbort
b Enter_DABORT ;DataAbort
b . ;ReservedHandler
b IRQ_Handler ;IRQHandler
b Enter_FIQ ;FIQHandler
对上面的代码进行分析,设置入口地址。
上电复位后直接到程序入口点执行,入口点一般为一个 跳转表,跳转到复位处理程序处开始执行ARM7TDMI系统 的初始化;
l 启动程序首先必须定义入中指针,而且整个应用程序只 有一个入口指针。
例如:AREA Init,CODE,READONLY
ARM要求中断向量必须设置在从OX00000000地址开始,连 续8*4字节的地址空间;
l 向量表包含一系列跳转指令,跳转到相应的中断服务程 序;
l 对各未用中断,使其指向一个含返回指令的哑函数,以 防止错误中断引起系统的混乱;
中断向量表
FIQ 0x1C 外部快速中断
IRQ 0x18 一般外部中断
(Reserved) 0x14 保留
Data Abort 0x10 数据异常
Frefetch Abort 0x0C 预取指异常
Software int 0x08 软件中断
Undef 0x04 未定义指令中断
Reset 0x00 复位中断
其中断向量表的程序如上面所示。
下面初始化堆栈:
bl InitStacks
InitStacks的代码如下:
InitStacks
;Don't use DRAM,such as stmfd,ldmfd......
;SVCstack is initialized before
;Under toolkit ver 2.50, 'msr cpsr,r1' can be used instead of 'msr cpsr_cxsf,r1'
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1 ;UndefMode
ldr sp,=UndefStack
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1 ;AbortMode
ldr sp,=AbortStack
orr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1 ;IRQMode
ldr sp,=IRQStack
orr r1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1 ;FIQMode
ldr sp,=FIQStack
;bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE|NOINT
msr cpsr_cxsf,r1 ;SVCMode
ldr sp,=SVCStack
;USER mode is not initialized.
mov pc,lr ;The LR register may be not valid for the mode changes.
下面,我们做rom重映射。因为我们的程序有的是进行读写的,而rom只能进行读操作,不能进行写操作。所以要把要读写的段重映射到ram中区。(储器地址重映射是当前很多先进控制器所具有的功能。在上一节中已经提
到了0 地址处存储器重映射的例子,简而言之,地址重映射就是可以通过软件配
置来改变一块存储器物理地址的一种机制或方法。
)
rom重映射有很多方法,我们采用在rom里 把代码copy到ram中去。
代码如下:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; copy excption table to sram at 0x0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IMPORT |Load$$EXCEPTION_EXEC$$Base|
IMPORT |Image$$EXCEPTION_EXEC$$Base|
IMPORT |Image$$EXCEPTION_EXEC$$Length|
ldr r0, =|Load$$EXCEPTION_EXEC$$Base| ;source data
ldr r1, =|Image$$EXCEPTION_EXEC$$Base| ;place exception talbe at 0x0
ldr r2, =|Image$$EXCEPTION_EXEC$$Length|
exception_cploop
sub r2, r2, #4
ldmia r0!, {r3}
stmia r1!, {r3}
cmp r2, #0
bge exception_cploop
其中,
其中,exception.s的内容是这样的:
ldr pc, =ColdReset ;reset
ldr pc, =Enter_UNDEF ;UndefinedInstruction
ldr pc, =Enter_SWI ;syscall_handler or SWI
ldr pc, =Enter_PABORT ;PrefetchAbort
ldr pc, =Enter_DABORT ;DataAbort
b . ;ReservedHandler
ldr pc, =IRQ_Handler ;IRQHandler
ldr pc, =Enter_FIQ ;FIQHandler
LTORG ;for save exception address
这里也就是中断向量表,只是这里用ldr指令而没有用l指令是因为现在执行的代码离这几条代码可能很远,用l跳转指令可能跳转不到这几条命令处。不过效应是一样的。
下面就可以呼叫c 函数了,我们用
IMPORT __main
BL __main ;Don't use main() because ......
B .
__main 函数是编译器自带的函数,此函数在呼叫main()函数之前做这样的工作:
对运行环境进行初始化,包括rw代码复制和zi代码段的赋初值0等操作。为c语言的main()函数提供运行环境。
好了,到现在为止,就可以执行c语言函数了,我们下次再接着讨论怎么具体移植uc0sii。
嘻嘻