通过这个例子应该可以明白两个问题
1、 在做单片机和arm程序的时候,都会遇到启动代码。在单片机编程中,很多时候启动代码都是已经由集成开发环境准备好了,并不要我们自己去写。但是在arm中,因为ram啊,flash啊,还有各种设备是根据电路设计来的,所以启动代码不自己写都得自己修改。
2、为什么不用C语言来写启动代码而必须是汇编?为什么不能在程序一开始就使用C语言的程序?C语言程序运行所需要的基本环境是什么?其实C语言程序并不一定要从main函数执行,也可以用自己定义的函数名,在例子后就会明白。
其实上面的两个问题对于用汇编来写程序的人来说,都是理所当然的,但是更多的人是从C语言开始学习编程的,其约定俗成的都是从main函数开始执行的,很多地方也把这个main函数解释为程序的入口,听起来貌似这个入口就很特别。其实这个main函数和普通函数是差不多的,都是被调用了才会执行的,但是如果不统一的话,你的程序入口是main(),我的程序入口是start(),就很难统一,所以约定俗成的就是这个函数都叫main,当然也有不用main的,比如MFC。既然main函数和其它函数一样,那么他在被调用的时候就会有个动作,压栈,但是现在寄存器SP,也就是栈指针是指向哪里的?对了,这个指针还没有被初始化!也就是说,如果你想调用C语言的函数,就会用到堆栈,但是堆栈还没初始化,在一开始就不能调用C语言的程序,于是,这个时候就只能使用汇编语言的程序了。
以下是一个在算S3C2440上执行的最简单的启动代码:
这就是一个简单的启动代码了,在设置完栈后,跳到了一个名叫primula的函数,这个函数就相当于是我们的入口函数,就是一般用的那个main函数啦,再来看看C语言的程序:
#define rGPBCON (*(volatile unsigned long*)0x56000010)
#define rGPBDAT (*(volatile unsigned long*)0x56000014)
void primula()
{
rGPBCON = 0x00015400;
rGPBDAT = 0x00000140;
}
这里面就只有两个关于寄存器的宏定义,和一个primula函数,将这两个文件编译连接后,就可以把生成的二进制文件下载到flash或者ram中运行试试了,在我的开发板上,led接在了GPIOB的5678端口上,这个primula函数中通过设置相关寄存器点亮了两个LED,但这个点led不是重点。重点是没有main函数但确实执行了primula这个函数并且把我的两个灯点亮了。没有main函数,没有include。
关于编译,可以写个makefile脚本(linux下)
我的脚本是这样写的:
c_XXXXXXn : startup.s led.c
arm-linux-gcc -nostdlib -g -c -o startup.o startup.s
arm-linux-gcc -nostdlib -g -c -o led.o led.c
arm-linux-ld -Ttext 0x00000000 -g startup.o led.o -o led_elf
arm-linux-objcopy -O binary -S led_elf c_XXXXXXn
clean:
rm -f c_led.dis c_XXXXXXn led_elf *.o
其中-nostdlib 这个选项是告诉编译器不连接系统标准启动文件和标准库文件,在有的gcc版本下貌似不用加上都不会出问题,但是我的编译器爆出了“undefined reference to `__aeabi_unwind_cpp”这样的错误,主要原因就是因为一般的C语言编译器,都会为了能够顺利执行你所写的C语言函数,而在你的程序前面加上一段代码,使得你的main函数会在运行时被自动调用,但是在这里,我们有自己的启动代码,去调用自己的函数,程序里连个main都木有!