现在的位置: 首页 > 综合 > 正文

arm-linux-gcc 裸机程序开发(一)

2013年10月01日 ⁄ 综合 ⁄ 共 6810字 ⁄ 字号 评论关闭

概述与SDRAM运行

        以前开发arm裸机程序都是在ADS1.2开发环境下编译和调试的。刚开始时初学嵌入式好多东西不懂,选择这个开发环境的理由,一是资料多的,mini2440开发板上提供了很多例程可以参考,网上几乎所有arm裸机程序都是基于ADS1.2开发的。二是开发环境友善,虽然后来感觉ADS1.2有点难用,但毕竟是IDE的环境,对初学者来说总比命令行的方式更加直观与方便。随着学习的深入,感觉它就像傻瓜相机一样,虽然好用但屏蔽了很多内容,影响了我们深入理解代码编译以及链接的细节。而且ADS对于程序的开发没有GNU工具链灵活。这段时间因为需要,又要编写一些arm裸机程序。自己已经用Linux习惯了,不想再切回windows下工作了。所以,最近对linux下arm的裸机程序开发进行了学习。要让程序在arm裸机下运行,主要解决如下问题:
    (1) 编译器问题,这个不用说肯定是GNU的大名鼎鼎的GCC了,与此相关的什么连接器,汇编器也都包含在内了。针对arm的GCC,当然就是arm-linux-gcc了,我所用的版本就是友善之臂光盘自带arm-linux-gcc 4.4.3。也有资料说也可以用arm-elf-gcc,这个与arm-linux-gcc带的c库不同,是uclibc,更精简更适合嵌入式。但是,相对于arm-elf-gcc而言,我对arm-linux-gcc更熟悉一点,毕竟编译u-boot的时候用过,所以选择了arm-linux-gcc作为编译器。
    (2) 启动代码问题:C程序运行是需要一定条件的,首先板子必须初始化好,然后堆栈必须初始化好,这样C代码才能够运行起来。mini2440的启动代码对于ADS1.2就是2440init.S。本来打算移植这个,因为ARM汇编与GNU汇编虽说在一般的汇编指令之间差别不大,但是在伪指令和语法结构上还是有很大的区别。所以移植起来会很困难,我果断放弃了这个想法。然后我想起了u-boot的start.S,这个是现成基于GNU汇编的启动代码。但是start.S毕竟是基于u-boot工程的,所以还是需要移植的。
    (3) 直接下载到内存中运行:其实这也是启动代码的问题。在ADS开发环境下,我一般编译程序都先是用NorFlash里的supervivi将程序下载到内存中运行看结果,默认的下载地址是0x30000000。所以用arm-linux-gcc编译也是一样,要首先实现在内存中下载运行。其实在supervivi这个环境下,不需要启动代码也是可以运行C程序的,因为这种情况下supervivi已经在运行了,它已经做过了启动代码应该做的事。不过这里还是要加上启动代码,这是未雨绸缪,因为我们的程序以后得自食其力。
    (4) NandFlash启动问题,S3C2440提供了两种启动方式,NorFlash启动以及NandFlash启动,第三个问题其实也是为了NorFlash启动,因为运行的环境是norflash启动后的。对于NandFlash启动,u-boot的启动代码与ADS的启动代码都提供了解决的办法。u-boot读写Nandflash的程序就是nand_read.c,而ADS用的是nand.c。其实这两个文件都做了同一件事,就是将NandFlash里的程序拷贝到内存中。具体选那种方式,还是要看移植时的修改量。看看nand_read.c前两个头文件#include
<common.h> #include <linux/mtd/nand.h> 顿时失去了修改他的想法,函数比较复杂。那么只剩下nand.c了,随便看了一下,函数很简单基本上不用修改什么。所以就选这个文件作为读写NandFlash的程序了。
    (5) 移植2440lib.c问题,这个文件包含一些操作板子资源的库函数。是mini2440自带的,因为是C语言编写的,所以可以轻松的移植到linux编译环境下。
    (6) 中断的问题。中断对嵌入式编程非常重要。所以编写稳定高效的中断处理程序很重要,也很必要。在ADS中我们只需要在自己的中断服务程序中加入一行字:_irq,编译器就会给我们保存中断现场,多智能呀。可是arm-linux-gcc目前我还没有发现这个功能,所以中断现成的保护与恢复就得我们自己来完成。
     
一. 编译器问题
        按照友善之臂说明书,安装好arm-linux-gcc4.4.3,我的编译器目录是/home/sun/study/crosstools/4.4.3/ ,我所用的开发环境为linux发行版 ubuntu10.10。
二. 启动代码问题
        首先修改start.S以及消除编译错误,这个最容易解决,只要去掉u-boot有关的一些宏定义,以及注释掉与nandflash启动相关的部分就可以了。最难的是能在开发板上正确的运行。在此之前,我们得先弄清楚一个问题,就是嵌入式程序的加载域与运行域的关系与区别。就拿u-boot来说明,我们知道友善之臂mini2440的u-boot,在u-boot.lds里有这一行代码
. = 0x33f80000;在编译u-boot的输出信息中,我们会发现的最后的链接选项有一个-Ttext 0x33f80000。那么这个0x33f80000究竟是什么意思呢,他都会影响什么呢?弄清楚这个问题之前,我们先清楚一些概念。我们知道一个链接好的可运行的二进制代码,无非是一条一条的机器指令罗列而成。u-boot.bin也是这样,每条指令相对于代码的开始都是有一个偏移的地址,第一条指令偏移为0,第二条为1,以此类推直到程序的结束,最后一条指令偏移量就是程序的长度。
        我们先抛开这个0x33f800000不说,先说一下CPU运行u-boot的过程。假如我们把u-boot下载到内存0x30000000处运行,那么u-boot第一条指令的地址就是0x30000000, CPU要去运行u-boot,那么首先必须执行类似这样的指令adr pc,#0x30000000,这样CPU就会取到0x30000000里的第一条u-boot指令。假如我们把u-boot下载到内存的0x0地址处运行,那么CPU是去0x0地址取指令。这个过程与你链接的时候指定什么地址无关,程序该到哪里运行就到哪里运行,一切地址都是相对与第一条指令在内存中的位置。这时程序就是运行在加载域,也就是刚刚下载进去的内存地址范围运行。
        回到问题的初衷,链接时我们指定了一个地址0x33f80000,这个究竟是做什么的。这个就涉及到了程序的绝对地址与运行域。也就是说二进制代码的指令有两种地址,一个就是相对地址,相对于你加载到内存中的位置,一个就是绝对地址,无论你下载到内存中的哪里,这个地址都是不变的。比如u-boot的这个0x33f80000,那u-boot的第一条指令的绝对地址就是0x33f80000了,以后的指令的绝对地址类推。说完了这个地址是什么,接下来说说他的作用,很明显他是程序运行域的开始地址。具体运行域的概念,我的理解就是,程序正常运行时所处的地址空间。这里的正常运行是指的代码自拷贝之后的状态,在u-boot有一段自拷贝程序,如果程序的加载地址与链接是指定的地址不相符的话,程序会把自己拷贝到链接指定的地址处,u-boot就是0x33f80000。所以0x33f80000这个地址的作用就是,提供程序运行域的开始地址。那么这个地址影响的代码,就是调用ldr伪指令的代码。ldr伪指令就是取标号的绝对地址的。如ldr
sp,=UndefStack,就是将UndefStack的绝对地址赋值给sp,这个地址一经链接无论程序下载到哪里都是不变的。如果写成adr sp,UndefStack 就是取UndefStack的相对地址,他会随程序下载的地址不同而不同。
       现在总结一下,程序刚开始下载的内存或者norFlash的位置就是下载域,当程序把自己拷贝到链接是指定的位置后就会运行在运行域。程序的代码有两种地址:绝对地址与相对地址。如果你把程序恰好下载到了链接时指定的地址,那么加载域就是运行域,绝对地址就是相对地址。如果不是,那么绝对地址与相对地址是不同的。当程序运行在运行域的时候,绝对地址等于相对地址。
注意: ldr 这个指令有两种身份,一种是普通数据装载指令,用法ldr r0,[r1]或者#define pWTCON 0x53000000 ldr r0,=pWTCON,这条指令就是把0x53000000赋值给r0,相对的就是str。一种就是伪指令,加载标号处的绝对地址,用法ldr r0, = label1。 相对应的就是adr,加载相对地址
       弄清楚了加载域与运行域,相对地址与绝对地址的关系。我们就要修改start.S使之正确的运行在内存中,下面就该解决第三个问题了。
三. 直接下载到内存中运行的问题
       要使得裸机程序能在内存中运行,实现先解决u-boot直接下载到内存中的运行的问题。我以前移植的u-boot是不能下载到内存中运行的,问题原因以前也没有注意,现在要用到了,一起解决吧。但是,找了很久一直不知道是什么原因,后来在开发板光盘中的u-boot的移植手册中发现了端倪,就是因为绝对地址与相对地址的关系的问题。问题出在了lowlevel_init.S上,这是一段初始化内存的代码。关键代码如下: 

_TEXT_BASE:
	.word	TEXT_BASE
.globl lowlevel_init
lowlevel_init:
	/* memory control configuration */
	/* make r0 relative the current location so that it */
	/* reads SMRDATA out of FLASH rather than memory ! */
	ldr     r0, =SMRDATA    @取SMADATA的绝对地址
	ldr	r1, _TEXT_BASE  @TEXT_BASE = 0x33f80000 所以r1 = 0x33f80000
	sub	r0, r0, r1      
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4
0:
	ldr     r3, [r0], #4
	str     r3, [r1], #4
	cmp     r2, r0
	bne     0b

	/* everything is fine now */
	mov	pc, lr

	.ltorg
/* the literal pools origin */

SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    .word 0x32
    .word 0x30
    .word 0x30

        拥有以上的代码的u-boot是不能运行下载到内存中运行的,因为ldr r0, =SMRDATA取的SMADATA的绝对地址,他和0x33f80000相减算出来的是SMRDATA相对于第一条绝对地址的偏移,这个只适用于程序下载到0x0处运行的情况。对于程序下载到内存的其他地方是不行的,所以因为地址的不同,赋值给内存初始化寄存器的内容,就不是最后那些数据,数据是随机的。这导致内存就不能工作,程序就死到这里了。下面是修改后的代码:

_TEXT_BASE:
	.word	0x33f80000

.globl lowlevel_init
lowlevel_init:
	/* memory control configuration */
	/* make r0 relative the current location so that it */
	/* reads SMRDATA out of FLASH rather than memory ! */
	ldr     r0, =SMRDATA           @取SMRDATA的绝对地址
	ldr	r1, =lowlevel_init     @取lowlevel_init的绝对地址

	sub	r0, r0, r1             @相减之后,r0里保存的是SMRDATA相对与lowlevel_init标号的偏移
	adr	r3, lowlevel_init      @取lowlevel_init的相对地址,这个随下载地址不同而不同
	/* r3 <- current position of code */
	add 	r0, r0, r3             @加上偏移之后就是SMRDATA的真实地址
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4

       以上修改之后u-boot就可以在内存中运行了,我们的启动代码也可以在内存中运行了。这个修改是友善之臂u-boot移植文档修改的,我感觉这样做有点罗嗦了,直接一个adr r0,SMRDATA就可以取到他的相对地址,事实证明的确是这样。所以这段程序就是这样了:

lowlevel_init:
	adr 	r0, SMRDATA           
	ldr	r1, =BWSCON	/* Bus Width Status Controller */
	add     r2, r0, #13*4

        接下来就写一个小小的测试程序,验证一下。就像程序员每次学习新语言时,第一个跑的程序都是hello world!一样,我们搞嵌入式的每次在新的架构上运行的都是流水灯。以下是流水灯的程序:

//延时函数
void delay(int a)  
{
       int i,j;
       for(i=0;i<a;i++)
          for(j=0;j<100;j++)
              ;

}
int Main(void)
{
    int light,i;
    
    //定义PB5~PB8为输出
    rGPBCON = (0x1<<10)|(0x1<<12)|(0x1<<14)|(0x1<<16);
    //使PB上拉功能失效
    rGPBUP  = 0x7ff;
    
    light = 0x10;
    light = light<<1;
    
    rGPBDAT = ~light;//第一个LED被点亮
    delay(5000);//延时一段时间
    //主程序死循环
    while(1) {
           //从一端向另一端
           for (i=0;i<3;i++) {
                  light = light<<1;
                  rGPBDAT = ~light;
                  delay(5000);
           }

           //返回
           for (i=0;i<3;i++) {
                  light = light>>1;
                  rGPBDAT = ~light;
                  delay(5000);  
           }
    } 
}

        就是这样一个简单的程序,实验的现象竟然是四个灯全亮,找了好久不知道是什么原因,注释掉循环之后可以点亮一个灯,说明程序运行是正确的,但就是循环不起作用,首先我想到的是CPU频率的问题,确定一下频率是没有问题的,因为我增大循环的的次数还是没有效果。最后终于找出了问题,我在编译的时候加了一个O2的选项,导致编译器把我的程序优化了。所以循环延时不起作用。所以以后编写嵌入式程序的时候这个优化还是要注意了。到此程序总算是可以用supervivi的D选项下载到内存中运行了。
  注:编译成功后的代码在我的资源里:  

http://download.csdn.net/detail/yaozhenguo2006/3749581.

  

抱歉!评论已关闭.