新闻  |   论坛  |   博客  |   在线研讨会
NAND Flash--嵌入式NAND Flash读写技术
mayer | 2009-06-02 18:34:04    阅读:2027   发布文章

NAND Flash--嵌入式NAND Flash读写技术

 

NAND Flash控制器

?S3C2410板的Nand Flash支持由两部分组成:Nand Flash控制器(集成在S3C2410 CPU)和Nand Flash存储芯片(K9F1208U0B)两部分组成。当要访问Nand Flash中的数据时,必须通过Nand Flash控制器发送命令才能完成。所以Nand Flash相当于S3C2410的一个外设,而不位于它的内存地址区.

?

???为了支持NAND Flash的启动装载,S3C2410A配置了一个叫Steppingstone的内部SRAM缓冲器。当系统启动时,NAND Flash存储器的前4KB将被自动加载到Steppingstone中,然后系统自动执行这些载入的启动代码。

???一般情况下,这4KB的启动代码需要将NAND Flash中的内容复制到SDRAM中。使用S3C2410A内部硬件ECC功能可以对NAND Flash的数据进行有效性的检查。复制完成后,将在SDRAM中执行主程序。

NAND Flash控制其具有以下特性:

?

????* NAND Flash模式:支持读/擦除/编程NAND Flash存储器。

????* 自动启动模式:复位后,启动代码被传送到Steppingstone中。传送完毕后,启动代码在Steppingstone中执行。

????* 具备硬件ECC(校验码:Error Correction Code)生成模块(硬件生成校验码,通过软件校验)

????* NAND Flash启动以后,4KB的内部SRAM缓冲器Steppingstone可以作为其他用途使用。

????* NAND Flash控制器不能通过DMA访问,可以使用LDM/STM指令来代替DMA操作。

?

自启动模式的执行步骤如下:

?

(1)完成复位

?

(2)如果自动启动模式使能,NAND Flash存储器的前4KB自动复制到Steppingstone内部缓冲器;

?

(3)Steppingstone映射到nGCS0;

?

(4)CPU在Steppingstone的4KB内部缓冲器中开始执行启动代码。

?

注意:在自动启动模式下,不进行ECC检测。因此,应确保NAND Flash的前4KB不能有位错误(一般NAND Flash厂家都能确保)。

?

?

?

NAND Flash模式需要进行以下配置:

?

(1)通过NFCONF寄存器设置NAND Flash配置;

?

(2)将NAND Flash命令写入NFCONF寄存器;

?

(3)将NAND Flash地址写入NFADDR寄存器;

?

(4)通过NFSTAT寄存器检查NAND Flash状态,并读/写数据。在读操作之前或者编程操作之后应该检查R/nB信号。

?

引脚配置

D[7:0]??数据/命令/地址的输入/输出口(与数据总线共享)

CLE??????命令锁存使能(输出)

ALE??????地址锁存使能(输出)

nFCE?????NAND Flash片选使能(输出)

nFRE?????NAND Flash读使能(输出)

nFWE?????NAND Flash写使能(输出)

R/nB?????NAND Flash就绪/忙(输入)

?

系统启动和NAND Flash所需的配置如下:

(1)OM[1:0]=00b:使能NAND Flash控制器为自动启动模式;

(2)NAND Flash存储器的页面大小应该为512字节;

(3)NCON:NAND Flash存储器寻址步数选择。0为3步;1为4步寻址。

?

相关寄存器

NAND Flash配置寄存器

NFCONF????地址0x4E000000

?

NAND Flash命令设置寄存器

NFCMD??????地址0x4E000004

?

NAND Flash地址设置寄存器

NFADDR?????地址0x4E000008

?

NAND Flash数据寄存器

NFDATA?????地址0x4E00000C

?

NAND Flash操作状态寄存器

NFSTAT?????地址0x4E000010

?

NAND Flash ECC寄存器

NFECC???????地址0x4E000014

?

?

?

下面针对三星的K9F1208U0M为例说明nand flash的读写。

?

NAND Flash物理组成

正如硬盘的盘片被分为磁道,每个磁道又分为若干扇区,一块nand flash也分为若干block,每个block分为如干page。一般而言,block、page之间的关系随着芯片的不同而不同,典型的分配是这样的:

1block = 32page

1page = 512bytes(datafield) + 16bytes(oob)

?

?

需要注意的是,对于flash的读写都是以一个page开始的,但是在读写之前必须进行flash的擦写,而擦写则是以一个block为单位的。按照这种组织方式形成三类地址

Column Address:列地址,地址的低8位

Page Address:页地址

Block Address:块地址

8个I/O引脚充当地址、数据、命令的复用端口,所以每次传地址只能传8位,而nand falsh的地址位位26位,因此读写一次nand flash需要传送4次(A[7:0] A[16:9] A[24:17]

A[25]

?

一页有528B,在每一页中,最后16个字节(OOB)用于nand flash执行完命令后设置状态用的,剩余512B又分为前半部(1st half Page Register)和后半部(2nd half Page Register)。可以通过nand flash命令对1st half和2nd half

以及OOB进行定位通过nand flash内置的指针指向各自的首地址

?

存储操作特点:

1.擦除操作的最小单位是块

2.Nand Flash芯片每一位只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前一定要将相应块擦除(擦除就是将相应块的位全部变为1

3 OOB部分的第六字节(即517字节)标志是否坏块,如果不是坏块该值为FF,否则为坏块

4 除OOB第六字节外,通常至少把OOB前3字节存放Nand Flash硬件ECC码

?

NAND Flash寻址方式

512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st half Page Register和2nd half Page Register,各自的访问由地址指针命令来选择,A[7:0]就是所谓的column address(列地址),在进行擦除操作时不需要列地址,为什么?因为以块为单位擦除。32个page需要5bit来表示,占用A[13:9],即该page在块内的相对地址。A8这一位地址被用来设置512byte的1st half page还是2nd half page,0表示1st,1表示2nd。Block的地址是由A14以上的bit来表示。

例如64MB(512Mb)的NAND flash(实际中由于存在spare area,故都大于这个值),共4096block,因此,需要12个bit来表示,即A[25:14],如果是128MB(1Gbit) 的528byte/page的NAND Flash,则block address用A[26:14]表示。由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。以NAND_ADDR 为例:

第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上,而halfpage pointer即A8 是由操作指令决定的,即指令决定在哪个halfpage 上进行读写,而真正的A8 的值是不需程序员关心的。

第2 步就是将NAND_ADDR 右移9位,将NAND_ADDR[16:9]传到I/O[7:0]上;

第3 步将NAND_ADDR[24:17]放到I/O上;

第4步需要将NAND_ADDR[25]放到I/O上;

因此,整个地址传递过程需要4 步才能完成,即4-step addressing。 如果NAND Flash 的容量是32MB(256Mbit)以下,那么,block adress最高位只到bit24,因此寻址只需要3步。

?

?

Nand flash主要的内设命令

Nand flash命令执行是通过将命令字送到Nand flash控制寄存器的命令寄存器中来执行的,其命令是分周期执行的,每条命令都有一个或多个执行周期,每个执行周期都有相应的代码表示将要执行的动作。

?

功能

第一时钟周期

第二时钟周期

读取数据寄存器

Read1

00h/01h

?

读取数据寄存器下半区(OOB)

Read2

50h

?

读取芯片ID

90h

?

RESET

FFh

?

写页面(page program)

(首先写入00h(A区)/01h(B区)/05h(C区)表示写入区;再写入80h开始编程模式(写入模式),接下来写入地址和数据,最后写入10h表示编程结束。

?

80h

10h

块擦除(block erase)

60h

D0h

读取状态(read status)

70h

?

?

?

?

?

Nand Flash地址的计算
Column Address: 列地址。Column Address其实就是指定Page上的某个Byte,指定这个Byte其实也就是指定此页的读写起始地址。

Paage Address:页地址。由于页地址总是以512Bytes对齐的,所以它的低9位总是0。确定读写操作是在Flash上的哪个页进行的。

当我们得到一个Nand Flash地址srcaddr时候,我们可以这样分解出Column Address和Page Address

columnaddr=srcaddr%512??//column address

pageaddr=srcaddr>>9?????//page address

?

也可以这么认为,一个Nand Flash地址的A0~A7是它的column_addr,A9~A25是它的Page Address。(注意地址位A8并没有出现,也就是A8被忽略,在下面你将了解到这是什么原因)

以read1命令为例:

Read1 命令的操作分为4个Cycle,发送完读命令00h或01h(00h与01h的区别请见下文描述)之后将分4个Cycle发送参数,1st.Cycle是发送Column Address。2nd.Cycle ,3rd.Cycle和4th.Cycle则是指定Page Address(每次向地址寄存器发送的数据只能是8位,所以17位的Page Address必须分成3次进行发送

Read1的命令里面出现了两个命令选项,分别是00h和01h。这里出现了两个读命是否令你意识到什么呢?是的,00h是用于读写1st half的命令,而01h是用于读取2nd half的命令。现在我可以结合上图给你说明为什么K9F1208U0B的DataField被分为2个half了。

如上文所提及的,Read1的1st.Cycle是发送Column Address,假设我现在指定的Column Address是0,那么读操作将从此页的第0号Byte开始一直读取到此页的最后一个Byte(包括Spare Field),如果我指定的Column Address是127,情况也与前面一样,但不知道你发现没有,用于传递Column Address的数据线有8条(I/O0~I/O7,对应A0~A7,这也是A8为什么不出现在我们传递的地址位中),也就是说我们能够指定的 Column Address范围为0~255,但不要忘了,1个Page的DataField是由512个Byte组成的,假设现在我要指定读命令从第256个字节处开始读取此页,那将会发生什么情景?我必须把Column Address设置为256,但Column Address最大只能是255,这就造成数据溢出。。。正是因为这个原因我们才把Data Field分为两个半区,当要读取的起始地址(Column Address)在0~255内时我们用00h命令,当读取的起始地址是在256~511时,则使用01h命令.假设现在我要指定从第256个byte开始读取此页,那么我将这样发送命令串

column_addr=256;

NF_CMD=0x01;?????????????????????????????????????????从2nd half开始读取

NF_ADDR=column_addr&0xff;???????????????????????1st Cycle

NF_ADDR=page_address&0xff;??????????????????????2nd.Cycle

NF_ADDR=(page_address>>8)&0xff;?????????????3rd.Cycle

NF_ADDR=(page_address>>16)&0xff;???????????4th.Cycle

其中NF_CMD和NF_ADDR分别是NandFlash的命令寄存器和地址寄存器的地址解引用,我一般这样定义它们,

#define rNFCMD????????(*(volatile unsigned char *)0x4e000004)????????//NADD Flash command

#define rNFADDR????????(*(volatile unsigned char *)0x4e000008)????????//NAND Flash address

事实上,当NF_CMD=0x01时,地址寄存器中的第8位(A8)将被设置为1(如上文分析,A8位不在我们传递的地址中,这个位其实就是硬件电路根据 01h或是00h这两个命令来置高位或是置低位),这样我们传递column_addr的值256随然由于数据溢出变为1,但A8位已经由于NF_CMD =0x01的关系被置为1了,所以我们传到地址寄存器里的值变成了

?

A0??A1??A2??A3??A4??A5??A6??A7??A8

0?????0????0????0????0?????0????0????0????1???& 0xff = 0000 0000

?

这8个位所表示的正好是256,这样读操作将从此页的第256号byte(2nd half的第0号byte)开始读取数据。

现在举一个例子,假设我要从Nand Flash中的第5000字节处开始读取1024个字节到内存的0x30000000处,我们这样调用read函数

nf_read(5000, 0x30000000,1024);

我们来分析5000这个src_addr.

根据???

column_addr=src_addr%512;???????

page_address=(src_addr>>9);?????

我们可得出column_addr=5000%512=392

page_address=(5000>>9)=9

于是我们可以知道5000这个地址是在第9页的第392个字节处,于是我们的nf_read函数将这样发送命令和参数

column_addr=5000%512;

>page_address=(5000>>9);

NF_CMD=0x01;???????????????????????????????????????????从2nd half开始读取

NF_ADDR= column_addr &0xff;?????????????????????1st Cycle A[7:0]

NF_ADDR=page_address&0xff;??????????????????????2nd.Cycle A[16:9]

NF_ADDR=(page_address>>8)&0xff;?????????????3rd.Cycle???A[24:17]

NF_ADDR=(page_address>>16)&0xff;???????????4th.Cycle???A[25]

向NandFlash的命令寄存器和地址寄存器发送完以上命令和参数之后,我们就可以从rNFDATA寄存器(NandFlash数据寄存器)读取数据了.

我用下面的代码进行数据的读取.

for(i=column_addr;i<512;i++)

{

????????*buf++=NF_RDDATA();

}

每当读取完一个Page之后,数据指针会落在下一个Page的0号Column(0号Byte).

?

例如实现一个从某字节处开始读取size大小的数据

static int NF_read(unsigned int src_addr,unsigned char *desc_addr,int size)

{

????int i;

????unsigned int column_addr = src_addr % 512;

????unsigned int page_address =(src_addr >> 9);

????unsigned char * buf = desc_addr;

???

????while((unsigned int)buf < (unsigned int)(desc_addr)+size)

????{

????????NF_nFCE_L();????//enable chip

???????

????????if(column_addr > 255)

????????????NF_CMD(0x01);

????????else

????????????NF_CMD(0x00);

?

???????

????????NF_ADDR(cloumn_addr & 0xff);????//column address A[7:0];

????????NF_ADDR(page_address & 0xff);???//page address A[16:9]

????????NF_ADDR((page_address >> 8) & 0xff);??//A[24:17]

????????NF_ADDR((page_address >>16) & 0xff);??//A[25];

?

????????for(i=0;i<10;i++);

????????NF_WAITRB();

?

????????for(i=column_addr;i<512;i++)

????????{

????????????*buf++=NF_RDDATA();

????????}

????????NF_nFCE_H();

????????column_addr = 0;

????????page_address ++;

????}

????return ;

}

?

?

打开s3c2410 的datasheet page 230:我们定义如下寄存器

?

#define rNFCONF?????(*(volatile unsigned *)0x4e000000)???//nand flash configuration

#define rNFCMD??????(*(volatile char *)0x4e000004??????//nand flash command

#define rNFADDR?(*(volatile char *)0x4e000008?????????//nand flash address

#define rNFDATA?????(*(volatile char *)0x4e00000c?????????//nand flash data

#define rNFSTAT?????(*(volatile unsigned *)0x4e000010??//nand flash opreation status

#define rNFECC??????(*(volatile int *)0x4e000014?????????//nand flash ecc

#define rNFECC0?????(*(volatile char *)0x4e000014

#define rNFECC1?????(*(volatile char *)0x4e000015

#define rNFECC2?????(*volatile char *)0x4e000016

?

#define NF_CMD(cmd)?{rNFCMD=cmd;}

#define NF_ADDR(addr)???{rNFADDR=addr;}

#define NF_nFCEL_L()????{rNFCONF &= ~(1<<11);}

#define NF_nFCLE_H()????{rNFCONF |= (1<<11);}

#define NF_RSTECC()??????{rNFCONF |= (1<<12);}??????????????????????????//Initialize ECC

#define NF_RDDATA()?(rNFDATA)

#define NF_WRDATA(data)?{rNFDATA=data;}

?

#define NF_WATRB()??{while(!(rNFSTAT&(1<<0)));}

?

//读一页数据的程序。????????????

static int NF_RreadPage(int block,int page,char *buffer)

{

????unsigned int blockpage;

????char *pbuf=buffer;

????char *oob[16];

????unsigned char ecc[3];

?

????page=page&0x1f;

????blockpage=(block << 5)+page;

?

????NF_RSTECC();????//Initialize ECC;

???

????NF_nFCE_L();

????NF_CMD(0x00);???//read command;

???

????NF_ADDR(0);?????//A[7:0]??column=0 从第0字节开始读一直读完512B

????NF_ADDR(blockpage&0xff);???//A[16:9];

????NF_ADDR((blockpage>>8)&0xff);???//A[24:17]

????NF_ADDR((blockpage>>16)&0xff); //A[25];

???

????for(i=0;i<10;i++);??//wait tWB(100ns)

???

????NF_WAITRB();????????//wait tR(max 12us)

???

????for(i=0;i<512;i++)

????{

????????*pbuf++=NF_RDDATA();

????}

???

????ecc[0]=rNFECC0;

????ecc[1]=rNFECC1;

????ecc[2]=rNFECC2;

????for(i=0;i<16;i++)

????{

????????oob[i]=NF_RDDATA();?//read oob;

????}

????NF_nFCE_H();

???

????if(ecc[0]==oob[0] && ecc[1] == oob[1] && ecc[2] == oob[2])??//Ecc校验;

????{

????????print("ECC OK:%x,%x,%x\n",oob[0],oob[1],oob[2]);

????????return 1;

????}else{

????????printf("ECC ERROR: read:%x,%x,%x, ECC reg:%x,%x,%x\n",oob[0],oob[1],oob[2],ecc[0],ecc[1],ecc[2]);

????????return 0;

????}

}

?

static int NF_WritePage(unsigned int block,unsigned int page,char *buffer)

{

????int i;

????unsigned int blockpage=(block<<5)+page;

????char *pbuf=buffer;

????oobbuf[16]={0xff};

?

????NF_RSRECC();

???

????NF_nFCE_L();

????NF_CMD(0x00);

????NF_ADDR(blockpage&0xff);

????NF_ADDR((blockpage>>8)&0xff);

????NF_ADDR((blockpage>>16)&0xff);

?

????for(i=0;i<512;i++)

????{

????????NF_WRDATA(*pbuf++);

????}

???

????oobbuf[0]=rNFECC0;

????oobbuf[1]=rNFECC1;

????oobbuf[2]=rNFECC2;

????oobbuf[5]=0xff;

?

????for(i=0;i<16;i++)

????{

????????NF_WRDATA(oobbuf[i]);

????}

?

????NF_CMD(0x10);???????//Write 2nd command;

?

????for(i=0;i<10;i++);??//tWB=100ns;

?

????NF_WAITRB();

?

????NF_CMD(0x70);????//read status command;

?

????for(i=0;i<3;i++);

?

????if(NF_RDDATA()&0x1)?????//write error

????{

????????NF_nFCE_H();

????????NF_MarkBadBlock(block);

????????return 0;

????}else{

????????NF_nFCE_H();

????????return 1;

????}

?

}

?

?

?

static int NF_EraseBlock(U32 block)

{

????U32 blockPage=(block<<5);

????int i;

?

#if BAD_CHECK//坏块校验

????if(NF_IsBadBlock(block))

????return 0;

#endif

?

????NF_nFCE_L();//NF的CE(片选)拉低

???

????NF_CMD(0x60);???// Erase one block 1st command

?

????NF_ADDR(blockPage&0xff);????????// 块擦除只针对页

????NF_ADDR((blockPage>>8)&0xff);??

????NF_ADDR((blockPage>>16)&0xff);

?

????NF_CMD(0xd0);???// Erase one blcok 2nd command

???

????for(i=0;i<10;i++); //wait tWB(100ns)//??????

?

????NF_WAITRB();????// Wait tBERS max 3ms.

????NF_CMD(0x70);???// Read status command

?

????if (NF_RDDATA()&0x1) // Erase error

????{??

????????NF_nFCE_H();

????Uart_Printf("[ERASE_ERROR:block#=%d]\n",block);

????NF_MarkBadBlock(block);

????return 0;

????}

????else

????{

????????NF_nFCE_H();////NF的CE(片选)拉高

????????return 1;

????}

}

?

?

NAND设备存在坏块,为和上层文件系统接口,NAND设备的驱动程序必须给文件系统提供一个可靠的存储空间,这就需要ECC(Error Corection Code)校验,坏块标注、地址映射等一系列的技术手段来达到可靠存储目的。

????SSFDC软件规范中,详细定义了如何利用NAND设备每个页中的冗余信息来实现上述功能。这个软件规范中,很重要的一个概念就是块的逻辑地址,它将在物理上可能不连续、不可靠的空间分配编号,为他们在逻辑空间上给系统文件提供一个连续可靠的存储空间。

表3给出了SSFDC规范中逻辑地址的标注方法。在系统初始化的时候,驱动程序先将所有的块扫描一遍,读出他们所对应的逻辑地址,并把逻辑地址和虚拟地址的映射表建好。系统运行时,驱动程序通过查询映射表,找到需要访问的逻辑地址所对应的物理地址然后进行数据读写。?????

?

????????????????表3 冗余字节定义

字节序号

内容

字节序号

内容

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客