从零开始写一个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下的裸板程序
最新推荐文章于 2023-03-27 17:22:36 发布