嵌入式软件工程师&&太极拳
全部博文(548)
分类: 嵌入式
2012-05-16 09:54:39
AT91初始化代码手册中文翻译版
AT91初始化代码手册中文翻译版
介绍
由于多种原因基于ARM的AT91的大多数应用代码使用C语言编写。然而,启动顺序需要初始化ARM处理器和严重依赖于寄存器结构的关键设备和内存映射处理机,和存储器重映射操作。由于这个原因,C启动序列必须用汇编编写。
这个应用笔记描述了一个AT91的C代码启动序列示例。他是使用ARM ADS1.1研发工具为AT91评估板写的基于C启动序列。更多的可用C启动序列示例见AT91库。在上电并且复位后C启动序列激活执行。
C启动次序
在设计ARM嵌入式应用设计中主要的考虑是规划内存图。特别是位于地址0x0的存储器。复位后,处理器从地址0x0处的指令处开始执行,因此必须能够从此处取得可执行代码。在嵌入式系统中,这需要初始化后在地址0xO处是NVM(非挥发存储器)。
最简单的规划是在存储器映射中将ROM定位在地址0。当他首先执行位于0x00地址的第一条指令后应用程式能够指向自动的实际入口。但是这也有一个缺点,ROM位窄(8、16位)且比RAM慢,存取他需要更多的等待周期。这将减缓处理器处理通过矢量表的异常尤其是中断。况且,假如矢量表在ROM中,他不能够被代码编辑。
由于RAM较ROM存取速度快且位宽,假如在RAM中0x00处存储器作为矢量表和中断句柄更好。虽然在通常运行中RAM被定位在0x0处数必需的,假如上电后RAM定位在地址0x0,再复位指令入口处没有一个合法的可执行指令。所以上电后ROM必须定位在0x0以确保此处有一个合法的复位矢量。从复位到正常运行的存储器图变换通常通过执行一个REMAP(重映射)来完成。
许多基于ARM的嵌入式应用程式包含在ROM中且在复位后执行。当编写嵌入式操作系统,或没有操作系统从复位后执行的嵌入式应用程式时有几个因素必须考虑,包括:
*变换ROM到RAM,以改善执行速度。
*初始化执行环境。例如异常向量,堆栈、I/O引脚
*初始化应用——例如,从ROM中到RAM中拷贝初始化值付值给初始化变量并且清除其他变量为0。
*连接嵌入式执行映像到存储器中放置程式和代码的指定区域。
对于没有操作系统的嵌入式应用,rom中的代码必须提供一个方法以初始化他自己并且开始执行。复位后不会自动初始化,因此应用程式入口在他调用C代码之前执行一些初始化。
复位后定位在地址零的初始化代码,必须:
*为初始化代码标记入口标记
*配置异常向量。
*初始化内存系统
*初始化堆栈指针寄存器
*初始化任何临界I/O器件
*初始化中断系统需求的任何RAM变量
*使能中断(假如通过初始化句柄)
*假如需要的话改变处理器模式
*假如需要的话改变处理器状态
环境初始化完成后,应用程式初始化继续并且将进入C代码入口。
C启动文档是上电后第一个执行的文档并且从复位后执行微控制器初始化然后调用其他应用程式的主例程。主程式将是个死循环且不应该返回。
ARM内核复位后从地址零处开始执行。对于嵌入式系统。这意味着系统复位后ROM必须在地址0。由于ROM的限制,异常处理的速度将受到影响并且异常向量不能被编辑。一个通用的策略是重映射ROM到RAM中并且启动后从ROM拷贝异常向量到RAM.
C启动示例
在AT91软件库中本笔记和其他笔记包含一个普通的启动文档。这个事例基于AT91研发板、使用ARM ADS 1.1研发工具在外接闪存中调试。这个文档必须被编辑以适应用户的需要。
每个AT91评估板描述在AT91库的\software\targets子目录中。每个这些子目录中包含下列文档:
*
*
*一个或多个cstartup.s,使用ARM SDT、ARM ADS和GREEN HILL MULTI 200的标准引导示例。
AT91库提供C启动文档解释如何引导AT91零件并且如何分支到MAIN主函数。启动代码提供一个如何考虑零件的特别性引导AT91零件以达到板子指定特性和调试级别需求的实例。
初始化代码的区域定义和入口指针
在ARM汇编语言源文档中,开始片断必须通过AREA伪指令标记。这个伪指令片断并且配置他的属性。属性被放置在名字之后,通过逗号分割。以上代码示例定义一个名为RESET的只读代码段。
一个可执行映像必须有一个入口指针。被放置在ROM中的可执行映像通常在0x0放置一个入口指针。在初始化代码中使用汇编伪指令定义一个入口指针。
;----------------------------------------------------------------------------------
;-区域定义
;----------------------------------------------------------------------------------
AREA reset,CODE,READONLY
;---------------------------------------------------------------------------------
;定义入口指针
;--------------------------------------------------------------------------------
ENTRY
配置异常向量
异常向量被配置为一个连续的指向到最近的标号或连接到子程式分支的地址空间。在程式的正常执行流程中,程式计数器增加使能处理器操作过内部或外部源产生的事件。在通常运行中异常发生后使处理器转移执行。这样的事件的例子是:
*外部产生中断
*处理器企图执行一个不明确的指令
当这样一个中断发生时,保护以前的处理器状态是必须的,以便当适当的异常程式完成后执行中的程式能够继续执行。
初始化代码必须配置所需的异常向量(见表一)。假如ROM定位在地址0,向量包含指向每个异常的操作硬指令序列。在重映射前,这些向量被映射在地址0。为确保一个合法的跳转他们必须是个关联地址模式。重映射后,这些向量被应设在地址0x01000000,并且仅能够由NRST中断或内部中断改变回去。
处理器的异常句柄通过向量表控制。向量表是个32字节(byte)的保留区域,通常在内寸图的底部。每个向量分配一个字的(word)空间,并且当前的保留字示于表一。因为此处有足够的空间包含句柄的全部代码,每个例外向量的入口包含一个分支指令或调入PC指令以便继续执行适当的处理。
表一。异常向量
;------------------------------------------------------------------------------
;-异常向量(重映射前)
;-------------------------------------------------------------------------------
B itReset ;reset
undefvec
B defvec ; Undefined Instruction
swivec
B ivec ; Software Interrupt
pabtvec
B btvec ;Prefetch Abort
dabtvec
B btvec ;Data Abort
rsvdvec
B rsvdvec ; reserved
irqvec
B irqvec ; reserved
fiqvec
B fiqvec ; reserved
图一。异常向量图
外部总线接口初始化平台
EBI平台被用来配置内存控制器。EBI依赖于器件、时钟和外部内存存取时间。这些植被定义在相应目标器件的“INCLUDE FILE”,例如对于AT91EB55评估板的eb55.inc文档。
;--------------------------------------------------------------------------
;-EBI初始化数据
;--------------------------------------------------------------------------
InitTableEBI
DCD EBI_CSR_0
DCD EBI_CSR_1
DCD EBI_CSR_2
DCD EBI_CSR_3
DCD EBI_CSR_4
DCD EBI_CSR_5
DCD EBI_CSR_6
DCD EBI_CSR_7
DCD 0x00000001 ; REMAP 命令
DCD 0x00000006 ; 6 存储器区域
PtEBIBase
DCDEBI_BASE ; EBI Base Address,标准读
复位处理
位于地址零的代码从这开始执行。应该注意的是,他被连接到0x100 0000。
;------------------------------------------------------------
;- 重映射前复位处理
;------------------------------------------------------------
InitReset
加速引导过程
复位后,扩展总线界面没有配置为片选0和片选0-8的等待状态分离。在重映射命令前,片选0配置能够通过编程具备精确引导内存特性的EBI_CSR0来编辑。,重映射后底部地址变得有效,但等待状态的新数目能够被立即改变。假如希望引导更快的话这是所希望的。
;---------------------------------------------------------------------------
;- 加速引导过程
;-------------------------------------------------------------------------
;-调入EBI系统的底部地址和CSR0的初始值
ldr r0, ptEBIBase
ldr r1, InitTableEBI ;相对值
;- 通过禁止片选0的等待状态加速代码执行
str r1, [r0]
低级初始化
当临界状态时应该考虑外围设备在使能终端前必须被初始化。假如这些外围设备在此处没有被初始化,当中断使能时可能引起虚假的中断。
;-------------------------------------------------------------------------------
;- 低级初始化
;-------------------------------------------------------------------------------
bl —low_level_init
例如:在AT91EB55评估板上,启动PLL(锁相环)。
在复位时,AT91M55800微控制器使用低速时钟(32.786KHZ〕启动以减小系统启动时的电源需求并 且主振荡器是禁止的。PLL能够通过配置高级电源管理控制来运行主振荡器来启动以加速启动过程。
_low_level_init函数在相应的评估板的AT91软件库中在汇编文档中定义。
高级中断控制器配置
复位后,高级中断控制器(AIC)没有配置。C启动文档通过配置缺省中断向量来初始化AIC。缺省中断操作函数定义在AT91函数库中。这些函数能够在应用代码中被重新定义。中断缺省操作的初始化见图2
;---------------------------------------------------------------------------
;- 高级终端控制器配置
;---------------------------------------------------------------------------
;- 配置缺省向量
;---------------------------------------------------------------------------
;- 调入AIC基础地址和缺省操作地址
add r0, pc,#-(8+.-AicData) ; @这里是读取相对值
ldmia r0, {r1-r4}
;- 配置伪造向量
str r4, [r1, #AIC_SPU] ; r4 =伪操作
;- 配置缺省中断操作向量
str r2, [r1, #AIC_SVR] ; SVR[0] for FIQ
add r1, r1, #AIC_SVR
mov r0, #31 ;计数器
LoopAic1
str r3, [r1, r0, LSL #2] ; SVRs for IRQs
subs r0, r0, #1 ; do not save FIQ
bhi LoopAic1
b EndInitAic
;---------------------------------------------------------------------------
;- 缺省中断操作
;---------------------------------------------------------------------------
AicData
DCD AIC_BASE ; AIC Base Address
IMPORT at91_default_fiq_handler
IMPORT at91_default_irq_handler
IMPORT at91_spurious_handler
PtDefaultHandler
DCD at91_default_fiq_handler
DCD at91_default_irq_handler
DCD at91_spurious_handler
EndInitAic
图2,中断的缺省操作初始化
高级中断控制器
拷贝异常向量到内部RAM
异常向量必须拷贝到内部RAM中。为确保在重映射操作中内核执行合法向量,在重映射前执行这个操作是重要的。这里仅仅有五个偏移作为引导使用。见表3。
;------------------------------------------------------------
;--- 在重映射前在内部RAM中配置异常向量
;------------------------------------------------------------
b SetupRamVectors
VectorTable
ldr pc, [pc, #&18] ; SoftReset软件复位
ldr pc, [pc, #&18] ; UndefHandler未定义操作
ldr pc, [pc, #&18] ; SWIHandler
ldr pc, [pc, #&18] ; PrefetchAbortHandler
ldr pc, [pc, #&18] ; DataAbortHandler
nop ; Reserved
ldr pc, [pc,#-0xF20] ; IRQ : read the AIC读AIC
ldr pc, [pc,#-0xF20] ; FIQ : read the AIC
;- 仅仅有五个偏移量作为向量使用
DCD SoftReset
DCD UndefHandler
DCD SWIHandler
DCD PrefetchAbortHandler
DCD DataAbortHandler
;- 运行在绝对地址的向量执行函数
SoftReset
b SoftReset
UndefHandler
b UndefHandler
SWIHandler
b SWIHandler
PrefetchAbortHandler
b PrefetchAbortHandler
DataAbortHandler
b DataAbortHandler
SetupRamVectors
mov r8, #RAM_BASE_BOOT ; @ 在RAM 0x300000的硬件向量
add r9, pc,#-(8+.-VectorTable) ; @从哪儿度相对值
ldmia r9!, {r0-r7} ; 读8向量
stmia r8!, {r0-r7} ; 存储他们到RAM中
ldmia r9!, {r0-r4} ; 读5绝对操作地址
stmia r8!, {r0-r4} ; 存储他们到RAM中
图3,在RAM中拷贝异常向量
存储器控制初始化和重映射命令
复位后AT91系列的RAM映射在地址0x0030 0000。连接到片选0的存储器映射在地址0。当重映射命令执行时,扩展存储器映射在片选寄存器0定义的地址。内部ram定义在地址0以允许在0x0和0x20之间的异常向量能够由软件编辑。
;---------------------------------------------------------------------------
;- 内存控制器初始化
;---------------------------------------------------------------------------
;- 拷贝内存控制器的映像
sub r10, pc,#(8+.-InitTableEBI) ; 得到芯片地址
; 选择寄存器映像
ldr r12, PtInitRemap ;得到真实的跳转地址
; ( 重映射后)
;- Copy Chip Select Register Image to Memory Controller and command remap
;-拷贝片选寄存器映像到内存控制器和重映射命令
ldmia r10!, {r0-r9,r11} ; 调入完整映像和
; EBI 基础地址
stmia r11!, {r0-r9} ; 存储完整映像包括
; 重映射命令
;- J跳转到rom新地址
mov pc, r12 ; jump and break the pipeline
PtInitRemap
DCD InitRemap ; address where to jump after REMAP
;---------------------------------------------------------------------------
;- From here, the code is executed from its link address, ie. 0x100 0000.
;- 从此处,代码开始从他的连接地址开始执行,例如,0x100 0000
;---------------------------------------------------------------------------
InitRemap
ARM处理器流水线确保"mov pc,r12"指令在重映射指令执行前得到读取。重映射后,下一条指令被从RAM中读取并且"mov pc,r12"指令执行完成跳转到ROM中r12(0x100011C)以前的装载地址因此如图4所示同时打断流水线。重映射后的“新”存储器图描述见图5。
图4,重映射命令期间arm内核流水线
重映射前 重映射后 mov PC,r12后
流水线
存储器
图5,重映射后内存图
初始化堆栈寄存器
快速中断、中断、异常终止未定义和。管理堆栈定位在内部存储器的顶部以加速操作速度。用户(应用程式,C)堆栈定位在扩展存储器顶部。
初始化代码初始化堆栈指针寄存器。依赖于中断和异常需要,下面一些或任何堆栈指针需要初始化:
*管理堆栈必须经常初始化
*假如使用IRQ中断的话则IRQ中断堆栈必须初始化。必须在中断使能前完成初始化。
*假如使用FIQ中断的话则FIQ中断堆栈必须初始化。必须在中断使能前完成初始化。
*数据和预取操作的Abort-stack必须初始化。
*未定义指令操作的未定义指令堆栈必须初始化。
通常,Abort-state和未定义指令在简单嵌入式系统中不使用。然而,初始化他们用于调试目的是更优越的。
当改变到用户模式执行应用程式时用户堆栈指针能够被配置。
;------------------------------------------------------------------------
;- 堆栈大小定义
;---------------------------------------------------------------------------
IRQ_STACK_SIZE EQU (3*8*4) ; 3 words
FIQ_STACK_SIZE EQU (3*4) ; 3 words
ABT_STACK_SIZE EQU (1*4) ; 1 word
UND_STACK_SIZE EQU (1*4) ; 1 word
假定使用IRQ_ENTRY/IRQ_EXIT宏指令,当使用向量时中断堆栈需要3个字*优先级8*4字节。中断堆栈依赖于中断操作调整。快速中断需要3个字*4字节,没有优先级。其他堆栈定义为使用缺省的每个一个字。用户堆栈没有定义并且由外接RAM的自由空间所限。
;---------------------------------------------------------------------------
;- 堆栈顶部定义
;---------------------------------------------------------------------------
TOP_EXCEPTION_STACK EQU RAM_LIMIT ; Defined in part
TOP_APPLICATION_STACK EQU EXT_SRAM_LIMIT ; Defined in Target
;---------------------------------------------------------------------------
;- 配置每种模式的堆栈
;---------------------------------------------------------------------------
ldr r0, =TOP_EXCEPTION_STACK
;- 配置快速中断模式和配置FIQ模式堆栈
msr CPSR_c, #ARM_MODE_FIQ:OR:I_BIT:OR:F_BIT
mov r13, r0 ; 初始化FIQ
sub r0, r0, #FIQ_STACK_SIZE
;- 配置中断模式和配置FIQ模式堆栈
msr CPSR_c, #ARM_MODE_IRQ:OR:I_BIT:OR:F_BIT
mov r13, r0 ; Init stack IRQ
sub r0, r0, #IRQ_STACK_SIZE
;- 配置异常终止模式和配置异常终止模式堆栈
msr CPSR_c, #ARM_MODE_ABORT:OR:I_BIT:OR:F_BIT
mov r13, r0 ; Init stack Abort
sub r0, r0, #ABT_STACK_SIZE
;- 配置未定义指令模式和配置未定义指令模式堆栈
msr CPSR_c, #ARM_MODE_UNDEF:OR:I_BIT:OR:F_BIT
mov r13, r0 ; Init stack Undef
sub r0, r0, #UND_STACK_SIZE
;- 配置管理模式和管理模式堆栈
msr CPSR_c, #ARM_MODE_SVC:OR:I_BIT:OR:F_BIT
mov r13, r0 ; Init stack Sup
改变处理器模式和使能中断
假如需要的话初始化代码现在能够通过清除CPSR的中断禁止位来使能中断。这是最早期的安全使能中断的操作。在此时处理器一直处于管理模式。假如应用程式运行在用户模式,改变到用户模式并且初始化用户模式堆栈。
;---------------------------------------------------------------------------
;- 配置应用程式运行模式和使能中断
;---------------------------------------------------------------------------
msr CPSR_c, #ARM_MODE_USER ; 配置用户模式
ldr r13, =TOP_APPLICATION_STACK ; 初始化用户堆栈
初始化软件变量并且指向主函数
下一个任务是初始化数据存储器进入一个循环写零到指定位置用来存储数据。这好形式多余的,但这样做有两个原因:
1.在C语言中,任何未初始化的变量假定包含0作为初始化值。
2.这能够使程式行为再现,即使不是任何变量明显初始化。
——初始化变量的初始化值列表(C语言方法)拷贝到RAM中变量所在的位置。
——连接器放置初始化值到RAM中相应的变量中。
任何初始化变量的初始化值必须从ROM中拷贝到RAM中。任何其他变量必须初始化为0。
当编译器编译调用MAIN()的函数时,他产生一个_MAIN的模型强制连接器从ANSI的C函数库中包含基础的C运行系统。初始化代码库调用_main执行拷贝和初始化。main()函数应当是个封闭的循环并且没有返回值。
;---------------------------------------------------------------------------
;- 交互方式转移到C代码主函数
;---------------------------------------------------------------------------
IMPORT __main
ldr r0, =__main
bx r0
end