從零開始寫一個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下的裸板程序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.