自从自己买了一块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
/* 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 IRQcopy_loop: ldr r3, [r0], #4 str r3, [r1], #4 cmp r1, r2 bne copy_loop@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
#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 */
#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)); }
#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; }
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_bsscopy_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
总结:(待续……)