ARM9启动代码(裸机版)
所谓启动代码,就是处理器在启动的时候执行的一段代码,主要任务是初始化处理器模式,设置堆栈,初始化变量等等。由于以上的操作均与处理器体系结构和系统配置密切相关,所以一般由汇编来编写。
在一般32位ARM应用系统中,软件大多数采用c语言进行编程,并且以嵌入式操作系统为开发平台,这样就大大提高了开发效率和软件性能。为了能够进行系统初始化,通常采用一个汇编文件作启动代码。它可以实现向量表定义、堆栈初始化、系统变量初始化、中断系统初始化、I/O初始化、地址重映射等操作。
启动代码是芯片复位后进入c语言的main函数前执行的一段代码,主要是为运行c语言程序提供基本运行环境。
在实际应用中,为提高系统的实时性,加快代码的执行速度,系统启动后程序往往要被搬移到RAM中,因为RAM的存取速度要比ROM快得多,这样大大提升系统的性能。启动程序要完成的任务包括:硬件初始化,系统存储系统的配置,复制二级中断向量表。
启动程序大概过程如下:
(1)系统硬件初始化
系统上电或复位后,程序从位于地址0x0的Reset Exception Vector处开始执行,因此需要在这里放置bootloader的第一条指令:b Reset,跳转到标号为Reset处进行第一阶段的硬件初始化,主要内容为:关看门狗定时器,关中断,初始化PLL和时钟,初始化存储器系统。执行完以上程序后,系统进行堆栈和存储器的初始化。系统堆栈初始化取决于用户使用了哪些中断,以及系统需要处理哪些错误类型。一般情况下,管理者堆栈必须设置,如果使用了IRQ中断,则IRQ堆栈也必须设置。如果系统使用了外设,则需要设置相关的寄存器,以确定其刷新频率、总线宽度等信息。
(2)代码复制到RAM中运行
因为嵌入式系统的代码通常都是固化在ROM或Flash中,上电后开始运行。由于ROM和Flash的读取速度相对较慢,这样无疑会降低代码的执行速度和系统的运行效率。因此,需要把系统的代码复制到RAM中运行。
(3)建立二级中断向量表
在ARM系统中,中断向量表位于0x0开始的地址处,意味着无论运行什么样的上层软件,一旦发生中断,程序就得到Flash存储器的中断向量表里去,降低系统的运行效率。因此在RAM中建立自己的二级中断向量表,当中断发生后,程序直接从RAM中取中断向量进入中断子程序。
(4)MMU的应用
MMU是存储器管理单元的缩写,是用来管理虚拟内存系统的器件。MMU完成的两个主要功能是:将虚地址转换成物理地址,控制存储器存取允许。MMU关掉时,虚地址直接输出到物理地址总线。
由于跑的是裸机,没有用到操作系统,以下启动代码不包含MMU的应用。
@下面是对arm处理器模式寄存器对应的常数进行赋值,arm处理器有一个CPSR寄存器,
@它的后五位决定了处理器处于哪个模式下。可以看出常数的定义都不会超过后5位的。*/
@Pre-defined constants
.equ USERMODE, 0x10
.equ FIQMODE,0x11
.equIRQMODE,0x12
.equSVCMODE,0x13
.equABORTMODE,0x17
.equUNDEFMODE,0x1b
.equMODEMASK,0x1f
.equNOINT,0xc0
@各个异常模式的堆栈
@The location of stacks
.equ_STACK_BASEADDRESS,0x33ff8000
.equ_ISR_STARTADDRESS,0x31ffff00
.equUserStack,(_STACK_BASEADDRESS-0x3800)@0x33ff4800 ~
.equSVCStack,(_STACK_BASEADDRESS-0x2800)@0x33ff5800 ~
.equUndefStack,(_STACK_BASEADDRESS-0x2400)@0x33ff5c00 ~
.equAbortStack,(_STACK_BASEADDRESS-0x2000)@0x33ff6000 ~
.equIRQStack,(_STACK_BASEADDRESS-0x1000)@0x33ff7000 ~
.equFIQStack,(_STACK_BASEADDRESS-0x0)@0x33ff8000 ~
.equINTMSK,0x4a000008@Interrupt mask control
.equINTSUBMSK,0x4a00001c@Interrupt sub mask
.equINTOFFSET,0x4a000014@Interruot request source offset
.macro HANDLER HandleLabel
subsp,sp,#4@decrement sp(to store jump address)
stmfdsp!,{r0}@PUSH the work register to stack(lr does not push because it return to original address)
ldrr0,=\HandleLabel@load the address of HandleXXX to r0
ldrr0,[r0]@load the contents(service routine start address) of HandleXXX
strr0,[sp,#4]@store the contents(ISR) of HandleXXX to stack
ldmfdsp!,{r0,pc}@POP the work register and pc(jump to ISR)
.endm
.externmain
.text
.global _start
_start:
@********************************************************************
@中断向量
@********************************************************************
bReset
@ 0x04:未定义指令中止模式的向量地址
bHandlerUndef
@ 0x08:管理模式的向量地址,通过SWI指令进入此模式
bHandlerSWI
@ 0x0c:指令预取终止导致的异常的向量地址
bHandlerPrefetchAbort
@ 0x10:数据访问终止导致的异常的向量地址
bHandlerDataAbort
@ 0x14:保留
bHandlerNotUsed
@ 0x18:中断模式的向量地址
bHandlerIRQ
@ 0x1c:快中断模式的向量地址
bHandlerFIQ
Reset:
ldr sp, =4096@设置栈指针,以下都是C函数,调用前需要设好栈
bldisable_watch_dog@关闭WATCHDOG,否则CPU会不断重启
ldrr0,=INTMSK
ldrr1,=0xffffffff@all interrupt disable
strr1,[r0]
ldrr0,=INTSUBMSK
ldrr1,=0x7fff@all sub interrupt disable
strr1,[r0]
blclock_init@设置MPLL,改变FCLK、HCLK、PCLK
blmemsetup@设置存储控制器以使用SDRAM
bls3c2440_nand_init
ldr r0, =0x30000000 @ 1.目标地址= 0x30000000,这是SDRAM的起始地址
mov r1, #4096@ 2.源地址= 4096,运行地址在SDRAM中的代码保存在NAND Flash 4096地址开始处
mov r2, #180*1024 @ 3.复制长度= 16K,对于本实验,这是足够了
blCopyCode2SDRAM@调用C函数CopyCode2SDRAM
blclean_bss@清除bss段,未初始化或初值为0的全局/静态变量保存在bss段
blInitStacks@inital the stack
@ Setup IRQ handler
ldrr0,=HandleIRQ@This routine is needed
ldrr1,=IsrIRQ@if there is not 'subs pc,lr,#4' at 0x18, 0x1c
strr1,[r0]@这三条语句很明显就是说明了,HandleIRQ这个中断向量
@的存储单元被赋上了IsrIRQ标号的地址,这样发生IRQ中
@断后就会直接去到二级表,去确认具体发生哪个中断。
ldr pc, =on_sdram@跳到SDRAM中继续执行
on_sdram:
msr cpsr_c, #0x5f@设置I-bit=0,开IRQ中断
ldr sp, =0x34000000@设置栈指针,
ldr lr, =halt_loop@设置返回地址
ldr pc, =main@调用main函数
halt_loop:
bhalt_loop
HandlerFIQ:
HANDLER HandleFIQ
HandlerIRQ:
HANDLER HandleIRQ
HandlerUndef:
HANDLER HandleUndef
HandlerSWI:
HANDLER HandleSWI
HandlerDataAbort:
HANDLER HandleDabort
HandlerPrefetchAbort:
HANDLER HandlePabort
HandlerNotUsed:
b.
InitStacks:
@Do not use DRAM,such as stmfd,ldmfd......
@SVCstack is initialized before
@Under toolkit ver 2.5, 'msr cpsr,r1' can be used instead of 'msr cpsr_cxsf,r1'
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_c,r1@UndefMode
ldr sp,=UndefStack@ UndefStack=0x33FF_5C00
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_c,r1@AbortMode
ldr sp,=AbortStack@ AbortStack=0x33FF_6000
orr r1,r0,#IRQMODE|NOINT
msr cpsr_c,r1@IRQMode
ldr sp,=IRQStack@ IRQStack=0x33FF_7000
orr r1,r0,#FIQMODE|NOINT
msr cpsr_c,r1@FIQMode
ldr sp,=FIQStack@ FIQStack=0x33FF_8000
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msr cpsr_c,r1@SVCMode
ldr sp,=SVCStack@ SVCStack=0x33FF_5800
@USER mode has not be initialized.
mov pc,lr
@the LR register will not be valid if the current mode is not SVC mode.
IsrIRQ:
sub lr, lr, #4@计算返回地址
stmfdsp!,{ r0-r12,lr }@保存使用到的寄存器
sub sp,sp,#4@reserved for PC;保留pc寄存器的值
stmfdsp!,{r8-r9}@把r8 r9按入堆栈
ldr lr, =int_return@设置调用ISR即EINT_Handle函数后的返回地址
ldr r9,=INTOFFSET@把中断偏移INTOFFSET的地址装入r9里面
ldr r9,[r9] @取出INTOFFSET单元里面的值给r9
ldr r8,=HandleEINT0@向量表的入口地址赋给r8
add r8,r8,r9,lsl #2@求出具体中断向量的地址
ldr r8,[r8] @中断向量里面存储的中断服务程序的入口地址赋给r8
str r8,[sp,#8]@按入堆栈
ldmfdsp!,{r8-r9,pc}@堆栈弹出,跳转到相应的中断服务程序
int_return:
ldmfdsp!,{ r0-r12,pc }^@中断返回, ^表示将spsr的值复制到cpsr
.align 4
.section .data
.equHandleReset,(_ISR_STARTADDRESS+0x0)
.equHandleUndef,(_ISR_STARTADDRESS+0x4)
.equHandleSWI,(_ISR_STARTADDRESS+0x8)
.equHandlePabort,(_ISR_STARTADDRESS+0xc)
.equHandleDabort,(_ISR_STARTADDRESS+0x10)
.equHandleReserved,(_ISR_STARTADDRESS+0x14)
.equHandleIRQ,(_ISR_STARTADDRESS+0x18)
.equHandleFIQ,(_ISR_STARTADDRESS+0x1c)
@Do not use the label 'IntVectorTable',
@The value of IntVectorTable is different with the address you think it may be.
@IntVectorTable
@0x33FF_FF20
.equHandleEINT0, (_ISR_STARTADDRESS+0x20)
.equHandleEINT1,(_ISR_STARTADDRESS+0x24)
.equHandleEINT2,(_ISR_STARTADDRESS+0x28)
.equHandleEINT3,(_ISR_STARTADDRESS+0x2c)
.equHandleEINT4_7,(_ISR_STARTADDRESS+0x30)
.equHandleEINT8_23,(_ISR_STARTADDRESS+0x34)
.equHandleCAM,(_ISR_STARTADDRESS+0x38)@ Added for 2440.
.equHandleBATFLT,(_ISR_STARTADDRESS+0x3c)
.equHandleTICK,(_ISR_STARTADDRESS+0x40)
.equHandleWDT,(_ISR_STARTADDRESS+0x44)
.equHandleTIMER0,(_ISR_STARTADDRESS+0x48)
.equHandleTIMER1,(_ISR_STARTADDRESS+0x4c)
.equHandleTIMER2,(_ISR_STARTADDRESS+0x50)
.equHandleTIMER3,(_ISR_STARTADDRESS+0x54)
.equHandleTIMER4,(_ISR_STARTADDRESS+0x58)
.equHandleUART2,(_ISR_STARTADDRESS+0x5c)
@0x33FF_FF60
.equHandleLCD,(_ISR_STARTADDRESS+0x60)
.equHandleDMA0,(_ISR_STARTADDRESS+0x64)
.equHandleDMA1,(_ISR_STARTADDRESS+0x68)
.equHandleDMA2,(_ISR_STARTADDRESS+0x6c)
.equHandleDMA3,(_ISR_STARTADDRESS+0x70)
.equHandleMMC,(_ISR_STARTADDRESS+0x74)
.equHandleSPI0,(_ISR_STARTADDRESS+0x78)
.equHandleUART1,(_ISR_STARTADDRESS+0x7c)
.equHandleNFCON, (_ISR_STARTADDRESS+0x80)@ Added for 2440.
.equHandleUSBD,(_ISR_STARTADDRESS+0x84)
.equHandleUSBH,(_ISR_STARTADDRESS+0x88)
.equHandleIIC,(_ISR_STARTADDRESS+0x8c)
.equHandleUART0, (_ISR_STARTADDRESS+0x90)
.equHandleSPI1,(_ISR_STARTADDRESS+0x94)
.equHandleRTC,(_ISR_STARTADDRESS+0x98)
.equHandleADC,(_ISR_STARTADDRESS+0x9c)
以下部分是我们bootloader中调用到的c程序部分,主要是完成:关看门狗、初始化存储器、初始化时钟、从Nandflash将代码拷贝到SDRAM、初始化数据段。
/*
*关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
WTCON = 0;//关闭WATCHDOG很简单,往这个寄存器写0即可
}
#define S3C2410_MPLL_200MHZ((0x5c<<12)|(0x04<<4)|(0x00))
#define S3C2440_MPLL_200MHZ((0x5c<<12)|(0x01<<4)|(0x02))
/*
*对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV
*有如下计算公式:
*S3C2410: MPLL(FCLK) = (m * Fin)/(p * 2^s)
*S3C2410: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
*其中: m = MDIV + 8, p = PDIV + 2, s = SDIV
*对于本开发板,Fin = 12MHz
*设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4,
* FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
*/
void clock_init(void)
{
// LOCKTIME = 0x00ffffff;//使用默认值即可
CLKDIVN= 0x03;// FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
/*如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode”*/
__asm__(
"mrcp15, 0, r1, c1, c0, 0\n"/*读出控制寄存器*/
"orrr1, r1, #0xc0000000\n"/*设置为“asynchronous bus mode”*/
"mcrp15, 0, r1, c1, c0, 0\n"/*写入控制寄存器*/
);
/*判断是S3C2410还是S3C2440 */
if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
{
MPLLCON = S3C2410_MPLL_200MHZ;/*现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
else
{
MPLLCON = S3C2440_MPLL_200MHZ;/*现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
}
//设置存储控制器以使用SDRAM
#defineMEM_CTL_BASE0x48000000//存储器控制器寄存器的起始地址
#defineSDRAM_BASE0x30000000
void memsetup(void)
{
volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
/*这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值
*写在数组中,是因为要生成”位置无关的代码”,使得这个函数可以在被复制到
* SDRAM之前就可以在steppingstone中运行
*/
/*存储控制器13个寄存器的值*/
p[0] = 0x22011110;//BWSCON
p[1] = 0x00000700;//BANKCON0
p[2] = 0x00000700;//BANKCON1
p[3] = 0x00000700;//BANKCON2
p[4] = 0x00000700;//BANKCON3
p[5] = 0x00000700;//BANKCON4
p[6] = 0x00000700;//BANKCON5
p[7] = 0x00018005;//BANKCON6
p[8] = 0x00018005;//BANKCON7
/* REFRESH,
* HCLK=12MHz:0x008C07A3,
* HCLK=100MHz: 0x008C04F4
*/
p[9]= 0x008C04F4;
p[10] = 0x000000B1;//BANKSIZE
p[11] = 0x00000030;//MRSRB6
p[12] = 0x00000030;//MRSRB7
}
/*
void copy_steppingstone_to_sdram(void)
{
unsigned int *pdwSrc= (unsigned int *)0;
unsigned int *pdwDest = (unsigned int *)0x30000000;
while (pdwSrc < (unsigned int *)4096)//copy 4K bytes
{
*pdwDest = *pdwSrc;
pdwDest++;
pdwSrc++;
}
}*/
int CopyCode2SDRAM(unsigned char *buf, unsigned long start_addr, int size)
{
extern void s3c2440_nand_read(unsigned char *buf, unsigned long start_addr, int size);
s3c2440_nand_read(buf, start_addr, size);
return 0;
}
void clean_bss(void)
{
extern int __bss_start, __bss_end;
int *p = &__bss_start;
for (; p < &__bss_end; p++)
*p = 0;
}
__bss_start, __bss_end在连接脚本中有定义,连接脚本(其书写方式在其它的文档中已有作介绍)如下:
SECTIONS {
. = 0x00000000;
.init : AT(0){ /home/txgcwm/weidongshan/arm_sources/code/obj/2440start.o /home/txgcwm/weidongshan/arm_sources/code/obj/init.o /home/txgcwm/weidongshan/arm_sources/code/obj/nand.o}
. = 0x30000000;
.text : AT(4096) { *(.text) }
.rodata ALIGN(4) : AT((LOADADDR(.text)+SIZEOF(.text)+3)&~(0x03)) {*(.rodata*)}
.data ALIGN(4): AT((LOADADDR(.rodata)+SIZEOF(.rodata)+3)&~(0x03)) { *(.data) }
__bss_start = .;
.bss ALIGN(4): { *(.bss)*(COMMON) }
__bss_end = .;
}
以上就是最基本的bootloader的书写(仅供参考),其中还有很大的不足,有兴趣的朋友可作补充修改。