找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4798|回复: 2
收起左侧

LPC11C14(Cortex-m0-->ARM7)启动代码分析

[复制链接]
ID:60266 发表于 2014-8-18 02:50 | 显示全部楼层 |阅读模式
本帖最后由 heise 于 2014-8-18 02:54 编辑

启动代码是芯片复位后进入C语言的main()函数前执行的一段代码,主要为运行C语言程序提供基本运行环境。启动代码文件:startup.s。startup.s包含异常向量表和系统初始化代码,保存C语言使用的堆和栈的开始位置,包括异常处理程序和目标板特殊的代码。汇编学习:ARM伪指令,在汇编程序中经常会被使用,包括以下几条:      —AREA   
—ALIGN   
   — CODE16 、CODE32   
    —ENTRY   
    —END   
    —EQU   
    —EXPORT (或GLOBAL )   
    —IMPORT   
    —EXTERN   
    — GET(或INCLUDE )   
    —INCBIN   
  —KEEP:告诉编译器将局部符号包含在目标文件的符号表中
  —NOFP:禁止源程序中包含浮点运算指令
  —REQUIRE:指定段之间的相互依赖关系
  — REQUIRE8及PRESERVE8:指示当前代码中(要求)数据栈8字节对齐
  —RN   
   —ROUT   
EQU:其作用类似于C 语言中的# define
Expression_name EQU Expression
  此后程序中凡需要用到该表达式指出,就可以用表达式名来代替了。可见,EQU的引入提高了程序的可读性,也使其容易修改。
  上始终的表达式可以是任何有效的操作数格式,可以是任何可求出常数值的表达式,也可以是任何有效的助记符。举例如下:
  CONSTANT EQU 256 数值赋以符号名
  DATA EQU HEIGHT+12 地址表达式赋以符号名
  ALPAHA EQU 7
EQU不是指令集,而是伪指令,一般我们常使用的MASM5.0以上都常用这个伪指令。它不是80X86的指令集合。而汇编在第一次扫描时只扫描了指令,而将伪指令中的东西作为“动态内容”作了标记而已。所以在第一次扫描所得到的清单中是没有看到它占用内存的。所以不会计算其中的数据的。而第二次扫描才能得到。
  指令集是属于机器CPU的,因有的,一个类型CPU就有这样一个指令集。而伪指令则是由汇编软件提供的,比如MASM5.0中提供了EQU的伪指令,那么汇编时是由于MASM5.0进行运算的。而计算空间时所得到的清单文件是关于指令的,所以伪指令并没有计算在内。
  不同类型的CPU会有不同的指令集,不管你使用什么样的汇编软件,同一个类型的CPU指令集是不会变的!而伪指令是由汇编软件提供,不同的汇编软件有不同的伪指令集。
  CPU的发展和软件的发展都有一个基础,因此出现了向下兼容的现象。8038680286相比,只在80286指令集的基础上增加了几个指令而成的。而软件也是,MASM6.0只是在5.0部分伪指令集的基础上增加了几条伪指令而已。但6.0却还有一大进步就是将50中的两次扫描一次完成。也就是说6.0只有一次扫描。而5.0却是两次扫描。
===================================================================
AREA
      AREA   STACK, NOINIT, READWRITE, ALIGN=3
语法格式:   
   AREA 段名 属性 1 ,属性 2 ,……   
   AREA 伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用 “ | ” 括起来,如 |1_test| 。还有一些代码段具有约定的名称,如|.text|表示C语言编译器产生的代码段或者是与C语言库相关的代码段。属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:   
    —CODE 属性:用于定义代码段,代码段的默认属性为 READONLY 。   
    — DATA 属性:用于定义数据段,数据段的默认属性为READWRITE 。   
  —NOINIT 属性:指定本数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或者将各内存       单元初始化为0.
    —READONLY 属性:指定本段为只读,代码段默认为 READONLY 。   
   —READWRITE 属性:指定本段为可读可写,数据段的默认属性为 READWRITE 。   
    —ALIGN 属性:使用方式为 ALIGN 表达式。在默认时, ELF (可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为 0 ~ 31 ,相应的对齐方式为 2 的 表达式次方。如表达式=3时为8字节对齐。
—ASSOC=section。指定与本段相关的ELF段。任何时候连接section段也必须包括sectionname段。
    —COMMON 属性:该属性定义一个通用的段,不包含任何的用户代码和数据。连接器将其初始化为0。各源文件中同名的COMMON 段共享同一段内存单元,连接器为其分配合适的尺寸。
— COMDEF 属性:该属性定义一个通用的段,该段可以包含代码或数据。在各源文件中,同名的COMDEF段必须相同。
通常可以用AREA伪操作将程序分为多个ELF格式的段。段名称可以相同,这时这些同名的段被放在同一个ELF段中。一个大的程序可以包括多个代码段和数据段,一个汇编语言程序至少要包含一个段。   
   使用示例:   
    AREAInit , CODE , READONLY   
   该伪操作定义了一个代码段,段名为 Init ,属性为只读
===================================================================
SPACE   
   
语法格式:   
   
标号 SPACE 表达式   
   SPACE
伪指令用于分配一片连续的存储区域并初始化为 0 。其中,表达式为要分配的字节数。   
   SPACE
也可用 代替。   
   
使用示例:   
   DataSpace SPACE 100
;分配连续100 字节的存储单元并初始化为 0
===================================================================
PRESERVE8
字节对齐关键词,以前用ADS编译器的时候可以不用,但是后来的keil编译器时需要加上(譬如用周立功模板时,将ADS工程转到keil工程时就必须加上)。指定当前文件堆栈8字节对齐
REQUIRE8 指令指定当前文件要求堆栈八字节对齐。它设置REQ8 编译属性以通知链接器。
PRESERVE8 指令指定当前文件保持堆栈八字节对齐,它设置 PRES8 编译属性以通知链接器。
链接器检查要求堆栈八字节对齐的任何代码是否仅由保持堆栈八字节对齐的代码直接或间接地调用。
语法REQUIRE8 {bool} PRESERVE8 {bool} 其中:bool是一个可选布尔常数,取值为 {TRUE} 或 {FALSE}。用法
如果您的代码保持堆栈八字节对齐,在需要时,可使用 PRESERVE8 设置文件的PRES8 编译属性。如果您的代码不保持堆栈八字节对齐,则可使用 PRESERVE8{FALSE} 确保不设置PRES8 编译属性。
Note
如果您省略 PRESERVE8 和 PRESERVE8 {FALSE},汇编程序会检查修改sp 的指令,以决定是否设置 PRES8 编译属性。 ARM 建议明确指定 PRESERVE8。
您可以通过以下形式启用警告:
armasm --diag_warning 1546
您将会收到类似以下警告:
"test.s", line 37: Warning: A1546W: Stack pointer updatepotentially breaks 8 byte stack alignment
3700000044         STMFD    sp!,{r2,r3,lr}

THUMB:告诉汇编器下面是32为的Thumb指令,如果需要汇编器将插入位以保证对齐
CODE16CODE32 [THUMB]
   语法格式:   
   CODE16 (或CODE32 )   
   CODE16 伪指令通知编译器,其后的指令序列为 16 位的Thumb 指令。   
   CODE32 伪指令通知编译器,其后的指令序列为 32 位的ARM 指令。   
   若在汇编源程序中同时包含ARM 指令和 Thumb 指令时,可用CODE16 伪指令通知编译器其后的指令序列为 16 位的Thumb 指令, CODE32 伪指令通知编译器其后的指令序列为 32 位的ARM 指令。因此,在使用 ARM 指令和Thumb 指令混合编程的代码里,可用这两条伪指令进行切换,但注意他们只通知编译器其后指令的类型,并不能对处理器进行状态的切换。   
   使用示例:   
    AREAInit ,CODE , READONLY   
   ……   
   CODE32 ;通知编译器其后的指令为32 位的 ARM 指令   
    LDRR0 ,=NEXT + 1 ;将跳转地址放入寄存器 R0   
    BXR0 ;程序跳转到新的位置执行,并将处理器切换到 Thumb 工作状态   
   ……   
   CODE16 ;通知编译器其后的指令为16 位的 Thumb 指令   
    NEXTLDR R3,=0x3FF   
   ……   
   END ;程序结束
===================================================================
EXPORT(或GLOBAL   
   
语法格式:   
   EXPORT
标号{[WEAK]}   
   EXPORT
伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。 EXPORT可用GLOBAL 代替。标号在程序中区分大小写, [WEAK] 选项声明其他的同名标号优先于该标号被引用。   
   
使用示例:   
    AREAInit
CODE READONLY   
    EXPORTStest
;声明一个可全局引用的标号Stest……   
   END   

===================================================================
DCD(或DCDU   
   
语法格式:   
   
标号DCD (或 DCDU 表达式   
   DCD
(或DCDU )伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。DCD 也可用 “ & ”代替。   
   
DCD 分配的字存储单元是字对齐的,而用 DCDU 分配的字存储单元并不严格字对齐。   
   
使用示例:   
   DataTest DCD 4

5 ,
6 ;分配一片连续的字存储单元并初始化。

===================================================================
汇编控制( Assembly Control )伪指令   
   
汇编控制伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下几条:   
    —IF
ELSE ENDIF   
    —WHILE
WEND   
    —MACRO
MEND   
    —MEXIT   
   
1IFELSEENDIF   
   
语法格式:   
   IF
逻辑表达式   
   
指令序列1   
   ELSE   
   
指令序列2   
   ENDIF   
   IF
ELSE ENDIF 伪指令能根据条件的成立与否决定是否执行某个指令序列。当 IF 后面的逻辑表达式为真,则执行指令序列 1 ,否则执行指令序列 2 。其中, ELSE 及指令序列 2 可以没有,此时,当 IF 后面的逻辑表达式为真,则执行指令序列 1 ,否则继续执行后面的指令。   
   IF
ELSE ENDIF 伪指令可以嵌套使用。   
   
使用示例:   
    GBLLTest
;声明一个全局的逻辑变量,变量名为 Test……   
    IFTest =TRUE   
   
指令序列1   
   ELSE   
   
指令序列2   
   ENDIF

   X:LAND:Y 表示将X和Y做逻辑与的操作。
  X:LOR:Y 表示将X和Y做逻辑或的操作。
  :LNOT:Y 表示将Y做逻辑非的操作。
X:LEOR:Y 表示将X和Y做逻辑异或的操作。
:LNOT: 逻辑预算符
:DEF:

IF     :LNOT::DEF:NO_CRP
;如果宏判断是否定义NO_CRP #ifndef
                AREA   |.ARM.__at_0x02FC|, CODE, READONLY
;自定义只读代码段
CRP_Key        DCD    0xFFFFFFFF
;加密等级见上注释
  ENDIF

===================================================================
PROC
过程就是子程序。一个过程可以被其它程序所调用(用CALL指令),过程的最后一条指令一般是返回指令(RET)。
  过程定义伪指令的格式为
   <过程名>   PROC [类型]
               …
               …  
               RET
   <过程名>   ENDP
注意:PROCENDP必须成对出现
过程的类型有两种:
   NEAR——(默认类型)表示段内调用
   FAR——表示段间调用
  调用一个过程的格式为:
      CALL <过程名>
IMPORT   
   语法格式:   
   IMPORT 标号 {[WEAK]}   
   IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。   
   标号在程序中区分大小写,[WEAK] 选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0 ,若该标号为 B 或 BL 指令引用,则将 B 或 BL指令置为NOP 操作。   
   使用示例:   
    AREAInit , CODE , READONLY   
    IMPORTMain ;通知编译器当前文件要引用标号Main,但Main 在其他源文件中定义……   
    END  
ldr只能在当前PC4KB范围内跳转
B
只能在当前PC32M范围内跳转
label标号实际上就是个地址
eg:
合法:
ldr r1,[r2]
ldr r1,[r2,#0x4];不能超过0xfff,否侧编译不能通过或者linker时有错
ldr r1,[r2,#label];所以这个经常是编译不能通过,因为label的值一般都大于0xfff
ldr r1,[r2],#0x4
ldr r1,label ;把label这个地址里面的内容赋给r1
ldr伪指令
ldr r1,=0x2000014
ldr r1,=label ;把label这个地址值赋给r1
ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令。比如想把数据从内存中某处读取到寄存器中,只能使用ldr
比如:ldr r0, 0x12345678
就是把0x12345678这个地址中的值存放到r0中。
而mov不能干这个活,mov只能在寄存器之间移动数据,或者把立即数移动到寄存器中,这个和x86这种CISC架构的芯片区别最大的地方。
x86中没有ldr这种指令,因为x86的mov指令可以将数据从内存中移动到寄存器中。
另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。ldr伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中,比如:
ldr r0, =0x12345678
这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。只不过mov指令限制了立即数的长度为8位,也就是不能超过512。而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。
===================================================================
BX{<cond>}Rm
<cond>为指令执行的条件码。当<cond>忽略时指令为无条件执行。
<Rm>该寄存器中为跳转的目标地址。当<Rm>寄存器的bit[0]为0时,目标地址处的指令为ARM指令;
当<Rm>寄存器的bit[0]为1时,目标地址处的指令为Thumb指令。
ARM指令是字对齐(指令的地址后两位为[1:0]=0b00),Thumb是半字对齐(指令的地址后两位为[1:0]=0bx0,x为0或1)。指令的地址的最后一位必为0。
===================================================================
__main函数的作用
1.1  问题描述
     __main函数的作用是什么呀?
1.2  问题剖析
     __main函数是C/C++运行时库的一个函数,嵌入式系统在进入应用主程序之前必须有一个初始化的过程,使用__main标号引导系统时必须将应用程序的入口定义为main()。
    在初始化的过程中,__main函数的作用主要有两点:
    (1)  完成对映像文件的初始化操作
     在介绍映像文件的初始化操作之前,先介绍以下几个概念:
     1.  映像文件
     链接器把多个目标文件链接成一个映像文件。
     2.  加载地址和执行地址
     映像文件可以有两种地址:加载地址和执行地址。加载地址是映像文件在存储器中的存储地址;执行地址就是映像文件运行时的地址。
     3.  加载域和执行域
     文件加载的存储区叫加载域,文件运行的存储区叫执行域。
     4.  从加载地址到执行地址
     在结构比较简单的系统中,加载地址就是执行地址;而在复杂系统中,程序运行前,常常会把映像文件的一部分或全部从存储区域移出去,此时执行地址就不再是加载地址。
     知道以上几个概念,__main函数对映像文件的初始操作就不难理解了。对于加载地址和执行地址不同的映像文件,__main函数会把加载地址的代码和数据复制到执行地址中,并且对被链接器指定为需要初始化为0的段,进行清零操作。
     (2)  调用__rt_entry函数,进入用户程序。__rt_entry函数的运行流程如图1.1所示。



图1.1  在__rt_entry()函数中的运行情况
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者就是我们自己编写的main()主函数;
===================================================================
ALIGN   
   语法格式:   
    ALIGN{ 表达式{ ,偏移量 }}   
   ALIGN 伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式 | 。其中,表达式的值用于指定对齐方式,可能的取值为 2 的幂,如1 、 2 、4 、 8 、16 等。若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2 的表达式次幂+偏移量。   
   使用示例:   
    AREAInit ,CODE , READONLY ,ALIEN = 3 ;指定后面的指令为8 字节对齐。   
   指令序列   
   END
===================================================================
__user_initial_stackheap() 返回:
·        r0 中的堆基址
·        r1 中的堆栈基址,即堆栈区中的最高地址
·        r2 中的堆限制
·        r3 中的堆栈限制,即堆栈区中的最低地址。
===================================================================
BX     LR
如果LR的值不是0xffffxxxx类型的,则PC跳至LR[31:1],而根据LR[0:0]则决定跳转后处理器进入的状态。如果LR[0:0]=1,则进入Thumb状态,否则进入ARM状态。 在CM3中不支持ARM状态,所以LR[0:0]必须是1——也就是LR必须是奇数  
在CM3中,如果以0xffff开头则有特殊的含义,命名为EXC_RETURN,它指示正在从异常返回,并决定返回的方式,在《Cortex-M3权威指南》中有重点介绍
===================================================================
__initial_sp
通过定义一个等于堆栈顶部的符号 __initial_sp 来指定初始堆栈指针
__initial_sp EQU 0x100000        ; equal to the top of the stack
__heap_base
过分别定义符号 __heap_base __heap_limit 来指定堆的开头和末尾。 完成后,您可以按通常方式使用堆函数。
__heap_limit
必须指向堆区中最后一个字节后面的字节。
EXPORT __heap_base__heap_base EQU 0x400000        ; equal to the start of the heap    EXPORT __heap_limit__heap_limit EQU 0x800000       ; equal to the end of the heap
===================================================================
;这个函数中,EXPORT 为符号导出,导出的符号须有相应的定义(符号地址),就像是C语言中的extern,extern某个函数或者某个变量,首先这个函数或变量需要有个实体后,才能导出。
Default_Handler PROC
EXPORT WAKEUP_IRQHandler        [WEAK]
               EXPORT CAN_IRQHandler           [WEAK]
                     。。。。。。

WAKEUP_IRQHandler
CAN_IRQHandler
         。。。。。。
            B      .                ;跳转到当前行
               ENDP

   EXPORT  __user_initial_stackheap
__user_initial_stackheap


                       
;

; <h> Stack Configuration
;  <o> Stack Size (in Bytes)<0x0-0xFFFFFFFF:8>
; </h>
Stack_Size     EQU    0x00000200
;定义statck_size标号为ox200的空间作为栈空间   
             AREA   STACK, NOINIT, READWRITE, ALIGN=3
;定义一个数据段,按8字节对齐
;AREA 段名 属性 1 ,属性 2 ,……
;AREA 伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用 “ | ” 括起来,如 |1_test| 。
;属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:
;— CODE 属性:用于定义代码段,默认为 READONLY 。
;— DATA 属性:用于定义数据段,默认为 READWRITE 。
;— READONLY 属性:指定本段为只读,代码段默认为 READONLY 。
;— READWRITE 属性:指定本段为可读可写,数据段的默认属性为 READWRITE 。
;— ALIGN 属性:使用方式为 ALIGN 表达式。在默认时, ELF (可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围;为0 ~ 31 ,相应的对齐方式为 2 表达式次方。
;— COMMON 属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的 COMMON 段共享同一段存储单元。
;一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。
Stack_Mem      SPACE   Stack_Size
;保留stack_size大小的堆栈空间,分配连雪的存储空间,并初始化为0
;SPACE 伪指令用于分配一片连续的存储区域并初始化为 0 。其中,表达式为要分配的字节数。
;SPACE 也可用 “ % ” 代替。
__initial_sp

; <h> Heap Configuration
;  <o>  Heap Size (inBytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size      EQU    0x00000000
               AREA    HEAP,NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem       SPACE   Heap_Size
;堆分析同上
__heap_limit
;代表堆栈地址的标号
                PRESERVE8
;制定当前文件堆栈按照8个字节对齐
                THUMB
;指示编译器为thumb指令

; Vector Table Mapped to Address 0 at Reset
                AREA   RESET, DATA, READONLY
;自定义只读数据段放到数据段中为于0地址
                EXPORT  __Vectors
;EXPORT 标号 {[WEAK]}
;EXPORT 伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。
;EXPORT可用GLOBAL 代替。标号在程序中区分大小写,
; [WEAK] 选项声明其他的同名标号优先于该标号被引用。
__Vectors      DCD    __initial_sp             ; Top of Stack
               DCD    Reset_Handler            ; Reset Handler
               DCD    NMI_Handler              ; NMI Handler
               DCD    HardFault_Handler        ; Hard Fault Handler
               DCD    MemManage_Handler        ; MPU Fault Handler
               DCD    BusFault_Handler         ; Bus Fault Handler
               DCD    UsageFault_Handler       ; Usage Fault Handler
               DCD    0                        ; Reserved
               DCD    0                        ; Reserved
               DCD    0                        ; Reserved
               DCD    0                        ; Reserved
               DCD    SVC_Handler              ; SVCall Handler
               DCD    DebugMon_Handler         ; Debug Monitor Handler
               DCD    0                        ; Reserved
               DCD    PendSV_Handler           ; PendSV Handler
               DCD    SysTick_Handler          ; SysTick Handler
               ; External Interrupts
               DCD    WAKEUP_IRQHandler        ; 15 wakeup sources for all the
               DCD    WAKEUP_IRQHandler        ; I/O pins starting from PIO0 (0:11)
               DCD    WAKEUP_IRQHandler        ; all 40 are routed to the sameISR                       
               DCD    WAKEUP_IRQHandler                        
               DCD    WAKEUP_IRQHandler                        
               DCD    WAKEUP_IRQHandler
               DCD    WAKEUP_IRQHandler
               DCD    WAKEUP_IRQHandler                       
               DCD    WAKEUP_IRQHandler                        
               DCD    WAKEUP_IRQHandler                        
               DCD    WAKEUP_IRQHandler
               DCD    WAKEUP_IRQHandler
               DCD    WAKEUP_IRQHandler        ; PIO1 (0:11)
               DCD    CAN_IRQHandler           ;CAN               
               DCD    SSP1_IRQHandler          ;SSP1               
               DCD    I2C_IRQHandler           ; I2C
               DCD    TIMER16_0_IRQHandler     ; 16-bit Timer0
               DCD    TIMER16_1_IRQHandler     ; 16-bit Timer1
               DCD    TIMER32_0_IRQHandler     ; 32-bit Timer0
               DCD    TIMER32_1_IRQHandler     ; 32-bit Timer1
               DCD    SSP0_IRQHandler          ; SSP0
               DCD    UART_IRQHandler          ; UART
               DCD    USB_IRQHandler           ; USB IRQ
               DCD    USB_FIQHandler           ; USB FIQ
               DCD    ADC_IRQHandler           ; A/D Converter
               DCD    WDT_IRQHandler           ; Watchdog timer
               DCD    BOD_IRQHandler           ; Brown Out Detect
               DCD    FMC_IRQHandler           ; IP2111 Flash Memory Controller
               DCD    PIOINT3_IRQHandler       ; PIO INT3
               DCD    PIOINT2_IRQHandler       ; PIO INT2
               DCD    PIOINT1_IRQHandler       ; PIO INT1
                DCD    PIOINT0_IRQHandler       ; PIO INT0
;标号DCD (或 DCDU ) 表达式
;DCD (或 DCDU )伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。
;DCD 也可用 “ & ” 代替。
;用DCD 分配的字存储单元是字对齐的,而用 DCDU 分配的字存储单元并不严格字对齐。
;// <h>  CORERead Protection level(CRP)
;// <o> CRP_Level:  
;//     <0xFFFFFFFF=>Disabled
;//     <0x12345678=>CRP1
;//     <0x87654321=>CRP2
;//     <0X43218765=>CRP3(OTP mode!!!!NOTIC)
;//</h>
;//CRP_Level  EQU    0xFFFFFFFF
               IF     :LNOT::DEF:NO_CRP
;如果宏判断是否定义NO_CRP #ifndef
                AREA   |.ARM.__at_0x02FC|, CODE, READONLY
;自定义只读代码段
CRP_Key        DCD    0xFFFFFFFF
;加密等级见上注释
               ENDIF

               AREA    |.text|,CODE, READONLY

; Reset Handler
Reset_Handler  PROC
  ;PROC 子程序开始伪指令         
              EXPORT Reset_Handler            [WEAK]
                IMPORT  __main
;IMPORT 标号 {[WEAK]}
;IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
;标号在程序中区分大小写,[WEAK] 选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为 0 ,若该标号为 B 或 BL 指令引用,则将 B 或 BL指令置为NOP 操作。
;__main为运行时提供的汗水,完成堆栈,堆的初始化
               LDR    R0, =__main
;使用=标示目前为伪指令,不是标准指令,=等于@取地址,把main 的地址给R0
                BX     R0
;BX带状态切换的跳转指令
               ENDP
;为子程序结束

; Dummy Exception Handlers (infinite loops which can bemodified)               
NMI_Handler    PROC
               EXPORT NMI_Handler              [WEAK]
               B      .
               ENDP
HardFault_Handler\
               PROC
               EXPORT HardFault_Handler        [WEAK]
               B      .
               ENDP
MemManage_Handler\
               PROC
               EXPORT MemManage_Handler        [WEAK]
               B      .
               ENDP
BusFault_Handler\
               PROC
               EXPORT BusFault_Handler         [WEAK]
               B      .
               ENDP
UsageFault_Handler\
               PROC
               EXPORT UsageFault_Handler       [WEAK]
               B      .
               ENDP
SVC_Handler    PROC
               EXPORT SVC_Handler              [WEAK]
               B      .
               ENDP
DebugMon_Handler\
               PROC
               EXPORT DebugMon_Handler         [WEAK]
               B      .
               ENDP
PendSV_Handler  PROC
               EXPORT PendSV_Handler           [WEAK]
               B      .
               ENDP
SysTick_Handler PROC
               EXPORT SysTick_Handler          [WEAK]
               B      .
               ENDP
Default_Handler PROC
               EXPORT WAKEUP_IRQHandler        [WEAK]
               EXPORT CAN_IRQHandler           [WEAK]
               EXPORT SSP1_IRQHandler          [WEAK]
               EXPORT I2C_IRQHandler           [WEAK]
               EXPORT TIMER16_0_IRQHandler     [WEAK]
               EXPORT TIMER16_1_IRQHandler     [WEAK]
               EXPORT TIMER32_0_IRQHandler     [WEAK]
               EXPORT TIMER32_1_IRQHandler     [WEAK]
               EXPORT SSP0_IRQHandler          [WEAK]
               EXPORT UART_IRQHandler          [WEAK]
               EXPORT USB_IRQHandler           [WEAK]
               EXPORT USB_FIQHandler           [WEAK]
               EXPORT ADC_IRQHandler           [WEAK]
               EXPORT WDT_IRQHandler           [WEAK]
               EXPORT BOD_IRQHandler           [WEAK]
               EXPORT FMC_IRQHandler           [WEAK]
               EXPORT PIOINT3_IRQHandler       [WEAK]
               EXPORT PIOINT2_IRQHandler       [WEAK]
               EXPORT PIOINT1_IRQHandler       [WEAK]
               EXPORT PIOINT0_IRQHandler       [WEAK]

WAKEUP_IRQHandler
CAN_IRQHandler
SSP1_IRQHandler
I2C_IRQHandler
TIMER16_0_IRQHandler
TIMER16_1_IRQHandler
TIMER32_0_IRQHandler
TIMER32_1_IRQHandler
SSP0_IRQHandler
UART_IRQHandler
USB_IRQHandler
USB_FIQHandler
ADC_IRQHandler
WDT_IRQHandler
BOD_IRQHandler
FMC_IRQHandler
PIOINT3_IRQHandler  
PIOINT2_IRQHandler
PIOINT1_IRQHandler
PIOINT0_IRQHandler
               B      .
               ENDP

               ALIGN

; User Initial Stack & Heap
                IF     :DEF:__MICROLIB
;是否使用外部Microlib,在编译器中设置   
;有时候使用外部microlib出错,注意是不是这个地方出错!!!            
               EXPORT  __initial_sp
               EXPORT  __heap_base
               EXPORT  __heap_limit
               
               ELSE
               
               IMPORT  __use_two_region_memory
               EXPORT  __user_initial_stackheap
__user_initial_stackheap
               LDR    R0, =  Heap_Mem
               LDR    R1, =(Stack_Mem + Stack_Size)
               LDR    R2, = (Heap_Mem +  Heap_Size)
               LDR    R3, = Stack_Mem
               BX     LR
               ALIGN
               ENDIF

               END

回复

使用道具 举报

ID:60266 发表于 2014-8-18 02:50 | 显示全部楼层
本帖最后由 heise 于 2014-8-18 02:54 编辑

多多指教
回复

使用道具 举报

ID:26188 发表于 2014-8-21 20:44 来自手机 | 显示全部楼层
果然是高手分析得很透彻,看了一个多小时终于看明白了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表