从代码開始(先写一个像普通单片机一样的代码):
/********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: 寄存器地址 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文件
对于这一句: -Ttext 0 init.o led.o -o led_elf 当中-Ttext的功能就是指定程度段的起始地址。这里不过指定了一下代码段的执行地址,后面的数据段并不会在后面接着存放。
也即是程序是跳转到0处(sran的起始地址)去执行的。
另一点就是链接init.0和led.o生成可elf的可执行文件,ELF()是一种可运行可链接格式的二进制文件,它能够用来表示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/文件下载到开发板中就能够,假如成功的话,当有按键按下的时候等就会亮。松开按键等就会灭。