跳至主要内容

【转】NAND闪存深入研究

对于许多消费类音视频产品而言,NAND闪存是一种比硬盘驱动器更好的存储方案,这在不超过4GB的低容量应用中表现得犹为明显。随着人们持续追求功耗更低、重量更轻和性能更佳的产品,NAND正被证明极具吸引力。

NAND闪存阵列分为一系列128kB的区块(block),这些区块是NAND器件中最小的可擦除实体。擦除一个区块就是把所有的位(bit)设置为"1"(而所有字节(byte)设置为FFh)。有必要通过编程,将已擦除的位从"1"变为"0"。最小的编程实体是字节(byte)。一些

NOR闪存能同时执行读写操作(见下图1)。虽然NAND不能同时执行读写操作,它可以采用称为"映射(shadowing)"的方法,在系统级实现这一点。这种方法在个人电脑上已经沿用多年,即将BIOS从速率较低的ROM加载到速率较高的RAM上。

NAND的效率较高,是因为NAND串中没有金属触点。NAND闪存单元的大小比

NOR要小(4F2:10F2)的原因,是NOR的每一个单元都需要独立的金属触点。NAND与硬盘驱动器类似,基于扇区(页),适合于存储连续的数据,如图片、音频或个人电脑数据。虽煌ü�咽�萦成涞�AM上,能在系统级实现随机存取,但是,这样做需要额外的RAM存储空间。此外,跟硬盘一样,NAND器件存在坏的扇区,需要纠错码(ECC)来维持数据的完整性。

存储单元面积越小,裸片的面积也就越小。在这种情况下,NAND就能够为当今的低成本消费市场提供存储容量更大的闪存产品。NAND闪存用于几乎所有可擦除的存储卡。NAND的复用接口为所有最新的器件和密度都提供了一种相似的引脚输出。这种引脚输出使得设计工程师无须改变电路板的硬件设计,就能从更小的密度移植到更大密度的设计上。

NAND与NOR闪存比较

NAND闪存的优点在于写(编程)和擦除操作的速率快,而NOR的优点是具有随机存取和对字节执行写(编程)操作的能力(见下图图2)。NOR的随机存取能力支持直接代码执行(XiP),而这是嵌入式应用经常需要的一个功能。NAND的缺点是随机存取的速率慢,NOR的缺点是受到读和擦除速度慢的性能制约。NAND较适合于存储文件。如今,越来越多的处理器具备直接NAND接口,并能直接从NAND(没有NOR)导入数据。

NAND的真正好处是编程速度快、擦除时间短。NAND支持速率超过5Mbps的持续写操作,其区块擦除时间短至2ms,而NOR是750ms。显然,NAND在某些方面具有绝对优势。然而,它不太适合于直接随机存取。

对于16位的器件,NOR闪存大约需要41个I/O引脚;相对而言,NAND器件仅需24个引脚。NAND器件能够复用指令、地址和数据总线,从而节省了引脚数量。复用接口的一项好处,就在于能够利用同样的硬件设计和电路板,支持较大的NAND器件。由于普通的TSOP-1封装已经沿用多年,该功能让客户能够把较高密度的NAND器件移植到相同的电路板上。NAND器件的另外一个好处显然是其封装选项:NAND提供一种厚膜的2Gb裸片或能够支持最多四颗堆叠裸片,容许在相同的TSOP-1封装中堆叠一个8Gb的器件。这就使得一种封装和接口能够在将来支持较高的密度。

300)this.width=300'>300)this.width=300" border=0>
图1 不同闪存单元的对比

300)this.width=300'>300)this.width=300" border=0>
图2 NOR闪存的随机存取时间为0.12ms,而NAND闪存的第一字节随机存取速度要慢得多

NAND基本操作

以2Gb NAND器件为例,它由2048个区块组成,每个区块有64个页(见图3)。

300)this.width=300'>300)this.width=300" border=0>
图3 2GB NAND闪存包含2,048个区块

每一个页均包含一个2048字节的数据区和64字节的空闲区,总共包含2,112字节。空闲区通常被用于ECC、耗损均衡(wear leveling)和其它软件开销功能,尽管它在物理上与其它页并没有区别。NAND器件具有8或16位接口。通过8或16位宽的双向数据总线,主数据被连接到NAND

存储器。在16位模式,指令和地址仅仅利用低8位,而高8位仅仅在数据传输周期使用。

擦除区块所需时间约为2ms。一旦数据被载入寄存器,对一个页的编程大约要300μs。读一个页面需要大约25μs,其中涉及到存储阵列访问页,并将页载入16,896位寄存器中。

除了I/O总线,NAND接口由6个主要控制信号构成:

1.芯片启动(Chip Enable, CE#):如果没有检测到CE信号,那么,NAND器件就保持待机模式,不对任何控制信号作出响应。

2.写使能(Write Enable, WE#): WE#负责将数据、地址或指令写入NAND之中。

3.读使能(Read Enable, RE#): RE#允许输出数据缓冲器。

4.指令锁存使能(Command Latch Enable, CLE): 当CLE为高时,在WE#信号的上升沿,指令被锁存到NAND指令寄存器中。

5.地址锁存使能(Address Latch Enable, ALE):当ALE为高时,在WE#信号的上升沿,地址被锁存到NAND地址寄存器中。

6.就绪/忙(Ready/Busy, R/B#):如果NAND器件忙,R/B#信号将变低。该信号是漏极开路,需要采用上拉电阻。

数据每次进/出NAND寄存器都是通过16位或8位接口。当进行编程操作的时候,待编程的数据进入数据寄存器,处于在WE#信号的上升沿。在寄存器内随机存取或移动数据,要采用专用指令以便于随机存取。

数据寄存器输出数据的方式与利用RE#信号的方式类似,负责输出现有的数据,并增加到下一个地址。WE#和RE#时钟运行速度极快,达到30ns的水准。当RE#或CE#不为低的时候,输出缓冲器将为三态。这种CE#和RE#的组合使能输出缓冲器,容许NAND闪存与NOR、SRAM或DRAM等其它类型存储器共享数据总线。该功能有时被称为"无需介意芯片启动(chip enable don't care)"。这种方案的初衷是适应较老的NAND器件,它们要求CE#在整个周期为低(译注:根据上下文改写)。

300)this.width=300'>300)this.width=300" border=0>
图4 输入寄存器接收到页编程(80h)指令时,内部就会全部重置为1s,使得用户可以只输入他想以0位编程的数据字节

300)this.width=300'>300)this.width=300" border=0>
图5 带有随机数据输入的编程指令。图中加亮的扇区显示,该指令只需要后面跟随着数据的2个字节的地址

所有NAND操作开始时,都提供一个指令周期(表1)。

300)this.width=300'>300)this.width=300" border=0>

当输出一串WE#时钟时,通过在I/O位7:0上设置指令、驱动CE#变低且CLE变高,就可以实现一个指令周期。注意:在WE#信号的上升沿上,指令、地址或数据被锁存到NAND器件之中。如表1所示,大多数指令在第二个指令周期之后要占用若干地址周期。注意:复位或读状态指令例外,如果器件忙,就不应该发送新的指令。

以2Gb NAND器件的寻址方案为例,第一和第二地址周期指定列地址,该列地址指定页内的起始字节(表2)。

300)this.width=300'>300)this.width=300" border=0>

注意:因为最后一列的位置是2112,该最后位置的地址就是08h(在第二字节中)和3Fh(在第一字节中)。PA5:0指定区块内的页地址,BA16:6指定区块的地址。虽然大多编程和读操作需要完整的5字节地址,在页内随机存取数据的操作仅仅用到第一和第二字节。块擦除操作仅仅需要三个最高字节(第三、第四和第五字节)来选择区块。

300)this.width=300'>300)this.width=300" border=0>
图6 典型的存储方法

300)this.width=300'>300)this.width=300" border=0>
图7 页读缓存模式

总体而言,NAND的基本操作包括:复位(Reset, FFh)操作、读ID(Read ID, 00h)操作、读状态(Read Status, 70h)操作、编程(Program)操作、随机数据输入(Random data input, 85h)操作和读(Read)操作等。

将NAND连接到处理器

选择内置NAND接口的处理器或控制器的好处很多。如果没有这个选择,有可能在NAND和几乎任何处理器之间设计一个"无粘接逻辑(glueless)"接口。NAND和NOR闪存的主要区别是复用地址和数据总线。该总线被用于指定指令、地址或数据。CLE信号指定指令周期,而ALE信号指定地址周期。利用这两个控制信号,有可能选择指令、地址或数据周期。把ALE连接到处理器的第五地址位,而把CLE连接到处理器的第四地址位,就能简单地通过改变处理器输出的地址,任意选择指令、地址或数据。这容许CLE和ALE在合适的时间自动设置为低。

为了提供指令,处理器在数据总线上输出想要的指令,并输出地址0010h;为了输出任意数量的地址周期,处理器仅仅要依次在处理器地址0020h之后输出想要的NAND地址。注意,许多处理器能在处理器的写信号周围指定若干时序参数,这对于建立合适的时序是至关重要的。利用该技术,你不必采用任何粘接逻辑,就可以直接从处理器存取指令、地址和数据。

多层单元

多层单元(MLC)的每一个单元存储两位,而传统的SLC仅仅能存储一位。MLC技术有显著的密度优越性,然而,与SLC相比(表3),其速度或可靠性稍逊。因此,SLC被用于大多数媒体卡和无线应用,而MLC器件通常被用于消费电子和其它低成本产品。

300)this.width=300'>300)this.width=300" border=0>

如上所述,NAND需要ECC以确保数据完整性。NAND闪存的每一个页面上都包括额外的存储空间,它就是64个字节的空闲区(每512字节的扇区有16字节)。该区能存储ECC代码及其它像磨损评级或逻辑到物理块映射之类的信息。ECC能在硬件或软件中执行,但是,硬件执行有明显的性能优势。在编程操作期间,ECC单元根据扇区中存储的数据来计算误码校正代码。数据区的ECC代码然后被分别写入到各自的空闲区。当数据被读出时,ECC代码也被读出;运用反操作可以核查读出的数据是否正确。

有可能采用ECC算法来校正数据错误。能校正的错误的数量取决于所用算法的校正强度。在硬件或软件中包含ECC,就提供了强大的系统级解决方案。最简单的硬件实现方案是采用简单的汉明(Simple Hamming)码,但是,只能校正单一位错误。瑞德索罗门(Reed-Solomon)码提供更为强大的纠错,并被目前的控制器广为采用。此外,BCH码由于比瑞德索罗门方法的效率高,应用也日益普及。

要用软件执行NAND闪存的区块管理。该软件负责磨损评级或逻辑到物理映射。该软件还提供ECC码,如果处理器不包含ECC硬件的话。

编程或擦除操作之后,重要的是读状态寄存器,因为它确认是否成功地完成了编程或擦除操作。如果操作失败,要把该区块标记为损坏且不能再使用。以前已编写进去的数据要从损坏的区块中搬出,转移到新的(好的)存储块之中。2Gb NAND的规范规定,它可以最多有40个坏的区块,这个数字在器件的生命周期(额定寿命为10万次编程/擦除周期)内都适用。一些有坏块的NAND器件能够出厂,主要就归根于其裸片面积大。管理器件的软件负责映射坏块并由好的存储块取而代之。

利用工厂对这些区块的标记,软件通过扫描块可以确定区块的好坏。坏块标记被固定在空闲区的第一个位置(列地址2048)。如果在0或1页的列地址2048上的数据是"non-FF",那么,该块要标记为坏,并映射出系统。初始化软件仅仅需要扫描所有区块确定以确定哪个为坏,然后建一个坏块表供将来参考。

小心不要擦除坏块标记,这一点很重要。工厂在宽温和宽电压范围内测试了NAND;一些由工厂标记为坏的区块可能在一定的温度或电压条件下仍然能工作,但是,将来可能会失效。如果坏块信息被擦除,就无法再恢复。

作者:Jim Cooke

首席应用工程师

存储器产品事业群

美光科技公司

评论

此博客中的热门博文

【转】VxWorks中的地址映射

在运用嵌入式系统VxWorks和MPC860进行通信系统设计开发时,会遇到一个映射地址不能访问的问题。 缺省情况下,VxWorks系统已经进行了如下地址的映射:   memory地址、bcsr(Board Control and Status)地址、PC_BASE_ADRS(PCMCIA)地址、Internal Memory地址、rom(Flach memory)地址等,但是当你的硬件开发中要加上别的外设时,如(falsh、dsp、FPGA等),对这些外设的访问也是通过地址形式进行读写,如果你没有加相应的地址映射,那么是无法访问这些外设的。   和VxWorks缺省地址映射类似,你也可以进行相应的地址映射。   如下是地址映射原理及实现:   1、 地址映射结构 在Tornado\target\h\vmLib.h文件中 typedef struct phys_mem_desc { void *virtualAddr; void *physicalAddr; UINT len; UINT initialStateMask; /* mask parameter to vmStateSet */ UINT initialState; /* state parameter to vmStateSet */ } PHYS_MEM_DESC; virtualAddr:你要映射的虚拟地址 physicalAddr:硬件设计时定义的实际物理地址 len;要进行映射的地址长度 initialStateMask:可以初始化的地址状态: 有如下状态: #define VM_STATE_MASK_VALID 0x03 #define VM_STATE_MASK_WRITABLE 0x0c #define VM_STATE_MASK_CACHEABLE 0x30 #define VM_STATE_MASK_MEM_COHERENCY 0x40 #define VM_STATE_MASK_GUARDED 0x80 不同的CPU芯片类型还有其特殊状态 initialState:实际初始化的地址状态: 有如下状态: #define VM_STATE_VALID 0x01 #define VM_STATE_VALID_NOT 0x00 #define VM_STATE_WRITA

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如下内容: Config ARM_CS8900    tristate "CS8900 support" depends on NET_ETHERNET && A

【转】多迷人Gtkmm啊

前边已经说过用glade设计界面然后动态装载,接下来再来看看怎么改变程序的皮肤(主题)     首先从 http://art.gnome.org/themes/gtk2 下载喜欢的主题,从压缩包里提取gtk-2.0文件夹让它和我们下边代码生成的可执行文件放在同一个目录下,这里我下载的的 http://art.gnome.org/download/themes/gtk2/1317/GTK2-CillopMidnite.tar.gz     然后用glade设计界面,命名为main.glade,一会让它和我们下边代码生成的可执行程序放在同一个目录下边     然后开始写代码如下: //main.cc #include <gtkmm.h> #include <libglademm/xml.h> int main(int argc, char *argv[]) {     Gtk::Main kit(argc,argv);         Gtk::Window *pWnd;        gtk_rc_parse("E:\\theme-viewer\\themes\\gtk-2.0\\gtkrc");       Glib::RefPtr<Gnome::Glade::Xml> refXml;     try     {         refXml = Gnome::Glade::Xml::create("main.glade");     }     catch(const Gnome::Glade::XmlError& ex)     {         Gtk::MessageDialog dialog("Load glade file failed!", false,       \                                   Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);         dialog.run();               return 1;     }         refXml->get_widget("main", pWnd);     if(pW