zoukankan      html  css  js  c++  java
  • OK6410裸机程序之开始模板(Linux环境)

    自从自己买了一块OK6410的开发板,一直想从裸机程序开始,但由于资金问题,而且觉得JLink太贵不划算,因此没有真正的弄过裸机程序,我的裸机程序开发是应用uboot来下载和启动的。这个方法省掉了一部分钱,但是有时也会出现难以避免的问题,例如串口程序就无法进行。开发裸机程序,我本人觉得:要从底层一步一步的自己写,自己分析,这才是裸机,我实在受不了Windows下的集成开发环境,当然,如果你要赶工期,那么Windows下是最好的,不过对于我们嵌入式爱好者和开发者,在Linux下开发无疑是最好的选择。下面便是记录我学习Linux下的裸机开发,同样目的只是记录自己所做的和给新手一个指导。

    一、 裸机程序的构成

         1. 基本的裸机程序由启动代码和C函数文件构成。而启动代码包括:硬件设备初始化、调用C函数。

    本次分析中代码文件有:

    start.S              启动代码,都是汇编写的

    commom.h   一些通用的函数,比如设置某寄存器的某位为1或0

    irq.c               中断初始化,中断处理等

    regs.h            6410的寄存器地址,需要哪些寄存器可以在本文件中声明和定义

    sdram.c          有关sdram的一些操作,如sdram初始化等

    time.c             系统时钟的有关设置,如PLLclock等

    led.c                这个就是主函数了,主程序就在这里编写,本次只是演示,控制开发板的led灯循环点亮,也就是流水灯

    led.lds             该文件为链接脚本,描述了各个输入文件的各个section如何映射到输出文件的各section中,并控制输出文件中section和符号的内存布局。

    Makefile         这个文件就不用说了吧。。。

         

    1.1 学习启动代码有助于我们以后开发uboot,uboot的启动代码跟裸机的差不多。

            下面把start.S代码贴出来,其中代码中也有注释。

    @**************************************
    @ File: start.S
    @ Function: cpu initial and jump to c program
    @ author: lixiaoming
    @ time: 2012/7/27 21:40
    @**************************************
    
    .extern main
    .text
    .global _start
    _start:
    	b	reset			@ when reset, cpu jump to 0 address
    	b	halt			@ldr	pc, _undefined_instruction
    	b	halt			@ldr	pc, _software_interrupt
    	b	halt			@ldr	pc, _prefetch_abort
    	b	halt			@ldr	pc, _data_abort
    	b	halt			@ldr	pc, _not_used
    	ldr	pc, _irq
    	b	halt			@ldr	pc, _fiq
    
    _irq:
    	.word vector_irq
    
    vector_irq:
    	ldr	sp, = 0x54000000	@ save location
    	sub	lr, lr, #4
    	stmdb	sp!, {r0-r12, lr}	
    
    	bl	do_irq			@ deal with exception
    	
    	@ backing out
    	ldmia	sp!, {r0-r12, pc}^
    
    reset:
    	ldr	r0, = 0x70000000	@ Peripheral port base address
    	orr	r0, r0, #0x13
    	mcr	p15,0,r0,c15,c2,4	@ 256M
    
    	ldr	r0, = 0x7e004000	@ watchdog register address
    	mov	r1, #0x0
    	str	r1, [r0]		@ write 0, disable watchdog
    
    	ldr	sp, =1024*8		@ set stack, notice: can't larger than 8K
    	bl	clock_init		@ system clock initial
    	bl	sdram_init		@ sdram initial
    
    	/* relocation */
    	adr	r0, _start		@ get _start's current address: 0 
    	ldr	r1, = _start		@ _start's link address
    	ldr	r2, = bss_start	@ bss section's begining link address
    	cmp	r0, r1
    	beq	clean_bss
    copy_loop:
    	ldr r3, [r0], #4
    	str r3, [r1], #4
    	cmp r1, r2
    	bne copy_loop
    /* clear bss section */ clean_bss: ldr r0, = bss_start ldr r1, = bss_end mov r3, #0 cmp r0, r1 ldreq pc, = on_ddr clean_loop: str r3, [r0], #4 cmp r0, r1 bne clean_loop ldr pc, = on_ddr on_ddr: bl irq_init @ initial IRQ
        @mrs     r0, cpsr
    	bic	r0, r0, #0x9f
    	orr	r0, r0, #0x10
    	msr	cpsr, r0				@ enter user mode
    
            ldr     sp, = 0x57000000
    	@bl	main				@ call c program's main function

    ldr pc, = main halt: b halt
     
    启动代码的一般流程如下:
            . 硬件相关设置:把外设基地址告诉CPU(ARM11特用)
              (分析:ldr r0, = 0x70000000 其中0x70000000是外设的基地址,从6410的datasheet的第二章存储器映射一章可以找到
                             orr r0, r0, #0x13 指r0中的值是256,代表256M,这是ARM11规定的,具体在ARM11datasheet中)
     
            . 关看门狗
          (分析:ldr r0, = 0x7e004000 加载地址0x7e004000上的数据放入r0中
                         mov r1, #0x0
                         str r1, [r0] 将r1中的数据存储到r0指向的存储单元中——把看门狗寄存器写0)
     
            .设置堆栈(后面要调用c函数,调用函数就要先设置栈,片内8K内存)
            .初始化时钟
            .初始化SDRAM
            .重定位
            .清BSS段
            .调用C函数
     
    1.2 commom.h共用的头文件
           里面编写了一些方便的函数,都是对寄存器的某位或多位进行操作的函数,缩短我们写代码的时间,下面贴出来,原理没什么可讲的,自己分析就知道了。
    #ifndef __COMMON_H
    #define __COMMON_H
    
    #define vi *( volatile unsigned int * ) 
    
    #define set_zero( addr, bit ) ( (vi addr) &= ( ~ ( 1 << (bit) ) ) )
    #define set_one( addr, bit ) ( (vi addr) |= ( 1 << ( bit ) ) )
    
    #define set_bit( addr, bit, val ) ( (vi addr) = (( vi addr)&=(~(1<<(bit))) ) | ( (val)<<(bit) ) )
    
    #define set_2bit( addr, bit, val ) ( (vi addr) = (( vi addr)&(~(3<<(bit))) ) | ( (val)<<(bit) ) )
    
    #define set_nbit( addr, bit, len,  val ) \
    	( (vi addr) = ((( vi addr)&(~(( ((1<<(len))-1) )<<(bit))))  | ( (val)<<(bit) ) ))
    
    #define get_bit( addr, bit ) ( (( vi addr ) & ( 1 << (bit) )) > 0  )
    
    #define get_val( addr, val ) ( (val) = vi addr )
    #define read_val( addr ) ( vi ( addr ) )
    #define set_val( addr, val ) ( (vi addr) = (val) )
    #define or_val( addr, val ) ( (vi addr) |= (val) ) 
    
    ///////////////////////////////
    
    typedef unsigned char u8;
    typedef unsigned short u16;
    typedef unsigned int u32;
    
    // function declare
    
    int delay( int );
    
    #endif /* __COMMON_H */
     
    1.3 irq.c
           这是一个中断初始化和中断处理函数文件。
    下面对ARM异常进行介绍:
            中断也是异常的一种,ARM处理器用7中工作模式:
    (1)用户模式(user)              usr     正常的执行模式
    (2)快速中断模式(FIQ)          fiq      高优先级中断产生进入的模式(高速数据传输等情况使用)
    (3)外部中断模式(IRQ)          irq      低优先级中断产生进入的模式(一般的外部中断)
    (4)特权模式(Superviser)      svc     复位或软中断,供操作系统使用的保护模式
    (5)数据访问中止模式(Abort)  abt      存取数据异常,用于虚拟存储或存储保护
    (6)未定义指令模式(undefine)und      当执行未定义指令时进入的模式
    (7)系统模式(system)          sys      运行特权级操作系统任务
    中断过程:
    (1)系统上电,CPU处于svc模式
    (2)如果发生中断,那么CPU进入IRQ模式;R13、R14切换到自己的R13、R14;跳到相应的中断向量地址
     
    如何进行中断编程?
    (1)中断初始化
    a. 设置中断源(也就是配置引脚模式)
    b. 设置中断控制器(参照6410datasheet中断控制器一章)
    c. 打开总中断开关(设置CPSR)
     
     
     
    1.4 regs.h
          这是一个6410中声明和定义寄存器的,需要哪个寄存器就在该文件当中定义,在后面的文件当中直接调用即可。
     
    1.5 sdram.c
          下面我们来看一下6410核心板DDR的原理图
    image
           从图中我们可以看到,该核心板有两个DDR级联而得,每个有16位,两个一共32位。一个DDR有15根地址线,寻址空间为2^15=32K,参照K4X1G163PC的datasheet可以算出总的容量为:
           64M*16*2 = 2G  则每块为1G,算下来地址线不够,因此地址肯定是多次发出来。
           BA0和BA1说明可以访问4块,而它提供13条行地址和10条列地址。而6410提供DDR控制器,只需要控制DDR控制器即可。那怎样初始化DDR?
            (1)地址线设置
            (2)告诉位宽
            (3)设置时序
    image
    设置DDR控制器
    image
    初始化DDR芯片
    image
    下面参照代码,看看初始化DDR的顺序
     
    #include "common.h"
    
    #define MEMCCMD		0x7e001004
    #define P1REFRESH	0x7e001010
    #define P1CASLAT	0x7e001014
    #define MEM_SYS_CFG	0x7e00f120
    #define P1MEMCFG	0x7e00100c
    #define P1T_DQSS	0x7e001018
    #define P1T_MRD		0x7e00101c
    #define P1T_RAS		0x7e001020
    #define P1T_RC		0x7e001024
    #define P1T_RCD		0x7e001028
    #define P1T_RFC		0x7e00102c
    #define P1T_RP		0x7e001030
    #define P1T_RRD		0x7e001034
    #define P1T_WR		0x7e001038
    #define P1T_WTR		0x7e00103c
    #define P1T_XP		0x7e001040
    #define P1T_XSR		0x7e001044
    #define P1T_ESR		0x7e001048
    #define P1MEMCFG2	0X7e00104c
    #define P1_chip_0_cfg	0x7e001200
    
    #define P1MEMSTAT	0x7e001000
    #define P1MEMCCMD	0x7e001004
    #define P1DIRECTCMD	0x7e001008
    
    	
    #define HCLK	133000000
    
    #define nstoclk(ns)	(ns/( 1000000000/HCLK)+1)
    
    void sdram_init( void )
    {
    	// tell dramc to configure
    	set_val(MEMCCMD, 0x4 );
    
    	// set refresh period
    	set_val( P1REFRESH, nstoclk(7800) );
    
    	// set timing para
    	set_val( P1CASLAT, ( 3 << 1 ) );  
    	set_val( P1T_DQSS, 0x1 );	// 0.75 - 1.25
    	set_val( P1T_MRD, 0x2 );
    	set_val( P1T_RAS, nstoclk(45) );
    	set_val( P1T_RC, nstoclk(68) );
    
    	u32 trcd = nstoclk( 23 );
    	set_val( P1T_RCD, trcd | (( trcd - 3 ) << 3 ) );
    	u32 trfc = nstoclk( 80 );
    	set_val( P1T_RFC, trfc | ( ( trfc-3 ) << 5 ) );   
    	u32 trp = nstoclk( 23 );
    	set_val( P1T_RP, trp | ( ( trp - 3 ) << 3 ) ); 
    	set_val( P1T_RRD, nstoclk(15) );
    	set_val( P1T_WR, nstoclk(15) );
    	set_val( P1T_WTR, 0x7 );
    	set_val( P1T_XP, 0x2 );
    	set_val( P1T_XSR, nstoclk(120) );
    	set_val( P1T_ESR, nstoclk(120) );
    
    	// set mem cfg 
    	set_nbit( P1MEMCFG, 0, 3, 0x2 );  /* 10 column address */
    
    	/* set_nbit: 把从第bit位开始的一共len位消零,然后把这几位设为val */
    	set_nbit( P1MEMCFG, 3, 3, 0x2 );  /* 13 row address */
    	set_zero( P1MEMCFG, 6 );		  /* A10/AP */
    	set_nbit( P1MEMCFG, 15, 3, 0x2 ); /* Burst 4 */
    
    	set_nbit( P1MEMCFG2, 0, 4, 0x5 );
    	set_2bit( P1MEMCFG2, 6, 0x1 );    /* 32 bit */
    	set_nbit( P1MEMCFG2, 8, 3, 0x3 ); /* Mobile DDR SDRAM */
    	set_2bit( P1MEMCFG2, 11, 0x1 );
    
    	set_one( P1_chip_0_cfg, 16 );     /* Bank-Row-Column organization */
    
    	// memory init
    	set_val( P1DIRECTCMD, 0xc0000 ); // NOP
    	set_val( P1DIRECTCMD, 0x000 );  // precharge
    	set_val( P1DIRECTCMD, 0x40000 );// auto refresh
    	set_val( P1DIRECTCMD, 0x40000 );// auto refresh
    	set_val( P1DIRECTCMD, 0xa0000 ); // EMRS
    	set_val( P1DIRECTCMD, 0x80032 ); // MRS
    
    	set_val( MEM_SYS_CFG, 0x0 );
    
    	// set dramc to "go" status	
    	set_val( P1MEMCCMD, 0x000 );
    
    	// wait ready
    	while( !(( read_val( P1MEMSTAT ) & 0x3 ) == 0x1));
    }
     
    1.6 time.c
           对系统时钟进行初始化设置,而对于6410的晶振是12M,需要通过一系列的变频,分频来产生500~600M的时钟。对时钟t进行设置需要参照6410的datasheet中的系统控制器一章的时钟体系。
    image
       初始化设置系统时钟,无非就是对相应的寄存器进行设置,设置分频等,下面说几个知识点:
      图中ARMCLK是ARM11的CPU时钟,一般设置为532MHz
             HCLK为133MHz,一般为NandFlash和DDR提供时钟,PCLK为67MHz
             SCLK为某些特殊设备提供时钟
    当系统上电,晶振开始起振,不可能一下子从12M就变为532M,需要一段缓冲的时间,这段时间称为LOCKTIME,如下图所示:
    image
    #define APLL_LOCK (*((volatile unsigned long *)0x7E00F000))
    #define MPLL_LOCK (*((volatile unsigned long *)0x7E00F004))
    #define EPLL_LOCK (*((volatile unsigned long *)0x7E00F008))
    
    #define OTHERS    (*((volatile unsigned long *)0x7e00f900))
    
    #define CLK_DIV0  (*((volatile unsigned long *)0x7E00F020))
    
    #define ARM_RATIO    0   /* ARMCLK = DOUTAPLL / (ARM_RATIO + 1)    */
    #define HCLKX2_RATIO 4   /* HCLKX2 = HCLKX2IN / (HCLKX2_RATIO + 1) = 100MHz */
    #define HCLK_RATIO   0   /* HCLK = HCLKX2 / (HCLK_RATIO + 1)   = 100MHz       */
    #define PCLK_RATIO   1   /* PCLK   = HCLKX2 / (PCLK_RATIO + 1) = 50MHz    */
    #define MPLL_RATIO   0   /* DOUTMPLL = MOUTMPLL / (MPLL_RATIO + 1)     */
    
    
    #define APLL_CON  (*((volatile unsigned long *)0x7E00F00C))
    #define APLL_CON_VAL  ((1<<31) | (266 << 16) | (3 << 8) | (1))
    
    #define MPLL_CON  (*((volatile unsigned long *)0x7E00F010))
    #define MPLL_CON_VAL  ((1<<31) | (266 << 16) | (3 << 8) | (1))
    
    #define CLK_SRC  (*((volatile unsigned long *)0x7E00F01C))
    
    void clock_init(void)
    {
    	APLL_LOCK = 0xffff;
    	MPLL_LOCK = 0xffff;
    	EPLL_LOCK = 0xffff;
    
    	/* set async mode */
    	OTHERS &= ~(0xc0);
    	while((OTHERS & 0xf00) != 0);
    
    	CLK_DIV0 = (ARM_RATIO) | (MPLL_RATIO << 4)
    		| (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) 
    		| (PCLK_RATIO << 12);
    		
    	APLL_CON = APLL_CON_VAL;  /* 532MHz */
    	MPLL_CON = MPLL_CON_VAL;  /* 532MHz */
    
    	CLK_SRC = 0x03;
    }
     
    下面对时钟设置步骤进行说明:
    (1)设置LockTime,包括APLL_LOCK,MPLL_LOCK,EPLL_LOCK,一般设置为默认值即可,也可以不用设置,因为它复位后就是默认值。
    (2)设置为异步模式,当CPU时钟和内存时钟不相等的时候,需要设置为异步模式,主要是设置寄存器OTHERS,然后在查询相对应位是否为0,一直等待设置完毕。
    (3)然后沿着上图的时钟体系设置PLL寄存器的值
     
    1.7 led.c
         主函数文件就不说了,关键在于你想实现什么功能了
     
    1.8  led.lds
         该裸机程序的链接脚本,首先先把该文件中的内容贴出来:
    SECTIONS {
    	. = 0x50000000;                     //当前地址
    	. = ALIGN(4);
    	.text	: {                          //段名称,放置所有文件的代码段
    		start.o (.text)
    		time.o (.text)
    		irq.o (.text)
    		led.o (.text)
    	}
    
    	. = ALIGN(4);                       //4位对齐
    	.rodata	: {
    		* (.rodata)
    	}
    
    	. = ALIGN(4);
    	.data	: {
    		* (.data)
    	}
    
    	. = ALIGN(4);
    	bss_start = .;                      //bss段开始处
    	.bss	: {                          //放置所用bss段
    		* (.bss)
    	}
    	bss_end = .;                        //bss段结束处
    }

    下面说一下,之前我们写的简单程序,没有用到DDR,只是将程序在6410的8K片内内存中运行,但是如果程序很大,那就不能指望在片内内存中运行我们的程序了。下面就要用到SDRAM,就要涉及到链接地址。

          简单的说,一个程序分为下面几个部分:

          (1)代码段(text):就是我们所写的代码,指令

          (2)数据段(data):有初始值的全局变量或静态变量

          (3)Bss段(Bss):未初始化或初始值为0的全局变量或静态变量

         分析反汇编文件我们得出:访问全局变量使用的是链接地址来访问的。在系统上电后,系统会自动的把NandFlash中的前8K程序拷贝到片内8K内存当中去,而一个程序要执行,应该位于链接地址。当程序的链接地址不等于当前地址时,就需要重定位,将程序拷贝到相应的链接地址中去执行。

         位置无关码:相对跳转指令,不访问全局变量。下面看一下重定位代码:

    /* relocation */
    	adr	r0, _start			@ get _start's current address: 0 
    	ldr	r1, = _start		       @ _start's link address
    	ldr	r2, = bss_start		@ bss section's begining link address
    	cmp	r0, r1                      @  compare isnot equal 
    	beq	clean_bss
    
    
    copy_loop:
    	ldr r3, [r0], #4
    	str r3, [r1], #4
    	cmp r1, r2
    	bne copy_loop
    	/* clear bss section */
    clean_bss:
    	ldr	r0, = bss_start
    	ldr	r1, = bss_end
    	mov	r3, #0
    	cmp	r0, r1
    	ldreq	pc, = on_ddr
    clean_loop:
    	str	r3, [r0], #4
    	cmp	r0, r1
    	bne	clean_loop
    	ldr	pc, = on_ddr
    在分析过程中,我们可以参照反汇编文件来分析!

    1.9 Makefile

          由于是在Linux下开发,了解Makefile也是很有必要的,下面是本模板的Makefile代码:

    CC = arm-linux-gcc
    LD = arm-linux-ld
    AR = arm-linux-ar
    OBJCOPY = arm-linux-objcopy
    OBJDUMP = arm-linux-objdump
    CFLAGS = -Wall -Os -fno-builtin-printf
    
    export CC LD AR OBJCOPY OBJDUMP CFLAGS
    
    objs := start.o time.o sdram.o irq.o led.o
    
    led.bin : $(objs)
    	$(LD) -Tled.lds -o led_elf $^
    	$(OBJCOPY) -O binary -S led_elf $@
    	$(OBJDUMP) -D -m arm led_elf > led.dis
    
    %.o : %.c	
    	$(CC) $(CFLAGS) -c -o $@ $<
    %.o : %.S
    	$(CC) $(CFLAGS) -c -o $@ $<
    
    clean:
    	rm -f *.dis *.bin *_elf *.o

           总结:(待续……)

       
           
  • 相关阅读:
    JS站点
    1011 World Cup Betting (20分)
    1007 Maximum Subsequence Sum (25分)(动态规划DP)
    1006 Sign In and Sign Out (25分)
    1005 Spell It Right (20分)
    1004 Counting Leaves (30分)(DFS)
    1003 Emergency (25分)(Dijkstra算法)
    1002 A+B for Polynomials (25分)
    1001 A+B Format (20分)
    canvas
  • 原文地址:https://www.cnblogs.com/lixiaoming90/p/2985362.html
Copyright © 2011-2022 走看看