ok6410裸機開發啓動程序模板

 本文轉自: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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章