最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

根据韦东山修改的mini2440nand flash裸机程序

IT圈 admin 45浏览 0评论

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}

}

发布评论

评论列表 (0)

  1. 暂无评论