下面以startup_LPC17xx.s为例,简单看一下CMSIS中的启动代码完成了哪些工作。
主要完成了三项工作:
- 堆栈以及堆的初始化;
- 定位中断向量表;
- 调用 Reset Handler。
1.1 堆栈以及堆的初始化
1.1.1 堆栈的初始化
程序清单 1.1 startup_LPC17xx.s 中的堆栈初始化代码
Stack_Size EQU 0x00000200
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
Stack_Size EQU 0x00000200,这个语句相当于 Stack_Size 这个标号(标号:链接器的术语,下文中提到的所有“标号”,指的都是链接器中的标号)等于 0x00000200,相当于 C 语言中的#define Stack_Size 0x00000200,也就是说此语句只是一个声明,并未分配地址。
AREA STACK, NOINIT, READWRITE, ALIGN=3,此语句定义了一个名叫 STACK 的代码段,并指明 8 字节对齐(ALIGN=3)。
Stack_Mem SPACE Stack_Size,为 Stack_Mem 分配 Stack_Size 大小的一块内存区域,注意这里分配的是 RAM。
__initial_sp此标号有一层隐含的意思那就是在 M3 中堆栈是满递减堆栈,因为它指定了堆栈指针位于堆栈的高地址(在 Stack_Mem 之后),具体如下图所示。
图 1.1 堆栈指针 SP 位置
上图来自一个 LPC1700 工程的.map 文件。可以看出栈的起始地址为 0x10001f70,大小为512 字节(即 0x00000200 = Stack_Size)。而堆栈指针的位置在 0x10002170,其等于栈的起始地址 0x10001f70 0x00000200,说明 LPC1700 系列的 Cortex-M3 微控制器的堆栈为满递减堆栈。
1.1.2 堆的初始化
程序清单 1.2 startup_LPC17xx.s 中的堆初始化代码
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
具体过程与栈的初始化相同。
1.2 中断向量表的初始化
程序清单 1.3 中断向量表的初始化代码
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
__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
PRESERVE8 指定了以下的代码位 8 字节对齐,这是 keil 编译器的一个编程要求,对齐情况如下图所示:
图 1.2 .list 文件中的 8 字节对齐示意图
THUMB 指定了接下来的代码为 THUMB 指令集。
AREA RESET, DATA, READONLY,此语句声明 RESET 数据段。
EXPORT __Vectors,导出向量表标号,EXPORT 作用类似于 C 语言中的 extern。之后的代码就是为向量表分配存储区域。
1.3 调用 Reset Handler
程序清单 1.4 调用 Reset Handler 的代码
; Reset Handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit ;
IMPORT __main
LDR R0, =SystemInit;
BLX R0 ;
LDR R0, =__main
BX R0
ENDP
此段代码只完成了一个功能,引导程序进入__main。 __main 的具体行为在第三章中有具体的描述。
1.4 其他的代码
程序清单 1.5 CRP 加密级别
IF :LNOT::DEF:NO_CRP
AREA |.ARM.__at_0x02FC|, CODE, READONLY
CRP_Key DCD 0xFFFFFFFF
ENDIF
此段代码指定了接下来的代码存储与 0x02FC 的地址,具体情况如下图所示。这段代码是 NXP 公司的 LPC1700 系列的 MCU 特有的一段代码,其他公司的 Cortex-M3 MCU 的启动程序是没有这段代码的。
这段代码是指定 LPC1700 的 CRP 加密级别的代码段,芯片上电后会自动读取这一地址的值以确定加密方式,其中 CRP_Key = 0xffffffff 为不加密(0 级加密), CRP_Key = 0x12345678为 1 级加密, CRP_Key = 0x87654321 为 2 级加密, CRP_Key = 0x43218765 为 3 级加密(最高级加密), 3 级加密将会禁止所有的 ISP 指令,也就是说,芯片将不能读写、不能擦除。
程序清单1.6 具体的堆栈以及堆的初始化行为
; User Initial Stack & Heap
IF :DEF:__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