简单!
全部博文(366)
分类: 嵌入式
2011-06-06 15:40:53
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
.equ IRQMODE, 0x12
.equ SVCMODE, 0x13
.equ ABORTMODE, 0x17
.equ UNDEFMODE, 0x1b
.equ MODEMASK, 0x1f
.equ NOINT, 0xc0
@各个异常模式的堆栈
@The location of stacks
.equ _STACK_BASEADDRESS, 0x33ff8000
.equ _ISR_STARTADDRESS, 0x31ffff00
.equ UserStack, (_STACK_BASEADDRESS-0x3800) @0x33ff4800 ~
.equ SVCStack, (_STACK_BASEADDRESS-0x2800) @0x33ff5800 ~
.equ UndefStack, (_STACK_BASEADDRESS-0x2400) @0x33ff5c00 ~
.equ AbortStack, (_STACK_BASEADDRESS-0x2000) @0x33ff6000 ~
.equ IRQStack, (_STACK_BASEADDRESS-0x1000) @0x33ff7000 ~
.equ FIQStack, (_STACK_BASEADDRESS-0x0) @0x33ff8000 ~
.equ INTMSK, 0x4a000008 @Interrupt mask control
.equ INTSUBMSK, 0x4a00001c @Interrupt sub mask
.equ INTOFFSET, 0x4a000014 @Interruot request source offset
.macro HANDLER HandleLabel
sub sp,sp,#4 @decrement sp(to store jump address)
stmfd sp!,{r0} @PUSH the work register to stack(lr does not push because it return to original address)
ldr r0,=\HandleLabel @load the address of HandleXXX to r0
ldr r0,[r0] @load the contents(service routine start address) of HandleXXX
str r0,[sp,#4] @store the contents(ISR) of HandleXXX to stack
ldmfd sp!,{r0,pc} @POP the work register and pc(jump to ISR)
.endm
.extern main
.text
.global _start
_start:
@********************************************************************
@ 中断向量
@********************************************************************
b Reset
@ 0x04: 未定义指令中止模式的向量地址
b HandlerUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
b HandlerSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
b HandlerPrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
b HandlerDataAbort
@ 0x14: 保留
b HandlerNotUsed
@ 0x18: 中断模式的向量地址
b HandlerIRQ
@ 0x1c: 快中断模式的向量地址
b HandlerFIQ
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
ldr r0,=INTMSK
ldr r1,=0xffffffff @all interrupt disable
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff @all sub interrupt disable
str r1,[r0]
bl clock_init @ 设置MPLL,改变FCLK、HCLK、PCLK
bl memsetup @ 设置存储控制器以使用SDRAM
bl s3c2440_nand_init
ldr r0, =0x30000000 @ 1. 目标地址 = 0x30000000,这是SDRAM的起始地址
mov r1, #4096 @ 2. 源地址 = 4096,运行地址在SDRAM中的代码保存在NAND Flash 4096地址开始处
mov r2, #180*1024 @ 3. 复制长度 = 16K,对于本实验,这是足够了
bl CopyCode2SDRAM @ 调用C函数CopyCode2SDRAM
bl clean_bss @ 清除bss段,未初始化或初值为0的全局/静态变量保存在bss段
bl InitStacks @inital the stack
@ Setup IRQ handler
ldr r0,=HandleIRQ @This routine is needed
ldr r1,=IsrIRQ @if there is not 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[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:
b halt_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 @ 计算返回地址
stmfd sp!,{ r0-r12,lr } @ 保存使用到的寄存器
sub sp,sp,#4 @reserved for PC ;保留pc寄存器的值
stmfd sp!,{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] @按入堆栈
ldmfd sp!,{r8-r9,pc} @堆栈弹出,跳转到相应的中断服务程序
int_return:
ldmfd sp!,{ r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
.align 4
.section .data
.equ HandleReset, (_ISR_STARTADDRESS+0x0)
.equ HandleUndef, (_ISR_STARTADDRESS+0x4)
.equ HandleSWI, (_ISR_STARTADDRESS+0x8)
.equ HandlePabort, (_ISR_STARTADDRESS+0xc)
.equ HandleDabort, (_ISR_STARTADDRESS+0x10)
.equ HandleReserved, (_ISR_STARTADDRESS+0x14)
.equ HandleIRQ, (_ISR_STARTADDRESS+0x18)
.equ HandleFIQ, (_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
.equ HandleEINT0, (_ISR_STARTADDRESS+0x20)
.equ HandleEINT1, (_ISR_STARTADDRESS+0x24)
.equ HandleEINT2, (_ISR_STARTADDRESS+0x28)
.equ HandleEINT3, (_ISR_STARTADDRESS+0x2c)
.equ HandleEINT4_7, (_ISR_STARTADDRESS+0x30)
.equ HandleEINT8_23, (_ISR_STARTADDRESS+0x34)
.equ HandleCAM, (_ISR_STARTADDRESS+0x38) @ Added for 2440.
.equ HandleBATFLT, (_ISR_STARTADDRESS+0x3c)
.equ HandleTICK, (_ISR_STARTADDRESS+0x40)
.equ HandleWDT, (_ISR_STARTADDRESS+0x44)
.equ HandleTIMER0, (_ISR_STARTADDRESS+0x48)
.equ HandleTIMER1, (_ISR_STARTADDRESS+0x4c)
.equ HandleTIMER2, (_ISR_STARTADDRESS+0x50)
.equ HandleTIMER3, (_ISR_STARTADDRESS+0x54)
.equ HandleTIMER4, (_ISR_STARTADDRESS+0x58)
.equ HandleUART2, (_ISR_STARTADDRESS+0x5c)
@0x33FF_FF60
.equ HandleLCD, (_ISR_STARTADDRESS+0x60)
.equ HandleDMA0, (_ISR_STARTADDRESS+0x64)
.equ HandleDMA1, (_ISR_STARTADDRESS+0x68)
.equ HandleDMA2, (_ISR_STARTADDRESS+0x6c)
.equ HandleDMA3, (_ISR_STARTADDRESS+0x70)
.equ HandleMMC, (_ISR_STARTADDRESS+0x74)
.equ HandleSPI0, (_ISR_STARTADDRESS+0x78)
.equ HandleUART1, (_ISR_STARTADDRESS+0x7c)
.equ HandleNFCON, (_ISR_STARTADDRESS+0x80) @ Added for 2440.
.equ HandleUSBD, (_ISR_STARTADDRESS+0x84)
.equ HandleUSBH, (_ISR_STARTADDRESS+0x88)
.equ HandleIIC, (_ISR_STARTADDRESS+0x8c)
.equ HandleUART0, (_ISR_STARTADDRESS+0x90)
.equ HandleSPI1, (_ISR_STARTADDRESS+0x94)
.equ HandleRTC, (_ISR_STARTADDRESS+0x98)
.equ HandleADC, (_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__(
"mrc p15, 0, r1, c1, c0, 0\n" /* 读出控制寄存器 */
"orr r1, r1, #0xc0000000\n" /* 设置为“asynchronous bus mode” */
"mcr p15, 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
#define MEM_CTL_BASE 0x48000000 //存储器控制器寄存器的起始地址
#define SDRAM_BASE 0x30000000
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的书写(仅供参考),其中还有很大的不足,有兴趣的朋友可作补充修改。