ARM中堆栈指针SP的设置
计算机中的堆栈主要用来保存临时数据、局部变量和中断/调用子程序后的返回地址。程序中栈主要用来保存函数中的局部变量、寄存器参数,如果使用了操作系统,栈中还可能保存当前进线程的上下文。
设置栈大小的一个原则是:保证栈不会溢出到数据空间或程序空间。所谓堆栈溢出,是指堆栈指针SP向下增长到其他段空间,堆栈溢出会修改其他段空间的值,严重情况下可能会造成死机。CPU在运行程序时,会自动的使用堆栈,所以堆栈指针SP必须在调用C程序之前设定。
CPU中的内存空间(RAM)的存放规律一般是分段的,从低地址到高地址,依次为:代码段(.text)、数据段、.bss段,然后上面可能会有堆空间,最上面才是堆栈段,这样安排堆栈,是由堆栈的特点决定的,所以堆栈的指针SP在初始化时,一般设置在堆栈段的最高地址处,也就是内存的最高地址处,然后让堆栈指针向下增长(其实是递减)。 这样做的好处是:堆栈空间远离了其他段,不会跟其他段重叠,就不会修改其他段的数据,避免不可预料的后果。
开始将堆栈指针设置在内部RAM,这是因为并不是所有的开发板都有外部RAM,而且外部RAM的大小也不相同,如果是SDRAM,还需要初始化SDRAM。在内部RAM,开始运行的程序一般都是一个小的引导程序,基本上不怎么使用堆栈,因此将堆栈设置在内部RAM,但是在引导程序中就不能随意使用大量的局部变量。
片内RAM的大小为4K(0-4096),SDRAM的大小为64M(0x3000 0000 - 0x33FF FFFF)。如果是Nand启动,SP的值设置为4096;如果是Nor启动,SP的值设置为0x4000 0000 + 4096;当程序在SDRAM运行的时候,SP的值设置为0x3400 0000;如果是Nand启动,SP的值设置为4096;如果是Nor启动,SP的值设置为0x4000 0000 + 4096。所以,一旦完成SDRAM的初始化,就将SP设置为0x3400 0000,此时就可以随意使用局部变量,而不会造成堆栈溢出。
如果是Nand启动,SP设置为4096,由于Nand启动需要先将Nand flash上前4K的代码复制到片内RAM上,然后从片内RAM的0地址处开始执行代码。如果这4K的代码中用到了栈,就会将片内RAM上最高地址处的代码给破坏掉,所以:在这4K的代码中,我们自己写的程序要避免使用SP,完成完成SDRAM的初始化之后,立即将SP设置为3400 0000,此时就可以随意的使用栈了,也不会破坏片内RAM上的代码。
栈的整体作用:
1、保存现场
2、传递参数
汇编代码调用C函数时,需要传递参数,如果本身传递的参数不多于4个,就可以通过寄存器r0~r3传递参数;如果本身传递的参数多于4个,寄存器不够用,就需要栈了。
3、临时变量保存在栈中
时钟的初始化:
S3C2440这个芯片上面,不仅有CPU,还有各种外设:
CPU: 采用FCLK时钟,最大400MHz;
高速外设: 挂载在AHB总线上,采用HCLK时钟,最大136MHz;
低速外设: 挂载在APB总线上,采用PCLK时钟,最大68MHz;
三个时钟的产生:
选择器OM[3:2],可以选择时钟源为外部时钟(XTIpll和XTOpll)或者EXTCLK时钟。
硬件电路连接图:
OM2为P13引脚,OM3为T13引脚,XTIpll为G14引脚,XTOPll为G15引脚。
焊接在XTIpll和XTOPll引脚上的时钟为12MHz晶振,选择器OM[3:2]接GND,也就是低电平,所以OM[3:2]=00,所以MPLL和UPLL都使用外部12MHz的晶振作为时钟源。
设置时钟:
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
LOCKTIME寄存器的地址为0x4C000000,设置LOCK Time时间。
上电后,当复位引脚变为高电平,CPU开始运行,FCLKF=Fin=12MHz,然后设置MPLLCON寄存器和CLKDIVN寄存器,一旦设置好MPLLCON后,晶振不再起振,CPU停止工作,等待LOCK Time时间后,FCLK=设置的时钟频率,CPU重新开始工作。
我们一上电,复位引脚会维持一段时间的低电平,等待电源电压稳定。那么谁来帮我们维持这个复位引脚(维持一段时间的低电平)?
IMP811T芯片的作用:一上电,ARM3.3V可能不稳定,会使得2号引脚一直输出低电平;当ARM3.3V稳定之后,2号引脚输出高电平;当复位按键S1按下之后,3号引脚为低电平,就会使得2号引脚输出低电平。
复位引脚接在H16引脚上。
一上电,复位引脚依然是低电平,当电源电压稳定后(也就是过一段时间),然后IMP811T这个芯片才会使得复位引脚为高电平。但是,一上电,晶振就开始起振,那么根据OM[3:2]的值,FCLK=12MHz晶振。此时,CPU还没有开始运行,当复位引脚变为高电平时,CPU才开始运行。然后PLL锁存OM[3:2]的值,同时CPU开始运行。CPU运行后,我们就可以设置PLL,一旦设置好PLL,再经过LOCK Time时间后,FCLK就等于设置好的新的时钟了,CPU重新开始运行,但是在LOCK Time这段时间内,晶振不在起振,CPU停止运行。
ldr r0, =0x4C000014
mov r1, #0x05; 或者ldr r1, =0x05
str r1, [r0]
CLKDIVN寄存器的地址为0x4C000014,用来设置FCLK、HCLK、PCLK三者间的比例
0x03:FCLK、HCLK、PCLK三者间比例为1:2:4 HCLK=FCLK/2,PCLK=FCLK/4
0x05:FCLK、HCLK、PCLK三者间比例为1:4:8 HCLK=FCLK/4,PCLK=FCLK/8
mrc p15, 0, r1, c1, c0, 0
orr r1, r1, #0xC0000000
mcr p15, 0, r1, c1, c0, 0
如果HDIVN非0,CPU总线模式从“fast bus mode”(快速总线模式)变为“asynchronous”(异步模式),因为数据手册就是这样要求的,也就是CPU总线必须设置为异步模式;
异步模式:将nF设置为1,nF为P15的1号寄存器的第31位;
ldr r0, =0x4C000004
ldr r1, =((0x5C << 12) | (0x01 << 4) | (0x01 << 0))
str r1, [r0]
MPLLCON寄存器的地址为0x4C000004;
S3C2440的CPU主频可达400MHz,开发板上的外接晶振为12MHz,需要通过时钟控制逻辑的PLL(Phase Locked Loop:锁相环电路)来倍频这个系统时钟。
S3C2440有两个PLL,一个是MPLL,另一个是UPLL:
UPLL专用于USB设备,常用频率为48MHz和96MHZ;
MPLL用于CPU及其他外围器件,用于产生FCLK、HCLK、PCLK三种频率。
上电时PLL并没有被启动,所以FCLK=Fin=12MHz,若要提高系统时钟,需要软件编程来启动PLL。
主分频、前置分频、后置分频的设置:
由于我们的S3C2440是400MHz,所以对于倍频值的选择为:MDIV=0x05,PDIV=0x01,SDIV=0x01。
在前面我们已经通过CLKDIVN寄存器设置了FCLK:HCLK:PCLK=1:2:4,所以只需要设置好FCLK为400MHz,则:HCLK=200MHz,PCLK=100MHz;
若FCLK:HCLK:PCLK=1:4:8,所以只需要设置好FCLK为400MHz,则:HCLK=100MHz,PCLK=50MHz;
SDRAM的初始化
对存储控制器提供的13个寄存器进行设置:
/*3 初始化SDRAM*/
ldr r0, =0x48000000
adr r1, sdram_config
add r3, r0, #(13 * 4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b
sdram_config:
.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 0x008C04F4 /*REFRESH*/
.long 0x000000B1 /*BANKSIZE*/
.long 0x00000030 /*MRSRB6*/
.long 0x00000030 /*MRSRB7*/
重定位
ldr sp, =0x34000000
/*初始化Nand flash*/
bl nand_init
/*重定位*/
mov r0, #0
ldr r1, =_start
ldr r2, =bss_start
sub r2, r2, r1
bl relocate
bl clean_bss
完成SDRAM的初始化之后,将SP的值设置为0x3400 0000,然后进行重定位。重定位需要将Nor flash或者Nand flash上的代码复制到SDRAM上,由于Nor flash上代码可以直接读取并复制到SDRAM上,而Nand flash需要先经过初始化并按照相关的读操作,才能将Nand flash上的代码读取并复制到SDRAM上。
重定位需要传递三个参数:
r0:Nor flash或者Nand flash的0地址,复制代码的起始地址
r1:SDRAM的链接地址,_start就是0x30000000(在链接脚本boot.lds中给出了)
r2:复制代码的长度,bss_start - _start(一共有这么多个字节需要复制,复制代码段、只读数据段、数据段)
链接脚本 boot.lds:
SECTIONS
{
. = 0x30000000;
. = ALIGN(4);
.text : { *(.text) }
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
bss_start = .;
.bss : { *(.bss) *(.COMMON) }
bss_end = .;
}
链接脚本如上:
.=0x30000000 表示SDRAM的链接地址;
.=ALIGN(4) 表示当前地址为4字节对齐;
.text : { *(.text) } 表示所有的代码段;
.rodata : { *(.rodata) } 表示所有的只读数据段;
.data : { *(.data) } 表示所有的数据段;
bss_start = . 表示bss段和COMMON段的起始地址;
.bss : { *(.bss) *(.COMMON) } 表示所有的bss段和COMMON段;
bss_end = . 表示bss段和COMMON段的终止地址;
relocate.c文件:
int IsBootFromNorFlash(void)
{
volatile unsigned int *p = (volatile unsigned int *)0;
unsigned int val;
val = *p; /*保存0地址处的原始内容*/
*p = 0x12345678; /*在0地址处重新写入内容*/
if(*p == 0x12345678) /*0地址被写入内容,说明是Nand启动*/
{
*p = val; /*在0地址处恢复原来的内容*/
return 0;
}
else /*0地址没有被写入内容,说明是Nor启动*/
return 1;
}
判断是Nor启动还是Nand启动,Nor flash可以像内存一样读(RAM可以直接读),但不能像内存一样写;而Nand flash,如果是Nand启动,Nand flash上前4K的代码需要先复制到片内RAM上(由硬件自动完成),所有说0地址对应的是片内RAM的0地址,它可以直接写。Nor flash的0地址不能直接写入,而片内RAM的0地址可以直接写入。
综上,判断是Nor启动还是Nand启动,就是根据0地址处是否可以直接写入,如果可以直接写入,就是Nand启动;如果不可以直接写入,就是Nor 启动。
void relocate(unsigned int *src, unsigned int *dest, unsigned int len)
{
int i = 0;
/*如果是Nor启动*/
if(IsBootFromNorFlash())
{
/*Nor flash可以像内存一样读,所以可以直接把代码从Nor flash复制到SDRAM*/
while(i < len)
{
*dest = *src;
src++;
dest++;
i += 4;
}
}
/*如果是Nand启动*/
else
{
/*Nand flash不能直接读,需要经过初始化和相应的读操作才能把内容读取出来*/
//nand_init();
nand_read(src, dest, len);
}
}
void clean_bss(void)
{
extern int bss_start, bss_end;
int *p = &bss_start;
for(; p < &bss_end; p++)
*p = 0;
}
清除bss段:
所有的.bss段和.COMMON段,存放那些初始值为0或者没有初始化的全局变量,假设有100万个这样的变量,我们不可能单独为每一个这样的变量开辟一块内存空间去存放变量的值,我们只需要将这所有的变量集中在一起放在.bss段和.COMMON段,然后将这段内存空间置0,就可以实现将变量置0的操作。
nand_flash.c文件:
#define NFCONF (*(volatile unsigned int *)(0x4E000000)) /*NAND flash 配置寄存器,用来设置时序*/
#define NFCONT (*(volatile unsigned int *)(0x4E000004)) /*NAND flash 控制寄存器,*/
#define NFCMMD (*(volatile unsigned char *)(0x4E000008)) /*NAND flash 发送命令寄存器,命令只有8位*/
#define NFADDR (*(volatile unsigned char *)(0x4E00000C)) /*NAND flash 发送地址寄存器,地址只有8位*/
#define NFDATA (*(volatile unsigned char *)(0x4E000010)) /*NAND flash 读/写数据寄存器,数据只有8位*/
#define NFSTAT (*(volatile unsigned char *)(0x4E000020)) /*NAND flash 运行状态寄存器,用于判断RnB脚*/
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
void nand_init(void)
{
/*设置Nand flash的时序*/
NFCONF = ((TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4));
/*bit0:使能Nand flash控制器 bit1:禁止片选 bit4:初始化ECC*/
NFCONT = ((1 << 0) | (1 << 1) | (1 << 4));
}
void nand_select(void)
{
int i;
/*使能片选信号,bit1置0*/
NFCONT &= ~(1 << 1);
/*延时,等待芯片使能成功*/
for(i = 0; i < 10; i++);
}
void nand_deselect(void)
{
int i;
/*禁止片选信号,bit1置1*/
NFCONT |= (1 << 1);
/*延时,等待芯片禁止成功*/
for(i = 0; i < 10; i++);
}
void nand_command(unsigned char command)
{
int i;
NFCMMD = command;
/*延时,等待写入命令成功*/
for(i = 0; i < 10; i++);
}
void nand_send_address(unsigned int address)
{
volatile int i;
unsigned int col = address % 2048; /*某一页的第几列*/
unsigned int page = address / 2048; /*第几页,也就是第几行*/
/*先发送2个列地址,再发送3个行地址*/
NFADDR = (col >> 0) & 0xFF; /*低8位,A7~A0,第一个周期*/
for(i = 0; i < 10; i++); /*延时,等待写入地址成功*/
NFADDR = (col >> 8) & 0x0F; /*高4位,A11~A8,第二个周期*/
for(i = 0; i < 10; i++);
NFADDR = (page >> 0) & 0xFF; /*低8位,A19~A12,第三个周期*/
for(i = 0; i < 10; i++);
NFADDR = (page >> 8) & 0xFF; /*高8位,A27~A20,第四个周期*/
for(i = 0; i < 10; i++);
NFADDR = (page >> 16) & 0x01; /*第17位,A28,第五个周期*/
for(i = 0; i < 10; i++);
}
unsigned char nand_read_data(void)
{
/*读取数据*/
unsigned char p = NFDATA;
return p;
}
void nand_wait_idel(void)
{
/*NFSTAT寄存器中的bit1,表示RnB引脚的高低电平:
为0:Nand flash中数据还没有准备好,一直循环;
为1:Nand flash中数据准备好,退出循环;*/
while(!(NFSTAT & 0x01));
}
void nand_read(unsigned int src, unsigned char *dest, unsigned int len)
{
/*src:源地址,为32位,所以用unsigned int表示*/
/*dest:目的地址,由于Nand flash上每一个地址存放一个字节的内容,所以用unsigned char表示*/
int i = 0;
/*第一次读,可能不是某一页的0地址处,需要记录在当前页中的列位置*/
unsigned int col = src % 2048;
nand_select(); /*使能片选信号*/
while (i < len)
{
nand_command(00); /*发送读命令0x00H*/
nand_send_address(src); /*分5个周期发送源地址*/
nand_command(0x30); /*发送读命令0x30H*/
nand_wait_idel(); /*等待数据准备就绪*/
/*连续读取页内数据*/
for (; (col < 2048) && (i < len); col++)
{
dest[i] = nand_read_data();
i++;
src++;
}
col = 0;
}
nand_deselect(); /*读完数据后,禁止片选信号*/
}
main.c文件:
int sdram_test(void)
{
volatile unsigned char *p = (volatile unsigned char*)0x30000000;
int i;
for(i = 0; i < 1000; i++)
p[i] = 0x55;
for(i = 0; i < 1000; i++)
if(p[i] != 0x55)
return -1;
return 0;
}
int main(void)
{
uart0_init();
if(sdram_test() == 0)
{
puts("Hello World!\n\r");
puts("error!\n\r");
}
while(1)
{
}
return 0;
}
一旦执行了volatile unsigned char p = (volatile unsigned char)0x30000000这条指令,由于地址为0x30000000,nGCS6片选引脚会自动的变为低电平,从而选中SDRAM。由于在start.S文件中已经对SDRAM进行了初始化,SDRAM可以直接进行读写,从0x30000000这个地址开始,每一个地址写入0x55(8位),一直写1000个数据;然后在将这1000个数据读取出来,判断是不是为0x55,是0x55说明写入成功,返回0后退出;若不是0x55,说明写入不成功,返回-1退出。
Makefile文件:
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
CFLAGS := -Wall -O2
CPPFLAGS := -nostdinc -nostdlib -fno-builtin
objs := start.o relocate.o nand_flash.o uart.o main.o
boot.bin: $(objs)
${LD} -Tboot.lds -o boot.elf $^
${OBJCOPY} -O binary -S boot.elf $@
${OBJDUMP} -D -m arm boot.elf > boot.dis
%.o:%.c
${CC} ${CFLAGS} ${CPPFLAGS } -c -o $@ $<
%.o:%.S
${CC} ${CFLAGS} ${CPPFLAGS } -c -o $@ $<
clean:
rm -f *.o *.bin *.elf *.dis
最后,整个程序文件如下:
start.S文件:
.text
.global _start
_start:
/*关闭看门狗*/
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/*初始化时钟*/
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000
mcr p15,0,r0,c1,c0,0
ldr r0, =0x4C000004
ldr r1, =((92 << 12) | (1 << 4)| (1 << 0))
str r1, [r0]
/*初始化SDRAM*/
mov r1, #0
ldr r0, [r1]
str r1, [r1]
ldr r2, [r1]
cmp r1, r2
ldr sp, =0x40000000+4096
moveq sp, #4096
streq r0, [r1]
ldr r0, =0x48000000
adr r1, sdram_config
add r3, r0, #(13 * 4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b
/*重定位*/
ldr sp, =0x34000000
/*初始化Nand flash*/
bl nand_init
mov r0, #0
ldr r1, =_start
ldr r2, =bss_start
sub r2, r2, r1
bl relocate
/*清除.bss段*/
bl clean_bss
ldr lr, =halt
ldr pc, =main
halt:
b halt
sdram_config:
.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 0x008C04F4 /*REFRESH*/
.long 0x000000B1 /*BANKSIZE*/
.long 0x00000030 /*MRSRB6*/
.long 0x00000030 /*MRSRB7*/
relocate.c文件:
int IsBootFromNorFlash(void)
{
volatile unsigned int *p = (volatile unsigned int *)0;
unsigned int val;
val = *p; /*保存0地址处的原始内容*/
*p = 0x12345678; /*在0地址处重新写入内容*/
if(*p == 0x12345678) /*0地址被写入内容,说明是Nand启动*/
{
*p = val; /*在0地址处恢复原来的内容*/
return 0;
}
else /*0地址没有被写入内容,说明是Nor启动*/
return 1;
}
void relocate(unsigned int *src, unsigned int *dest, unsigned int len)
{
int i = 0;
/*如果是Nor启动*/
if(IsBootFromNorFlash())
{
/*Nor flash可以像内存一样读,所以可以直接把代码从Nor flash复制到SDRAM*/
while(i < len)
{
*dest = *src;
src++;
dest++;
i += 4;
}
}
/*如果是Nand启动*/
else
{
/*Nand flash不能直接读,需要经过初始化和相应的读操作才能把内容读取出来*/
//nand_init();
nand_read(src, dest, len);
}
}
void clean_bss(void)
{
extern int bss_start, bss_end;
int *p = &bss_start;
for(; p < &bss_end; p++)
*p = 0;
}
nand_flash.c文件:
#define NFCONF (*(volatile unsigned int *)(0x4E000000)) /*NAND flash 配置寄存器,用来设置时序*/
#define NFCONT (*(volatile unsigned int *)(0x4E000004)) /*NAND flash 控制寄存器,*/
#define NFCMMD (*(volatile unsigned char *)(0x4E000008)) /*NAND flash 发送命令寄存器,命令只有8位*/
#define NFADDR (*(volatile unsigned char *)(0x4E00000C)) /*NAND flash 发送地址寄存器,地址只有8位*/
#define NFDATA (*(volatile unsigned char *)(0x4E000010)) /*NAND flash 读/写数据寄存器,数据只有8位*/
#define NFSTAT (*(volatile unsigned char *)(0x4E000020)) /*NAND flash 运行状态寄存器,用于判断RnB脚*/
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0
void nand_init(void)
{
/*设置Nand flash的时序*/
NFCONF = ((TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4));
/*bit0:使能Nand flash控制器 bit1:禁止片选 bit4:初始化ECC*/
NFCONT = ((1 << 0) | (1 << 1) | (1 << 4));
}
void nand_select(void)
{
int i;
/*使能片选信号,bit1置0*/
NFCONT &= ~(1 << 1);
/*延时,等待芯片使能成功*/
for(i = 0; i < 10; i++);
}
void nand_deselect(void)
{
int i;
/*禁止片选信号,bit1置1*/
NFCONT |= (1 << 1);
/*延时,等待芯片禁止成功*/
for(i = 0; i < 10; i++);
}
void nand_command(unsigned char command)
{
int i;
NFCMMD = command;
/*延时,等待写入命令成功*/
for(i = 0; i < 10; i++);
}
void nand_send_address(unsigned int address)
{
volatile int i;
unsigned int col = address % 2048; /*某一页的第几列*/
unsigned int page = address / 2048; /*第几页,也就是第几行*/
/*先发送2个列地址,再发送3个行地址*/
NFADDR = (col >> 0) & 0xFF; /*低8位,A7~A0,第一个周期*/
for(i = 0; i < 10; i++); /*延时,等待写入地址成功*/
NFADDR = (col >> 8) & 0x0F; /*高4位,A11~A8,第二个周期*/
for(i = 0; i < 10; i++);
NFADDR = (page >> 0) & 0xFF; /*低8位,A19~A12,第三个周期*/
for(i = 0; i < 10; i++);
NFADDR = (page >> 8) & 0xFF; /*高8位,A27~A20,第四个周期*/
for(i = 0; i < 10; i++);
NFADDR = (page >> 16) & 0x01; /*第17位,A28,第五个周期*/
for(i = 0; i < 10; i++);
}
unsigned char nand_read_data(void)
{
/*读取数据*/
unsigned char p = NFDATA;
return p;
}
void nand_wait_idel(void)
{
/*NFSTAT寄存器中的bit1,表示RnB引脚的高低电平:
为0:Nand flash中数据还没有准备好,一直循环;
为1:Nand flash中数据准备好,退出循环;*/
while(!(NFSTAT & 0x01));
}
void nand_read(unsigned int src, unsigned char *dest, unsigned int len)
{
/*src:源地址,为32位,所以用unsigned int表示*/
/*dest:目的地址,由于Nand flash上每一个地址存放一个字节的内容,所以用unsigned char表示*/
int i = 0;
/*第一次读,可能不是某一页的0地址处,需要记录在当前页中的列位置*/
unsigned int col = src % 2048;
nand_select(); /*使能片选信号*/
while (i < len)
{
nand_command(00); /*发送读命令0x00H*/
nand_send_address(src); /*分5个周期发送源地址*/
nand_command(0x30); /*发送读命令0x30H*/
nand_wait_idel(); /*等待数据准备就绪*/
/*连续读取页内数据*/
for (; (col < 2048) && (i < len); col++)
{
dest[i] = nand_read_data();
i++;
src++;
}
col = 0;
}
nand_deselect(); /*读完数据后,禁止片选信号*/
}
uart.c文件:
#define ULCON0 (*(volatile unsigned int *)(0x50000000)) /*UART 0 line control*/
#define UCON0 (*(volatile unsigned int *)(0x50000004)) /*UART 0 control*/
#define UTRSTAT0 (*(volatile unsigned int *)(0x50000010)) /*UART 0 Tx/Rx status*/
#define UTXH0 (*(volatile unsigned char *)(0x50000020)) /*UART 0 transmission hold*/
#define URXH0 (*(volatile unsigned char *)(0x50000024)) /*UART 0 receive buffer*/
#define UBRDIV0 (*(volatile unsigned int *)(0x50000028)) /*UART 0 baud rate divisor*/
#define GPHCON (*(volatile unsigned int *)(0x56000070)) /*Port H control*/
#define GPHUP (*(volatile unsigned int *)(0x56000078)) /*Pull-up control H*/
void uart0_init()
{
/*设置引脚用于串口*/
/*GPH2,3用于TxD0、RxD0*/
GPHCON &= ~((3<<4) | (3<<6));
GPHCON |= ((2<<4) | (2<<6));
/*使能内部上拉*/
GPHUP &= ~((1<<2) | (1<<3));
/*设置波特率*/
/*PCLK,中断请求/查询模式*/
UCON0 = 0x00000005;
/*波特率115200*/
UBRDIV0 = 26;
/*设置数据格式 8个数据位,无校验位,1个停止位*/
ULCON0 = 0x00000003;
}
/*发送一个字符*/
int putchar(int c)
{
/*为1说明数据已经发送完成*/
while(!(UTRSTAT0 & (1<<2)));
UTXH0 = (unsigned char)c;
}
/*接收一个字符*/
int getchar(void)
{
/*为1说明已经接收到了数据*/
while(!(UTRSTAT0 & (1<<0)));
return URXH0;
}
/*发送一个字符串*/
int puts(const char *str)
{
while(*str)
{
putchar(*str);
str++;
}
}
main.c文件:
int sdram_test(void)
{
volatile unsigned char *p = (volatile unsigned char*)0x30000000;
int i;
for(i = 0; i < 1000; i++)
p[i] = 0x55;
for(i = 0; i < 1000; i++)
if(p[i] != 0x55)
return -1;
return 0;
}
int main(void)
{
unsigned char c;
uart0_init();
if(sdram_test() == 0)
{
puts("Hello World!\n\r");
puts("error!\n\r");
}
while(1)
{
}
return 0;
}
链接脚本boot.lds文件:
SECTIONS
{
. = 0x30000000;
. = ALIGN(4);
.text : { *(.text) }
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
bss_start = .;
.bss : { *(.bss) *(.COMMON) }
bss_end = .;
}
Makefile文件:
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
CFLAGS := -Wall -O2
CPPFLAGS := -nostdinc -nostdlib -fno-builtin
objs := start.o relocate.o nand_flash.o uart.o main.o
boot.bin: $(objs)
${LD} -Tboot.lds -o boot.elf $^
${OBJCOPY} -O binary -S boot.elf $@
${OBJDUMP} -D -m arm boot.elf > boot.dis
%.o:%.c
${CC} ${CFLAGS } ${CPPFLAGS} -c -o $@ $<
%.o:%.S
${CC} ${CFLAGS } ${CPPFLAGS} -c -o $@ $<
clean:
rm -f *.o *.bin *.elf *.dis