经由上面的理论知识的铺垫,我们已经对 ARM(LPC17xx)的启动过程有了一个清晰的认识,下面就来实战一下,编写一个自己的 ARM 启动代码。
1.1.1 启动代码必须完成的三项事情
对付一个繁芜系统的话,则启动代码须要完成的事情比较多过程也比较繁芜,比如一个存储体系比较繁芜的系统(在一个别系中由内部、外部的 RAM 又有内部和外部的ROM)或带有操作系统的。对付不同的 ARM 架构的芯片其启动代码差别也比较大,比如在 ARM9 中各种运行模式的堆栈的分配、管理以及切换全部都要由用户自己实现,而在 M0 或 M3 中就不须要用户去实现这些过程。对付 Cortex-M3 内核的处理器,其启动代码必须完成三项任务:
指定 MSP 的初始值;指定程序运行的入口;初始化一个堆栈(如果实行的是 C 代码则须要实行此过程,否则可以省略)。1.1.2 程序实现(Startup.s Version-1.0)
ARM 处理器的运行是从 ROM 空间的 0x00000000 地址开始的,而在 Cortex-M3 中从0x00000000 地址开始的区域里掩护了一张中断向量表。向量表中的第一个字中存储的是主堆栈指针 MSP 的值, 第二个字中存储的是程序的入口地址(一些资料上说这个字里存储的是复位向量或复位程序的入口地址,实际上这种说法不足准确,这个字可以不用来指定复位程序的入口。),之后便是真正的中断向量表了。
结合 Cortex-M3 内核的处理器启动代码必须完成三项任务,首先须要指定 ROM 空间的第一个以及第二个字的内容,个中第二个字我们将其指定为__main,为 C 措辞的运行搭建环境,以此为入口开始运行程序,个中__main 的运行须要用户自己实现__user_initial_stackheap,故启动代码实现如下:
程序清单 1.1 Startup.s(V1.0)
;/; File : Startup.s; Version : 1.0; Date : 11-18-2011; Author : ZhengYuanChao; ; LPC1700 ARM Cortex-M3 启动代码; /; /; 解释: Cortex-M3 启动须要完成以下三项事情(在利用标准 C Library 的情形下); 1. 指定栈顶; 2. 引入__main 并跳转过去; 3. 重写__user_initial_stackheap; / PRESERVE8 ; 指定 8 字节对齐(keil 的哀求) AREA LPC1700Startup, DATA, READONLY ; 指天命据段 IMPORT __main ; 导入__main 标号__Vectors DCD 0x10008000 ; 指定 MSP DCD __main; 跳转到__mainAREA |.text|, CODE, READONLY; 指定代码段 EXPORT __user_initial_stackheap ; 导出此标号供__main 调用__user_initial_stackheap BX LR; 不实行任何操作直接跳回__mian ALIGN; 对齐添补使代码段双字对齐 END
1.1.3 指定加载办法让程序跑起来
仅仅完成了 Startup.s 的编写还是不足的,还须要为程序天生的镜像文件指定加载办法(第四章有详细描述),否则程序无法运行。对付 Version-1.0 的启动代码加载办法的指定比较大略,只需将__Vectors 放在 ROM 的顶端即可,至于其他代码的摆放则无关紧要,可由链接器自行安排。描述如下:
程序清单 1.2 分散加载代码(V1.0)
ROM_LOAD 0x00000000{ VECTOR 0x00000000 { .o (LPC1700Startup, +FIRST) ; 将__Vectors 放在 ROM 的顶端 .ANY (+RO) } SRAM 0x10000000 { (+RW,+ZI) }}
现在程序可以运行了,利用软仿照调试可以创造程序顺利的进入了 main()函数。
1.1.4 Version-1.0 的缺陷
Version-1.0 只对处理器进行了最大略配置并令其运行起来,它乃至连中断向量表都没有初始化,也便是说处理器将不能相应中断,这当然是不可以的。故此代码是没有什么利用代价的。
1.2 一个实用的启动代码(Startup.s Version-2.0)
1.2.1 设计描述
要让启动代码真正具有实用性必须还要定位一张向量表,某些程序还可能用到堆,以是我们还要再管理一个堆,对付 NXP 的 LPC17xx 还要在启动代码中指定芯片的加密办法。
由于 Version-1.0 的启动代码已经成功的配置了 C 措辞的运行环境,故接下来的代码我们将用 C 措辞来编写。
1.2.2 程序实现(Startup.s)
程序清单 1.3 Startup.s(V2.0)
; /; 解释: Cortex-M3 启动须要完成以下三项事情(在利用标准 C Library 的情形下); 1. 指定栈顶; 2. 引入__main 并跳转过去; 3. 重写__user_initial_stackheap; 4. 对付 NXP 的 LPC1700 系列还要指定其加密级别; /SP_TOP EQU 0x10008000HEAP_TOP EQU 0x10007000 PRESERVE8 ; 指定 8 字节对齐(keil 的哀求) AREA StartupEntry, DATA, READONLY ; 指天命据段 IMPORT __main ; 导入__main 标号__Start DCD SP_TOP ; 指定 MSP DCD __main ; 跳转到__main IF :LNOT::DEF:NO_CRPAREA |.ARM.__at_0x02FC|, CODE, READONLY ; 加密标志存储于 0x02fc 地址 CRP_Key DCD 0xFFFFFFFF; 0 级加密(即不加密) ENDIF AREA |.text|, CODE, READONLY; 指定代码段 EXPORT __user_initial_stackheap; 导出此标号供__main 调用__user_initial_stackheap LDR R0, =HEAP_TOP ; 指定堆顶 BX LR; 跳回__mian ALIGN; 对齐添补使代码段双字对齐 END
1.2.3 向量表 Vectors.h 和 Vectors.c
程序清单 1.4 Vectors.h
Vectors.h 的代码如下:#ifndef __VECTORS_H__#define __VECTORS_H__#define MAX_VICTORS 51#define NMI_Handler defaultVectorHandle#define HardFault_Handler defaultVectorHandle#define MemManage_Handler defaultVectorHandle#define BusFault_Handler defaultVectorHandle#define UsageFault_Handler defaultVectorHandle#define SVC_Handler defaultVectorHandle#define DebugMon_Handler defaultVectorHandle#define PendSV_Handler defaultVectorHandle#define SysTick_Handler defaultVectorHandle……#define QEI_IRQHandler defaultVectorHandle#define PLL1_IRQHandler defaultVectorHandle#define USBActivity_IRQHandler defaultVectorHandle#define CANActivity_IRQHandler defaultVectorHandle#endif
由于中断向量表存储的是中断函数的入口地址而且又是一张表,故我们用函数指针数组来表示中断向量表, Vectors.c 的代码如下:
程序清单 1.5 Vectors.c
#include "vectors.h"#include "stddef.h"#include "syscfg.h"void const __VectorsTable[MAX_VICTORS] ={ (void )(NMI_Handler), (void )(HardFault_Handler), (void )(MemManage_Handler), (void )(BusFault_Handler), (void )(UsageFault_Handler), (void )defaultVectorHandle, (void )defaultVectorHandle, (void )defaultVectorHandle, (void )defaultVectorHandle, (void )(SVC_Handler), (void )(DebugMon_Handler), NULL, (void )(PendSV_Handler), (void )(SysTick_Handler), …… (void )(QEI_IRQHandler), (void )(PLL1_IRQHandler), (void )(USBActivity_IRQHandler), (void )(CANActivity_IRQHandler)};
这里须要把稳的是,数组必须用 const 限定,由于中断向量表必须是 Read-Only 的,末了还须要在创建一个文件重写 defaultVectorHandle 函数即可(此段代码省略)。
1.2.4 指定加载办法
向量表必须定位在__Start 标号之后,否则是无意义的,故描述如下:
程序清单 1.6 分散加载代码(V2.0)
ROM_LOAD 0x00000000{ STARTUP 0x00000000 { .o (StartupEntry, +FIRST) } VECTORS 0x00000008 { vectors.o(+RO-DATA, +FIRST) .ANY (+RO) } SRAM 0x10000000 { (+RW,+ZI) }}
之后再将芯片的时钟以及 MPU 等详细的硬件初始化代码添加进来我们的芯片就可以正常运行了。