[置顶]       基于gnu-arm-linux的LPC2220的简单工程模板

1:源头

我们学习arm嵌入式开发,一般接触到的是ADS1.2、kei的工程模板,这些模板对初学者入门来说是一种福音,但是想深入了解一下芯片启动过程、
编译和链接、映像文件结构、如何初始化、移植标准库等这些内容的话,这些商业IDE就显得隐藏了很多细节,不利于进一步学习。基于上述缘由,
我写了一个基于gnu arm-linux开发环境的LPC2220的简单工程,此工程实现了芯片开机初始化、加载映像到运行映像的转换、ZI段的清零、堆栈
的设置、引导高层C语言函数、移植标准库、在高层实现了printf用于调试。

2:例程实现的功能

初始化LPC2220芯片:

1,实现LPC2220中断向量表。

        2,设置ARM芯片各个模式运行时所需的堆栈空间。本例程中将系统模式的堆栈设置在LPC2220内部RAM的最顶端,即0x40004000。这里是因为芯片默认的运行模式就是系

统模式,也即我们的用户程序都是在系统模式下运行的。又因为ARM芯片默认是从高地址向低地址使用堆栈,因此将LPC2220内部RAM的最顶端设置为系统模式下的堆栈

指针。其他模式的堆栈设置在0x400000000开始的内部RAM中。具体怎么实现的,我们下面例程代码中会讲解。

3,初始化目标板,主要设置各种总线时钟、向量表映射、存储BANK设置、存储器加速模块、实时时钟等系统控制模块。

4:将RW段从NorFlash COPY到SDram中,清零ZI段。

5:跳转到main函数执行。

6:使用标准库并实现printf,用于调试。

7:利用printf打印全局变量,局部变量内容,地址。用来说明全局变量,局部变量以及链接器链接等相关知识。


3:过程中遇到的困难

第一个是我想使用LPC2220的外部BANK1,我使用的硬件板,BANK1上接的是RAM,一开始程序怎么也调试不过,以为是链接脚本书写错误。但后来发现是对LPC2220的引脚功能设置的问题。这不是什么知识点,但让我在这上面浪费了不少时间,特此记录一下,以安慰我那颗受伤的心。所以,记得一些硬件资源要先看datasheet初始化好后再使用。

第二个是编译代码的时候,出现一堆的undefinedreference to `__umoddi3'之类的错误,而且我还发现了只要我代码里有取整取余等操作时就会报上述错误,真是百思不得其解,后来到网上去查资料,得出这个错误确实是因为使用了除法导致的。而且,ARM7不支持除法指令,需要软件进行辅助除法运算,而一般是通过标准库的形式提供。我们使用ADS1.2,keil等集成IDE开发程序时,它们自带的库里有对除法的支持。而我现在使用的编译链是从网上下载的别人编译好的arm-linux-gcc3.4.1,可能不支持除法、软浮点支持。我不知道怎么解决这个问题,所以,干脆我使用了我自己使用源码编译的交叉编译链(4.6.0版本的)。我编译交叉连的时候选择了除法、软浮点支持。问题得以解决。具体交叉编译链的制作,参考:制作S3C6410的交叉编译链(arm-linux-gcc4.6.0

 

第三个问题是编译的时候提示undefinedreference to `__exidx_start'        undefined reference to`__exidx_end'等错误,而且是当我使用sprintf等相关格式化字符串的时候就提示这些错误,我没有找到合适的解决方案。所以,我参考网络资源自己实现了简单的格式化函数。这不是根本解决之道,我对使用gnu开发工具链进行嵌入式开发如何使用标准库函数也存在很多疑点。


4:部分例程代码

例程总览:该工程编译,链接出的映像下载到LPC2220的BANK0(Norflash)中,其地址为0X80000000。运行时,所有的RO段运行在BANK0,RW段和ZI段运行于LPC2220的BANK1(RAM),其地址为0X81000000。装载地址和运行地址不一致,所以在启动代码启动后,应将RW段COPY到运行时的地址处,将ZI段清零。这些工作都在startup.s中完成,最后startup.s将引导最终的main函数。
startup.s
@****************************************************************************** @ 文件名  :startup.s @ 功    能:初始化LPC2220:初始化各种运行模式的堆栈空间,各种exception @           入口。 @ 说明	  :此工程为gcc for arm的工程,书写此工程启动代码目的是学习嵌入式开发过程中 @          的一些要点知识。gcc for arm 是开源的编译,链接器,可以学习程序开发的更多 @          细节。其他商业开发工具隐藏太多细节,不利于其初学者学习。 @ 备注    :大家可以根据工程需要修改其内容。    @ 作者    :张连聘 @ 创建时间:2014-07-27 @******************************************************************************  @define the stack size for each mode @定义各种运行模式堆栈大小 	.equ  FIQ_STACK_LEGTH ,256  	.equ  IRQ_STACK_LEGTH ,9*8  	.equ  ABT_STACK_LEGTH ,256  	.equ  UND_STACK_LEGTH ,256  	.equ  NoInt     ,0x80 	.equ  NoFIQ	    ,0x40 	 	.equ  USR32Mode ,0x10 	.equ  SVC32Mode ,0x13 	.equ  SYS32Mode ,0x1f 	.equ  IRQ32Mode ,0x12 	.equ  FIQ32Mode ,0x11  	 	.equ PINSEL2    ,0xE002C014 	.equ BCFG0      ,0xFFE00000 	.equ BCFG1      ,0xFFE00004 	.equ BCFG2      ,0xFFE00008 	.equ BCFG3      ,0xFFE0000C 		 @The imported labels         @引入的外部标号在这声明     .extern  FIQ_Exception                    @Fast interrupt exceptions handler 快速中断异常处理程序     .extern  main                             @The entry point to the main function C语言主程序入口      .extern  TargetResetInit                  @initialize the target board 目标板基本初始化     .extern  SoftwareInterrupt 	.extern  Copydata 	.extern  ClearBssData .global Reset	 .text @interrupt vectors @中断向量表 Reset:         LDR     PC, ResetAddr         LDR     PC, UndefinedAddr         LDR     PC, SWI_Addr         LDR     PC, PrefetchAddr         LDR     PC, DataAbortAddr         .word   0xb9205f80         LDR     PC, [PC, #-0xff0]         LDR     PC, FIQ_Addr  ResetAddr: 				.word     ResetInit UndefinedAddr:       				.word     Undefined SWI_Addr: 				.word     SoftwareInterrupt PrefetchAddr: 				.word     PrefetchAbort DataAbortAddr:   				.word     DataAbort Nouse:          .word     0 IRQ_Addr: 				.word     0 FIQ_Addr:         				.word     FIQ_Handler 				 @未定义指令 Undefined:         B       Undefined        @取指令中止 PrefetchAbort:         B       PrefetchAbort  @取数据中止 DataAbort:         B       DataAbort  @快速中断 FIQ_Handler:         STMFD   SP!, {R0-R3, LR}         LDR     PC, =FIQ_Exception         LDMFD   SP!, {R0-R3, LR}         SUBS    PC,  LR,  #4 		  /********************************************************************************************************* **函数名称: 	InitStack **功能描述: 	Initialize the stacks  初始化堆栈 **输 入:   	None  **输 出 :  	None  **全局变量: 	None  **调用模块: 	None  ********************************************************************************************************/  InitStack:             MOV     R0, LR @Build the SVC stack @设置中断模式堆栈         MSR     CPSR_c, #0xd2         LDR     SP, StackIrq @Build the FIQ stack	 @设置快速中断模式堆栈         MSR     CPSR_c, #0xd1         LDR     SP, StackFiq @Build the DATAABORT stack @设置中止模式堆栈         MSR     CPSR_c, #0xd7         LDR     SP, StackAbt @Build the UDF stack @设置未定义模式堆栈         MSR     CPSR_c, #0xdb         LDR     SP, StackUnd @Build the SYS stack @设置系统模式堆栈         MSR     CPSR_c, #0xdf         LDR     SP, =StackUsr          BX     R0 		 /********************************************************************************************************* **函数名称: 	ResetInit **功能描述: 	RESET  复位入口 **输 入:   	None  **输 出 :  	None  **全局变量: 	None  **调用模块: 	None  **------------------------------------------------------------------------------------------------------- ********************************************************************************************************/ ResetInit: @Initial the extenal bus controller @初始化外部总线控制器,根据目标板决定配置  		LDR     R0, =PINSEL2         LDR     R1, =0x0f814914 		STR     R1, [R0] 		         LDR     R1, =0x0f814914         LDR     R0, =BCFG0         LDR     R1, =0x1000ffef         STR     R1, [R0]          LDR     R0, =BCFG1         LDR     R1, =0x1000ffef         STR     R1, [R0]          LDR     R0, =BCFG2         LDR     R1, =0x0000fbef         STR     R1, [R0]          LDR     R0, =BCFG3         LDR     R1, =0x10001460         STR     R1, [R0] 		 		                  BL        InitStack               @ Initialize the stack 初始化堆栈         BL        TargetResetInit         @ Initialize the target board 目标板基本初始化 		BL        Copydata 		BL        ClearBssData         B          main                    @ Jump to the entry point of C program 跳转到c语言入口       StackIrq:            .word     IrqStackSpace  +(IRQ_STACK_LEGTH - 1)*4 StackFiq:            .word     FiqStackSpace  +(FIQ_STACK_LEGTH - 1)*4 StackAbt:  .word     AbtStackSpace  +(ABT_STACK_LEGTH - 1)*4 StackUnd:  .word     UndtStackSpace +(UND_STACK_LEGTH - 1)*4   /* 分配堆栈空间 */  .bss  .align 4  IrqStackSpace:       				  .space   IRQ_STACK_LEGTH * 4  @Stack spaces for Interrupt ReQuest Mode 中断模式堆栈空间 FiqStackSpace:       				  .space   FIQ_STACK_LEGTH * 4  @Stack spaces for Fast Interrupt reQuest Mode 快速中断模式堆栈空间 AbtStackSpace:       				  .space   ABT_STACK_LEGTH * 4  @Stack spaces for Suspend Mode 中止义模式堆栈空间 UndtStackSpace: 				  .space   UND_STACK_LEGTH * 4  @Stack spaces for Undefined Mode 未定义模式堆栈      .end	 				 				 			   	 
main.c
/****************************************************************************** * 文件名  :main.c * 功    能:初始化系统后,利用P2.28控制led灯闪烁 *  * 作者    :张连聘 * 创建时间:2014-07-27 *******************************************************************************/  #include    "LPC2220.h" #include    "uart0.h" #include    "print.h" #define LEDCON   (1<<28)                                    void Delay(int ms); void myprintf(char *fmt,...);  int  globalvalue = 8; int main(void) { 	int  localvalue =88; 	IO2DIR = LEDCON;    			//配置P2.28为输出口 	PINSEL0 = 0x00000005;		    // 设置I/O连接到UART0     UART0_Init(); 	 	 	while (1) 	{ 		myprintf("the globalvalue is %d\r\n",globalvalue); 		myprintf("the  address of globalvalue is 0X%x\r\n",&globalvalue); 		myprintf("the localvalue is %d\r\n",localvalue); 		myprintf("the  address of localvalue is 0X%x\r\n",&localvalue); 		myprintf("\n\n\n\n"); 		Delay(50); 		IO2SET = LEDCON; //点亮LED灯 		Delay(10); 		IO2CLR = LEDCON; //熄灭LED灯 		Delay(10); 		//UART0_SendStr((unsigned char const *)str); 		 	} 	return 0; } /****************************************************************************** * 名    称:Delay * 功    能:软件延时 * 入口参数:ms  * 出口参数:无 ******************************************************************************/ void Delay(int ms) { 	int i,j; 	for(i=0;i<5000;i++) 		for(j=0;j<ms;j++) 		; }   void myprintf(char *fmt,...) {     va_list ap;     char string[256];      va_start(ap,fmt);     myvsprintf(string,fmt,ap);     UART0_SendStr(string);     va_end(ap); }   void FIQ_Exception(void) { 	 } 
link_script.lds
/*链接脚本文件。*/  OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(Reset)  MEMORY {    rom   (rx)  : ORIGIN = 0x80000000, LENGTH = 2M    ram   (!rx) : ORIGIN = 0x81000000, LENGTH = 16M    stack (!rx) : ORIGIN = 0x40000000, LENGTH = 64K }   SECTIONS {  	. = 0x80000000 ; 	.text :  	{	 		startup.o(.text) 		*(.text)  		_end_text = .; 	} >rom 	 	.rodata : 	{ 		*(.rodata) 	} >rom 	 	. = 0x40000000; 	.MYSTACK : 	{ 		_datastack_start = . ; 		startup.o(.bss) 	 	} >stack 	 	   . = 0x40004000 ; 		StackUsr = . ; 		_datastack_end   = . ; 		 	. = 0x81000000 ; 	.data : 	AT (ADDR(.rodata)+SIZEOF(.rodata)) 	{	 		_data_src = ADDR(.rodata)+SIZEOF(.rodata); 		_start_data = . ;		 		*(.data) 		_end_data = . ; 	} >ram 	 	.bss : 	AT (ADDR(.data)+SIZEOF(.data)) 	{ 		_bss_start = . ; 		*(.bss) 		_bss_end = . ; 	} >ram 	 }

Makefile
CC=arm-linux-gcc       LD=arm-linux-ld  CFLAGS :=  -Wall -c -g -mcpu=arm7tdmi  OBJCOPY=arm-linux-objcopy OBJDUMP=arm-s3c6410-linux-gnueabi-objcopy 	 OBJECTS    :=startup.o target.o  uart0.o print.o  main.o    control_led.bin:control_led_elf	 	$(OBJDUMP) -O binary -S $^  $@ 	 control_led_elf:$(OBJECTS)  	$(LD)  -Bstatic   -Tlink_script.lds $^ -Map control_led_elf.map   -o $@  \ 	-L/usr/local/S3C6410/arm-s3c6410-linux-gnueabi/lib/gcc/arm-s3c6410-linux-gnueabi/4.6.0 \     -L/usr/local/S3C6410/arm-s3c6410-linux-gnueabi/lib  \ 	-L/usr/local/S3C6410/arm-s3c6410-linux-gnueabi/arm-s3c6410-linux-gnueabi/sysroot/usr/lib \ 	--start-group  -lgcc -lgcc_eh -lgcov -lc --end-group  %.o:%.s 	$(CC) $(CFLAGS) -o $@ $< %.o:%.c 	$(CC) $(CFLAGS) -o $@ $< 	 .PHONY : clean clean: 	rm -f control_led.bin control_led_elf *.o




5:重点知识概要

关于LPC2220的启动代码分析网上一大堆,这里就不再赘述相关内容。不过,网上开发工具大多使用的是ADS或者keil,而这里是GNU arm,需要一些gnu arm 汇编伪指令的知识。另外,使用ADS或者keil的工程一般会使用其提供的一些库函数功能,比如RW、ZI段的搬运,堆栈的初始化,库函数的初始化等。而我们这里使用gnu 的链接脚本特性和代码自己实现了这些功能,有利于从整体上理解在某一硬件平台上进行开发的细节和流程。
工程中gnu的链接脚本,这里主要知识点是如何使用脚本语言描述映像文件,在链接脚本中定义的变量怎么在C语言中使用。
工程初始化时为什么要设置堆栈指针。
针对全局变量和局部变量的理解。
这次主要讲解一下编译、链接出来的映像文件,以及如何从装载映像转向运行映像。
本工程装载到NorFlash中映像文件:
     
关于装载映像说明,其中的ZI段在装载映像文件中并不占用空间,只是运行时分配相应的空间即可。
       

运行时的映像:
实现上述映像的转换,是通过startup.s中的 Copydata和 ClearBssData函数实现。其基本思路是我知道每一段映像装载的具体地址和运行时的地址。然后根据这些信息进行复制内容和清除指定内存区域。在上述映像中,所有RO代码段装载和运行地址是一直的,所以不需要关心。我们以RW段的搬移为例来说明,怎么实现。其他ZI段清零自行分析。
我们先看RW段的装载地址怎么定义的,在链接脚本里,RW段的属性,
. = 0x81000000 ;
.data :
AT (ADDR(.rodata)+SIZEOF(.rodata))
{
_data_src = ADDR(.rodata)+SIZEOF(.rodata);
_start_data = . ;
*(.data)
_end_data = . ;
} >ram
其装载地址是AT指定的,我们定义了一个新的标号_data_src ,它的值等于装载时的地址值。然后我们还需要知道它的运行地址,运行地址通过. = 0x81000000 ;指定了_start_data ,_end_data 是它的结束地址。那现在我们知道了data数据段的装载地址,运行地址以及长度。然后我们看看在C语言里怎么使用在链接脚本里定义的标号。
void Copydata(void)
{
extern char  _data_src,_start_data,_end_data;
char * data_src=\'#\'" > char * data_des=&_start_data;
char * data_end=&_end_data;
int len =data_end - data_des;
while(len>=0)
{
*data_des++= *data_src++;
len --;
}
在链接脚本里定义的标号,相当于地址,我们在c语言里定义一些指针,将这些标号以取地址的方式赋给这些指针变量来使用。

下面说说全局变量和局部变量,全局变量是在内存中预留出空间的,而局部变量在堆栈里。根据上述内容,我们知道我们的全局变量放在了0X81000000的地址上,而系统模式堆栈设置在了0X40004000。
看main.c中的代码:
int  globalvalue = 8;
int main(void)
{
int  localvalue =88;
IO2DIR = LEDCON;    //配置P2.28为输出口
PINSEL0 = 0x00000005;   // 设置I/O连接到UART0
    UART0_Init();


while (1)
{
myprintf("the globalvalue is %d\r\n",globalvalue);
myprintf("the  address of globalvalue is 0X%x\r\n",&globalvalue);
myprintf("the localvalue is %d\r\n",localvalue);
myprintf("the  address of localvalue is 0X%x\r\n",&localvalue);
myprintf("\n\n\n\n");

我在这里面定义了一个 全局变量globalvalue ,将其地址打印出来,定义了一个局部变量localvalue,将其地址打印出来。
在我的硬件板上打印输出:
the globalvalue is 8
the  address of globalvalue is 0X81000000
the localvalue is 88
the  address of localvalue is 0X40003ff4

局部变量的地址为0X40003ff4,不是0X40004000的原因是进入main函数时还需要使用堆栈空间保存一些函数返回值等其他内容。
详解: ucos在s3c2410上运行过程整体剖析之基础知识-c语言和堆栈

6:未实现的功能

对标准库的初始化和使用存在疑问,没有使用标准库实现格式化字符串函数功能。
未实现标准库堆的初始化工作,故程序中还不能使用堆空间。即还不能使用malloc等系列函数。
针对嵌入式 gnu 编译链下使用什么库,怎么初始化,这些问题需要进一步学习。

7:总结

通过使用gnu arm编译链书写LPC2220的工程,对链接脚本、makefile和嵌入式硬件、软件基本初始化有了进一步的理解。

8:附录

本例程中源码下载:

基于gnu-arm交叉编译链的LPC2220的简单工程模板

版权声明:本文为博主原创文章,未经博主允许不得转载。

你可能感兴趣的:(makefile,ARM,gnu,链接脚本,gnu汇编,装载映像-----运行映像)