基于gnu-arm-linux的LPC2220的简单工程模板

来源:互联网 发布:郑州大学网络教育学费 编辑:程序博客网 时间:2024/06/01 15:29

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_AddrResetAddr:.word     ResetInitUndefinedAddr:      .word     UndefinedSWI_Addr:.word     SoftwareInterruptPrefetchAddr:.word     PrefetchAbortDataAbortAddr:  .word     DataAbortNouse:          .word     0IRQ_Addr:.word     0FIQ_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, =0x0f814914STR     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        CopydataBL        ClearBssData        B          main                    @ Jump to the entry point of C program 跳转到c语言入口     StackIrq:           .word     IrqStackSpace  +(IRQ_STACK_LEGTH - 1)*4StackFiq:           .word     FiqStackSpace  +(FIQ_STACK_LEGTH - 1)*4StackAbt:  .word     AbtStackSpace  +(ABT_STACK_LEGTH - 1)*4StackUnd:  .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-ldCFLAGS :=  -Wall -c -g -mcpu=arm7tdmiOBJCOPY=arm-linux-objcopyOBJDUMP=arm-s3c6410-linux-gnueabi-objcopyOBJECTS    :=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 : cleanclean: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=&_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的简单工程模板

0 0
原创粉丝点击