NANDFLASH启动与标准库问题


把u-boot的start.S移植到我的程序上,这样程序可以用supervivi的D功能下载到内存中运行了,但是还不够。因为程序在内存里,如果掉电程序就没有了,所以我们得将程序固化在flash里面。这里我们要固化在NandFlash里,这就要求程序可以能够NandFlash启动。这里我参考了mini2440的nandlfash读写程序nand.c,里面有一个函数CopyProgramFromNand就是将Nandflash里的程序复制到内存里。在这之前我一直用u-boot默认的下载地址0x33f80000,这个是为了u-boot引导内核方便而定的,因为内核要下载到前面的内存中,既然我的程序没有这个功能,那下载到0x30000000里就可以了。把start.S中的TEXT_BASE改成0x30000000就可以了。还有在start.S里,复制完程序判断r0是否为零,如果不是那么程序就会进入死循环。根据AAPCS,C程序返回的值就保存在r0里,这里是判断程序是否返回了0,在CopyProgramFromNand里默认没有返回值,所以要在最后加上返回值。接下来就是消除编译错误。将nand.c链接到我们的程序里,注意一定要保证在程序的前4k的位置范围内,因为Nandflash启动的时候,S3C2440将Nandflsh的前4K的内容复制到芯片内部的SRAM里,如果NandFlash的读写程序不在这4k的代码里就无法完成程序的拷贝。


编译好的程序在我的下载资源里:http://download.csdn.net/detail/yaozhenguo2006/3752515


Nandflash启动进行的还算顺利,修改之后下到nandflash里,然后开发板切到nandflash启动,程序正常运行。这里我注意到流水灯不像下载到内存中运行的那样立即就运行,而是等了一段时间,说明拷贝代码用了一些时间。接下来就是要移植mini2440自带库函数了,对应的文件是2440lib.c,我本来以为简单的加到Makefile里就会成功。可是事情不是想象的那样简单,当编译的时候出现了一大堆错误。这些错误刚看起来都莫名奇妙,而且不是编译的错误,都是链接的错误。编译的时候没错说明不是语法错误,链接错误说明是库出了问题。主要提示的错误信息如下:

第一类:

2440lib.o: In function `Uart_Init':

2440lib.c:(.text+0x370): undefined reference to `__aeabi_i2d'

2440lib.c:(.text+0x390): undefined reference to `__aeabi_ddiv'

2440lib.c:(.text+0x3a8): undefined reference to `__aeabi_i2d'

2440lib.c:(.text+0x3c4): undefined reference to `__aeabi_ddiv'


在Uart_Init这个函数中,主要用了一些浮点数的运算和除法的运算,我们知道armv4指令集是没有除法和浮点运算指令的,所以必须软件模拟。编译的时候模拟除法与浮点运算需要的库,在链接的时候链接器没有找到,所以出现了这个问题。为了验证我的猜测,我把浮点数运算以及除法运算都注释掉,错误提示就没有了。说明就是没有找到浮点运算以及除法运算的库。

第二类:

2440lib.o: In function `Uart_GetIntNum':

2440lib.c:(.text+0x994): undefined reference to `strlen'

2440lib.c:(.text+0xa20): undefined reference to `atoi'

2440lib.c:(.text+0xa5c): undefined reference to `__ctype_b_loc'

2440lib.c:(.text+0xa90): undefined reference to `__ctype_b_loc'

2440lib.o: In function `Uart_Printf':

2440lib.c:(.text+0xd90): undefined reference to `vsprintf'


在Uart_GetIntNum这个函数中主要调用了标准C库中的strlen和atio函数,找不到符号,说明链接的时候没有找到标准C库。Uart_Printf也应该是同样的错误。


对于以上两种问题都是库的问题。既然链接器找不到相关的库。我们就加上一定的链接选项让他找到。在做这个之前我们先了解一下,gcc开发环境下程序链接的两种方式。第一种使用gcc自带的链接功能。以arm-linux-gcc来说明,这种链接方式命令基本形式如下

$(CC)  -static -Wl,-Tboot.lds,-Map,system.map -nostartfiles -o boot.elf $(objs)

     CC 这里就是arm-linux-gcc 

    -Wl,-Tboot.lds,-Map,system.map -Wl表示后面的参数是传递给链接器的,参数以逗号分割。-Tboot.lds 是指定的链接脚本,规划了程序在内存中的位置 -Map,system.map 这是链接后生成的符号表  

    -nostartfile 不让链接器加入默认启动代码,如果不加这个选项,链接器就会默认加入一个启动代码,因为我们要链接自己的启动代码,当然不需要它默认的了。

    -o boot.elf 最后生成的elf格式的文件。

    $(objs) 我们编译的.o文件


用以上形式链接程序,arm-linux-gcc会调用collect2链接器,这个链接器有一定的默认选项,比如默认寻找库的路径,默认链接的库。通过给arm-linux-gcc 加上-v 选项,我们可以查看他的默认行为。现在以我的开发环境为例,链接的时候会有如下的输出:

 /home/sun/study/crosstools/4.4.3/bin/../libexec/gcc/arm-none-linux-gnueabi/4.4.3/collect2 

        从这个输出中,我们可以发现,arm-linux-gcc调用了collect2。

--sysroot=/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root 

-Bstatic -dynamic-linker /lib/ld-linux.so.3 -X -m armelf_linux_eabi -o boot.elf

        sysroot暂时没有发现是做什么的,-Bstatic 很显然是静态链接 

-L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3 

-L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc 

-L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3/../../../../arm-none-linux-gnueabi/lib 

-L/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root/lib 

-L/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root/usr/lib

        以上就是collect2默认寻找的库路径 

-Tboot.lds -Map system.map 

        传递过来的链接器参数

start.o lowlevel_init.o nand.o interrupt.o main.o 2440lib.o print.o

        要链接的.o文件 

--start-group -lgcc -lgcc_eh -lc --end-group


这个选项很重要,链接器链接的静态库。-lc 代表链接标准c库, -lgcc 代表要链接libgcc.a,这个库应该是gcc扩展的库。


下面说一下另外一种链接方式,就是用ld命令来链接,这里就是arm-linux-ld,这种方式也就是我链接出错的链接方式。这种链接方式,没有默认的参数,寻找库的路径以及链接库都要自己添加。之所以链接出错就是因为我没有指定链接的库以及寻找库的路径。既然如此,那么使用第一种链接方式链接我的程序就应该是没错的。我将Makefile修改了一下再make,还是有错,令人欣喜的就是错误减少了,就只剩下如下的这一种错误:

/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3/libgcc_eh.a(unwind-arm.o): In function `get_eit_entry':

/opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/libgcc/../gcc/config/arm/unwind-arm.c:673: undefined reference to `__exidx_end'

/opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/libgcc/../gcc/config/arm/unwind-arm.c:673: undefined reference to `__exidx_start'


经过分析我认为是Uart_Printf函数里调用vsprintf的缘故,将Uart_Printf注释掉,然后再make。链接就通过了,说明就是这个vsprintf的问题。放下这个问题先不管,我试一下ld这种链接方式,在加上寻找库的路径以及要链接的库名的情况下是否可以成功make。修改Makefile如下:

CC = arm-linux-gcc

LD = arm-linux-ld

OBJCOPY = arm-linux-objcopy

objs := start.o lowlevel_init.o  main.o nand.o 2440lib.o

boot.bin: $(objs)

        $(LD) -Bstatic -Tboot.lds -Ttext 0x33F80000 $(objs)

        -L/home/sun/study/crosstools/4.4.3/lib/gcc/arm-none-linux-gnueabi/4.4.3

        -L/home/sun/study/crosstools/4.4.3/arm-none-linux-gnueabi/sys-root/usr/lib 

        -Map boot.map -o boot.elf --start-group -lgcc -lgcc_eh -lgcov -lc --end-group

        $(OBJCOPY)  -O binary  boot.elf boot.bin

%.o:%.c

        $(CC) -Wall  -c -o $@ {1}lt;

%.o:%.S

        $(CC) -Wall  -c -o $@ {1}lt;

clean:

        rm -f *.bin *elf  *.o 


用以上的Makefile,make通过了。程序下载到板子上现象也是正确的。这说明ld链接器正确链接了程序。现在两种链接方式都可以正确的链接程序了。那么就剩下一个问题,就是Uart_Printf函数里vsprintf。这个函数是一个格式化字符串转换函数,应该是标准C库里的函数,在链接器能够找到c库的情况下,两种链接方式都提示同样的错误。我猜测vsprintf这里面还调用了其他库的函数,我找了很久也没有找到到底调用了什么库。打印函数对于程序调试是必须的。既然vsprintf不能使用,那么就自己实现一个。参考u-boot的printf的实现,我自己编写一了一个vsprintf,然后结合Uart_SendString实现了串口打印功能。


简单的说一下实现printf的方法,首先要解决函数可变参数的问题,printf(char *fmt,...)显然是一个可变参数函数,第一个参数为字符串,后面是格式化输出参数列表。c语言中函数的参数都是压进栈里的,可变参数函数必须有一个参数表示参数的个数,才能让编译器知道要压进栈多少参数,以及函数返回时弹出多少参数,printf(char *fmt,...)实现这个功能的就是fmt字符串,里面有多少'%',就代表后面有多少个参数,所以我们必须提取出fmt字符串中'%'的个数,以及针对'%'后面不同的字符来处理参数。printf实现类似如下:

void myprintf(char *fmt,...)

{

    va_list ap;

    char string[256];

 

    va_start(ap,fmt);

    myvsprintf(string,fmt,ap);

    Uart_SendString(string);

    va_end(ap);

}


va_list 其实就是*char类型,va_list ap 也就是开始定义了一个char类型的指针变量ap。 va_start是一个宏,作用就是取得fmt指针地址,并跳过这个地址赋值给ap,这样ap就指向了除了fmt指针的第一个可变参数在内存中的地址,然后通过myvsprintf(string,fmt,ap)对fmt字符串结合可变参数进行转化,并把转化的结果赋值给string,最后通过Uart_SendString()函数将字符串发送给串口。具体实现可以看我的源代码。在我的资源里 http://download.csdn.net/detail/yaozhenguo2006/3774535


标准C库的问题就算暂时解决了,不过还是给我提了一个醒,在arm-linux-gcc上开发裸机程序,如果用到标准C库一定要注意,不是所有的函数都可以使用,比如vsnprintf就不能使用。其他还有什么函数不能使用还是个未知数,所以尽量不用标准C库的函数才是保证程序安全的办法。典型的例子就是u-boot,它就没有使用标准c库,所用的相关函数都是自己实现的,这样就保证u-boot很强的可移植性。也许arm-elf-gcc会没有这个问题,毕竟链接的是ulibc库,专门针对嵌入式的,以后有机会一定要验证一下。


关键字:arm-linux-gcc  裸机  程序开发 引用地址:arm-linux-gcc 裸机程序开发(二)

上一篇:arm-linux-gcc裸机程序开发(三)
下一篇:基于mini2440的USB视频采集

推荐阅读

2018年10月12日,云从科技承办的“国家人工智能基础资源公共服务平台发布会”即将在北京启幕。来自重庆市政府、中国科学院等单位机构的30多位重要嘉宾,近20个行业的企业创始人和首席技术官将齐聚该会议,共话人工智能技术前沿与产业融合趋势。此时距云从科技承接该项目已近2年,云从科技的也从当时的“小独角兽”实现了数倍的增长,堪称全球发展最快的AI...
10月1日,河北省政府办公厅印发《关于支持数字经济加快发展的若干政策》(以下简称《若干政策》)。政策涉及数字基础设施建设、提升产业数字化能力等5个方面,旨在深入实施《河北省数字经济发展规划(2020-2025年)》,着力壮大新增长点,形成发展新动能。《若干政策》提出,加大技术创新投入力度,支持关键技术攻关及转化,组织实施一批重大科技攻关项目,...
一.硬件方案本电路是由AT89C52单片机为控制核心,具有在线程功能,低功耗,能在3V超低压工作;时钟电路有DS1302提供,它是一种高性能.低功耗,带RAM的实时时钟电路,它可以对年,月,日,周日,时,分,秒进行及时,同时具有闰年补偿功能,工作电压为2.5~5.5V.采用三线接口与CPU进行同步通信,并可采用突发方式一次产送多个字节的时钟信号或RAM数据.具有寿命长精度高和低功...

史海拾趣

小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved