摘 要: 本文以
ARM7TDMI为例,对
嵌入式系统从
ROM和
RAM引导的特点及技术实现进行了比较,对异常和中断概念给出了详细的辨析,介绍了如何实现
嵌入式系统从RAM快速引导技术。
前言
嵌入式系统是以各种嵌入式微 处理器为内核,运行 RTOS的面向应用的计算机 控制系统,也是SOC技术的一个重要分支。ARM 是一个IP(知识产权) 公司,以arm体系结构为基础的各种 RISC 微处理器针对不同应用领域提供了不同的指令集(ARM、THUMB、 DSP、XSCALE)可以为各种实时应用提供灵活的选择。
在研制开发基于ARM处理器的嵌入式系统过程中,如何让系统正常快速的启动是一个关键环节,本文主要分析讨论嵌入式系统启动的问题。相关代码以ArmStd2.51IDE环境为参考。
几个相关的概念
Arm/Thumb状态
Arm,Thumb分别是ARM处理器的32/16bits的指令集,对应处理器的两种执行状态。
异常(Exception)
由内/外部源引起的需要处理器干预的一个事件,每种异常模式有自己的特殊功能寄存器,堆栈。处理异常需要保护处理器的当前状态,以便在异常处理后可以恢复执行。当异常发生时,系统强制从固定的地址执行程序, 如表1 所示。
无论在Arm/Thumb状态进入异常,处理程序都是在Arm状态下执行,PC->R14和CPSR->SPSR保存PC和处理器状态,返回时CPSR->SPSR,R14->PC。
中断处理
ARM提供了两种中断源IRQ,FIQ,发生中断时,会进入相应的IRQ,FIQ异常模式,然后异常处理程序会识别不同的中断,调用相应的中断服务程序。所以中断只是异常的一个子集。未用中断通常指向一个哑函数。
在嵌入式系统设计中应正确的辨析异常和中断。
系统启动(start-up)
嵌入式系统的应用程序通常都是固化在ROM中运行。通常用 汇编语言编写启动程序完成系统 硬件和软件运行环境的初始化。启动程序与应用程序一起固化在ROM中。系统在上电和复位会跳到复位异常向量入口地址处。
在目标文件中,代码、数据放在不同的段中。源文件编译链接生成含.data、. text段的目标文件,且链接器生成的.data段是以系统RAM为参考地址,故在系统启动时需要拷贝ROM中的.data段到RAM,以完成对RAM的初始化。拷贝从.text结束位置开始,一般以2kbytes对齐取到下一个2kbytes,确定data的初始位置。这样,就定位.data 、.text段在链接文件中所确定的链接位置。
CPU对ROM或Flash ROM访问速度慢,在一定程度上降低了系统的性能。当ROM在地址0x0时,ARM内核使用ROM 0x0 到0x1c作为异常向量区,那么当异常发生的时候,CPU访问ROM区的入口。我们可以在RAM建立异常向量表镜像,这样可以提高系统的性能(镜像建立)。最优的方法,就是让系统RAM配置在0x0,把初始化程序放在RAM中运行(RAM启动),建立异常向量表的自己对应关系。
为了实现异常的快速处理:
1、 在图2,虚线框中表示的是当RAM在0x0时的情况,这是一种直接对应的关系。直接在向量入口处放置sys_**_handler处理程序。
2、 当ROM在0x0时需要建立了一种镜像的关系。地址指针表示对应的数据存储单元的物理地址,**_hander表示对应的处理程序在ROM的入口。Handle**是物理的存储单元地址,里面放置了处理程序的入口指针。异常发生时经过**_handler---handler**---sys_**_handler的过程。Handler**定义在RAM中。
图1 在文件、ROM、运行时段的分配
图2 ROM/RAM 启动时异常向量表比较
图3 从RAM启动时的过程
启动过程分析
设置异常向量
ARM7要求中断向量表必须设置在从0地址开始,连续8×4字节的空间,具体分配如表1。如果ROM定位于0地址,向量表包含一系列指令跳转到中断服务程序,否则应使用一串位置无关代码( PIC)处理,使用直接加载PC指针的指令。可以在启动程序中添加一段代码,使其在运行时将这段PIC指令拷贝到对应地址开始的存储器空间。
这段代码建立了ROM中的异常入口地址和RAM中的处理代码的镜像关系。这种处理需要在RAM中手动的建立异常向量表,从RAM启动时不需要。(以FIQ的处理为例)
FIQ_Handler /* 从这里开始进入FIQ异常模式,*/
SUBsp, sp, #4
STMFD sp!, {r0}/*装入并减一个字,使用r0,应该首先压入堆栈*/
LDR r0, =HandleFiq /*从这里开始进入在RAM中的异常处理程序*/
LDR r0, [r0]
STR r0, [sp, #4]
LDMFD sp!, {r0, pc} /*返回退出FIQ异常处理模式*/
异常处理程序
Sys_Fiq_Handler
IMPORT ISR_FiqHandler /*进入异常处理,保存 寄存器,每种模式有自己的分组寄存器(banked registe rs)*/
STMFD sp!, {r0-r7, lr} /*发生*/
BL ISR_FiqHandler /*进入异常处理程序*/
LDMFD sp!, {r0-r7, lr}
SUBS pc, lr, #4 /*恢复寄存器,退出异常处理模式*/
对比具体的讨论从ROM/RAM启动的实现代码
(说明【1】从RAM启动 【2】从ROM启动)
AREA Init, CODE, RE ADONLY //初始化代码
ENTRY /*设置入口指针*/
/*启动程序首先必须定义入口指针,而且整个应用程序只有一个入口指针
*/
IF :DEF: ROM_AT_ADDRESS_ZERO
B Reset_Handler B Reset_Handler
B **_Handler B sys_**_Handler
建立镜像关系【2】 直接跳转【1】。
ELSE
/*如果不是从ROM在0x0启动,那么必须把直接加载指令拷贝到0x0位置,这是必须使用ldr完成*/
MOV R8, #0
/*ADR伪指令把PC相关的地址装入寄存器*/
ADR R9, Vector_Init_Block
/* 块加载存储指令,IA = 加载后继增*/
LDMIA R9!, {R0-R7}
STMIA R8!, {R0-R7}
LDMIA R9!, {R0-R7}
STMIA R8!, {R0-R7}
把这些指令放在0x0的位置,实现跳转。直接加载相应的处理程序的地址到PC指针。
Vector_Init_Block //如果不是从rom在0x0启动,这里是一组直接加载PC的指令
LDR PC, Reset_Addr /*在执行拷贝过程建立了异常处理,继续执行Reset_Handler*/
。。。。。。。。。。。。。。。。。。
LDR PC, **_Addr
/*定义 地址指针Reset_Addr 其值为reset_Handler*/
Reset_Addr DCD Reset_Handler
。。。。。。。。。。。。。。。。。。。。。。。。
**_addr DCD **_handler
ENDIF
AREA Main, CODE, READONLY //配置存储器,为运行程序作准备。
从这里进入reset异常处理模式
EXPORT Reset_Handler
Reset_Handler ;/* 复位入口点,关闭所有中断 */
LDR r1, =IntMask
LDR r0, =0xFFFFFFFF
STR r0, [r1]
INI TIALIZE_ST ACK /*初始化堆栈*/
。。。。。。。。。。。。。。。。。。。。。。。。。。。
LDR sp, =SUP_STACK ; 改变CPSR,进入SVC模式
SYNC_ DRAM_CONFIGURATION 配置RAM空间
LDR r0, =0x3FF0000
LDR r1, =0x83FFFF90 ; 赋值 = 0x83FFFF91
STR r1, [r0] ; 特殊功能寄存器Start_addr = 0x3FF00000
;ROM 和 RAM空间配置
;ADRL r0, SysInitDataSDRAM【1】
LDR r0, =SysInitDataSDRAM【2】
LDMIA r0, {r1-r12}
LDR r0, =0x3FF0000 + 0x3010 ; ROMCntr Offset : 0x3010
STMIA r0, {r1-r12}
在RAM中建立异常向量表的镜像入口。【2】
EXCEPTION_VECTOR_TABLE_SETUP
LDR r0, =HandleReset ; 分配的异常向量表在存储区的位置.
LDR r1, =ExceptionHandlerTable ; 异常向量表
MOV r2, #8; 向量数
ExceptLoop /*建立过程*/
LDR r3, [r1], #4
STR r3, [r0], #4
SUBS r2, r2, #1;
BNE ExceptLoop
把代码从ROM拷贝到RAM【1】
ROM2 SDRAM_COPY_START
LDR r0, =|Image$$RO$$Base| ;
指向 ROM 数据的指针
LDR r1, =|Image$$RO$$Limit| ;
LDR r2, =DRAM_BASE ;
RAM区的基地址
SUB r1, r1, r0 ; [r1] 循环计数
ADD r1, r1, #4 ; [r1]
ROM2SDRAM_COPY_LOOP
LDR r3, [r0], #4
STR r3, [r2], #4
SUBS r1, r1, #4 ; 减计数
BNE ROM2SDRAM_COPY_LOOP
改变ROM ,RAM的基地址
ADRL r0, SysInitDataSDRAM_S
/*装载新的地址表,重新配置ROM和RAM*/
LDMIA r0, {r1-r12}
LDR r0, =0x3FF0000 + 0x3010 ;
ROMCntr 偏移地址值 : 0x3010
STMIA r0, {r1-r12}
异常模式下堆栈的初始化
系统堆栈初始化取决于用户使用了哪些中断,以及系统需要处理哪些错误类型。一般来说管理者堆栈必须设置,如果使用了IRQ中断,则IRQ堆栈也必须设置。
初始化 C语言所需的存储器空间:拷贝初始化数据
改变到用户模式并设置用户堆栈
MRS r0, cpsr
B ICr0, r0, #LOCKOUT | MODE_MASK
ORR r1, r0, #USR_MODE
MSR cpsr_cf, r0
LDR sp, =USR_STACK
呼叫C程序
; 进入C程序 IMPORT C_Entry
BL C_Entry
AREA ROMDATA, DATA, READONLY 在ROM中定义的常量
SysInitDataSDRAM 特殊功寄存器常量的定义的入口地址
SysInitDataSDRAM_S
/* 用于在ROM启动时建立异常向量表镜像的地址定义,存放的是异常发生时跳转的地址,是异常处理程序的入口,这个表的位置可以自己分配。
异常向量表【2】
^ DRAM_BASE
HandleReset # 4
HandleUndef # 4
HandleSwi # 4
HandlePrefetch # 4
HandleAbort # 4
HandleReserv # 4
HandleIrq # 4
HandleFiq # 4
图4 ROM/RAM启动系统存储器映射
结语
在嵌入式系统设计开发的过程中,对基本原理的深刻理解有利于设计优化。本文详细辨析了嵌入式设计在系统启动时一些概念,最后在上述分析的基础上给出了实现从RAM快速启动的具体步骤。
前言
嵌入式系统是以各种嵌入式微 处理器为内核,运行 RTOS的面向应用的计算机 控制系统,也是SOC技术的一个重要分支。ARM 是一个IP(知识产权) 公司,以arm体系结构为基础的各种 RISC 微处理器针对不同应用领域提供了不同的指令集(ARM、THUMB、 DSP、XSCALE)可以为各种实时应用提供灵活的选择。
在研制开发基于ARM处理器的嵌入式系统过程中,如何让系统正常快速的启动是一个关键环节,本文主要分析讨论嵌入式系统启动的问题。相关代码以ArmStd2.51IDE环境为参考。
几个相关的概念
Arm/Thumb状态
Arm,Thumb分别是ARM处理器的32/16bits的指令集,对应处理器的两种执行状态。
异常(Exception)
由内/外部源引起的需要处理器干预的一个事件,每种异常模式有自己的特殊功能寄存器,堆栈。处理异常需要保护处理器的当前状态,以便在异常处理后可以恢复执行。当异常发生时,系统强制从固定的地址执行程序, 如表1 所示。
无论在Arm/Thumb状态进入异常,处理程序都是在Arm状态下执行,PC->R14和CPSR->SPSR保存PC和处理器状态,返回时CPSR->SPSR,R14->PC。
中断处理
ARM提供了两种中断源IRQ,FIQ,发生中断时,会进入相应的IRQ,FIQ异常模式,然后异常处理程序会识别不同的中断,调用相应的中断服务程序。所以中断只是异常的一个子集。未用中断通常指向一个哑函数。
在嵌入式系统设计中应正确的辨析异常和中断。
系统启动(start-up)
嵌入式系统的应用程序通常都是固化在ROM中运行。通常用 汇编语言编写启动程序完成系统 硬件和软件运行环境的初始化。启动程序与应用程序一起固化在ROM中。系统在上电和复位会跳到复位异常向量入口地址处。
在目标文件中,代码、数据放在不同的段中。源文件编译链接生成含.data、. text段的目标文件,且链接器生成的.data段是以系统RAM为参考地址,故在系统启动时需要拷贝ROM中的.data段到RAM,以完成对RAM的初始化。拷贝从.text结束位置开始,一般以2kbytes对齐取到下一个2kbytes,确定data的初始位置。这样,就定位.data 、.text段在链接文件中所确定的链接位置。
CPU对ROM或Flash ROM访问速度慢,在一定程度上降低了系统的性能。当ROM在地址0x0时,ARM内核使用ROM 0x0 到0x1c作为异常向量区,那么当异常发生的时候,CPU访问ROM区的入口。我们可以在RAM建立异常向量表镜像,这样可以提高系统的性能(镜像建立)。最优的方法,就是让系统RAM配置在0x0,把初始化程序放在RAM中运行(RAM启动),建立异常向量表的自己对应关系。
为了实现异常的快速处理:
1、 在图2,虚线框中表示的是当RAM在0x0时的情况,这是一种直接对应的关系。直接在向量入口处放置sys_**_handler处理程序。
2、 当ROM在0x0时需要建立了一种镜像的关系。地址指针表示对应的数据存储单元的物理地址,**_hander表示对应的处理程序在ROM的入口。Handle**是物理的存储单元地址,里面放置了处理程序的入口指针。异常发生时经过**_handler---handler**---sys_**_handler的过程。Handler**定义在RAM中。
图1 在文件、ROM、运行时段的分配
图2 ROM/RAM 启动时异常向量表比较
图3 从RAM启动时的过程
启动过程分析
设置异常向量
ARM7要求中断向量表必须设置在从0地址开始,连续8×4字节的空间,具体分配如表1。如果ROM定位于0地址,向量表包含一系列指令跳转到中断服务程序,否则应使用一串位置无关代码( PIC)处理,使用直接加载PC指针的指令。可以在启动程序中添加一段代码,使其在运行时将这段PIC指令拷贝到对应地址开始的存储器空间。
这段代码建立了ROM中的异常入口地址和RAM中的处理代码的镜像关系。这种处理需要在RAM中手动的建立异常向量表,从RAM启动时不需要。(以FIQ的处理为例)
FIQ_Handler /* 从这里开始进入FIQ异常模式,*/
SUBsp, sp, #4
STMFD sp!, {r0}/*装入并减一个字,使用r0,应该首先压入堆栈*/
LDR r0, =HandleFiq /*从这里开始进入在RAM中的异常处理程序*/
LDR r0, [r0]
STR r0, [sp, #4]
LDMFD sp!, {r0, pc} /*返回退出FIQ异常处理模式*/
异常处理程序
Sys_Fiq_Handler
IMPORT ISR_FiqHandler /*进入异常处理,保存 寄存器,每种模式有自己的分组寄存器(banked registe rs)*/
STMFD sp!, {r0-r7, lr} /*发生*/
BL ISR_FiqHandler /*进入异常处理程序*/
LDMFD sp!, {r0-r7, lr}
SUBS pc, lr, #4 /*恢复寄存器,退出异常处理模式*/
对比具体的讨论从ROM/RAM启动的实现代码
(说明【1】从RAM启动 【2】从ROM启动)
AREA Init, CODE, RE ADONLY //初始化代码
ENTRY /*设置入口指针*/
/*启动程序首先必须定义入口指针,而且整个应用程序只有一个入口指针
*/
IF :DEF: ROM_AT_ADDRESS_ZERO
B Reset_Handler B Reset_Handler
B **_Handler B sys_**_Handler
建立镜像关系【2】 直接跳转【1】。
ELSE
/*如果不是从ROM在0x0启动,那么必须把直接加载指令拷贝到0x0位置,这是必须使用ldr完成*/
MOV R8, #0
/*ADR伪指令把PC相关的地址装入寄存器*/
ADR R9, Vector_Init_Block
/* 块加载存储指令,IA = 加载后继增*/
LDMIA R9!, {R0-R7}
STMIA R8!, {R0-R7}
LDMIA R9!, {R0-R7}
STMIA R8!, {R0-R7}
把这些指令放在0x0的位置,实现跳转。直接加载相应的处理程序的地址到PC指针。
Vector_Init_Block //如果不是从rom在0x0启动,这里是一组直接加载PC的指令
LDR PC, Reset_Addr /*在执行拷贝过程建立了异常处理,继续执行Reset_Handler*/
。。。。。。。。。。。。。。。。。。
LDR PC, **_Addr
/*定义 地址指针Reset_Addr 其值为reset_Handler*/
Reset_Addr DCD Reset_Handler
。。。。。。。。。。。。。。。。。。。。。。。。
**_addr DCD **_handler
ENDIF
AREA Main, CODE, READONLY //配置存储器,为运行程序作准备。
从这里进入reset异常处理模式
EXPORT Reset_Handler
Reset_Handler ;/* 复位入口点,关闭所有中断 */
LDR r1, =IntMask
LDR r0, =0xFFFFFFFF
STR r0, [r1]
INI TIALIZE_ST ACK /*初始化堆栈*/
。。。。。。。。。。。。。。。。。。。。。。。。。。。
LDR sp, =SUP_STACK ; 改变CPSR,进入SVC模式
SYNC_ DRAM_CONFIGURATION 配置RAM空间
LDR r0, =0x3FF0000
LDR r1, =0x83FFFF90 ; 赋值 = 0x83FFFF91
STR r1, [r0] ; 特殊功能寄存器Start_addr = 0x3FF00000
;ROM 和 RAM空间配置
;ADRL r0, SysInitDataSDRAM【1】
LDR r0, =SysInitDataSDRAM【2】
LDMIA r0, {r1-r12}
LDR r0, =0x3FF0000 + 0x3010 ; ROMCntr Offset : 0x3010
STMIA r0, {r1-r12}
在RAM中建立异常向量表的镜像入口。【2】
EXCEPTION_VECTOR_TABLE_SETUP
LDR r0, =HandleReset ; 分配的异常向量表在存储区的位置.
LDR r1, =ExceptionHandlerTable ; 异常向量表
MOV r2, #8; 向量数
ExceptLoop /*建立过程*/
LDR r3, [r1], #4
STR r3, [r0], #4
SUBS r2, r2, #1;
BNE ExceptLoop
把代码从ROM拷贝到RAM【1】
ROM2 SDRAM_COPY_START
LDR r0, =|Image$$RO$$Base| ;
指向 ROM 数据的指针
LDR r1, =|Image$$RO$$Limit| ;
LDR r2, =DRAM_BASE ;
RAM区的基地址
SUB r1, r1, r0 ; [r1] 循环计数
ADD r1, r1, #4 ; [r1]
ROM2SDRAM_COPY_LOOP
LDR r3, [r0], #4
STR r3, [r2], #4
SUBS r1, r1, #4 ; 减计数
BNE ROM2SDRAM_COPY_LOOP
改变ROM ,RAM的基地址
ADRL r0, SysInitDataSDRAM_S
/*装载新的地址表,重新配置ROM和RAM*/
LDMIA r0, {r1-r12}
LDR r0, =0x3FF0000 + 0x3010 ;
ROMCntr 偏移地址值 : 0x3010
STMIA r0, {r1-r12}
异常模式下堆栈的初始化
系统堆栈初始化取决于用户使用了哪些中断,以及系统需要处理哪些错误类型。一般来说管理者堆栈必须设置,如果使用了IRQ中断,则IRQ堆栈也必须设置。
初始化 C语言所需的存储器空间:拷贝初始化数据
改变到用户模式并设置用户堆栈
MRS r0, cpsr
B ICr0, r0, #LOCKOUT | MODE_MASK
ORR r1, r0, #USR_MODE
MSR cpsr_cf, r0
LDR sp, =USR_STACK
呼叫C程序
; 进入C程序 IMPORT C_Entry
BL C_Entry
AREA ROMDATA, DATA, READONLY 在ROM中定义的常量
SysInitDataSDRAM 特殊功寄存器常量的定义的入口地址
SysInitDataSDRAM_S
/* 用于在ROM启动时建立异常向量表镜像的地址定义,存放的是异常发生时跳转的地址,是异常处理程序的入口,这个表的位置可以自己分配。
异常向量表【2】
^ DRAM_BASE
HandleReset # 4
HandleUndef # 4
HandleSwi # 4
HandlePrefetch # 4
HandleAbort # 4
HandleReserv # 4
HandleIrq # 4
HandleFiq # 4
图4 ROM/RAM启动系统存储器映射
结语
在嵌入式系统设计开发的过程中,对基本原理的深刻理解有利于设计优化。本文详细辨析了嵌入式设计在系统启动时一些概念,最后在上述分析的基础上给出了实现从RAM快速启动的具体步骤。
评论
查看更多