ok6410裸机开发启动程序模板

来源:互联网 发布:好看的网游小说知乎 编辑:程序博客网 时间:2024/05/18 02:03

 本文转自:http://www.verydemo.com/demo_c167_i72493.html

一、 裸机程序的构成

     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 registeraddress 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  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

 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 

 
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 configureset_val(MEMCCMD, 0x4 ); // set refresh period set_val( P1REFRESH, nstoclk(7800) ); // set timing paraset_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 );   set_nbit( P1MEMCFG, 3, 3, 0x2 ); set_zero( P1MEMCFG, 6 );  set_nbit( P1MEMCFG, 15, 3, 0x2 );  set_nbit( P1MEMCFG2, 0, 4, 0x5 ); set_2bit( P1MEMCFG2, 6, 0x1 );  set_nbit( P1MEMCFG2, 8, 3, 0x3 );  set_2bit( P1MEMCFG2, 11, 0x1 ); set_one( P1_chip_0_cfg, 16 );  // 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 (*((volatileunsigned 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  #define HCLKX2_RATIO 4  #define HCLK_RATIO 0 #define PCLK_RATIO 1  #define MPLL_RATIO 0  #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;  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;  MPLL_CON = MPLL_CON_VAL;  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内存当中去,而一个程序要执行,应该位于链接地址。当程序的链接地址不等于当前地址时,就需要重定位,将程序拷贝到相应的链接地址中去执行。

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

 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

 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

 

0 0
原创粉丝点击