從零開始寫一個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	./


發佈了58 篇原創文章 · 獲贊 65 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章