2024年2月22日发(作者:荣智伟)
目录:
说明.................................................................................................................................................................................................2
知识点梳理.....................................................................................................................................................................................2
页的绝对地址与相对地址.....................................................................................................................................................2
NAND FLASH地址周期:...................................................................................................................................................3
代码解释:...............................................................................................................................................................4
mini2440开发板的时钟.........................................................................................................................................................5
NandFlash—物理结构及地址传送................................................................................................................................................5
编译生成bin文件,下载运行......................................................................................................................................................9
代码源文件.....................................................................................................................................................................................9
Makefile文件内容:..............................................................................................................................................................9
head.S文件内容:...............................................................................................................................................................10
main.c文件内容:...............................................................................................................................................................12
nand.c文件内容:...............................................................................................................................................................12
文件内容:.............................................................................................................................................................18
/u/3104586975
说明
开发板:mini2440
Nand flash:K9F2G08U0B2,256M
本程序实现功能:我是参考韦东山的第8章的裸机程序,但是他的nand flash型号是64MB的K9F12080M,所以我依照他的程序加以修改,下载到nand flash,从nand flash实现将main.c程序复制到sdram中执行。
参考书:《嵌入式应用开发完全手册》
《ARM处理器裸机开发实战---机制而非策略》
知识点梳理
在nand.c中有一个函数void RdNF2SDRAM(),它的功能是将nand flah的内容复制到sdram,如下:
页的绝对地址与相对地址
nand flash的页读取函数原型是void nf_readpage(unsigned int block,unsigned int page,unsigned char *dstaddr),它的功能是从nand flash的第block块的第page页读取1页(对于K9F2G来说,1页等于2KB)的内容,存放到指针dstaddr所指的地址处。有的人可能就会说了,既然nf_readpage可以读取1页的数据到指定的地址处,而sdram的地址是0x30000000,那直接可以使用它将nand flash的代码复制到sdram去啊,答案当然是可以啊!但是有个前提,复制到sdram的内容必须在2KB以内,一旦超过了2KB,那就需要借用RdNF2SDRAM()函数了,相信这样解释就能明白这两个函数的区别于联系了。
再说说绝对地址与相对地址,首先知道k9f2g有2048个块,每个块有64个页,每页有2K字节。我们平常所说的第几块第几页就是相对地址,比如第2块第3页是相对地址。而绝对地址=块号*64+页号,例如前边的第2块第3页换成绝对地址就是131(131=2*64+3)。
下面解释一下RdNF2SDRAM()函数:
113行:从117行可以看出,这里的i指的是相对地址
114行:在nand flash内的起始地址,这里的4096表示的是字节,k9f2g内存大小是256Mbyte,这里之所以令start_addr=4096,是因为咱们在文件中,将main.c文件的地址定义在了4096处。如下图:
如果AT(4096)改为AT(5120),那么这里start_addr就等于5120。还要注意这里4096byte也就是4KB,为什么把他定义在nand flash的4KB之后呢,因为硬件电路会自动将nand flash里的前4KB的内容搬移到stepping stone中去执行,如果定义在3KB之后,main.c函数直接就执行了,哪里还用复制。
115行:定义了读取的nand flash的内容存放的位置,这里0x30000000是sdram的物理地址。
116行:定义了一共读取nand flash多少字节,这里定义的是1M,当然小小的一个main.c代码也就是不到2KB。
117-123行:这才是该函数的关键。因为nf_readpage函数是以页为单位进行读取的,每次读取2KB数据,因此,数据指针的移动是页对齐的,即每次移动一页。
117行:因为每page大小为2KB即2048Byte,这里start_addr>>11位,相当于start_addr/2048,这样就得到了给定地址多对应的页。
119行:这里的i是绝对地址,因为每块包含64页,i/64得到给定地址处于哪一块;i%64是该页在块内的第几页。
NAND FLASH地址周期:
下面咱们讲讲地址周期。对于mini2440的开发板,它的nand flash的接口电路如下图:
从接口电路可以看到,地址线和数据线是复用的,接口线宽是8位,因此每次只能发送一个字节,又因为nand flash的地址是28位的,需要5个地址周期才能将地址发送完毕,如图1所示。发送完地址后,NAND FLASH内部的地址译码电路会自动将收到的地址进行组合,不需要我们关心,但是需要注意发送的顺序,按照先发送低地址,再发送高地址的顺序发送。
那么为什么是29根地址线,而不是28根或者30根呢?这里肯定有它的原因的。通常来说,nand flash的地址的表示形式为:
地址线
地址表示
[A28:A18]
块地址
[A17:A12]
页地址
[A11:A0]
页内偏移地址
对于k9f2g来说,它有2048个块,故用11根地址线寻址(2^11=2048);每个块有64页,故用6根地址线寻址(2^6=64);每页有2048字节的数据和64字节的信息,也就是说它有2112字节,故用12根地址线寻址。理解了这里的话就很容易明白下面的发送地址了。
不论是在nand flash页读取函数还是也写入函数内,都需要向地址寄存器送地址,如下图:
为什么是这样呢?由于页读取和写入函数均是以页为单位进行的,所以每次都要在页的第0个字节开始,也就是说页内偏移地址为0,所以对地址线的低12位设为0即可,如第93行,94行。
第95行,此时处于第3个地址周期因此需要发送blockpage(此时blockpage是页的绝对地址)的A12-A19位,因此,将blockpage与0xff相与即可。一定要注意页的绝对地址是A28-A12共同表示的,与A11-A0无关。
第96行,此时处于第3个地址周期因此需要发送blockpage(此时blockpage是页的绝对地址)的A20-A27位,因此,将blockpage右移8位,与0xff相与即可。
第97行,此时处于第3个地址周期因此需要发送blockpage(此时blockpage是页的绝对地址)的A28位,从图1可以看到,此时虽然是发送8位,但是只有第0位是有效的,其他位无效。因此,将blockpage右移16位,然后再与0x1相与,只保留第0位。
代码解释:
如果代码小于4096字节,那么开发板启动后它们被自动复制进“Steppingstone”中;本实例的目的就是把一部分代码存放在NAND Flash地址4096之后,当程序启动通过NAND Flash控制器将它们读出来、执行。就是实现这个目的的,即连接脚本把代码分为两部分,代码如下:
第2行表示head.o、init.o、nand.o这3个文件的运行地址为0,它们在生成的映像文件中的偏移地址也为0(从0开始存放)
第3行表示main.o的运行地址为0x30000000,它在生成的映像文件中的偏移地址为4096。
此时生成的bin文件大小为4.10KB。
如果将第3行的AT(4096)去掉,其他的文件程序不变的话,make生成的bin文件大小为1.7KB。这样子对比就可以看出来AT真的起作用了。
mini2440开发板的时钟
Fin=12MHz FCLK=400MHz HCLK=100MHz PCLK=50MHz
所以FCLK:HCLK:PCLK=1:4:8,CLKDIV_VAL=5
MDIV=127 PDIV=2 SDIV=1
mini 2440 时钟配置: /fengyaqi123/article/details/7795941
NandFlash—物理结构及地址传送
-----以MINI2440开发板上的K9F2G08U0B为例
K9F2G08U0A是三星公司生产的总容量为256M的NandFlash,常用于手持设备等消费电子产品。还是那句话,搞底层就得会看datasheet,我们就从它的datasheet看起。
这就是 K9F2G08U0B的内部结构,具体的各个部件的介绍,就不详细介绍了,想了解的话可以参考韦东山的《嵌入式linux应用开发完全手册》。
现在就看一下重点的,NandFlash的存储单元的组织结构,K9F2G08U0B的存储单元的组织结构如下:
结合上面的图,我们现在来分析一下:
NandFlash的存储单元是由Block组成的,Block又是由Page组成的,数据就是存储在Page上。
K9F2G08U0B的总容量是256M,由图可以看出,它一共有2048块,每一块有64页,每一页有(2K+64)Bytes。每一页又分为main区和spare区,2K就是指main区,64就是指spare区。
2K是指每一页上存放数据的有效空间,64是指每一页附加的空间,它不能用来存放我们写入的数据,它主要是用来存放ECC校验和坏块标记的信息数据。
还有一个比较重要的知识就是对NandFlash的访问,你不会访问它,那你肯定不会用它!
首先要明白NandFlash是以页(Page)为最小单位进行读写的,以块(Block)为最小单位进行擦除的,也就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本Page的最后一个 Byte为止(可以包括Spare Field)。
在弄清楚怎么读写NandFlash之前,还有一个准备工作要做,那就是我们要告诉NandFlash,我要读写哪个地方的数据,如果我们不告诉它地址,那它就不知道我们到读哪些数据。那如何计算我们要传入的地址呢?以一个例子来说明
以K9F2G08U0B为例,此nand flash,一共有2048个块,每个块内有64页,每个页是2K+64
Bytes,假设,我们要访问其中的第1500个块中的第25页中的1208字节处的地址,此时,我们就要先把具体的地址算出来:
物理地址=块大小×块号+页大小×页号+页内地址=1500×128K+25×2K+1208=0xBB8CCB8(196660408),接下来,我们就看看,怎么才能把这个实际的物理地址,转化为nand Flash所要求的格式。在解释地址组成之前,先要来看看其datasheet中关于地址周期的介绍:
由上图可以看出:K9F2G08U0B是用5个周期来实现,由于NAND flash以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址:
Column Address:Starting Address of the Register. 翻成中文为列地址,地址的低8位,即页内偏移地址
Page Address :页地址
Block Address :块地址
我们知道K9F2G08U0B的每一页有(2K+64)=2112Byte,2112byte 需要12bit来表示,对于2112byte系列的NAND,这2112byte被分成1st half Page Register和2nd half Page
Register,各自的访问由地址指针命令来选择,A[11:0]就是所谓的column address(列地址),在进行擦除操作时不需要它,因为以块为单位擦除。64个page需要6bit来表示,占用A[17:12],即该page在块内的相对地址,也就是确定位于哪一页。A11这一位地址被用来设置2048byte的1st half page还是2nd half page,0表示1st,1表示2nd。Block的地址是由A18以上的bit来表示,也就是确定位于哪一块。用A[0:28]这29位,就可以将
K9F2G08U0A这256M的数据存储空间全部访问到。
2的29次方是2GBit,256MByte=2GBit。
NAND Flash 的地址表示为: Block Address|Page Address in block|Column Address 地址传送顺序是Column Address,Page Address,Block Address。
由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。 以K9F2G08U0A 为例:
第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上。
第2 步就是将NAND_ADDR 右移8位,将NAND_ADDR[11:8]传到I/O[7:0]上;
第3 步将NAND_ADDR[19:12]放到I/O上;
第4步需要将NAND_ADDR[27:20]放到I/O上;
第5步需要将 NAND_ADDR[28]放到I/O上;
因此,整个地址传递过程需要5步才能完成,即5-step addressing。 如果NAND Flash 的容量是32MB(256Mbit)以下,那么,block adress最高位只到bit24,因此寻址只需要3步。
简单解释完了地址组成,那么就很容易分析上面例子中的地址了:
0xBB8CCB8 = 00001011 1011 1000 1100 1100 1011 1000,分别分配到5个地址周期就是:
B B 8 C C B 8
第一个周期:A[0:7] 也就是B8
第二个周期:A[8:11] 取四位 1100, 再添4位0,也就是0000 1100 即0C
第三个周期:A[12:19] 取八位 1000 1100 即8C
第四个周期:A[20:27] 取八位 1011 1011 即BB
第五个周期:A[28] 取一位 0,补齐八位 即 0000 0000 也就是00
再往回分析,NandFlash收到前两个周期的地址A[0:11] 也就是0CB8,即 1100 1011 1000,分析:A[10:0]= 100 1011 1000=1208, 表示第1208个Byte
收到后三个周期后,即 0000 0000 1011 1011 1000 1100,A[16:11]=011001=25,表示第25页
0000 0000 1011 1011 100 处理后为 0101 1101 1100 =1500,表示第1500块
编译生成bin文件,下载运行
本实验一共包含6个文件,分别为Makefile,head.S,init.c,main.c,nand.c,,最后会把文件源代码全部贴出来。
将6个文件复制到虚拟机的linux系统中,然后make编译生成文件,通过supervivi的v命令,结合DNW将它下载到板子中,然后板子从nand flash启动,可以看到4个led灯全亮。
代码源文件
Makefile文件内容:
objs := head.o init.o nand.o main.o
: $(objs)
arm-linux-ld - -o nand_elf $^
arm-linux-objcopy -O binary -S nand_elf $@
arm-linux-objdump -D -m arm nand_elf >
%.o:%.c
arm-linux-gcc -Wall -c -O2 -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -c -O2 -o $@ $<
clean:
rm -f nand_elf *.o
head.S文件内容:
@******************************************************************************
@ File:head.s
@ 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行
@******************************************************************************
.text
.global _start
_start:
@函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义
ldr sp, =4096 @设置堆栈
bl disable_watch_dog @关WATCH DOG
bl clock_init
bl memsetup @初始化SDRAM
bl nand_init @初始化NAND Flash
bl RdNF2SDRAM @将main.c程序复制到sdram指定地址处
ldr sp, =0x34000000 @设置栈
ldr lr, =halt_loop @设置返回地址
ldr pc, =main @b指令和bl指令只能前后跳转32M的范围,所以这里使用向
@pc赋值的方法进行跳转
halt_loop:
b halt_loop
init.c文件内容:
/* WOTCH DOG register */
#define WTCON (*(volatile unsigned long *)0x53000000)
#define CLKDIVN (*(volatile unsigned long *)0x4c000014)
#define MPLLCON (*(volatile unsigned long *)0x4c000004)
/* SDRAM regisers */
#define S3C2440_MPLL_400MHZ ((0x7f<<12)|(0x02<<4)|(0x01))
#define MEM_CTL_BASE 0x48000000
void disable_watch_dog();
void memsetup();
void clock_init(void);
/*上电后,WATCH DOG默认是开着的,要把它关掉 */
void disable_watch_dog()
{
WTCON = 0;
}
void clock_init(void)
{
// LOCKTIME = 0x00ffffff; // 使用默认值即可
CLKDIVN = 0x05; // FCLK:HCLK:PCLK=1:4:8, HDIVN=2,PDIVN=1
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0n" /* 读出控制寄存器 */
"orr r1, r1, #0xc0000000n" /* 设置为“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0n" /* 写入控制寄存器 */
);
MPLLCON = S3C2440_MPLL_400MHZ; /* 现在,FCLK=400MHz,HCLK=100MHz,PCLK=50MHz */
}
/* 设置控制SDRAM的13个寄存器 */
void memsetup()
{
int i = 0;
unsigned long *p = (unsigned long *)MEM_CTL_BASE;
/* SDRAM 13个寄存器的值 */
unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x00018005, //BANKCON6
0x00018005, //BANKCON7
0x008C07A3, //REFRESH
0x000000B1, //BANKSIZE
0x00000030, //MRSRB6
0x00000030, //MRSRB7
};
for(; i < 13; i++)
p[i] = mem_cfg_val[i];
}
main.c文件内容:
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))
void wait(unsigned long dly)
{
for(; dly > 0; dly--);
}
int main(void)
{
unsigned long i = 0;
GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out; // 将LED1-4对应的GPB5/6/7/8四个引脚设为输出
while(1){
wait(30000);
GPBDAT = (~(i<<5)); // 根据i的值,点亮LED1-4
if(++i == 16)
i = 0;
}
return 0;
}
nand.c文件内容:
//我所用的开发板是mini2440,nand flash型号是K9F2G08U0B,大小为256M
//函数功能:对该型号的nand flash的读写等功能的实现,另外还有一个将
// nand flash的程序复制到sdram运行的函数
// NAND Flash registers
#define NFCONF (*(volatile unsigned long *)0x4e000000) //nand falsh配置寄存器
#define NFCONT (*(volatile unsigned long *)0x4e000004) //nand flash控制寄存器
#define NFCMD (*(volatile unsigned long *)0x4e000008) //nand flash命令寄存器
#define NFADDR (*(volatile unsigned long *)0x4e00000C) //nand flash地址寄存器
#define NFDATA (*(volatile unsigned char *)0x4e000010) //nand flash数据寄存器
#define NFSTAT (*(volatile unsigned long *)0x4e000020) //nand flash状态寄存器
//注意上述的NFDATA为什么定义为char型而不是long型?可以查看板子上的nand flash接口电路,注意到数据线//是IO0-IO7
//只有8根数据线,因此每次只能发送一个字节的数据,只需要使用数据寄存器NFDATA的低8位即可。
#define NAND_SECTOR_SIZE_LP 2048 //K9F2G型号nand falsh的1page=2048KB
#define NAND_BLOCK_MASK_LP (NAND_SECTOR_SIZE_LP - 1)
//nand flash的时序参数
#define TACLS 1
#define TWRPH0 5
#define TWRPH1 0
//为了方便,将各条命令以宏定义的形式写出
#define readp1 0x00
#define readp2 0x30
#define readid 0x90
#define writep1 0x80
#define writep2 0x10
#define eraseb1 0x60
#define eraseb2 0xd0
#define randomwrite 0x85
#define randomreadp1 0x05
#define randomreadp2 0xe0
#define readstatus 0x70
#define reset 0xff //复位
//使用宏的形式来实现规模较小的函数
#define send_cmd(cmd) {NFCMD = (cmd); } //向nand flash发送命令
#define send_addr(addr) {NFADDR = (addr); } //向nand flssh发送地址
#define send_data(date) {NFDATA = (date); } //向nand flash发送数据
#define read_byte() (NFDATA) //从nand falsh读取一个字节
#define enable() {NFCONT &= ~(1<<1);} //nand flash控制器使能
#define disable() {NFCONT |= (1<<1); } //使nand flash无效
#define enable_rb() {NFSTAT |= (1<<2); } //开启rnb监视模式;
#define check_busy() {while(!(NFSTAT&(1<<2)));} //相当于等待rnb置1----这说明nand flash不忙了
#define waitrb() {while(!(NFSTAT&(1<<0)));}
/* S3C2440的NAND Flash处理函数 */
void nand_init(void); //nand flash初始化函数
void nf_readpage(unsigned int block,unsigned int page,unsigned char *dstaddr); //nand flash页读取函数
void nf_writepage(unsigned int block,unsigned int page,unsigned char *buffer); //nand flash页写入函数
void erase_block(unsigned int block); //nand flash块擦除函数
void read_id(unsigned char *buf); //读取nand flash ID函数
void ramwrite(unsigned int block,unsigned int page,unsigned int add,unsigned char dat);//nand flash随机写函数
unsigned char ramread(unsigned int block,unsigned int page,unsigned int add);//nand flash随机读函数
static void nand_reset(void);
/* s3c2440的nand flash操作函数 */
static void nand_reset(void)
{
enable();
enable_rb();
send_cmd(reset); // 复位命令
check_busy();
disable();
}
void nand_init(void)
{
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(3<<2)|(1<<1); /*需要加上|(3<<2)|(1<<1) */
/* 初始化ECC, 禁止片选 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
NFSTAT =0x0;
/* 复位NAND Flash */
nand_reset();
}
void nf_readpage(unsigned int block,unsigned int page,unsigned char *dstaddr)
{
unsigned int i;
unsigned int blockpage=(block<<6)+page;
//页的绝对地址=块号*64+页号,乘法使用了移位运算来实现,左移6位相当于乘以64
/*计算page绝对地址*/
nand_reset(); //这三行是,复位nand flash,然后打开nand flash(在复位结束后关闭了nand flash,因此需要重新
enable(); //打开),同时开启忙信号检测功能,以后就可以对nand flash进行操作,然后通过检测忙信号来获取
enable_rb(); //nand falsh内部的工作状态
send_cmd(readp1);
send_addr(0x00); //地址的低12位用于页内寻址,这里是对整页写入,因此,低12位设为0即可
send_addr(0x00);
send_addr((blockpage)&0xff); //发送页的绝对地址
send_addr((blockpage>>8)&0xff);
send_addr((blockpage>>16)&0x01);
send_cmd(readp2);
check_busy();
for(i=0;i<2048;i++)
{
dstaddr[i]=read_byte();
}
disable();
}
void RdNF2SDRAM(void) //将nand flash的代码复制到sdram中去
{
unsigned int i;
unsigned int start_addr =4096; //4096表示第4096个字节
unsigned char * dstaddr = (unsigned char *)0x30000000;/*留心指针类型转换*/
unsigned int size = 0x100000; //大小1MB
//因为NAND_SECTOR_SIZE等于2048个字节,所以page就等于start_addr右移了11位,因为
//2^11=2048。这里i表示页的绝对地址,即该页距离第0页的绝对地址
{
nf_readpage(i/64,i%64, dstaddr);
size -= 2048;
dstaddr += 2048;
}
}
void nf_writepage(unsigned int block,unsigned int page,unsigned char *buffer)
{
unsigned int i,blockpage=(block<<6)+page;
/*计算page绝对地址*/
unsigned char *buf=buffer;
nand_reset();
enable();
enable_rb();
send_cmd(writep1);
send_addr(0x00);
send_addr(0x00);
send_addr((blockpage)&0xff);
send_addr((blockpage>>8)&0xff);
send_addr((blockpage>>16)&0x01);
for(i=0;i<2048;i++)
{
send_data(*buf++);
}
send_cmd(writep2);
check_busy();
disable();
}
void erase_block(unsigned int block)
{
unsigned int blocknum=block<<6;
nand_reset();
enable();
send_cmd(eraseb1);
/*块地址A18-A28*/
send_addr(blocknum&0xff);
send_addr((blocknum>>8)&0xff);
send_addr((blocknum>>16)&0xff);
send_cmd(eraseb2);
check_busy();
disable();
}
void read_id(unsigned char *buf)
{
int i;
nand_reset();
enable();
enable_rb();
send_cmd(readid);
send_addr(0x0);
for(i=0;i<100;i++);
*buf =read_byte();
*(buf+1) =read_byte();
*(buf+2) =read_byte();
*(buf+3) =read_byte();
*(buf+4) =read_byte();
disable();
}
void ramwrite(unsigned int block,unsigned int page,unsigned int add,unsigned char dat)
{
unsigned int page_number = (block<<6)+page;
nand_reset();
enable();
enable_rb();
send_cmd(writep1);
send_addr(0x00);
send_addr(0x00);
send_addr((page_number)&0xff);
send_addr((page_number>>8)&0xff);
send_addr((page_number>>16)&0xff);
send_cmd(randomwrite);
send_addr((char)add&0xff);
send_addr((char)((add>>8)&0x0f));
send_data(dat);
send_cmd(writep2);
check_busy();
disable();
}
unsigned char ramread(unsigned int block,unsigned int page,unsigned int add)
{
unsigned char buf;
unsigned int page_number=(block<<6)+page;
nand_reset();
enable();
enable_rb();
send_cmd(readp1);
send_addr(0x00);
send_addr(0x00);
send_addr((page_number)&0xff);
send_addr((page_number>>8)&0xff);
send_addr((page_number>>16)&0xff);
}
send_cmd(readp2);
check_busy();
send_cmd(randomreadp1);
send_addr((char)(add&0xff));
send_addr((char)((add>>8)&0x0f));
send_cmd(randomreadp2);
check_busy();
buf=read_byte();
disable();
return buf;
文件内容:
SECTIONS {
firtst 0x00000000 : { head.o init.o nand.o }
second 0x30000000 : AT(4096) {main.o}
}
2024年2月22日发(作者:荣智伟)
目录:
说明.................................................................................................................................................................................................2
知识点梳理.....................................................................................................................................................................................2
页的绝对地址与相对地址.....................................................................................................................................................2
NAND FLASH地址周期:...................................................................................................................................................3
代码解释:...............................................................................................................................................................4
mini2440开发板的时钟.........................................................................................................................................................5
NandFlash—物理结构及地址传送................................................................................................................................................5
编译生成bin文件,下载运行......................................................................................................................................................9
代码源文件.....................................................................................................................................................................................9
Makefile文件内容:..............................................................................................................................................................9
head.S文件内容:...............................................................................................................................................................10
main.c文件内容:...............................................................................................................................................................12
nand.c文件内容:...............................................................................................................................................................12
文件内容:.............................................................................................................................................................18
/u/3104586975
说明
开发板:mini2440
Nand flash:K9F2G08U0B2,256M
本程序实现功能:我是参考韦东山的第8章的裸机程序,但是他的nand flash型号是64MB的K9F12080M,所以我依照他的程序加以修改,下载到nand flash,从nand flash实现将main.c程序复制到sdram中执行。
参考书:《嵌入式应用开发完全手册》
《ARM处理器裸机开发实战---机制而非策略》
知识点梳理
在nand.c中有一个函数void RdNF2SDRAM(),它的功能是将nand flah的内容复制到sdram,如下:
页的绝对地址与相对地址
nand flash的页读取函数原型是void nf_readpage(unsigned int block,unsigned int page,unsigned char *dstaddr),它的功能是从nand flash的第block块的第page页读取1页(对于K9F2G来说,1页等于2KB)的内容,存放到指针dstaddr所指的地址处。有的人可能就会说了,既然nf_readpage可以读取1页的数据到指定的地址处,而sdram的地址是0x30000000,那直接可以使用它将nand flash的代码复制到sdram去啊,答案当然是可以啊!但是有个前提,复制到sdram的内容必须在2KB以内,一旦超过了2KB,那就需要借用RdNF2SDRAM()函数了,相信这样解释就能明白这两个函数的区别于联系了。
再说说绝对地址与相对地址,首先知道k9f2g有2048个块,每个块有64个页,每页有2K字节。我们平常所说的第几块第几页就是相对地址,比如第2块第3页是相对地址。而绝对地址=块号*64+页号,例如前边的第2块第3页换成绝对地址就是131(131=2*64+3)。
下面解释一下RdNF2SDRAM()函数:
113行:从117行可以看出,这里的i指的是相对地址
114行:在nand flash内的起始地址,这里的4096表示的是字节,k9f2g内存大小是256Mbyte,这里之所以令start_addr=4096,是因为咱们在文件中,将main.c文件的地址定义在了4096处。如下图:
如果AT(4096)改为AT(5120),那么这里start_addr就等于5120。还要注意这里4096byte也就是4KB,为什么把他定义在nand flash的4KB之后呢,因为硬件电路会自动将nand flash里的前4KB的内容搬移到stepping stone中去执行,如果定义在3KB之后,main.c函数直接就执行了,哪里还用复制。
115行:定义了读取的nand flash的内容存放的位置,这里0x30000000是sdram的物理地址。
116行:定义了一共读取nand flash多少字节,这里定义的是1M,当然小小的一个main.c代码也就是不到2KB。
117-123行:这才是该函数的关键。因为nf_readpage函数是以页为单位进行读取的,每次读取2KB数据,因此,数据指针的移动是页对齐的,即每次移动一页。
117行:因为每page大小为2KB即2048Byte,这里start_addr>>11位,相当于start_addr/2048,这样就得到了给定地址多对应的页。
119行:这里的i是绝对地址,因为每块包含64页,i/64得到给定地址处于哪一块;i%64是该页在块内的第几页。
NAND FLASH地址周期:
下面咱们讲讲地址周期。对于mini2440的开发板,它的nand flash的接口电路如下图:
从接口电路可以看到,地址线和数据线是复用的,接口线宽是8位,因此每次只能发送一个字节,又因为nand flash的地址是28位的,需要5个地址周期才能将地址发送完毕,如图1所示。发送完地址后,NAND FLASH内部的地址译码电路会自动将收到的地址进行组合,不需要我们关心,但是需要注意发送的顺序,按照先发送低地址,再发送高地址的顺序发送。
那么为什么是29根地址线,而不是28根或者30根呢?这里肯定有它的原因的。通常来说,nand flash的地址的表示形式为:
地址线
地址表示
[A28:A18]
块地址
[A17:A12]
页地址
[A11:A0]
页内偏移地址
对于k9f2g来说,它有2048个块,故用11根地址线寻址(2^11=2048);每个块有64页,故用6根地址线寻址(2^6=64);每页有2048字节的数据和64字节的信息,也就是说它有2112字节,故用12根地址线寻址。理解了这里的话就很容易明白下面的发送地址了。
不论是在nand flash页读取函数还是也写入函数内,都需要向地址寄存器送地址,如下图:
为什么是这样呢?由于页读取和写入函数均是以页为单位进行的,所以每次都要在页的第0个字节开始,也就是说页内偏移地址为0,所以对地址线的低12位设为0即可,如第93行,94行。
第95行,此时处于第3个地址周期因此需要发送blockpage(此时blockpage是页的绝对地址)的A12-A19位,因此,将blockpage与0xff相与即可。一定要注意页的绝对地址是A28-A12共同表示的,与A11-A0无关。
第96行,此时处于第3个地址周期因此需要发送blockpage(此时blockpage是页的绝对地址)的A20-A27位,因此,将blockpage右移8位,与0xff相与即可。
第97行,此时处于第3个地址周期因此需要发送blockpage(此时blockpage是页的绝对地址)的A28位,从图1可以看到,此时虽然是发送8位,但是只有第0位是有效的,其他位无效。因此,将blockpage右移16位,然后再与0x1相与,只保留第0位。
代码解释:
如果代码小于4096字节,那么开发板启动后它们被自动复制进“Steppingstone”中;本实例的目的就是把一部分代码存放在NAND Flash地址4096之后,当程序启动通过NAND Flash控制器将它们读出来、执行。就是实现这个目的的,即连接脚本把代码分为两部分,代码如下:
第2行表示head.o、init.o、nand.o这3个文件的运行地址为0,它们在生成的映像文件中的偏移地址也为0(从0开始存放)
第3行表示main.o的运行地址为0x30000000,它在生成的映像文件中的偏移地址为4096。
此时生成的bin文件大小为4.10KB。
如果将第3行的AT(4096)去掉,其他的文件程序不变的话,make生成的bin文件大小为1.7KB。这样子对比就可以看出来AT真的起作用了。
mini2440开发板的时钟
Fin=12MHz FCLK=400MHz HCLK=100MHz PCLK=50MHz
所以FCLK:HCLK:PCLK=1:4:8,CLKDIV_VAL=5
MDIV=127 PDIV=2 SDIV=1
mini 2440 时钟配置: /fengyaqi123/article/details/7795941
NandFlash—物理结构及地址传送
-----以MINI2440开发板上的K9F2G08U0B为例
K9F2G08U0A是三星公司生产的总容量为256M的NandFlash,常用于手持设备等消费电子产品。还是那句话,搞底层就得会看datasheet,我们就从它的datasheet看起。
这就是 K9F2G08U0B的内部结构,具体的各个部件的介绍,就不详细介绍了,想了解的话可以参考韦东山的《嵌入式linux应用开发完全手册》。
现在就看一下重点的,NandFlash的存储单元的组织结构,K9F2G08U0B的存储单元的组织结构如下:
结合上面的图,我们现在来分析一下:
NandFlash的存储单元是由Block组成的,Block又是由Page组成的,数据就是存储在Page上。
K9F2G08U0B的总容量是256M,由图可以看出,它一共有2048块,每一块有64页,每一页有(2K+64)Bytes。每一页又分为main区和spare区,2K就是指main区,64就是指spare区。
2K是指每一页上存放数据的有效空间,64是指每一页附加的空间,它不能用来存放我们写入的数据,它主要是用来存放ECC校验和坏块标记的信息数据。
还有一个比较重要的知识就是对NandFlash的访问,你不会访问它,那你肯定不会用它!
首先要明白NandFlash是以页(Page)为最小单位进行读写的,以块(Block)为最小单位进行擦除的,也就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本Page的最后一个 Byte为止(可以包括Spare Field)。
在弄清楚怎么读写NandFlash之前,还有一个准备工作要做,那就是我们要告诉NandFlash,我要读写哪个地方的数据,如果我们不告诉它地址,那它就不知道我们到读哪些数据。那如何计算我们要传入的地址呢?以一个例子来说明
以K9F2G08U0B为例,此nand flash,一共有2048个块,每个块内有64页,每个页是2K+64
Bytes,假设,我们要访问其中的第1500个块中的第25页中的1208字节处的地址,此时,我们就要先把具体的地址算出来:
物理地址=块大小×块号+页大小×页号+页内地址=1500×128K+25×2K+1208=0xBB8CCB8(196660408),接下来,我们就看看,怎么才能把这个实际的物理地址,转化为nand Flash所要求的格式。在解释地址组成之前,先要来看看其datasheet中关于地址周期的介绍:
由上图可以看出:K9F2G08U0B是用5个周期来实现,由于NAND flash以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址:
Column Address:Starting Address of the Register. 翻成中文为列地址,地址的低8位,即页内偏移地址
Page Address :页地址
Block Address :块地址
我们知道K9F2G08U0B的每一页有(2K+64)=2112Byte,2112byte 需要12bit来表示,对于2112byte系列的NAND,这2112byte被分成1st half Page Register和2nd half Page
Register,各自的访问由地址指针命令来选择,A[11:0]就是所谓的column address(列地址),在进行擦除操作时不需要它,因为以块为单位擦除。64个page需要6bit来表示,占用A[17:12],即该page在块内的相对地址,也就是确定位于哪一页。A11这一位地址被用来设置2048byte的1st half page还是2nd half page,0表示1st,1表示2nd。Block的地址是由A18以上的bit来表示,也就是确定位于哪一块。用A[0:28]这29位,就可以将
K9F2G08U0A这256M的数据存储空间全部访问到。
2的29次方是2GBit,256MByte=2GBit。
NAND Flash 的地址表示为: Block Address|Page Address in block|Column Address 地址传送顺序是Column Address,Page Address,Block Address。
由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。 以K9F2G08U0A 为例:
第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上。
第2 步就是将NAND_ADDR 右移8位,将NAND_ADDR[11:8]传到I/O[7:0]上;
第3 步将NAND_ADDR[19:12]放到I/O上;
第4步需要将NAND_ADDR[27:20]放到I/O上;
第5步需要将 NAND_ADDR[28]放到I/O上;
因此,整个地址传递过程需要5步才能完成,即5-step addressing。 如果NAND Flash 的容量是32MB(256Mbit)以下,那么,block adress最高位只到bit24,因此寻址只需要3步。
简单解释完了地址组成,那么就很容易分析上面例子中的地址了:
0xBB8CCB8 = 00001011 1011 1000 1100 1100 1011 1000,分别分配到5个地址周期就是:
B B 8 C C B 8
第一个周期:A[0:7] 也就是B8
第二个周期:A[8:11] 取四位 1100, 再添4位0,也就是0000 1100 即0C
第三个周期:A[12:19] 取八位 1000 1100 即8C
第四个周期:A[20:27] 取八位 1011 1011 即BB
第五个周期:A[28] 取一位 0,补齐八位 即 0000 0000 也就是00
再往回分析,NandFlash收到前两个周期的地址A[0:11] 也就是0CB8,即 1100 1011 1000,分析:A[10:0]= 100 1011 1000=1208, 表示第1208个Byte
收到后三个周期后,即 0000 0000 1011 1011 1000 1100,A[16:11]=011001=25,表示第25页
0000 0000 1011 1011 100 处理后为 0101 1101 1100 =1500,表示第1500块
编译生成bin文件,下载运行
本实验一共包含6个文件,分别为Makefile,head.S,init.c,main.c,nand.c,,最后会把文件源代码全部贴出来。
将6个文件复制到虚拟机的linux系统中,然后make编译生成文件,通过supervivi的v命令,结合DNW将它下载到板子中,然后板子从nand flash启动,可以看到4个led灯全亮。
代码源文件
Makefile文件内容:
objs := head.o init.o nand.o main.o
: $(objs)
arm-linux-ld - -o nand_elf $^
arm-linux-objcopy -O binary -S nand_elf $@
arm-linux-objdump -D -m arm nand_elf >
%.o:%.c
arm-linux-gcc -Wall -c -O2 -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -c -O2 -o $@ $<
clean:
rm -f nand_elf *.o
head.S文件内容:
@******************************************************************************
@ File:head.s
@ 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行
@******************************************************************************
.text
.global _start
_start:
@函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义
ldr sp, =4096 @设置堆栈
bl disable_watch_dog @关WATCH DOG
bl clock_init
bl memsetup @初始化SDRAM
bl nand_init @初始化NAND Flash
bl RdNF2SDRAM @将main.c程序复制到sdram指定地址处
ldr sp, =0x34000000 @设置栈
ldr lr, =halt_loop @设置返回地址
ldr pc, =main @b指令和bl指令只能前后跳转32M的范围,所以这里使用向
@pc赋值的方法进行跳转
halt_loop:
b halt_loop
init.c文件内容:
/* WOTCH DOG register */
#define WTCON (*(volatile unsigned long *)0x53000000)
#define CLKDIVN (*(volatile unsigned long *)0x4c000014)
#define MPLLCON (*(volatile unsigned long *)0x4c000004)
/* SDRAM regisers */
#define S3C2440_MPLL_400MHZ ((0x7f<<12)|(0x02<<4)|(0x01))
#define MEM_CTL_BASE 0x48000000
void disable_watch_dog();
void memsetup();
void clock_init(void);
/*上电后,WATCH DOG默认是开着的,要把它关掉 */
void disable_watch_dog()
{
WTCON = 0;
}
void clock_init(void)
{
// LOCKTIME = 0x00ffffff; // 使用默认值即可
CLKDIVN = 0x05; // FCLK:HCLK:PCLK=1:4:8, HDIVN=2,PDIVN=1
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0n" /* 读出控制寄存器 */
"orr r1, r1, #0xc0000000n" /* 设置为“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0n" /* 写入控制寄存器 */
);
MPLLCON = S3C2440_MPLL_400MHZ; /* 现在,FCLK=400MHz,HCLK=100MHz,PCLK=50MHz */
}
/* 设置控制SDRAM的13个寄存器 */
void memsetup()
{
int i = 0;
unsigned long *p = (unsigned long *)MEM_CTL_BASE;
/* SDRAM 13个寄存器的值 */
unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x00018005, //BANKCON6
0x00018005, //BANKCON7
0x008C07A3, //REFRESH
0x000000B1, //BANKSIZE
0x00000030, //MRSRB6
0x00000030, //MRSRB7
};
for(; i < 13; i++)
p[i] = mem_cfg_val[i];
}
main.c文件内容:
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))
void wait(unsigned long dly)
{
for(; dly > 0; dly--);
}
int main(void)
{
unsigned long i = 0;
GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out; // 将LED1-4对应的GPB5/6/7/8四个引脚设为输出
while(1){
wait(30000);
GPBDAT = (~(i<<5)); // 根据i的值,点亮LED1-4
if(++i == 16)
i = 0;
}
return 0;
}
nand.c文件内容:
//我所用的开发板是mini2440,nand flash型号是K9F2G08U0B,大小为256M
//函数功能:对该型号的nand flash的读写等功能的实现,另外还有一个将
// nand flash的程序复制到sdram运行的函数
// NAND Flash registers
#define NFCONF (*(volatile unsigned long *)0x4e000000) //nand falsh配置寄存器
#define NFCONT (*(volatile unsigned long *)0x4e000004) //nand flash控制寄存器
#define NFCMD (*(volatile unsigned long *)0x4e000008) //nand flash命令寄存器
#define NFADDR (*(volatile unsigned long *)0x4e00000C) //nand flash地址寄存器
#define NFDATA (*(volatile unsigned char *)0x4e000010) //nand flash数据寄存器
#define NFSTAT (*(volatile unsigned long *)0x4e000020) //nand flash状态寄存器
//注意上述的NFDATA为什么定义为char型而不是long型?可以查看板子上的nand flash接口电路,注意到数据线//是IO0-IO7
//只有8根数据线,因此每次只能发送一个字节的数据,只需要使用数据寄存器NFDATA的低8位即可。
#define NAND_SECTOR_SIZE_LP 2048 //K9F2G型号nand falsh的1page=2048KB
#define NAND_BLOCK_MASK_LP (NAND_SECTOR_SIZE_LP - 1)
//nand flash的时序参数
#define TACLS 1
#define TWRPH0 5
#define TWRPH1 0
//为了方便,将各条命令以宏定义的形式写出
#define readp1 0x00
#define readp2 0x30
#define readid 0x90
#define writep1 0x80
#define writep2 0x10
#define eraseb1 0x60
#define eraseb2 0xd0
#define randomwrite 0x85
#define randomreadp1 0x05
#define randomreadp2 0xe0
#define readstatus 0x70
#define reset 0xff //复位
//使用宏的形式来实现规模较小的函数
#define send_cmd(cmd) {NFCMD = (cmd); } //向nand flash发送命令
#define send_addr(addr) {NFADDR = (addr); } //向nand flssh发送地址
#define send_data(date) {NFDATA = (date); } //向nand flash发送数据
#define read_byte() (NFDATA) //从nand falsh读取一个字节
#define enable() {NFCONT &= ~(1<<1);} //nand flash控制器使能
#define disable() {NFCONT |= (1<<1); } //使nand flash无效
#define enable_rb() {NFSTAT |= (1<<2); } //开启rnb监视模式;
#define check_busy() {while(!(NFSTAT&(1<<2)));} //相当于等待rnb置1----这说明nand flash不忙了
#define waitrb() {while(!(NFSTAT&(1<<0)));}
/* S3C2440的NAND Flash处理函数 */
void nand_init(void); //nand flash初始化函数
void nf_readpage(unsigned int block,unsigned int page,unsigned char *dstaddr); //nand flash页读取函数
void nf_writepage(unsigned int block,unsigned int page,unsigned char *buffer); //nand flash页写入函数
void erase_block(unsigned int block); //nand flash块擦除函数
void read_id(unsigned char *buf); //读取nand flash ID函数
void ramwrite(unsigned int block,unsigned int page,unsigned int add,unsigned char dat);//nand flash随机写函数
unsigned char ramread(unsigned int block,unsigned int page,unsigned int add);//nand flash随机读函数
static void nand_reset(void);
/* s3c2440的nand flash操作函数 */
static void nand_reset(void)
{
enable();
enable_rb();
send_cmd(reset); // 复位命令
check_busy();
disable();
}
void nand_init(void)
{
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(3<<2)|(1<<1); /*需要加上|(3<<2)|(1<<1) */
/* 初始化ECC, 禁止片选 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
NFSTAT =0x0;
/* 复位NAND Flash */
nand_reset();
}
void nf_readpage(unsigned int block,unsigned int page,unsigned char *dstaddr)
{
unsigned int i;
unsigned int blockpage=(block<<6)+page;
//页的绝对地址=块号*64+页号,乘法使用了移位运算来实现,左移6位相当于乘以64
/*计算page绝对地址*/
nand_reset(); //这三行是,复位nand flash,然后打开nand flash(在复位结束后关闭了nand flash,因此需要重新
enable(); //打开),同时开启忙信号检测功能,以后就可以对nand flash进行操作,然后通过检测忙信号来获取
enable_rb(); //nand falsh内部的工作状态
send_cmd(readp1);
send_addr(0x00); //地址的低12位用于页内寻址,这里是对整页写入,因此,低12位设为0即可
send_addr(0x00);
send_addr((blockpage)&0xff); //发送页的绝对地址
send_addr((blockpage>>8)&0xff);
send_addr((blockpage>>16)&0x01);
send_cmd(readp2);
check_busy();
for(i=0;i<2048;i++)
{
dstaddr[i]=read_byte();
}
disable();
}
void RdNF2SDRAM(void) //将nand flash的代码复制到sdram中去
{
unsigned int i;
unsigned int start_addr =4096; //4096表示第4096个字节
unsigned char * dstaddr = (unsigned char *)0x30000000;/*留心指针类型转换*/
unsigned int size = 0x100000; //大小1MB
//因为NAND_SECTOR_SIZE等于2048个字节,所以page就等于start_addr右移了11位,因为
//2^11=2048。这里i表示页的绝对地址,即该页距离第0页的绝对地址
{
nf_readpage(i/64,i%64, dstaddr);
size -= 2048;
dstaddr += 2048;
}
}
void nf_writepage(unsigned int block,unsigned int page,unsigned char *buffer)
{
unsigned int i,blockpage=(block<<6)+page;
/*计算page绝对地址*/
unsigned char *buf=buffer;
nand_reset();
enable();
enable_rb();
send_cmd(writep1);
send_addr(0x00);
send_addr(0x00);
send_addr((blockpage)&0xff);
send_addr((blockpage>>8)&0xff);
send_addr((blockpage>>16)&0x01);
for(i=0;i<2048;i++)
{
send_data(*buf++);
}
send_cmd(writep2);
check_busy();
disable();
}
void erase_block(unsigned int block)
{
unsigned int blocknum=block<<6;
nand_reset();
enable();
send_cmd(eraseb1);
/*块地址A18-A28*/
send_addr(blocknum&0xff);
send_addr((blocknum>>8)&0xff);
send_addr((blocknum>>16)&0xff);
send_cmd(eraseb2);
check_busy();
disable();
}
void read_id(unsigned char *buf)
{
int i;
nand_reset();
enable();
enable_rb();
send_cmd(readid);
send_addr(0x0);
for(i=0;i<100;i++);
*buf =read_byte();
*(buf+1) =read_byte();
*(buf+2) =read_byte();
*(buf+3) =read_byte();
*(buf+4) =read_byte();
disable();
}
void ramwrite(unsigned int block,unsigned int page,unsigned int add,unsigned char dat)
{
unsigned int page_number = (block<<6)+page;
nand_reset();
enable();
enable_rb();
send_cmd(writep1);
send_addr(0x00);
send_addr(0x00);
send_addr((page_number)&0xff);
send_addr((page_number>>8)&0xff);
send_addr((page_number>>16)&0xff);
send_cmd(randomwrite);
send_addr((char)add&0xff);
send_addr((char)((add>>8)&0x0f));
send_data(dat);
send_cmd(writep2);
check_busy();
disable();
}
unsigned char ramread(unsigned int block,unsigned int page,unsigned int add)
{
unsigned char buf;
unsigned int page_number=(block<<6)+page;
nand_reset();
enable();
enable_rb();
send_cmd(readp1);
send_addr(0x00);
send_addr(0x00);
send_addr((page_number)&0xff);
send_addr((page_number>>8)&0xff);
send_addr((page_number>>16)&0xff);
}
send_cmd(readp2);
check_busy();
send_cmd(randomreadp1);
send_addr((char)(add&0xff));
send_addr((char)((add>>8)&0x0f));
send_cmd(randomreadp2);
check_busy();
buf=read_byte();
disable();
return buf;
文件内容:
SECTIONS {
firtst 0x00000000 : { head.o init.o nand.o }
second 0x30000000 : AT(4096) {main.o}
}