首页 > 代码库 > 从零开始写一个arm下的裸板程序

从零开始写一个arm下的裸板程序

从零开始写一个arm下的裸板程序.我们整个程序是基于uboot运行的.
所有我们可以借助uboot中的printf来输出,默认开发版的标准输出是串口.
电脑的默认标准输出的屏幕.


1.需要创建的文件由include文件夹,用来存放头文件.
2.创建一个hw.h头文件.
3.编写一个common.h,它定义了借用uboot的printf的宏.和NULL这个宏的定义.
4.hw.c     硬件相关的文件.
5.main.c   c文件.
6.start.s  汇编文件.
7.ld.lds   链接脚本,
8.Makefile 用来管理编写项目的配置文件.




下面开始是每个步骤的详细解析:

/**********************************************/
1.创建include文件.

zshh@HP:~/work/android/code/hardware$ mkdir my01porject               //创建一个项目文件.
zshh@HP:~/work/android/code/hardware$ cd my01porject/                 //切换到当前项目路径.
zshh@HP:~/work/android/code/hardware/my01porject$ mkdir include       //创建一个include文件夹.

/**********************************************/

2.创建hw.h硬件相关的操作, 

#ifndef __MY_HW_H
#define __MY_HW_H

extern int hw_init(void);//这个是硬件初始化操作. 
extern int hw_opts(void);//这个是硬件操作函数.
#endif

/**********************************************/
3创建一个common.h.

#ifndef __MY_COMMON_H
#define __MY_COMMON_H

#define NULL (void *)0                                                        //定义了宏 NULL

//定义了一个printf宏,(__VA_ARGS__)在定义变参数的时候必须要加这个表示,
// int (*)(const char *, ...)是一个函数指针的类型.
// 我们把这个0x43e11434数字转化成当前类型的函数指针类型.
#define printf(...) (((int (*)(const char *, ...))0x43e11434)(__VA_ARGS__))   

#if 0 

//需要注意的是我们需要说下他的由来.0x43e11434,
//切换到uboot源代码所在目录.
zshh@HP:~/work/arm/arm资料/exynos4412_lzy/src/uboot/uboot-2012-12$ ls
api         COPYING      examples     Makefile     README           u-boot
arch        COPYING.txt  fs           mkconfig     rules.mk         u-boot.bin
board       CREDITS      include      nand_spl     sd_fuse          u-boot.lds
boards.cfg  disk         lib          net          snapshot.commit  u-boot.map
common      doc          MAINTAINERS  onenand_ipl  System.map       u-boot.srec
config.mk   drivers      MAKEALL      post         tools
#endif

在Makefile中有怎么一段,就是告示你,当前函数的所有链接地址都放在.System.map中.
SYSTEM_MAP = 		$(NM) $1 | 		grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | 		LC_ALL=C sort
$(obj)System.map:	$(obj)u-boot
		@$(call SYSTEM_MAP,$<) > $(obj)System.map

这个是我拷贝这个System.map打开的一部分.其中43e11434 T printf就是uboot的链接地址.
43e11294 T serial_printf
43e112d8 T fgetc
43e11304 T ftstc
43e11330 T fputc
43e11358 T fputs
43e11380 T fprintf
43e113cc T getc
43e113e4 T tstc
43e113fc T putc
43e11418 T puts
43e11434 T printf
43e11478 T vprintf
//再c中我们找到一个函数的地址.就可以调用给函数.方式如上.

#endif

/**********************************************/

4.编写一个测试类hw.c 文件.

#include<hw.h>

//初始化当前设置的值,
int hw_init(void)
{
	//我们做个参数.只输出当前函数的名称,和当前行所在的值.
	printf("%s ,%d",__FUNCTION__, __LINE__);
	return 0 ;
}

//对当前设备进行操作.
int hw_opts(void)
{

	//我们做个参数.只输出当前函数的名称,和当前行所在的值.
	printf("%s ,%d",__FUNCTION__, __LINE__);
	return 0 ;
}

/**********************************************/
5.编写main.c进行调用.

#include<stdio.h>
int main(void) 
{
	hw_init(); 
	hw_opts();
	return 0;
}


/**********************************************/
6.编写start.s文件.这个是程序执行的入口.
//这个表示该_start是一个外部标号,如果不使用.global,进行修饰它默认是是一个内部标号.
//当你你可以把他下载到内存的某个地址上.如何使用go 50000000执行.它相当于执行了
//如下操作. bl _start,再跳转之前他会把uboot中的bl _start的下一条指令存放的 lr寄存器中.

.global _start    
_start:
	b reset      //b reset则是跳转到reset:标号下执行.
reset:
	stmfd sp!, {r0-r12, lr} //这句话的意思是讲.r0-r12, 和lr进行压栈.是为了保存这些值,
	bl main                 //这只就是跳转到我们c函数中的main中执行.再跳转之前它会讲当前lr赋值为ldmfd的值.
				//执行完毕之后.会跳转回来执行.ldmfd sp!, {r0-r12, lr} ,将压栈的东西拿出来,
	ldmfd sp!, {r0-r12, lr} 
	@mov pc, lr	       //这个是注释行.在汇编中使用@进行注释.
	bx lr                  //之后跳转会uboot执行.为什么步b指针进行跳转,
                               //是以为lr是一个寄存器.b不能操作寄存器,它只能操作标号.而bx可以.因为他只能操作寄存器.



/**********************************************/
7.
每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 
链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 
并控制输出文件内各部分在程序地址空间内的布局. 
但你也可以用连接命令做一些其他事情.


OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")  // 定义三种输出文件的格式(大小端)
OUTPUT_ARCH(arm)//设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一。可以用命令objdump -f查看
ENTRY(_start)   //ENTRY(SYMBOL) :将符号SYMBOL的值设置成入口地址。
SECTIONS
{
 . = 0x50000000;       //这个说明的是一个链接地址.
 . = ALIGN(4);         //进行4字节对齐.
 .text :               //定义一个.text段.
 {
  ./start.o (.text)    //链接的第一个文件是./start.o 其他的文件链接.
  *(.text)             //其他文件的文件.不管他们的顺序.  下面是定义其他段.
 }

 . = ALIGN(4);
 .rodata : { *(.rodata) }

 . = ALIGN(4);
 .data : {
  *(.data)
 }

 . = ALIGN(4);
 .bss : {
  *(.bss)
 }
}
/**********************************************/
编写Makefile文件.

TARGET			:=arm                    //定义目标变量名称.
BIN			:=$(TARGET).bin          //定义生成目标文件的bin文件.
START_OBJ		:=start.o		 //定义start.s生成的目标名称.
OBJS			:=main.o hw.o		 //定义其他 需要编译的.c文件生成的.o文件的名称.
LD_ADDR			:=0x50000000             //定义链接地址.

CFLAGS += -Wall -I./include                      //CFLAGES += -Wall表示打开警告.编译是自动查找./include目录下的头文件. 

CROSS_COMPILE	:=arm-linux-		         //定义一个前缀名称.
CC				:=$(CROSS_COMPILE)gcc $(CFLAGS)    //定义编译编译命令
AS				:=$(CROSS_COMPILE)as               //定义汇编命令.
LD				:=$(CROSS_COMPILE)ld               //定义链接命令.
OBJCOPY			:=$(CROSS_COMPILE)objcopy -O binary        //定义去掉elf格式的命令.
OBJDUMP			:=$(CROSS_COMPILE)objdump -D               //定义反汇编命令.
NM				:=$(CROSS_COMPILE)nm               //定义链接之后各个函数对应的链接地址.命令.

RM				:=rm -rf                           //定义一个rm 命令.也就是删除命令.

##############################################
all:$(TARGET)                     				   //定义一个目标.                                
	@$(OBJCOPY) $< $(BIN)                            
	@echo OBJCOPY	$<
	@$(OBJDUMP) $< >$<.s
	@echo OBJDUMP	$<
	@$(NM) $< >System.map
	@echo NM	$<
	@$(RM) $<
		
#	./down.sh

$(TARGET):$(START_OBJ) $(OBJS)              
	#@$(LD) $^ -o $@ -Ttext $(LD_ADDR)	
	@$(LD) $(OBJS) -o $@ -T ld.lds     
	@echo LD	$@

%.o:%.s                       //这里是要生成start.o依赖start.s文件.
	@$(AS) $< -o $@       //$<会自动匹配所有的依赖, $@会自动匹配所有目标.    
	@echo AS     $@       //输出生成的目标.

%.o:%.c
	@$(CC) $< -c -o $@   
	@echo CC	$@

clean:
	@$(RM) $(START_OBJ) $(OBJS) $(BIN)	
	@echo RM	./


从零开始写一个arm下的裸板程序