龙空技术网

Cortex-M3内核芯片进阶之编写自己的启动代码(基于 LPC17xx)

LiNUS工程师笔记 748

前言:

如今咱们对“armc语言编程”大约比较注意,大家都想要了解一些“armc语言编程”的相关资讯。那么小编也在网络上收集了一些有关“armc语言编程””的相关知识,希望小伙伴们能喜欢,兄弟们快快来了解一下吧!

1.1 最简单的启动代码(Startup.s Version-1.0

经过上面的理论知识的铺垫,我们已经对 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 等具体的硬件初始化代码添加进来我们的芯片就可以正常运行了。

标签: #armc语言编程