Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3950794
  • 博文数量: 366
  • 博客积分: 9916
  • 博客等级: 中将
  • 技术积分: 7195
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-29 23:27
个人简介

简单!

文章分类

全部博文(366)

文章存档

2013年(51)

2012年(269)

2011年(46)

分类: 嵌入式

2011-06-06 15:40:53

ARM9启动代码(裸机版)

所谓启动代码,就是处理器在启动的时候执行的一段代码,主要任务是初始化处理器模式,设置堆栈,初始化变量等等。由于以上的操作均与处理器体系结构和系统配置密切相关,所以一般由汇编来编写。

在一般32ARM应用系统中,软件大多数采用c语言进行编程,并且以嵌入式操作系统为开发平台,这样就大大提高了开发效率和软件性能。为了能够进行系统初始化,通常采用一个汇编文件作启动代码。它可以实现向量表定义、堆栈初始化、系统变量初始化、中断系统初始化、I/O初始化、地址重映射等操作。

     启动代码是芯片复位后进入c语言的main函数前执行的一段代码,主要是为运行c语言程序提供基本运行环境。

     在实际应用中,为提高系统的实时性,加快代码的执行速度,系统启动后程序往往要被搬移到RAM中,因为RAM的存取速度要比ROM快得多,这样大大提升系统的性能。启动程序要完成的任务包括:硬件初始化,系统存储系统的配置,复制二级中断向量表。

     启动程序大概过程如下:

1)系统硬件初始化

系统上电或复位后,程序从位于地址0x0Reset Exception Vector处开始执行,因此需要在这里放置bootloader的第一条指令:b Reset,跳转到标号为Reset处进行第一阶段的硬件初始化,主要内容为:关看门狗定时器,关中断,初始化PLL和时钟,初始化存储器系统。执行完以上程序后,系统进行堆栈和存储器的初始化。系统堆栈初始化取决于用户使用了哪些中断,以及系统需要处理哪些错误类型。一般情况下,管理者堆栈必须设置,如果使用了IRQ中断,则IRQ堆栈也必须设置。如果系统使用了外设,则需要设置相关的寄存器,以确定其刷新频率、总线宽度等信息。

     2)代码复制到RAM中运行

因为嵌入式系统的代码通常都是固化在ROMFlash中,上电后开始运行。由于ROMFlash的读取速度相对较慢,这样无疑会降低代码的执行速度和系统的运行效率。因此,需要把系统的代码复制到RAM中运行。

     3)建立二级中断向量表

     ARM系统中,中断向量表位于0x0开始的地址处,意味着无论运行什么样的上层软件,一旦发生中断,程序就得到Flash存储器的中断向量表里去,降低系统的运行效率。因此在RAM中建立自己的二级中断向量表,当中断发生后,程序直接从RAM中取中断向量进入中断子程序。

     4MMU的应用

     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,改变FCLKHCLKPCLK

    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             @ 设置调用ISREINT_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

 

    /* 如果HDIVN0CPU的总线模式应该从“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的书写(仅供参考),其中还有很大的不足,有兴趣的朋友可作补充修改。

 

 

 

 裸机bootloader.doc   

阅读(9494) | 评论(0) | 转发(5) |
给主人留下些什么吧!~~