1:PRESERVE8:

Reguire8和Preserve8

C和汇编有8位对齐的要求,这两个伪指令可以满足此要求,存在REQUIRE8<——> PRESERVE8的对应关系,但不是说有一个REQUIRE8就要有一个 PRESERVE8,如果是一个c文件和一个汇编文件的调用,也就涉及一个PRESERVE8或者是一个REQUIRE8.
另外,REQUIRE8和PRESERVE8并不完成8 byte 对齐的操作,对齐由ALIGN完成。

将ADS的代码移植到KEIL MDK上需要做的修改:
当用户拥有ADS遗留工程的所有源代码时,使用MDK重新编译链接全部代码是最好的解决方法,MDK中的新版本编译工具会重新生成满足堆栈8BYTE对齐要求的目标文件,避免由于堆栈不对齐引起的链接错误.

从ADS到KEIL很重要的一个修改的地方就是这里的8BYTE对齐,想要编译通过,在startup.s里面我们必须加入PRESERVE8指令,使得寄存器8BYTE对齐.

代码:
CODE32
PRESERVE8 ;这个在KEIL里面是必须的,要求8BYTE对齐.在ADS的启动代码中就没有.
AREA vectors,CODE,READONLY


2: ARM的处理器可工作于多种模式,下面设置个模式的一些参数.
Mode_USR        EQU     0x10 用户模式
Mode_FIQ        EQU     0x11 快中断模式
Mode_IRQ        EQU     0x12 中断模式
Mode_SVC        EQU     0x13 管理模式
Mode_ABT        EQU     0x17 中止模式
Mode_UND        EQU     0x1B 未定义模式
Mode_SYS        EQU     0x1F 系统模式
参数的由来:这里各个模式的参数是由寄存器CPSR的模式位设置M[4:0]得来的,比如这里的用户模式,CPSR的M[4:0]设置为10000就是0x10,同理其他.详见<<ARM嵌入式系统基础教程>>P47页,CPSR设置很关键!

3:
I_Bit           EQU     0x80            ; when I bit is set, IRQ is disabled
F_Bit           EQU     0x40            ; when F bit is set, FIQ is disabled
也和CPSR寄存器的设置有关,这里两位是禁止/开启快速中断和一般中断的设置.

4: 各模式下定义的堆栈地址.
UND_Stack_Size EQU     0x00000000
SVC_Stack_Size EQU     0x00000100
ABT_Stack_Size EQU     0x00000000
FIQ_Stack_Size EQU     0x00000000
IRQ_Stack_Size EQU     0x00000100
USR_Stack_Size EQU     0x00000200

设置堆栈大小
Stack_Size      EQU     (UND_Stack_Size + SVC_Stack_Size + ABT_Stack_Size +
                         FIQ_Stack_Size + IRQ_Stack_Size + USR_Stack_Size)

               
  AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
Stack_Top       EQU     Stack_Mem + Stack_Size
堆栈大小的设置,各公司写的启动代码有所不同,但是不影响大局,可以借鉴一些你认为比较简单的启动代码,然后写自己的堆栈地址和大小设置程序.

5:堆的设置
Heap_Size       EQU     0x00000000
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
Heap_Mem        SPACE   Heap_Size

AREA Init,CODE,READONLY,ALIGN=3 //指定后面的指令为8位对齐(2的3次方)
align n
它的含义就是使得下面的代码按一定规则对齐,.align n 指令的对齐值有两种方案,n 或 2^n ,各种平台最初的汇编器一般都不是gas,采取方案1或2的都很多,gas的目标是取代原来的汇编器,必然要保持和原来汇编器的兼容,因此在gas中如何解释 .align指令会显得有些混乱,原因在于保持兼容。arm-linux是按照2^n的方案对齐的. ARM的.align 5就是2的5次方(32位)对齐,也就是4字节(32位)对齐.

6: AREA    RESET, CODE, READONLY
   ARM
下面的是ARM的代码,不是THUMB.

基于ARM的芯片多数为复杂的片上系统,这种复杂系统里的多数硬件模块都是可配置的,需要由软件来设置其需要的工作状态。因此在用户的应用程序之前,需要由专门的一段代码来完成对系统的初始化。由于这类代码直接面对处理器内核和硬件控制器进行编程,一般都是用汇编语言。一般通用的内容包括:


中断向量表
初始化存储器系统
初始化堆栈
初始化有特殊要求的断口,设备
初始化用户程序执行环境
改变处理器模式
呼叫主应用程序


中断向量表

ARM芯片上电或复位后,系统进入管理模式、ARM状态、PC(R15)指向0x00000000地址处。中断向量表为每一个中断设置1个字的存储空间,存放一条跳转指令,通过这条指令使PC指针指向相应的中断服务程序入口,继而执行相应的中断处理程序。

ARM要求中断向量表必须放置在从0地址开始,连续8X4字节的空间内。每当一个中断发生以后,ARM处理器便强制把PC指针置为向量表中对应中断类型的地址值。因为每个中断只占据向量表中1个字的存储空间,只能放置一条ARM指令,使程序跳转到存储器的其他地方,再执行中断处理。

中断向量表的程序实现通常如下表示:

CODE32

AREA    StartUp,CODE,READONLY

ENTRY

;中断向量表

Vectors
 LDR     PC, ResetAddr  //把Reset_Addr地址处的内容放入PC中
        LDR     PC, UndefinedAddr
        LDR     PC, SWI_Addr
        LDR     PC, PrefetchAddr
        LDR     PC, DataAbortAddr
        DCD     0xb9205f80
        LDR     PC, [PC, #-0xff0] //LDR  PC, IRQ_Addr  当前PC值减去0xFF0等于IRQ中断入口地址 
        LDR     PC, FIQ_Addr

Reset_Addr      DCD     Reset_Handler //#该地址标号存放Reset_Handler程序段的入口地址
Undef_Addr      DCD     Undef_Handler
SWI_Addr        DCD     SWI_Handler
PAbt_Addr       DCD     PAbt_Handler
DAbt_Addr       DCD     DAbt_Handler
                DCD     0                      ; Reserved Address
IRQ_Addr        DCD     IRQ_Handler
FIQ_Addr        DCD     FIQ_Handler

Undef_Handler   B       Undef_Handler
SWI_Handler     B       SWI_Handler
PAbt_Handler    B       PAbt_Handler
DAbt_Handler    B       DAbt_Handler
IRQ_Handler     B       IRQ_Handler
FIQ_Handler     B       FIQ_Handler

注:
1、其中关键字ENTRY是指定编译器保留这段代码,因为编译器可能会认为这是一段亢余代码而加以优化。链接的时候要确保这段代码被链接在0地址处,并且作为整个程序的入口。

2、异常是由内部或外部源产生的以引起处理器处理的一个事件。ARM处理器核支持7种类型的异常。异常出现后,CPU强制从异常类型对应的固定存储地址开始执行程序。这个固定的地址就是异常向量。向量从上到下依次为复位、未定义指令异常、软件中断、预取指令中止、预取数据中止、保留的异常、IRQ和 FIQ。

3、IRQ向量“LDR   PC, [PC, #-0xff0]” 使用的指令与其它向量不同。在正常情况下这条指令所在地址0X00000018。当CPU执行这条指令但还没有跳转时,PC的值为0X00000020,0X00000020减去 0X00000FF0为 0XFFFFF030,这是向量中断控制器(VIC)的特殊寄存器VICVectAddr。这个寄存器保存当前将要服务的IRQ的中断服务程序的入口,用这一条指令就可以直接跳转到需要的中断服务程序中。
VICVectAddr寄存器的地址0xFFFFF030:PC当 前位置是0x18,读出的值为0x18+8 = 0x20,0x20-0xff0 = 0xFFFFF030

4、至于在保留的异常向量“DCD 0xb9205f80”位置填数据0xb9205f8是为了使向量表中所有的数据32位累加和为0。

初始化存储器系统

1、存储器类型和时序配置
通常Flash和SRAM同属于静态存储器类型,可以合用同一个存储器端口;而DRAM因为有动态刷新和地址线复用等特性,通常配有专用的存储器端口。存储器端口的接口时序优化是非常重要的,这会影响到整个系统的性能。因为一般系统运行的速度瓶颈都存在于存储器访问,所以存储器访问时序应尽可能的快;而同时又要考虑到由此带来的稳定性问题。

2、存储器地址分布
一种典型的情况是启动ROM的地址重映射。

初始化堆栈

因为ARM有7种执行状态,每一种状态的堆栈指针寄存器(SP)都是独立的。因此,对程序中需要用到的每一种模式都要给SP定义一个堆栈地址。方法是改变状态寄存器内的状态位,使处理器切换到不同的状态,让后给SP赋值。注意:不要切换到User模式进行User模式的堆栈设置,因为进入User模式后就不能再操作CPSR回到别的模式了,可能会对接下去的程序执行造成影响。

InitStack  

        MOV     R0, LR  ;使用R0来保存返回地址 

        MSR     CPSR_c, #0xd2 ;设置中断模式堆栈 /切换到IRQ模式,并初始化IRQ模式的堆栈/

        LDR     SP, StackIrq 

        MSR     CPSR_c, #0xd1 ;设置快速中断模式堆栈

        LDR     SP, StackFiq

        MSR     CPSR_c, #0xd7 ;设置中止模式堆栈

        LDR     SP, StackAbt

        MSR     CPSR_c, #0xdb ;设置未定义模式堆栈

        LDR     SP, StackUnd

        MSR     CPSR_c, #0xdf ;设置系统模式堆栈

        LDR     SP, StackSys

        MOV     PC, R0  ;使用R0来返回

注:

1、MSR  CPSR_c, #0xdf, 这一句把ARM的工作模式设置为系统模式,或者也可以说是用户模式, 因为系统模式与用户模式是共享相同的寄存器组. 用0xdf对CPSR寄存器赋值,就把IRQ中断关闭了(可以查一下CRSR的详细说明), 代码正常执行时处理器是处在用户模式的,所以IRQ中断是不会执行的.

2、并不是每一种模式下的堆栈都用设置的, 比如说如果你的程序中不会用到FIQ,就可以不用设置快速中断下的堆栈.

3、LDR  SP, =StackUsr这个语句, 其它都是没有=号的, 为什么这个要用等号呢? 这就是LDR伪指令与LDR指令的区别了, LDR  SP, =StackUsr是把StackUsr表示的地址装载到sp, LDR  SP, StackUnd是把StackUnd表示地址的内容装载到sp

StackIrq DCD (IrqStackSpace + IRQ_STACK_LEGTH * 4 - 4)

StackFiq DCD     (FiqStackSpace + FIQ_STACK_LEGTH * 4 - 4)

StackAbt DCD     (AbtStackSpace + ABT_STACK_LEGTH * 4 - 4)

StackUnd DCD     (UndtStackSpace + UND_STACK_LEGTH * 4 - 4)

StackSys DCD (SysStackSpace + SYS_STACK_LEGTH * 4 -4 )

可以看到,没有”=”的标号都已经用DCD初始化了, 而StackUsr到底是什么呢, 它是由下面的语句决定的

/* 分配堆栈空间 */

AREA    MyStacks, DATA, NOINIT

IrqStackSpace   SPACE IRQ_STACK_LEGTH * 4 ;中断模式堆栈

FiqStackSpace   SPACE FIQ_STACK_LEGTH * 4  ;快速中断模式堆栈

AbtStackSpace   SPACE ABT_STACK_LEGTH * 4  ;中止义模式堆栈

UndtStackSpace  SPACE UND_STACK_LEGTH * 4  ;未定义模式堆栈

SysStackSpace   SPACE SYS_STACK_LEGTH * 4  ;系统模式堆栈

因为程序需要切换模式,而且程序退出时CPU的模式已经不再是管理模式而是系统模式LR已经不再保存返回程序地址,所以程序首先把返回地址保存到 R0中,同时使用R0返回。然后程序把处理器模式转化为IRQ模式,并设置IRQ模式的堆栈指针。其中变量Stacklrq保存着IRQ模式的堆栈指针的初始值,Irqstackspace是分配给 IRQ模式的堆栈空间的开始地址,IRQ_STACK_LEGTH是用户定义的常量,用于设置 IRQ模式的堆栈空间的大小。程序使用同样的方法设置FIQ模式堆栈指针、中止模式堆栈指针、未定义堆栈指针和系统模式堆栈指针。

程序使用编译器分配的空间作为堆栈,而不是按照通常的做法把堆栈分配到 RAM的顶端,之所以这样是因为这样做不必知道RAM顶端位置,移植更加方便;编译器给出的占用RAM空间的大小就是实际占用的大小,便于控制RAM的分配。

初始化有特殊要求的端口,设备

初始化应用程序执行环境

映像一开始总是存储在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面执行,也可以转移到速度更快的RAM中执行;而RW和ZI这两部分是必须转移到可写的RAM里去。所谓应用程序执行环境的初始化,就是完成必要的从ROM到RAM的数据传输和内容清零。
下面是在ADS下,一种常用存储器模型的直接实现:
LDR    r0,=|Image$$RO$$Limit| //得到RW数据源的起始地址
LDR    r1,=|Image$$RW$$Base|  //RW区在RAM里的执行区起始地址
LDR    r2,=|Image$$ZI$$Base|   //ZI区在RAM里面的起始地址
CMP    r0,r1                  比较它们是否相等
      BEQ    %F1
0     CMP    r1,r3
      LDRCC  r2,[r0],#4
      STRCC  r2,[r1],#4
      BCC    %B0
1     LDR    r1,=|Image$$ZI$$Limit|
      MOV   r2,#0
2     CMP    r3,r1
      STRCC  r2,[r3],#4
      BCC    %B2
程序实现了RW数据的拷贝和ZI区域的清零功能。其中引用到的4个符号是由链接器第一输出的。
|Image$$RO$$Limit|:表示RO区末地址后面的地址,即RW数据源的起始地址
|Image$$RW$$Base|:RW区在RAM里的执行区起始地址,也就是编译器选项RW_Base指定的地址
|Image$$ZI$$Base|:ZI区在RAM里面的起始地址
|Image$$ZI$$Limit|:ZI区在RAM里面的结束地址后面的一个地址
程序先把ROM里|Image$$RO$$Limt|开始的RW初始数据拷贝到RAM里面|Image$$RW$$Base|开始的地址,当RAM这边的目标地址到达|Image$$ZI$$Base|后就表示RW区的结束和ZI区的开始,接下去就对这片ZI区进行清零操作,直到遇到结束地址|Image$$ZI$$Limit|

改变处理器模式

因为在初始化过程中,许多操作需要在特权模式下才能进行(比如对CPSR的修改),所以要特别注意不能过早的进入用户模式。
内核级的中断使能也可以考虑在这一步进行。如果系统中另外存在一个专门的中断控制器,这么做总是安全的。

呼叫主应用程序

当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序。最简单的一种情况是:

IMPORT main

B      main

直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。

在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。

IMPORT __main

B     __main

__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()函数。

进入C应用程序

至此,系统各部分的初始化基本完成,可以直接从启动代码转入到应用程序的main()函数入口。从启动代码转入到应用程序的实例代码如下:

IMPORT main
LDR R0,=main
BX R0