2440裸机lEd

来源:互联网 发布:网络协议的四层模型 编辑:程序博客网 时间:2024/04/30 20:29

从代码开始(先写一个像普通单片机一样的代码):

/********led.c************************/

 

#define GPFCON  (*(volatile unsigned long *)0x56000050)

#define GPFDAT  (*(volatile unsigned long *)0x56000054)

/*后面的数字是引脚的寄存器的地址,(volatile unsignedlong *)这是强制转换,就是吧后面的数字强制转换成volatile unsigned long 的指针类型,最前面的“*”是代表该地址存储的数据*/

static voidpin_init(void)

{

/*首先看原理图led链接的那个引脚,然后在datasheet找到该引脚寄存器地址*/

GPFCON&=~(3<<8); //GPF4对应控制位清0

GPFCON&=~(3<<10); //GPF5对应控制位清0

GPFCON&=~(3<<12); //GPF6对应控制位清0

GPFCON |=1<<8;   //GPF4对应控制位设置为输出

GPFCON |=1<<10;

GPFCON |=1<<12;

 

GPFDAT&=~(1<<4);//GPF4设置输出0

GPFDAT&=~(1<<5);//GPF5设置输出0

GPFDAT&=~(1<<6);//GPF6设置输出0

}

 

static voiddelay(volatile unsigned int a)

{

        int k;

        k=30000;

        while(--k>0)

                for(;a>0;a--);

}

 

int main ()

{

        int temp=1; //用了反转的标志位

        pin_init();

        while(1)

        {

        if (temp>0)

        {

                GPFDAT |=1<<4;

                delay(6000);

                GPFDAT |=1<<5;

                delay(6000);        

                GPFDAT |=1<<6;

                delay(6000);

                temp=-1;

        }

        else

        {

                GPFDAT &=~(1<<4);

                delay(6000);

                GPFDAT &=~(1<<5);

                delay(6000);

                GPFDAT &=~(1<<6);

                delay(6000);

                temp=1;

        }

 

        }

        return 0;

}

c代码比较简单,接下来就要用启动文件了。

启动文件就是包含一些初始化信息相关的初始化的工作包括:

(1)建立异常向量表、设置栈以及硬件初始化

硬件相关的初始化的工作包括:

①关闭看门狗   
②初始化时钟
③初始化SDRAM

(2)设置main函数的返回地址
(3)调用main函数
(4)完成一些清理工作

简要说明:

首先,异常向量表不是必须的,起作用是将用户程序与启动代码之间及其启动代码之间建立关系,由一系列的跳转指令完成,当系统发生异常的时候,处理器将把PC指针由硬件强制指向异常向量表的对应的异常处理函数中,开发板为了上电后可以进入复位异常处理函数中(该函数完成下面个步骤的初始化工作),所以异常向量表要写在启动代码的最前面。

然后设置栈是必须的

对于关闭看门狗,这个是必须的,不然芯片处于不断重启的过程。

初始化时钟就是设置时钟频率,默认的系统时钟12M,根据自己实际设置。

假如要用到SDRAM就要进行SDRAM的初始化。

由于本次程序比较简单,代码编译出来肯定小于4k,因此可以对于启动文件的书写可以分两次实验:

运行程序直接在SRAM Steppingstone)运行,不在将nand flash的内容拷贝到SDRAM中。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;裸机的启动文件   init.s

.text
.global _start
_start:
           ldr    r0, =0x53000000     ; WATCHDOG寄存器地址
           mov     r1,#0x0                     
           str   r1,[r0]             ;赋值WATCHDOG为0,关闭看狗            
           ldr     sp, =4096       ;设置堆栈,sram是4k


           bl     main               ; 调用C程序中的main函数
loop:
           b       loop

上面没有初始化sdran,使用的默认时钟12M。此时的运行地址在makefile中就要设置-Ttext为0

 

⑵按键控制led

这里只是修改写led.c文件就可以

对于本人使用的开发板来说,开发板的3个按键分别连接2440的GPF0   GPF2  GPG3引脚,因此在这里修改led.c文件如下:

/*****************************************************

按键控制led

原理图得知3个按键分别连接2440的GPF0   GPF2  GPG3

三个led 分别对应 GPF4GPF5   GPF6

 

*************************************************/

 

 

 

#defineGPFCON  (*(volatile unsigned long*)0x56000050)

#defineGPFDAT  (*(volatile unsigned long*)0x56000054)

 

#defineGPGCON  (*(volatile unsigned long*)0x56000060)  

#defineGPGDAT  (*(volatile unsigned long*)0x56000064)

 

static voidpin_init(void)

{

/*led*/

GPFCON&=~(3<<8);

GPFCON&=~(3<<10); //GPF5对应控制位清0

GPFCON&=~(3<<12); //GPF6对应控制位清0

 

GPFCON |=1<<8;   //GPF4对应控制位设置为输出

GPFCON |=1<<10;

GPFCON |=1<<12;

 

GPFDAT&=~(1<<4);//GPF4设置输出0

GPFDAT&=~(1<<5);//GPF5设置输出0

GPFDAT&=~(1<<6);//GPF6设置输出0

 

/*key*/

GPFCON&=~(3<<0);

GPFCON&=~(3<<(2*2)); //GPF2对应控制位清0

GPGCON&=~(3<<(2*3)); //GPG3对应控制位清0

 

/*清零的同时也把按键对应的引脚设置成输入*/

 

 

}

 

int main ()

{

        unsigned long key_state; //记录按键的状态

        pin_init();

        while(1)

        {

                     /*当有按键按下led亮,松开按键led灭*/

                     key_state=GPFDAT;

                     if(key_state&(1<<0))

                            GPFDAT|=(1<<4);//GPF4设置输出1

                     else

                            GPFDAT&=~(1<<4);//GPF4设置输出0

                     if(key_state&(1<<2))

                            GPFDAT|=(1<<5);//GPF5设置输出1

                     else

                            GPFDAT&=~(1<<5);//GPF5设置输出0

                           

                     key_state=GPGDAT;

                     if(key_state&(1<<3))

                            GPFDAT|=(1<<6);//GPF6设置输出1

                     else

                            GPFDAT&=~(1<<6);//GPF6设置输出0

                           

              }

        return 0;

}

 

修改后,重新make ,下载到开发板中有现象,当有按键按下时led亮,松开按键,led灭。

 

运行程序在SDRAM运行,需要将SRAM的内容拷贝到SDRAM中。

下面给出一个完整的启动代码,代码的解释参考《mini启动代码的编写(第三版)》代码的编写大都参考了ads1.2库文件的启动代码,这里下载的是百问网的。

具体的代码文件见附录的裸机代码部分需要的文件有:startup.S   init.c   nand .c s3c24xx.h

此时编译的时候需要设置运行地址为:-Ttext为0x30000000

这样,代码的启动流程就是把程序下载到nand flash中,然后硬件自动吧nand的前4k代码(主要是初始化代码)拷贝到SRAM中,然后代码开始在SRAM的0地址处开始执行,然后通过代码将sram中的代码拷贝到SDRAM中,然后有程序控制跳转到SDRAM中去执行。

;*************************************************************************

; File:init.S

; 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行

;*************************************************************************      

 

.equ        MEM_CTL_BASE,       0x48000000

.equ        SDRAM_BASE,         0x30000000

 

.text

.global _start

_start:

    bl disable_watch_dog               ; 关闭WATCHDOG,否则CPU会不断重启

    bl memsetup                        ; 设置存储控制器

    bl copy_steppingstone_to_sdram     ; 复制代码到SDRAM中

    ldr pc, =on_sdram                   ; 跳到SDRAM中继续执行

on_sdram:

    ldr sp, =0x34000000                 ; 设置堆栈

    bl main

halt_loop:

    b  halt_loop

 

disable_watch_dog:

    ; 往WATCHDOG寄存器写0即可

    mov r1,    #0x53000000

    mov r2,    #0x0

    str r2,    [r1]

    mov pc,    lr      ; 返回

 

copy_steppingstone_to_sdram:

    ; 将Steppingstone的4K数据全部复制到SDRAM中去

    ; Steppingstone起始地址为0x00000000,SDRAM中起始地址为0x30000000

   

    mov r1, #0

    ldr r2, =SDRAM_BASE

    mov r3, #4*1024

1: 

    ldr r4, [r1],#4     ; 从Steppingstone读取4字节的数据,并让源地址加4

    str r4, [r2],#4     ; 将此4字节的数据复制到SDRAM中,并让目地地址加4

    cmp r1, r3          ; 判断是否完成:源地址等于Steppingstone的未地址?

    bne 1b              ; 若没有复制完,继续

    mov pc,    lr      ; 返回

 

memsetup:

    ; 设置存储控制器以便使用SDRAM等外设

 

    mov r1,    #MEM_CTL_BASE       ; 存储控制器的13个寄存器的开始地址

    adrl   r2, mem_cfg_val         ; 这13个值的起始存储地址

    add r3,    r1, #52             ; 13*4 = 54

1: 

    ldr r4,    [r2], #4            ; 读取设置值,并让r2加4

    str r4,    [r1], #4            ; 将此值写入寄存器,并让r1加4

    cmp r1,    r3                  ; 判断是否设置完所有13个寄存器

    bne 1b                          ; 若没有写成,继续

    mov pc,    lr                  ; 返回

 

 

.align 4

mem_cfg_val:

    ; 存储控制器13个寄存器的设置值

    .long  0x22011110      ; BWSCON

    .long  0x00000700      ; BANKCON0

    .long  0x00000700      ; BANKCON1

    .long  0x00000700      ; BANKCON2

    .long  0x00000700      ; BANKCON3 

    .long  0x00000700      ; BANKCON4

    .long  0x00000700      ; BANKCON5

    .long  0x00018005      ; BANKCON6

    .long  0x00018005      ; BANKCON7

    .long  0x008C07A3      ; REFRESH

    .long  0x000000B1      ; BANKSIZE

    .long  0x00000030      ; MRSRB6

    .long  0x00000030      ; MRSRB7

 

接下来开始写makefile,在写makefile文件之前,首先需要了解一下gcc的编译过程:

程序编译过程可分为四步:

预处理(Preproceessing):主要是.c和.h文件生成一个文件 (处理一写头文件、宏定义、预编译指令等)

编译(Compilation):上步生成文件生成.s文件 (包括语法分析、优化等等)

汇编(Assembly):.s文件生成.o文件(把汇编语言代码翻译成目标机器指令)

链接(Linking):.o和.a等连接成可执行文件默认为a.out(将有关的目标文件彼此相连接)

下面写代码来说明各个过程:

/*hello.c*/

#include<stdio.h>

int main ()

{

printf(“hello\n”)

return 0;

}

首先进行预处理:

gcc –E hello.c

-E参数就是让gcc只是预处理,不进行编译、汇编和链接。

执行的时候会在终端打印出好多信息。这就是预处理后生成的文件。

 

然后执行 gcc –S  hello.c

-S参数就是让gcc只进行编译,不进行汇编和链接

执行该命令后会得到一个.s的汇编文件,内容为:

        .file   "hello.c"

        .section        .rodata

.LC0:

        .string "hello"

        .text

        .globl  main

        .type   main, @function

main:

.LFB0:

        .cfi_startproc

        pushl   %ebp

        .cfi_def_cfa_offset 8

        .cfi_offset 5, -8

        movl   %esp, %ebp

        .cfi_def_cfa_register5

        andl    $-16, %esp

        subl    $16, %esp

        movl    $.LC0, (%esp)

        call    puts

        movl    $0, %eax

        leave

        .cfi_restore 5

        .cfi_def_cfa 4, 4

        ret

        .cfi_endproc

.LFE0:

        .size   main, .-main

        .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1)4.8.2"

        .section        .note.GNU-stack,"",@progbits

再者运行:

gcc -c hello.c

得到hello.o, hello.o的内容为机器码,不能以普通文本形式的查看

 

最后运行进行链接,链接要用到ld命令,这里直接使用:

gcc hello.c 

可以完成所有的步骤,这是默认的生成.out文件

 

接下来些makefile文件

Makefile文件:

对于将在sram执行时候可为:

CFLAGS  := -Wall -Wstrict-prototypes -O2  -nostdlib

led.bin :init.S led.c

        arm-linux-gcc $(CFLAGS) -c -o init.oinit.S

        arm-linux-gcc $(CFLAGS) -c -o led.o led.c

        arm-linux-ld -Ttext 0  init.o led.o -o led_elf

        arm-linux-objcopy -O binary -S  led_elf led.bin

        #arm-linux-objdump -D -m arm  led_elf >  led.dis

clean:

        rm -rf *.o *elf  *bin *.dis

 

对于CFAGS,也就是gcc的选项解释如下:

-Wall生成所有警告信息。

-Wstrict-prototypes 如果一个函数定义或者声明没有指定参数类型发出警告。

-O设置一共有五种:-O0、-O1、-O2、-O3和-Os

O0:这个等级(字母“O”后面跟个零)关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置-O等级时的默认等级。这样就不会优化代码,这通常不是我们想要的。
-O1:这是最基本的优化等级。编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。 
-O2:-O1的进阶。这是推荐的优化等级,除非你有特殊的需求。-O2会比-O1启用多一些标记。设置了-O2后,编译器会试图提高代码性能而不会增大体积和大量占用的编译时间。
-O3:这是最高最危险的优化等级。用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用。自从3.x版本以来gcc的行为已经有了极大地改变。在3.x,-O3生成的代码也只是比-O2快一点点而已,而gcc4.x中还未必更快。用-O3来编译所有的软件包将产生更大体积更耗内存的二进制文件,大大增加编译失败的机会或不可预知的程序行为(包括错误)。这样做将得不偿失,记住过犹不及。在gcc 4.x.中使用-O3是不推荐的。
-Os:这个等级用来优化代码尺寸。其中启用了-O2中不会增加磁盘空间占用的代码生成选项。这对于磁盘空间极其紧张或者CPU缓存较小的机器非常有用。但也可能产生些许问题,因此软件树中的大部分ebuild都过滤掉这个等级的优化。使用-Os是不推荐的。

(所有的参数可以在man gcc 查看)

接下来两行是分别汇编成.o文件

对于这一句: arm-linux-ld -Ttext 0 init.o led.o -o led_elf  其中-Ttext的功能就是指定程度段的起始地址。这里仅仅是指定了一下代码段的运行地址,后面的数据段并不会在后面接着存放。也即是程序是跳转到0处(sran的起始地址)去运行的。还有一点就是链接init.0和led.o生成可elf的可执行文件,ELF(excutable and linking format)是一种可执行可链接格式的二进制文件,它可以用来表示relocatablefile、executablefile或者sharedobject file,这三者都是目标文件(objectfile)。所谓“可执行”指可以被调入内存供CPU直接运行;“可链接”指多个ELF格式的目标文件可以被链接在一起形成一个可执行文件。

arm-linux-objcopy被用来复制一个目标文件的内容到另一个文件中.此选项可以进行格式的转换.这里就是将elf格式的文件转化为.bin二进制文件。对于 arm-linux-objcopy -Obinary -S  led_elf  led.bin –O后面的是输出的问价类型,-S 是说不要从输入文件复制重定位信息和符号信息到目标文件中。

至于led.c文件,仍使用上面的那个按键点灯的程序。

一切就绪后,就可以编译然后将最终生成的led.bin/文件下载到开发板中就可以,假如成功的话,当有按键按下的时候等就会亮,松开按键等就会灭。

 

0 0
原创粉丝点击