一般来讲,在32位ARM应用系统中,软件大多数采用C语言进行编程,并且以嵌入式操作系统为开发平台,这样就大大的提高了开发效率及软件性能。由于C语 言程序的运行需要具备一定的条件,例如分配好的外部数据空间、堆栈空间和中断入口等。因此,在编写应用程序时必须在程序的入口处加入一些必要的初始化代码 (启动代码),使系统在执行C语言程序之前先完成对自身的初始化,从而才能正确地执行用户代码。
1
、启动代码要点与示例
为了能够进行系统初始化,采用一个汇编文件作启动代码是最常用的做法,典型的初始化程序必须完成以下任务:
- 定义入口地址;
- 建立异常中断处理向量表,实现向量表定义;
- 初始化存储器系统;
- 初始化堆栈;
- 系统变量初始化;
- 中断系统初始化;
- 时钟初始化、I/O初始化;
- 进入C语言程序的运行。
LPC2220
初始化示例代码:
;声明要引入的外部标号
IMPORT FIQ_Exception ;快速中断异常处理程序
IMPORT __main ;C语言主程序入口
IMPORT TargetResetInit ;目标板基本初始化函数
CODE32 ;指明32位代码
AREA vectors,CODE,READONLY
ENTRY ;指明入口地址
;中断向量表
Reset
LDR PC, ResetAddr ;跳到复位处理
LDR PC, UndefinedAddr ;跳到未定义异常处理
LDR PC, SWI_Addr ;跳到软件中断处理
LDR PC, PrefetchAddr ;跳到预取指中止处理
LDR PC, DataAbortAddr ;跳到数据中止处理
DCD 0xb9205f80 ;保留的异常
LDR PC, [PC, # -0xff0] ;跳到IRQ中断处理
LDR PC, FIQ_Addr ;跳到FIQ中断处理
ResetAddr DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr DCD SoftwareInterrupt
PrefetchAddr DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse DCD 0
IRQ_Addr DCD 0
FIQ_Addr DCD FIQ_Handler
Undefined ;未定义指令
B Undefined
SoftwareInterrupt ;软中断
B SoftwareInterrupt
PrefetchAbort ;取指令中止
B PrefetchAbort
DataAbort ;取数据中止
B DataAbort
FIQ_Handler ;快速中断
STMFD SP!, {R0-R3, LR} ;R0-R3, LR入栈
BL FIQ_Exception ;跳到快速中断处理程序,用户编写
LDMFD SP!, {R0-R3, LR} ;出栈恢复R0-R3, LR数据
SUBS PC, LR, #4 ;FIQ返回
ResetInit ;复位处理,下一节讲述
Undefined ;未定义指令
B Undefined
SoftwareInterrupt ;软中断
B SoftwareInterrupt
PrefetchAbort ;取指令中止
B PrefetchAbort
|
2
、初始化代码分析
ARM公司只设计核心,不自己生产芯片,只是把核心授权给其他厂商,其他厂商购买了授权后加入自己的外设后生产各具特色的芯片,这样就促进了基于ARM处 理器核的芯片多元化,但也使得每一种芯片的启动代码差别很大,不易于编写出统一的初始化代码。所以启动代码与芯片的特性有着紧密的联系,本章以 LPC2200系列的ARM芯片为基础,详细分析一下LPC2220的初始化代码。
1)定义入口地址
初始化代码中必须指明入口地址,一般采用ARM汇编语言中的ENTRY语句。
CODE32 ;指明32位代码
AREA vectors,CODE,READONLY
ENTRY ;指明入口地址
|
2)向量表定义
初始化代码首先必须建立起所需要的异常中断向量表(即异常中断处理程序地址表)。ARM芯片复位后,系统进入管理模式、ARM状态,PC寄存器(运行指 针)的值为0x00,所以必须保证向量表代码定位在0x00处,或者映射到0x00处。实际上,中断向量表就是一系列分支语句,每条分支语句跳转到相应的 中断处理程序。
LPC2220处理器上的中断向量表地址为0x0000 0000 ~ 0x0000 001C,LPC2220的启动代码中,向量表的定义如下所列。
;中断向量表
Reset
LDR PC, ResetAddr ;跳到复位处理 0x0000 0000
LDR PC, UndefinedAddr ;跳到未定义异常处理 0x0000 0004
LDR PC, SWI_Addr ;跳到软件中断处理 0x0000 0008
LDR PC, PrefetchAddr ;跳到预取指中止处理 0x0000 000C
LDR PC, DataAbortAddr ;跳到数据中止处理 0x0000 0010
DCD 0xb9205f80 ;保留的异常 0x0000 0014
LDR PC, [PC, # -0xff0] ;跳到IRQ中断处理 0x0000 0008
LDR PC, FIQ_Addr ;跳到FIQ中断处理 0x0000 001C
ResetAddr DCD ResetInit
UndefinedAddr DCD Undefined
SWI_Addr DCD SoftwareInterrupt
PrefetchAddr DCD PrefetchAbort
DataAbortAddr DCD DataAbort
Nouse DCD 0
IRQ_Addr DCD 0
FIQ_Addr DCD FIQ_Handler
|
3)系统变量初始化
如果一旦产生中断,微控制器会切换到相应的模式,并跳转到向量表指定地址去执行程序。例如:一旦产生FIQ中断,处理器将会切换到FIQ模式,并跳转到向 量表0x0000001C地址执行程序。程序将跳到FIQ_Handler标号处,处理FIQ中断服务程序。各个标号定义如下:
Undefined ;未定义指令
B Undefined
SoftwareInterrupt ;软中断
B SoftwareInterrupt
PrefetchAbort ;取指令中止
B PrefetchAbort
DataAbort ;取数据中止
B DataAbort
FIQ_Handler ;快速中断
STMFD SP!, {R0-R3, LR} ;R0-R3, LR入栈
BL FIQ_Exception ;跳到快速中断处理程序,用户编写
LDMFD SP!, {R0-R3, LR} ;出栈恢复R0-R3, LR数据
SUBS PC, LR, #4 ;FIQ返回
|
4)中断系统初始化
建立好中断向量表后,一旦芯片上电或复位,程序就会跳转到
ResetInit 处执行。将会对中断系统进行初始化。
//-----------------初始化VIC中断系统-------------------
VICIntEnClr = 0xffffffff; //关闭所有中断,并且中断地址清0
VICVectAddr = 0;
VICIntSelect = 0;
|
5)初始化存储器系统
再对存储器的初始化(即配置外部总线控制器)
//-----------------初始化存储器系统-------------------
BCFG0 = 0x1000ffef; //外部存储器Bank0~Bank3控制器设置,配置成最慢的外部总线速度
BCFG1 = 0x1000ffef;
BCFG2 = 0x2000ffef;
BCFG3 = 0x2000ffef;
|
6)初始化堆栈
然后调用
InitStack子程序 来初始化各个模式下的堆栈;
;定义各个模式下的堆栈大小
USR_STACK_LEGTH EQU 256
SVC_STACK_LEGTH EQU 0
FIQ_STACK_LEGTH EQU 0
IRQ_STACK_LEGTH EQU 256
ABT_STACK_LEGTH EQU 0
UND_STACK_LEGTH EQU 0
;* * * * * * 初始化堆栈 * * * * * *
InitStack
MOV R0, LR ;保存链接寄存器LR的值
;设置管理模式堆栈
MSR CPSR_c, #0xd3 ;切换到管理模式
LDR SP, StackSvc ;设置管理模式下的堆栈指针SP
;设置中断模式堆栈
MSR CPSR_c, #0xd2 ;切换到中断模式
LDR SP, StackIrq ;设置中断模式下的堆栈指针SP
;设置快速中断模式堆栈
MSR CPSR_c, #0xd1
LDR SP, StackFiq
;设置中止模式堆栈
MSR CPSR_c, #0xd7
LDR SP, StackAbt
;设置未定义模式堆栈
MSR CPSR_c, #0xdb
LDR SP, StackUnd
;设置系统模式堆栈
MSR CPSR_c, #0x5f ;切换到系统模式,并打开中断
LDR SP, =StackUsr
MOV PC, R0 ;程序返回
;定义字单元,保存各个模式下的堆栈栈底
StackSvc DCD SvcStackSpace + (SVC_STACK_LEGTH - 1)* 4
StackIrq DCD IrqStackSpace + (IRQ_STACK_LEGTH - 1)* 4
StackFiq DCD FiqStackSpace + (FIQ_STACK_LEGTH - 1)* 4
StackAbt DCD AbtStackSpace + (ABT_STACK_LEGTH - 1)* 4
StackUnd DCD UndtStackSpace + (UND_STACK_LEGTH - 1)* 4
;定义数据段MyStacks,给各个模式分配堆栈空间
AREA MyStacks, DATA, NOINIT, ALIGN=2
SvcStackSpace SPACE SVC_STACK_LEGTH * 4 ;管理模式堆栈空间
IrqStackSpace SPACE IRQ_STACK_LEGTH * 4 ;中断模式堆栈空间
FiqStackSpace SPACE FIQ_STACK_LEGTH * 4 ;快速中断模式堆栈空间
AbtStackSpace SPACE ABT_STACK_LEGTH * 4 ;中止义模式堆栈空间
UndtStackSpace SPACE UND_STACK_LEGTH * 4 ;未定义模式堆栈
END
|
7)时钟初始化、I/O初始化
时钟是芯片各部分正常工作的基础,虽然时钟可以在任何时候设置,但为了避免混乱,最好在进入main函数之前设置好,时钟初始化如下:
//-----------------初始化系统时钟-------------------
PLLCON = 1; //PLL使能
/*根据Fpclk和Fcclk的设置(在whole.h头文件里定义了),配置VPBDIV寄存器*/
#if (Fpclk / (Fcclk / 4)) == 1
VPBDIV = 0;
#endif
#if (Fpclk / (Fcclk / 4)) == 2
VPBDIV = 2;
#endif
#if (Fpclk / (Fcclk / 4)) == 4
VPBDIV = 1;
#endif
/*根据Fcco和Fcclk的设置(在whole.h头文件里定义了),配置PLLCFG寄存器*/
#if (Fcco / Fcclk) == 2
PLLCFG = ((Fcclk / Fosc) - 1) | (0 << 5);
#endif
#if (Fcco / Fcclk) == 4
PLLCFG = ((Fcclk / Fosc) - 1) | (1 << 5);
#endif
#if (Fcco / Fcclk) == 8
PLLCFG = ((Fcclk / Fosc) - 1) | (2 << 5);
#endif
#if (Fcco / Fcclk) == 16
PLLCFG = ((Fcclk / Fosc) - 1) | (3 << 5);
#endif
PLLFEED = 0xaa;
PLLFEED = 0x55; //两个连续的PLLFEED写操作,确定以上的配置生效
while((PLLSTAT & (1 << 10)) == 0); //等待PLL锁定,否则一直等
PLLCON = 3; //PLL使能,并且PLL连接
PLLFEED = 0xaa;
PLLFEED = 0x55;
···
/*IO口初始化*/
PINSEL0 = 0x00055555; //使能UART0、IIC、SPI0、UART1模块的引脚连接
PINSEL1 = 0x2e8036a9; //使能SPI1模块的引脚连接
PINSEL2 = 0x0fe14914; //PINSEL2寄存器设置
//以上3个PINSEL的设置,使得用户在使用实验板时,不需另行配置引脚连接模块了
|
8)进入C语言程序的运行
最后调用ADS提供的
__main ,初始化库函数执行环境并进入用户的main( )函数。