【详解】Nand Flash 必看知识1
常见缩略词
- BBM (BBM):Bad Block Management 坏块管理
- BBT (BBT):Bad Block Table 坏块表
- ECC (ECC):Error Correction Code 错误校验码
- E2PROM/EEPROM (EEPROM):Electrically Erasable Programmable Read-Only Memory:电可擦只读存储器
- MLC (MLC):Multi Level Cell 多层单元
- MOSFET (MOSFET):Metal-Oxide–Semiconductor Field Effect Transistor 金属氧化物半导体场效应晶体管
- MTD (MTD):Memory Technology Device 内存技术设备
- NVM (NVM):Non-Volatile Memory
- NDA (NDA):None-Disclosure Agreement 非公开协议,保密协议
- OTP (OTP):One Time Programmable 一次性可编程(存储器)
- SLC (SLC):Single Level Cell 单层单元,单层式存储
1. 名词解释
1.1 Non-Volatile Memory (非易失性存储器)
NVM,即 NV (RAM)Memory,断电数据也不会丢失的存储器,比如 Nand Flash,Nor Flash,硬盘等等。于此相对的是,断电了数据会丢失的存储器,比如 DRAM 等。
1.2 OTP 一次性可编程存储器
OTP,一种非易失性存储器,但是只允许一次性写入数据,写入(或称烧写)数据之后,就不能修改了。
OTP的好处或者说用途是,常用于写入一些和芯片相关的一些特定数据,用于加密的一些数据等。
与一次性写入数据的 OTP 相对应的是,像 Nand Flash,硬盘等存储器,可以被多次写入数据。只要硬盘这类的存储器没坏,你愿意写入几次就写入几次,而 OTP 就只能写入一次,就没法再修改里面的数据了。
1.3 NDA 保密协议
NDA,中文可以翻译为,非公开协议,保密协议。
常用于这种情况: 某家厂商的某种技术或资料,是保密的,不希望公开的。但是呢,如果你要用他家的芯片之类的东西,在开发过程中,又必须得到对应的技术和资料,才能开发产品,所以,他就会要求和你签订这样的 NDA 协议,意思就是,你可以用我的技术和资料,但是你不能公开给(我未授权的)其他人。如果非法泄露我的机密技术,那势必要走法律程序控告你之类的。
1.4 Datasheet 数据手册和 Specification 规范
- 英文 datasheet,中文一般翻译为数据手册。
- 指的是对应某个硬件,多为芯片,的功能说明,定义了如何操作该硬件,达到你要的功能,这其中主要包括芯片中的相关寄存器的定义,如何发送命令,发送什么命令,以此来操作此硬件等等。
- 英文 Specification,引文常缩写为Spec.,中文一般翻译为规范。
- 多指某个组织(盈利的或非盈利的),定义了一些规矩,如果你要用某种东西,在计算机领域,常常指的是某硬件和相关的软件协议,就要按照此规矩来操作,这个组织保证你只要实现了此规范,设备就能按照你所期望的运行,能够实现对应的功能,而你的芯片实现了此规范,就叫做,是和此规范兼容(compatible)的。
1.5 (Bad)Block Management(坏)块管理
Nand Flash 由于其物理特性,只有有限的擦写次数,超过那个次数,基本上就是坏了。在使用过程中,有些 Nand Flash的 block 会出现被用坏了,当发现了,要及时将此 block 标注为坏块,不再使用。于此相关的管理工作,属于 Nand Flash 的坏块管理的一部分工作。
1.6 Wear-Leveling 负载平衡
Nand Flash 的 block 的管理,还包括负载平衡。
正是由于 Nand Flash 的 block 都是有一定寿命限制的,所以如果你每次都往同一个 block 擦除然后写入数据,那么那个 block 就很容易被用坏了,所以我们要去管理一下,将这么多次的对同一个 block 的操作,平均分布到其他一些 block 上面,使得在 block 的使用上,相对较平均,这样相对来说,可以更能充分利用 Nand Flash。以此延长 Nand Flash 的使用寿命或者说更加充分利用 Nand Flash。
1.7 ECC 错误校验码
Nand Flash 物理特性上使得其数据读写过程中会发生一定几率的错误,所以要有个对应的错误检测和纠正的机制,于是才有此 ECC,用于数据错误的检测与纠正。Nand Flash 的 ECC。
常见的算法有 汉明码(Hamming Code)、BCH(Bose-Chaudhuri-Hocquenghem)码、RS码(Reed-Solomon Code)和LDPC码(Low-Density Parity-Check Code)。
- 汉明码(Hamming Code):
- 特点:能够纠正单比特错误,检测双比特错误。
- 使用场景:常用于早期的NAND Flash和小容量的存储器。
- 优点:实现简单,计算开销小。
- 缺点:纠错能力有限,不适用于高密度存储器。
- BCH码(Bose–Chaudhuri–Hocquenghem Code):
- 特点:能够纠正多个比特错误,灵活性强。
- 使用场景:广泛应用于现代NAND Flash存储器,尤其是MLC(多层单元)和TLC(三层单元)NAND。
- 优点:可以纠正多个随机错误,适应性强。
- 缺点:实现复杂度较高,计算开销大。
- RS码(Reed-Solomon Code):
- 特点:能够纠正突发错误,非常适合纠正连续的比特错误。
- 使用场景:主要用于NAND Flash存储器的控制器中,用于数据块级别的错误校正。
- 优点:适合纠正突发性错误,纠错能力强。
- 缺点:实现复杂度高,计算开销大。
- LDPC码(Low-Density Parity-Check Code):
- 特点:纠错能力强,接近香农极限,适合长码字。
- 使用场景:逐渐被应用于高密度和高性能的NAND Flash存储器中。
- 优点:纠错性能优越,特别适用于高数据速率和大容量存储。
- 缺点:实现复杂度高,解码算法需要较大的计算资源。
在 NAND Flash 中,选择适当的 ECC 算法主要取决于以下因素:
- 存储密度:高密度的 NAND Flash(如MLC、TLC)需要更强的纠错能力。
- 性能需求:实时性要求高的系统需要高效的ECC算法。
- 硬件资源:嵌入式系统和控制器的处理能力和存储资源限制了可用的 ECC 算法。
BCH 码和 LDPC 码是现代 NAND Flash 存储器中最常用的 ECC 算法。BCH 码因其灵活性和较强的纠错能力被广泛应用于大多数 NAND Flash,而 LDPC 码则因其优越的纠错性能逐渐在高性能存储器中占据一席之地。
这类算法的实现,可以是软件也可以是硬件。不同系统,根据自己的需求,采用对应的软件或者是硬件。
相对来说,硬件实现这类 ECC 算法,肯定要比软件速度要快,但是多加了对应的硬件部分,所以成本相对要高些。如果系统对于性能要求不是很高,那么可以采用软件实现这类 ECC 算法,但是由于增加了数据读取和写入前后要做的数据错误检测和纠错,所以性能相对要降低一些,即Nand Flash的读取和写入速度相对会有所影响。
其中,Linux 中的软件实现 ECC 算法,即 NAND_ECC_SOFT 模式,就是用的对应的汉明码。
而对于目前常见的 MLC 的 Nand Flash 来说,由于容量比较大,动辄 2GB,4GB,8GB 等,常用 BCH 算法。BCH 算法,相对来说,算法比较复杂。
笔者由于水平有限,目前仍未完全搞懂 BCH 算法的原理,所以不讲原理。
BCH 算法,通常是由对应的 Nand Flash 的 Controller 中,包含对应的硬件 BCH ECC 模块,实现了 BCH 算法,而作为软件方面,需要在读取数据后,写入数据之前,分别操作对应 BCH 相关的寄存器,设置成 BCH 模式,然后读取对应的 BCH 状态寄存器,得知是否有错误,和生成的 BCH 校验码,用于写入。
其具体代码是如何操作这些寄存器的,由于是和具体的硬件,具体的 nand flash 的 controller 不同而不同,无法用同一的代码。如果你是 nand flash 驱动开发者,自然会得到对应的起 nand flash 的 controller 部分的 datasheet,按照手册说明,去操作即可。
不过,额外说明一下的是,关于 BCH 算法,往往是要从专门的做软件算法的厂家购买的。
2. 硬件特性
2.1 什么是 Flash
Flash 全名叫做 Flash Memory,是一种数据存储设备,存储设备有很多类,Flash 属于非易失性存储设备 (Non-volatile Memory Device),与此相对应的是易失性存储设备 (Volatile Memory Device)。关于什么是非易失性/易失性,从名字中就可以看出,非易失性就是不容易丢失,数据存储在这类设备中,即使断电了,也不会丢失,这类设备,除了 Flash,还有其他比较常见的入硬盘,ROM 等,与此相对的,易失性就是断电了,数据就丢失了,比如大家常用的内存,不论是以前的 SDRAM,DDR SDRAM,还是现在的 DDR2,DDR3 等,都是断电后,数据就没了。
2.2 Flash 的硬件实现机制
Flash 的内部存储是 MOSFET,里面有个悬浮门 (Floating Gate),是真正存储数据的单元。
在 Flash 之前,紫外线可擦除 (uv-erasable) 的 EPROM,就已经采用了 Floating Gate 存储数据这一技术了。
数据在 Flash 内存单元中是以电荷 (electrical charge) 形式存储的。存储电荷的多少,取决于图中的外部门(external gate)所被施加的电压,其控制了是向存储单元中冲入电荷还是使其释放电荷。而数据的表示,以所存储的电荷的电压是否超过一个特定的阈值 Vth 来表示,因此,Flash 的存储单元的默认值,不是0(其他常见的存储设备,比如硬盘灯,默认值为0),而是1,而如果将电荷释放掉,电压降低到一定程度,表述数字0。
2.3 什么是 Nand Flash
Flash 主要分两种,Nand Flash 和 nor flash。
NAND Flash和NOR Flash是两种常见的非易失性存储器,它们在结构、性能、应用和优缺点上有明显的区别。
以下是它们的主要区别:
2.3.1 结构和存储方式
-
NAND Flash:
- 结构:由多个单元组成的串联链,每个单元与下一个单元串联,形成一个块。
- 存储方式:数据以页(page)为单位进行读取和写入,以块(block)为单位进行擦除。页通常为512字节到16KB,块由多个页组成。
- 访问方式:串行访问,读取和写入速度较快。
-
NOR Flash:
- 结构:每个存储单元直接与位线相连,形成一个类似于传统内存的阵列。
- 存储方式:数据可以随机读取和写入,类似于RAM的访问方式。
- 访问方式:并行访问,具有较快的读取速度。
2.3.2 性能
-
NAND Flash:
- 读取速度:相对较慢,适用于顺序读取。
- 写入速度:较快,适合大容量数据写入。
- 擦除速度:较慢,以块为单位进行擦除。
-
NOR Flash:
- 读取速度:较快,适合随机读取。
- 写入速度:较慢,特别是对单个字节的写入。
- 擦除速度:较慢,以扇区(sector)为单位进行擦除。
2.3.3 存储密度和成本
-
NAND Flash:
- 存储密度:较高,适合大容量存储应用。
- 成本:较低,每单位存储容量的成本更低。
-
NOR Flash:
- 存储密度:较低,适合小容量存储应用。
- 成本:较高,每单位存储容量的成本更高。
2.3.4 应用场景
-
NAND Flash:
- 适用场景:大容量数据存储,如固态硬盘(SSD)、USB闪存盘、存储卡和嵌入式存储设备。
- 优点:适合顺序存储和大容量数据存储,成本较低。
-
NOR Flash:
- 适用场景:需要快速随机读取的小容量存储应用,如固件存储、代码执行存储器(如微控制器的程序存储)和嵌入式系统启动代码。
- 优点:适合快速随机读取,读取速度较快。
2.3.5 优缺点总结
-
NAND Flash:
- 优点:存储密度高、成本低、适合大容量数据存储和写入。
- 缺点:随机读取速度较慢,数据可靠性稍低,需要额外的ECC(错误校验码)来确保数据完整性。
-
NOR Flash:
- 优点:读取速度快、适合随机读取和小容量存储。
- 缺点:存储密度低、成本高、写入速度较慢,特别是对单个字节的写入。
所以,一个常见的应用组合就是,用小容量的 Nor Flash 存储启动代码,比如 uboot,用大容量的 Nand Flash 做整个系统和用户数据的存储。
而一般的嵌入式平台的启动流程也就是,系统从装有启动代码的 Nor Flash 启动后,初始化对应的硬件,包括 SDRAM等,然后将 Nand Flash 上的 Linux 内核读取到内存中,做好该做的事情后,就跳转到 SDRAM 中去执行内核了,然后内核解压(如果是压缩内核的话,否则就直接运行了)后,开始运行,在 Linux 内核启动最后,去 Nand Flash 上,挂载根文件,比如 jffs2,yaffs2 等,挂载完成,运行初始化脚本,启动 consle 交互,才允许你通过 console 和内核交互。至此完成整个系统启动过程。
而 Nor Flash 就分别存放的是 Uboot,Nand Flash 存放的是 Linux 的内核镜像和根文件系统,以及余下的空间分成一个数据区。
2.4 Nand Flash 的详细分类
Nand Flash,按照硬件类型,可以分为:
- Bare NAND chips: 裸片。单独的 Nand Flash 芯片。
- SmartMediaCards: 裸片+一层薄塑料。常用于数码相机和 MP3 播放器中。之所以称 smart,是由于其软件smart,而不是硬件本身有啥 smart 之处。
- DiskOnChip: 裸片+glue logic。glue logic = 硬件ECC产生器+用于静态的 nand 芯片控制的寄存器+直接访问一小片地址窗口,那块地址中包含了引导代码的 stub 桩,其可以从 Nand Flash 中拷贝真正的引导代码。
2.5 SLC 和 MLC 的实现机制
Nand Flash 按照内部存储数据单元的电压的不同层次,也就是单个内存单元中,是存储1位数据,还是多位数据,可以分为 SLC 和 MLC。
2.5.1 SLC(Single Level Cell)
单个存储单元,只存储一位数据,表示1或0。
就是上面介绍的,对于数据的表示,单个存储单元中内部所存储电荷的电压,和某个特定的阈值电压 Vth,相比,如果大于此 Vth 值,就是表示1,反之,小于 Vth,就表示0。
对于 Nand Flash 的数据的写入1,就是控制 External Gate 去充电,使得存储的电荷够多,超过阈值 Vth,就表示1了。而对于写入0,就是将其放电,电荷减少到小于 Vth,就表示0了。
关于为何 Nand Flash 不能从0变成1,我的理解是,理论上来说,是可以实现每一位的从0变成1的,但是实际上,对于实际的物理实现,出于效率的考虑,如果对于每一个存储单元都能单独控制,即,0变成1就是,对每一个存储单元单独去充电,所需要的硬件实现就很复杂和昂贵,同时,所进行对块擦除的操作,也就无法实现之前所说的,Flash 一闪而过的速度了,也就失去了 Flash 的众多特性了。
2.5.2 MLC(Multi Level Cell)
与 SLC 相对应的,就是单个存储单元,可以存储多个位,比如2位,4位等。其实现机制,说起来比较简单,就是通过控制内部电荷的多少,分成多个阈值,通过控制里面的电荷多少,而达到我们所需要的存储成不同的数据。比如,假设输入电压是 Vin=4V(实际没有这样的电压,此处只是为了举例方便),那么,可以设计出2的2次方=4个阈值, 1/4 的 Vin=1V,2/4 的 Vin=2V,3/4 的 Vin=3V,Vin=4V,分别表示2位数据00,01,10,11,对于写入数据,就是充电,通过控制内部的电荷的多少,对应表示不同的数据。
对于读取,则是通过对应的内部的电流(与 Vth 成反比),然后通过一系列解码电路完成读取,解析出所存储的数据。这些具体的物理实现,都是有足够精确的设备和技术,才能实现精确的数据写入和读出的。
单个存储单元可以存储2位数据的,称作2的2次方=4 Level Cell,而不是 2 Level Cell,关于这点,之前看 Nand flash 的数据手册(datasheet)的时候,差点搞晕了。同理,对于新出的单个存储单元可以存储4位数据的,称作 2 的 4次方=16 Level Cell。
2.6 如何识别 SLC 还是 MLC
Nand Flash 设计中,有个命令叫做 Read ID,读取ID,意思是读取芯片的ID,就像大家的身份证一样,这里读取的 ID中,是读取好几个字节,一般最少是4个,新的芯片,支持5个甚至更多,从这些字节中,可以解析出很多相关的信息,比如此 Nand Flash 内部是几个芯片(chip)所组成的,每个 chip 包含了几片(Plane),每一片中的页大小,块大小,等等。在这些信息中,其中有一个,就是识别此 flash 是 SLC 还是 MLC。下面这个就是最常见的 Nand Flash 的 datasheet 中所规定的,第3个字节,3rd byte,所表示的信息,其中就有 SLC/MLC 的识别信息:
表 1.1. Nand Flash第3个ID的含义
Description | I/O7 | I/O6 | I/O5 I/O4 | I/O3 I/O2 | I/O2 I/O1 | |
---|---|---|---|---|---|---|
内部芯片数 (Internal Chip Number) | 1 2 4 8 | 0 0 0 1 1 0 1 1 | ||||
单元类型 (Cell Type) | 2 Level Cell 4 Level Cell 4 Level Cell 8 Level Cell | 0 0 0 1 1 0 1 1 | ||||
同时编程页数 (Number of Simultaneously Programmed Pages) | 1 2 4 8 | 0 0 0 1 1 0 1 1 | ||||
多芯片之间的交错编程 (Interleave Program Between multiple chips) | Not Support Support | 0 1 | ||||
缓存程序 (Cache Program) | Not Support Support | 0 1 |
字节的位定义:
- 表格顶部的列 I/O7 到 I/O0 分别代表第 3 个字节中的 8 个比特(从最高位到最低位)。
内部芯片数 (Internal Chip Number):
- 描述了 NAND Flash 中的内部芯片数量。
- 比特组合:
00
(I/O1 I/O0) 代表 1 个芯片。01
(I/O1 I/O0) 代表 2 个芯片。10
(I/O1 I/O0) 代表 4 个芯片。11
(I/O1 I/O0) 代表 8 个芯片。
单元类型 (Cell Type):
- 描述了 NAND Flash 的单元类型,通常指的是 SLC(Single-Level Cell)或 MLC(Multi-Level Cell)。
- 比特组合:
00
(I/O3 I/O2) 代表 2 Level Cell(通常是 SLC)。01
(I/O3 I/O2) 代表 4 Level Cell。10
(I/O3 I/O2) 代表 4 Level Cell。11
(I/O3 I/O2) 代表 8 Level Cell(通常是 MLC 或更高级的单元)。
同时编程页数 (Number of Simultaneously Programmed Pages):
- 描述了可以同时编程的页数。
- 比特组合:
00
(I/O5 I/O4) 代表 1 页。01
(I/O5 I/O4) 代表 2 页。10
(I/O5 I/O4) 代表 4 页。11
(I/O5 I/O4) 代表 8 页。
多芯片之间的交错编程 (Interleave Program Between multiple chips):
- 描述了是否支持多芯片之间的交错编程。
- 比特:
0
(I/O6) 代表不支持。1
(I/O6) 代表支持。
缓存程序 (Cache Program):
- 描述了是否支持缓存编程。
- 比特:
0
(I/O7) 代表不支持。1
(I/O7) 代表支持。
例子:
如果第 3 个字节的值是
10101100
,则对应的信息如下:
- I/O7 = 1, 支持缓存程序。
- I/O6 = 0, 不支持多芯片之间的交错编程。
- I/O5 I/O4 = 10, 同时编程页数为 4 页。
- I/O3 I/O2 = 10, 单元类型为 4 Level Cell。
- I/O1 I/O0 = 00, 内部芯片数量为 1 个。
2.7 Nand Flash 数据存储单元的整体架构
简单说就是,常见的 Nand Flash,内部只有一个 chip,每个 chip 只有一个 plane。
而有些复杂的,容量更大的 Nand Flash,内部有多个 chip,每个 chip 有多个 plane。这类的 Nand Flash,往往也有更加高级的功能,比如下面要介绍的 Multi Plane Program 和 Interleave Page Program 等。
概念上,由大到小来说,就是:Nand Flash ⇒ Chip ⇒ Plane ⇒ Block ⇒ Page ⇒ oob
用图表来表示,更加易懂:
比如,型号为 K9K8G08U0A 这块 Nand Flash(有时候也被称为此块 chip 芯片),其内部有两个 K9F4G08U0A 的chip,chip#1 和 chip#2,每个 K9F4G08U0A 的 chip 包含了2个 Plane,每个 Plane 是 2Gb bit,所以 K9F4G08U0A 的大小是 2Gb×2 = 4Gb = 512MB,因此,K9K8G08U0A 内部有2个 K9F4G08U0A,或者说4个 Plane,总大小是×256MB=1GB。
注意:4Gb ≠ 4GB,4Gb = 4G bit,4GB = 4G Byte,所以 4G bit = 4 ×1024 ÷ 8 = 512 MB,(1Byte = 8 bit)
用公式表示如下:
K9K8G08U0A 的物理结构所组成的总容量
K9K8G08U0A(这块 Nand Flash)
= 2 × K9F4G08U0A(K9F4G08U0A 是 chip,1 K9F4G08U0A = 2 Plane)
= 2 × 2个Plane
= 4 Plane(1 Plane = 2048 Block)
= 4 × 2048个Block(1 Block = 64 Page)
= 4 × 2048 × 64Page(1 Page = 2KB)
= 4 × 2048 × 64 × 2KB
= 4 × 2048 × 128KB(1 Block = 128KB)
= 4 × 256MB(1 Plane = 2Gb = 256MB)
= 2 × 512MB(1 K9F4G08U0A = 4Gb = 512MB)
= 1GB(1 K9K8G08U0A = 1GB)
而型号是 K9WAG08U1A 的 Nand Flash,内部包含了2个 K9K8G08U0A,所以,总容量是 K9K8G08U0A 的两倍=1GB×2=2GB,类似地 K9NBG08U5A,内部包含了4个 K9K8G08U0A,总大小就是 4×1GB=4GB。
通常只关心 Nand 的总大小
上面所说的 block,page 等 Nand Flash 的物理上的组织结构,是在 chip 的基础上来说的,但是软件编程的时候,除非你要用到 Multi Plane Program 和 Interleave Page Program 等,一般很少区分内部有几个 chip 以及每个 chip 有几个 plane,而最关心的只是 Nand Flash 的总体容量 size 有多大,比如是 1GB 还是 2GB 等等。
2.8 Nand Flash的物理存储单元的阵列组织结构
上图是 K9K8G08U0A 的 datasheet 中的描述。
2.8.1 Block 块
一个 Nand Flash(的chip,芯片)由很多个块(Block)组成,块的大小一般是128KB,256KB,512KB,此处是128 KB。其他的小于 128KB 的,比如 64KB,一般都是下面将要介绍到的 small block 的 Nand Flash。
块 Block,是 Nand Flash 的擦除操作的基本/最小单位。
2.8.2 Page 页
每个块里面又包含了很多页(page)。每个页的大小,对于现在常见的 Nand Flash 多数是2KB,最新的 Nand Flash 的是4KB、8KB等,这类的页大小大于2KB的 Nand Flash,被称作 big block 的 Nand Flash,对应的发读写命令地址,一共5个周期(cycle),而老的 Nand Flash,页大小是256B,512B,这类的 Nand Flash 被称作 small block,地址周期只有4个。
页 Page,是读写操作的基本单位。
不过,也有例外的是,有些 Nand Flash 支持 subpage(1/2页或1/4页)子页的读写操作,不过一般很少见。
2.8.3 oob / Redundant Area / Spare Area
每一个页,对应还有一块区域,叫做空闲区域(spare area)/冗余区域(redundant area),而 Linux 系统中,一般叫做 OOB(Out Of Band),这个区域,是最初基于 Nand Flash 的硬件特性:数据在读写时候相对容易错误,所以为了保证数据的正确性,必须要有对应的检测和纠错机制,此机制被叫做 EDC(Error Detection Code)/ECC(Error Code Correction, 或者 Error Checking and Correcting),所以设计了多余的区域,用于放置数据的校验值。
Oob 的读写操作,一般是随着页的操作一起完成的,即读写页的时候,对应地就读写了 oob。
关于 oob 具体用途,总结起来有:
- 标记是否是坏快
- 存储 ECC 数据
- 存储一些和文件系统相关的数据。如 jffs2 就会用到这些空间存储一些特定信息,而 yaffs2 文件系统,会在 oob 中,存放很多和自己文件系统相关的信息。
2.9 Flash 名称的由来
Flash 的擦除操作是以 block 块为单位的,与此相对应的是其他很多存储设备,是以 bit 位为最小读取/写入的单位,Flash 是一次性地擦除整个块:在发送一个擦除命令后,一次性地将一个 block,常见的块的大小是 128KB/256KB,全部擦除为1,也就是里面的内容全部都是 0xFF 了,由于是一下子就擦除了,相对来说,擦除用的时间很短,可以用一闪而过来形容,所以,叫做 Flash Memory。所以一般将 Flash 翻译为 (快速)闪存。
2.10 Flash 相对于普通设备的特殊性
一般存储设备,比如硬盘或内存,读取和写入都是以位(bit)为单位,读取一个 bit 的值,将某个值写入对应的地址的位,都是可以按位操作的。
但是 Flash 由于物理特性,使得内部存储的数据,只能从1变成0,这点,这点可以从前面的内部实现机制了解到,对于最初始值,都是1,所以是 0xFFFFFFFF,而数据的写入,即是将对应的变成0,而将数据的擦出掉,就是统一地,以 block为单位,全部一起充电,所有位,都变成初始的1,而不是像普通存储设备那样,每一个位去擦除为0。而数据的写入,就是电荷放电的过程,代表的数据也从1变为了0。
所以,总结一下 Flash 的特殊性如下:
普通设备(硬盘/内存等) | Flash | |
---|---|---|
读取/写入的叫法 | 读取/写入 | 读取/编程(Program) |
读取/写入的最小单位 | Bit/位 | Page/页 |
擦除(Erase)操作的最小单位 | Bit/位 | Block/块 |
擦除操作的含义 | 将数据删除/全部写入0 | 将整个块都擦除成全是1,也就是里面的数据都是0xFF |
对于写操作 | 直接写即可 | 在写数据之前,要先擦除,然后再写 |
- 之所以将写操作叫做编程,是因为 flash 是从之前的 EPROM、EEPROM 等继承发展而来,而之前的 EEPROM,往里面写入数据,就叫做编程 Program,之所以这么称呼,是因为其对数据的写入,是需要用电去擦除/写入的,所以叫做编程。
- 对于目前常见的页大小是 2K/4K 的 Nand Flash,其块的大小有128KB/256KB/512KB 等。而对于 Nor Flash,常见的块大小有64K/32K 等。
- 在写数据之前,要先擦除,内部就都变成 0xFF 了,然后才能写入数据,也就是将对应的位由1变成0。
2.11 Nand Flash 的位反转特性
Nand Flash 的位反转,也叫做位翻转,对应的英文表达有:Bit Flip = Bit Flipping = Bit-Flip = Bit twiddling。
Nand Flash 由于本身硬件的内在特性,会导致(极其)偶尔的出现位反转的现象。
所谓的位反转,bit flip,指的是原先 Nand Flash 中的某个位,变化了,即要么从1变成0了,要么从0变成1了。
2.12 Nand Flash 位反转的原因
Nand Flash 的位反转现象,主要是由以下一些原因/效应所导致:
-
漂移效应(Drifting Effects)
漂移效应指的是,Nand Flash 中 cell 的电压值,慢慢地变了,变的和原始值不一样了。
-
编程干扰所产生的错误(Program-Disturb Errors)
此现象有时候也叫做,过度编程效应(over-program effect)。
对于某个页面的编程操作,即写操作,引起非相关的其他的页面的某个位跳变了。
-
读操作干扰产生的错误(Read-Disturb Errors)
此效应是,对一个页进行数据读取操作,却使得对应的某个位的数据,产生了永久性的变化,即 Nand Flash 上的该位的值变了。
2.13 Nand Flash 位反转的影响
位反转,说白了,就是读取数据的时候,数据出错了。
因此,如果你读取的数据正好是属于某个重要的文件中的数据,比如系统的配置文件等,那么此时错了一位,都会导致系统出现异常,问题相对会很严重。
而如果此数据属于音视频流中的数据,那么此时即使错了一位,对整个音视频的播放产生的影响也很小,所以问题也不大。
2.14 Nand Flash 位反转的类型和解决办法
对应的位反转的类型,有两种:
- 一种是 nand flash 物理上的数据存储的单元上的数据,是正确的,只是在读取此数据出来的数据中的某位,发生变化,出现了位反转,即读取出来的数据中,某位错了,本来是0变成1,或者本来是1变成0了。此处可以成为软件上位反转。此数据位的错误,当然可以通过一定的校验算法检测并纠正。
- 另外一种,就是 nand flash 中的物理存储单元中,对应的某个位,物理上发生了变化,原来是1的,变成了0,或原来是0的,变成了1,发生了物理上的位的数据变化。此处可以成为硬件上的位反转。此错误,由于是物理上发生的,虽然读取出来的数据的错误,可以通过软件或硬件去检测并纠正过来,但是物理上真正发生的位的变化,则没办法改变了。不过个人理解,好像也是可以通过擦除 Erase 整个数据块 Block 的方式去擦除此错误,不过在之后的 Nand Flash 的使用过程中,估计此位还是很可能继续发生同样的硬件的位反转的错误。
以上两种类型的位反转,其实对于从 Nand Flash 读取出来的数据来说,解决其中的错误的位的方法,都是一样的,即通过一定的校验算法,常称为 ECC,去检测出来,或检测并纠正错误。
如果只是单独检测错误,那么如果发现数据有误,那么再重新读取一次即可。
实际中更多的做法是,ECC 校验发现有错误,会有对应的算法去找出哪位错误并且纠正过来。
其中对错误的检测和纠正,具体的实现方式,有软件算法,也有硬件实现,即硬件 Nand Flash 的控制器 controller 本身包含对应的硬件模块以实现数据的校验和纠错的。
2.15 Nand Flash 引脚(Pin)的说明
引脚名称 | 引脚功能 |
---|---|
I/O0 ~ I/O7 | 用于输入地址/数据/命令,输出数据 |
CLE | Command Latch Enable,命令锁存使能,在输入命令之前,要先在模式寄存器中,设置CLE使能 |
ALE | Address Latch Enable,地址锁存使能,在输入地址之前,要先在模式寄存器中,设置ALE使能 |
CE# | Chip Enable,芯片使能,在操作Nand Flash之前,要先选中此芯片,才能操作 |
RE# | Read Enable,读使能,在读取数据之前,要先使 CE#有效。 |
WE# | Write Enable,写使能, 在写取数据之前,要先使 WE#有效 |
WP# | Write Protect,写保护 |
R/B# | Ready/Busy Output,就绪/忙,主要用于在发送完编程/擦除命令后,检测这些操作是否完成,忙,表示编程/擦除操作仍在进行中,就绪表示操作完成 |
Vcc | Power,电源 |
Vss | Ground,接地 |
N.C | Non-Connection,未定义,未连接 |
数据手册中的#表示低电平
在数据手册中,你常会看到,对于一个引脚定义,有些字母上面带一横杠的,那是说明此引脚/信号是低电平有效,比如你上面看到的RE头上有个横线,就是说明,此RE是低电平有效,此外,为了书写方便,在字母后面加“#”,也是表示低电平有效,比如我上面写的CE#;如果字母头上啥都没有,就是默认的高电平有效,比如上面的CLE,就是高电平有效。
- CLE 上升沿,命令被锁存
- ALE 上升沿,地址被锁存
- R/B 处于低电平时,表示有编程、擦除或随机读操作正在进行。当 R/B 处于低电平时,主控无法对 FLASH 进行读写操作,只有当 R/B 处于高电平时,主控才能对 FLASH 进行读写操作。
- CE:片选,大致来说,一个 DIE 有一个 CE,多 CE 的 单片闪存,就是多管芯的闪存,相当于一片闪存那么大的地方,装下了不止一片闪存,特别是 BGA 的板子,能实现单片双通道,就是这个原理。
2.16 为何需要 ALE 和 CLE
硬件上,有了电源的 Vcc 和接地的 Vss 等引脚,很好理解,但是为何还要有 ALE 和 CLE 这样的引脚,为何设计这么多的命令,把整个系统搞这么复杂,关于这点,最后终于想明白了:
设计命令锁存使能(Command Latch Enable, CLE) 和 地址锁存使能(Address Latch Enable,ALE),那是因为,Nand Flash 就 8 个 I/O,而且是复用的,也就是,可以传数据,也可以传地址,也可以传命令,为了区分你当前传入的到底是啥,所以,先要用发一个 CLE(或ALE)命令,告诉 Nand Flash 的控制器一声,我下面要传的是命令(或地址),这样,里面才能根据传入的内容,进行对应的动作。否则,Nand Flash 内部,怎么知道你传入的是数据,还是地址,还是命令,也就无法实现正确的操作了。
2.17 Nand Flash 只有 8个 I/O 引脚的好处
在 Nand Flash 的硬件设计中,你会发现很多个引脚。关于硬件上为何设计这样的引脚,而不是直接像其他存储设备,比如普通的 RAM,直接是一对数据线引出来,多么方便和好理解啊。
关于这样设计的好处:
- 减少外围连线
- 相对于并口 (Parellel) 的 Nor Flash 的 48 或 52 个引脚来说,的确是大大减小了引脚数目,这样封装后的芯片体积,就小很多。现在芯片在向体积更小,功能更强,功耗更低发展,减小芯片体积,就是很大的优势。同时,减少芯片接口,也意味着使用此芯片的相关的外围电路会更简化,避免了繁琐的硬件连线。
- 提高系统的可扩展性
- 因为没有像其他设备一样用物理大小对应的完全数目的 addr 引脚,在芯片内部换了芯片的大小等的改动,对于用全部的地址 addr 的引脚,那么就会引起这些引脚数目的增加,比如容量扩大一倍,地址空间/寻址空间扩大一倍,所以,地址线数目/addr 引脚数目,就要多加一个,而对于统一用8个 I/O 的引脚的 Nand Flash,由于对外提供的都是统一的8个引脚,内部的芯片大小的变化或者其他的变化,对于外部使用者(比如编写 Nand Flash 驱动的人)来说,不需要关心,只是保证新的芯片,还是遵循同样的接口,同样的时序,同样的命令,就可以了。这样就提高了系统的扩展性。
说白了,对于旧的Nand Flash所实现的驱动,这些软件工作,在换新的硬件的Nand Flash的情况下,仍然可以工作,或者是通过极少的修改,就同样可以工作,使得软硬件兼容性大大提高。
2.18 Nand Flash 的一些典型 (typical) 的特性
-
页擦除时间是200us,有些慢的有800us
-
块擦除时间是1.5ms
-
页数据读取到数据寄存器的时间一般是20us
-
串行访问(Serial access)读取一个数据的时间是25ns,而一些旧的 Nand Flash 是30ns,甚至是50ns
-
输入输出端口是地址和数据以及命令一起multiplex复用的
-
Nand Flash 的编程/擦除的寿命:即,最多允许的擦除的次数
以前老的 Nand Flash,编程/擦除时间比较短,比如 K9G8G08U0M,才5K次,而后来的多数也只有10K=1万次,而现在很多新的 Nand Flash,技术提高了,比如,Micron 的 MT29F1GxxABB,Numonyx 的 NAND04G-B2D/NAND08G-BxC,都可以达到100K,也就是10万次的编程/擦除,达到和接近于之前常见的 Nor Flash,几乎是同样的使用寿命了。
-
封装形式
48引脚的 TSOP1 封装 或 52引脚的 ULGA 封装
2.19 Nand Flash 控制器与 Nand Flash 芯片
关于 Nand Flash 的控制器 Controller 和 Nand Flash 芯片 chip 之间的关系,觉得有必要解释一下:
首先,我们要知道的是,我们写驱动,是写 Nand Flash 控制器的驱动,而不是 Nand Flash 芯片的驱动,因为独立的 Nand Flash 芯片,一般来说,是很少直接拿来用的,多数都是硬件上有对应的硬件的 Nand Flash 的控制器,去操作和控制 Nand Flash,包括提供时钟信号,提供硬件 ECC 校验等等功能,我们所写的驱动软件,是去操作 Nand Flash 的控制器.
然后由控制器去操作 Nand Flash 芯片,实现我们所要的功能。
2.20 Nand Flash 中的特殊硬件结构
由于 Nand Flash 相对其他常见设备来说,比较特殊,所以,特殊的设备,就有特殊的设计,就对应某些特殊的硬件特性,就有必要解释解释:
页寄存器(Page Register):
由于 Nand Flash 读取和编程操作来说,一般最小单位是页,所以 Nand Flash 在硬件设计时候,考虑到这一特性,对于每一片(Plane),都有一个对应的区域专门用于存放,将要写入到物理存储单元中去的或者刚从存储单元中读取出来的,一页的数据,这个数据缓存区,本质上就是一个缓存 buffer,但是只是此处 datasheet 里面把其叫做页寄存器 page register 而已,实际将其理解为页缓存,更贴切原意。
若是不了解内部结构,容易产生误解,以为内存里面的数据,通过 Nand Flash 的 FIFO,写入到 Nand Flash 里面去,就以为立刻实现了实际数据写入到物理存储单元中了,而实际上只是写到了这个页缓存中,只有当你再发送了对应的编程第二阶段的确认命令,即 0x10,之后,实际的编程动作才开始,才开始把页缓存中的数据,一点点写到物理存储单元中去。
所以,简单总结一下就是,对于数据的流向,实际是经过了如下步骤:
2.21 Nand Flash 中的坏块 (Bad Block)
Nand Flash 中,一个块中含有1个或多个位是坏的,就称为其为坏块 Bad Block。
坏块的稳定性是无法保证的,也就是说,不能保证你写入的数据是对的,或者写入对了,读出来也不一定对的。与此对应的正常的块,肯定是写入读出都是正常的。
2.21.1 坏块的分类
坏块有两种:
-
出厂时就有存在的坏块
一种是出厂的时候,也就是,你买到的新的,还没用过的 Nand Flash,就可以包含了坏块。此类出厂时就有的坏块,被称作 factory (masked) bad block 或 initial bad/invalid block,在出厂之前,就会做对应的标记,标为坏块。
-
使用过程中产生的坏块
第二类叫做在使用过程中产生的,由于使用过程时间长了,在擦块除的时候,出错了,说明此块坏了,也要在程序运行过程中,发现,并且标记成坏块的。具体标记的位置,和上面一样。这类块叫做 worn-out bad block。即用坏了的块。
2.21.2 坏块的标记
具体标记的地方是,对于现在常见的页大小为 2K 的 Nand Flash,是块中第一个页的 oob 起始位置(关于什么是页和 oob,下面会有详细解释)的第1个字节(旧的小页面,pagesize 是 512B 甚至 256B 的 Nand Flash,坏块标记是第6个字节),如果不是 0xFF,就说明是坏块。相对应的是,所有正常的块,好的块,里面所有数据都是 0xFF 的。
不过,对于现在新出的有些 Nand Flash,很多标记方式,有些变化,有的变成该坏块的第一个页或者第二个页,也有的是,倒数最后一个或倒数第二个页,用于标记坏块的。
具体的信息,请参考对应的 Nand Flash 的数据手册,其中会有说明。
对于坏块的标记,本质上,也只是对应的 flash 上的某些字节的数据是非 0xFF 而已,所以,只要是数据,就是可以读取和写入的。也就意味着,可以写入其他值,也就把这个坏块标记信息破坏了。对于出厂时的坏块,一般是不建议将标记好的信息擦除掉的。
uboot 中有个命令是
nand scrub
就可以将块中所有的内容都擦除了,包括坏块标记,不论是出厂时的,还是后来使用过程中出现而新标记的。一般来说,不建议用这个。
不过,在实际的驱动编程开发过程中,为了方便起见,我倒是经常用,其实也没啥大碍,呵呵。不过呢,其实最好的做法是,用
nand erase
只擦除好的块,对于已经标记坏块的块,不要轻易擦除掉,否则就很难区分哪些是出厂时就坏的,哪些是后来使用过程中用坏的了。
2.21.3 坏块的管理
对于坏块的管理,在 Linux 系统中,叫做坏块管理(BBM,Bad Block Management),对应的会有一个表去记录好块,坏块的信息,以及坏块是出厂就有的,还是后来使用产生的,这个表叫做坏块表(BBT,Bad Block Table)。在Linux 内核 MTD 架构下的 Nand Flash 驱动,和 Uboot 中 Nand Flash 驱动中,在加载完驱动之后,如果你没有加入参数主动要求跳过坏块扫描的话,那么都会去主动扫描坏块,建立必要的 BBT 的,以备后面坏块管理所使用。
2.21.4 坏块的比例
关于好块和坏块,Nand Flash 在出厂的时候,会做出保证:
- 关于好的,可以使用的块的数目达到一定的数目,比如三星的 K9G8G08U0M,整个 flash 一共有4096个块,出厂的时候,保证好的块至少大于3996个,也就是意思是,你新买到这个型号的 Nand Flash,最坏的可能, 有3096-3996=100个坏块。不过,事实上,现在出厂时的坏块,比较少,绝大多数,都是使用时间长了,在使用过程中出现的。
- 保证第一个块是好的,并且一般相对来说比较耐用。做此保证的主要原因是,很多 Nand Flash 坏块管理方法中,就是将第一个块,用来存储上面提到的 BBT,否则,都是出错几率一样的块,那么也就不太好管理了,连放 BBT 的地方,都不好找了,_。
一般来说,不同型号的 Nand Flash 的数据手册中,也会提到,自己的这个 Nand Flash,最多允许多少个坏块。就比如上面提到的,三星的 K9G8G08U0M,最多有100个坏块。
2.22 Nand Flash 中页的访问顺序
在一个块内,对每一个页进行编程的话,必须是顺序的,而不能是随机的。比如,一个块中有128个页,那么你只能先对page0 编程,再对 page1 编程,而不能随机的,比如先对 page3,再 page1,page2,page0,page4。
关于此处对于只能顺序给页编程的说法,只是翻译自 datasheet,但是实际情况却发现是,程序中没有按照此逻辑处理,可以任意对某 Block 内的 Page 去做 Program 的动作,而不必是顺序的。但是 datasheet 为何如此解释,原因未知,有待知情者给解释一下。
2.23 常见的 Nand Flash 的操作
要实现对 Nand Flash 的操作,比如读取一页的数据,写入一页的数据等,都要发送对应的命令,而且要符合硬件的规定,如图:Nand Flash K9K8G08U0A的命令集合
从上图可以看到,如果要实现读一个页的数据,就要发送Read的命令,而且是分两个周期(Cycle),即分两次发送对应的命令,第一次是0x00h,第二次是0x30h,而两次命令中间,需要发送对应的你所要读取的页的地址.
对应地,其他常见的一些操作,比如写一个页的数据(Page Program),就是先发送0x80h,然后发生要写入的地址,再发送0x10h。
注意
对于不同厂家的不同型号的 Nand Flash 的基本操作,即读页数据 Read Page,写页数据(对页进行编程)Page Program,擦除整个块的数据 Erase Block 等操作,所用的命令都是一样的,但是针对一些 Nand Flash 的高级的一些特性,比如交错页编程(Interleave Page Program),多片同时编程 (Simultaneously Program Multi Plane)等,所用的命令,未必一样,不过对于同一厂家的 Nand Flash 的芯片,那一般来说,都是统一的。
2.23.1 页编程(Page Program)注意事项
Nand flash 的写操作叫做编程 Program,编程,一般情况下,是以页为单位的。
有的 Nand Flash,比如 K9K8G08U0A,支持部分页编程(Partial Page Program),但是有一些限制:在同一个页内的,连续的部分页的编程,不能超过4次。
一般情况下,都是以页为单位进行编程操作的,很少使用到部分页编程。
关于这个部分页编程,本来是一个页的写操作,却用两个命令或更多的命令去实现,看起来是操作多余,效率不高,但是实际上,有其特殊考虑:
至少对于块擦除来说,开始的命令 0x60 是擦除设置命令(erase setup comman),然后传入要擦除的块地址,然后再传入擦除确认命令(erase confirm command)0xD0,以开始擦除的操作。
这种完成单个操作要分两步发送命令的设计,即先开始设置,再最后确认的命令方式,是为了避免由于外部由于无意的/未预料而产生的噪音,比如,由于某种噪音,而产生了 0x60 命令,此时,即使被 Nand Flash 误认为是擦除操作,但是没有之后的确认操作 0xD0,Nand Flash 就不会去擦除数据,这样使得数据更安全,不会由于噪音而误操作。
2.23.2 读(Read)操作过程详解
下面以最简单的 read 操作为例,解释如何理解时序图,以及将时序图中的要求,转化为代码。
解释时序图之前,让我们先要搞清楚,我们要做的事情:
从 Nand Flash 的某个页 Page 里面,读取我们要的数据。
要实现此功能,会涉及到几部分的知识,即使我们不太懂 Nand Flash 的细节,但是通过前面的基本知识的介绍,那么以我们的常识,至少很容易想到的就是,需要用到哪些命令,怎么发这些命令,怎么计算所需要的地址,怎么读取我们要的数据等等。
下面就一步步的解释,需要做什么,以及如何去做:
-
需要使用何种命令
- 首先,是要了解,对于读取数据,要用什么命令:根据前面关于 Nand Flash 的命令集合介绍,我们知道,要读取数据,要用到 Read 命令,该命令需要2个周期,第一个周期发 0x00,第二个周期发 0x30。
-
发送命令前的准备工作以及时序图各个信号的具体含义
- 知道了用何命令后,再去了解如何发送这些命令。
提示
使能(Enable),是指使其(某个信号)有效,使其生效的意思,“使其”“能够”怎么怎么样。。。。比如,上面图中的CLE线号,是高电平有效,如果此时将其设为高电平,我们就叫做,将 CLE 使能,也就是使其生效的意思。
图 2.23.2 Nand Flash 数据读取操作的时序图
我们来一起看看,图6中的特意标注的①边上的黄色竖线。
黄色竖线所处的时刻,是在发送读操作的第一个周期的命令 0x00 之前的那一刻。
让我们看看,在那一刻,其所穿过好几行都对应什么值,以及进一步理解,为何要那个值。
- 黄色竖线穿过的第一行,是 CLE。将 CLE 置1,就说明你将要通过 I/O 复用端口发送进入 Nand Flash 的,是命令,而不是地址或者其他类型的数据。只有这样将 CLE 置1,使其有效,才能去通知了内部硬件逻辑,你接下来将收到的是命令,内部硬件逻辑,才会将受到的命令,放到命令寄存器中,才能实现后面正确的操作,否则,不去将 CLE 置1使其有效,硬件会无所适从,不知道你传入的到底是数据还是命令了。
- 而第二行,是 CE#,那一刻的值是0。这个道理很简单,你既然要向 Nand Flash 发命令,那么先要选中它,所以,要保证 CE# 为低电平,使其有效,也就是片选有效。
- 第三行是 WE#,意思是写使能。因为接下来是往 Nand Flash 里面写命令,所以,要使得 WE# 有效,所以设为低电平。
- 第四行,是 ALE 是低电平,而 ALE 是高电平有效,此时意思就是使其无效。而对应地,前面介绍的,使 CLE 有效,因为将要数据的是命令(此时是发送图示所示的读命令第二周期的 0x30),而不是地址。如果在其他某些场合,比如接下来的要输入地址的时候,就要使其有效,而使 CLE 无效了。
- 第五行,RE#,此时是高电平,无效。可以看到,知道后面低6阶段,才变成低电平,才有效,因为那时候,要发生读取命令,去读取数据。
- 第六行,就是我们重点要介绍的,复用的输入输出 I/O 端口了,此刻,还没有输入数据,接下来,在不同的阶段,会输入或输出不同的数据/地址。
- 第七行,R/B#,高电平,表示 R(Ready)/就绪,因为到了后面的第5阶段,硬件内部,在第四阶段,接受了外界的读取命令后,把该页的数据一点点送到页寄存器中,这段时间,属于系统在忙着干活,属于忙的阶段,所以,R/B# 才变成低,表示 Busy 忙的状态的。
介绍了时刻①的各个信号的值,以及为何是这个值之后,相信,后面的各个时刻,对应的不同信号的各个值,大家就会自己慢慢分析了,也就容易理解具体的操作顺序和原理了。
2.24 如何计算出我们要传入的行地址和列地址
在介绍具体读取数据的详细流程之前,还要做一件事,那就是,先要搞懂我们要访问的地址,以及这些地址,如何分解后,一点点传入进去,使得硬件能识别才行。
此处还是以 K9K8G08U0A 为例,此 Nand Flash,一共有8192个块,每个块内有64页,每个页是2K+64 Bytes。
假设,我们要访问其中的第 7000 个块中的第64页中的 1208 字节处的地址,此时,我们就要先把具体的地址算出来:
物理地址 = 块大小×块号 + 页大小×页号 + 页内地址
= 128K×7000 + 2K×64 + 1208
= 0x36B204B8
接下来,我们就看看,怎么才能把这个实际的物理地址,转化为 Nand Flash 所要求的格式。
在解释地址组成之前,先要来看看其 datasheet 中关于地址周期的介绍:
图 2.24 Nand Flash的地址周期组成
结合图 2.23.2“Nand Flash数据读取操作的时序图”中的 2,3阶段,我们可以看出,此 Nand Flash 地址周期共有5个,2个列(Column)周期,3个行(Row)周期。
-
对应地,列地址 A0~A10,就是页内地址,地址范围是从0到2047。
细心的读者可能注意到了,为何此处多出来个A11呢?
这样从 A0 到 A11,一共就是12位,可以表示的范围就是0 ~ 212,即 0 ~ 4096了。
实际上,由于我们访问页内地址,可能会访问到 oob 的位置,即 2048-2111 这 64 个字节的范围内,所以,此处实际上只用到了 2048~2111,用于表示页内的 oob 区域,其大小是64字节。
-
对应地,A12~A30,称作页号,页的号码,可以定位到具体是哪一个页。
A18~A30,表示对应的块号,即属于哪个块。
简单解释完了地址组成,那么就很容易分析上面例子中的地址了。
注意,下面这样的方法,是错误的:
0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000,分别分配到5个地址周期就是:
1st周期 | A7 ~ A0 | 1011 1000 = 0xB8 |
---|---|---|
2nd周期 | A11~ A8 | 0100 = 0x04 |
3rd周期 | A19~A12 | 0010 0000 = 0x20 |
4th周期 | A27~A20 | 0110 1011 = 0x6B |
5th周期 | A30~A28 | 11 = 0x03 |
注意:
与图 2.23.2 “Nand Flash 数据读取操作的时序图”中对应的,*L,意思是地电平,由于未用到那些位,datasheet 中强制要求设为0,所以,才有上面的 2nd 周期中的高4位是 0000.其他的 A30 之后的位也是类似原理,都是0。
而至于上述计算方法为何是错误的,那是因为上面计算过程中,把第11位的值,本来是属于页号的位A11,给算成页内地址里面的值了。
应该是这样计算,才是对的:
0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000
1st周期 | A7 ~ A0 | 1011 1000 = 0xB8 |
---|---|---|
2nd周期 | A10~ A8 | 100 = 0x04 |
3rd周期 | A19~A12 | 010 0000 0 = 0x40 |
4th周期 | A27~A20 | 110 1011 0 = 0xD6 |
5th周期 | A30~A28 | 11 0 = 0x06 |
那有人会问了,上面表11中,不是明明写的 A0 到 A30,其中包括 A11,不是正好对应着此处地址中的 bit0 到 bit30吗?
实际上,表11中的 A11,是比较特殊的,只有当我们访问页内地址处于 oob 的位置,即属于2048 ~ 2111的时候,A11才会其效果,才会用 A0-A11 用来表示对应的某个属于 2048 ~ 2111 的某个值,属于 oob 的某个位置。
而我们此处的页内地址为 1208,还没有超过 2047呢,所以A11肯定是0。
这么解释,显得很绕,很难看懂。
换种方式来解释,就容易听懂了:
说白了,我们就是要访问第7000个块中的第64页中的1208字节处,对应着
页内地址 = 1208 = 0x4B8
- 页号 = 块数×页数/块 + 块内的页号
- = 7000×(128K/2K) + 64
- = 7000×64 + 64
- = 448064
- = 0x6D640
也就是,我们要访问 0x6D640 页内的 0x4B8 地址,这样很好理解吧,_。
然后对应的:
页内地址 = 0x4B8
分成两个对应的列地址,就变成
0x4B8 :列地址1 = 0xB8,列地址2 = 0x04
-
页号 = 0x6D640,分成三个行号就是:
- 0x6D640:行号1 = 0x40,行号2 = 0xD6,行号3 = 0x06
-
再回头看看上面的计算方法,
-
最开始计算出来的:
- 列地址1 = 0xB8
- 列地址2 = 0x04
- 行号1 = 0x20
- 行号2 = 0x6B
- 行号3 = 0x03
-
是错误的。
-
而第二次计算正确的:
- 列地址1 = 0xB8
- 列地址2 = 0x04
- 行号1 = 0x40
- 行号2 = 0xD6
- 行号3 = 0x06
-
才是对的,也和我们此处自己手动计算,是一致的。
-
第一次之所以计算错,就是错误的把行地址的最低一位 A11,放到列地址中的最高位了。
-
至此,才算把如何手动计算行地址和列地址,解释明白和正确了。
-
因此,我们要访问第7000个块中的第64页中的1208字节处的话,所要传入的地址就是分5个周期:
-
分别传入两个列地址的:
- 列地址1 = 0xB8
- 列地址2 = 0x04
-
然后再传3个行地址的:
- 行号1 = 0x40
- 行号2 = 0xD6
- 行号3 = 0x06
-
这样硬件才能识别。
-
而接下来的内容,也就是介绍硬件是如何处理这些输入的。
2.25 读操作过程的解释
准备工作终于完了,下面就可以开始解释说明,对于读操作的,上面图中标出来的,1-6个阶段,具体是什么含义。
操作准备阶段:此处是读(Read)操作,所以,先发一个读命令的第一个阶段的 0x00,表示,让硬件先准备一下,接下来的操作是读。
发送两个周期的列地址。也就是页内地址,表示,我要从一个页的什么位置开始读取数据。
接下来再传入三个行地址。对应的也就是页号。
然后再发一个读操作的第二个周期的命令0x30。接下来,就是硬件内部自己的事情了。
Nand Flash 内部硬件逻辑,负责去按照你的要求,根据传入的地址,找到哪个块中的哪个页,然后把整个这一页的数据,都一点点搬运到页缓存中去。而在此期间,你所能做的事,也就只需要去读取状态寄存器,看看对应的位的值,也就是 R/B# 那一位,是1还是0,0的话,就表示,系统是 busy,仍在”忙“(着读取数据),如果是1,就说系统活干完了,忙清了,已经把整个页的数据都搬运到页缓存里去了,你可以接下来读取你要的数据了。
对于这里。估计有人会问了,这一个页一共 2048+64 字节,如果我传入的页内地址,就像上面给的1208一类的值,只是想读取 1208 到 2011 这部分数据,而不是页开始的0地址整个页的数据,那么内部硬件却读取整个页的数据出来,岂不是很浪费吗?答案是,的确很浪费,效率看起来不高,但是实际就是这么做的,而且本身读取整个页的数据,相对时间并不长,而且读出来之后,内部数据指针会定位到你刚才所制定的 1208 的那个位置。
接下来,就是你“窃取“系统忙了半天之后的劳动成果的时候了,呵呵。通过先去 Nand Flash 的控制器中的数据寄存器中写入你要读取多少个字节(byte)/字(word),然后就可以去 Nand Flash 的控制器的 FIFO 中,一点点读取你要的数据了。
至此,整个 Nand Flash 的读操作就完成了。
对于其他操作,可以根据我上面的分析,一点点自己去看 datasheet,根据里面的时序图去分析具体的操作过程,然后对照代码,会更加清楚具体是如何实现的。
3. Nand Flash 的一些高级特性
3.1 Nand Flash 的 Unique ID
Unique ID,翻译为中文就是,独一无二的ID,唯一性标识。
很明显,这个 Unique ID 是为了用来识别某些东西的,每一个东西都拥有一个独一无二的标识信息。
在 Nand Flash 里面的 Unique ID,主要是某个ID信息,保证每个 Nand Flash 都是独一无二的。主要用于其它的使用 Nand Flash 的用户,根据此 unique id 去做加密等应用,实现某些安全方面的应用。
简而言之,就是用 Nand Flash 的 Unique ID 来实现安全相关的应用,比如加密,版权保护等等。
3.2 片选无关(CE don’t-care)技术
很多 Nand flash 支持一个叫做 CE don’t-care 的技术,字面意思就是,不关心是否片选。
对此也许有人会问了,如果不片选,那还能对其操作吗?答案就是,这个技术,主要用在当时是不需要选中芯片,但是芯片内部却仍可以继续操作的这些情况:在某些应用,比如录音,音频播放等应用中,外部使用的微秒(us)级的时钟周期,此处假设是比较少的2us,在进行读取一页或者对页编程时,是对 Nand Flash 操作,这样的串行(Serial Access)访问的周期都是 20/30/50ns,都是纳秒(ns)级的,此处假设是50ns,当你已经发了对应的读或写的命令之后,接下来只是需要 Nand Flash 内部去自己操作,将数据读取除了或写入进去到内部的数据寄存器中而已,此处,如果可以把片选取消,CE# 是低电平有效,取消片选就是拉高电平,这样会在下一个外部命令发送过来之前,即微秒量级的时间里面,即2us-50ns≈2us,这段时间的取消片选,可以降低很少的系统功耗,但是多次的操作,就可以在很大程度上降低整体的功耗了。
总的来说就是:由于某些外部应用所需要的访问 Nand Flash 的频率比较低,而 Nand Flash 内部操作速度比较快,所以在针对 Nand Flash 的读或写操作的大部分时间里面,都是在等待外部命令的输入,同时却选中芯片,产生了多余的功耗,此“不关心片选”技术,就是在 Nand Flash 的内部的相对快速的操作(读或写)完成之后,就取消片选,以节省系统功耗。待下次外部命令/数据/地址输入来的时候,再选中芯片,即可正常继续操作了。这样,整体上,就可以大大降低系统功耗了。
提示
- 如果想要操作硬件 Nand Flash 芯片,先要将对应的 CE#(低有效)片选信号拉低,选中该芯片,然后才能做接下来的读写操作所要做的发命令,发数据等动作。
- Nand Flash 的片选与否,功耗差别会有很大。如果数据没有记错的话,我之前遇到我们系统里面的 Nand Flash 的片选,大概有5个mA 的电流输出呢,也许你对 5mA 没太多概念,给你说个数据你就知道了:当时为了针对 MP3 播放功耗进行优化,整个系统优化之后的待机功耗,也才10个 mA 左右的,所以节省 5mA 已经算是很不错的功耗优化了。
3.3 带 EDC 的拷回操作以及 Sector 的定义(Copy-Back Operation with EDC & Sector Definition for EDC)
Copy-Back 功能,简单的说就是,将一个页的数据,拷贝到另一个页。
如果没有 Copy-Back 功能,那么正常的做法就是,先要将那个页的数据拷贝出来放到内存的数据 buffer 中,读出来之后,再用写命令将这页的数据,写到新的页里面。
而 Copy-Back 功能的好处在于,不需要用到外部的存储空间,不需要读出来放到外部的 buffer 里面,而是可以直接读取数据到内部的页寄存器(page register)然后写到新的页里面去。
而且,为了保证数据的正确,要硬件支持 EDC(Error Detection Code)的,否则,在数据的拷贝过程中,可能会出现错误,并且拷贝次数多了,可能会累积更多错误。
而对于错误检测来说,硬件一般支持的是512字节数据,对应有16字节用来存放校验产生的 ECC 数值,而这512字节一般叫做一个扇区。对于 2K+64 字节大小的页来说,按照512字节分,分别叫做 A,B,C,D 区,而后面的64字节的 oob 区域,按照16字节一个区,分别叫做 E,F,G,H 区,对应存放 A,B,C,D 数据区的 ECC 的值。
Copy-Back 编程的主要作用在于,去掉了数据串行读取出来,再串行写入进去的时间,所以,而这部分操作,是比较耗时的,所以此技术可以提高编程效率,提高系统整体性能。
3.4 多片同时编程(Simultaneously Program Multi Plane)
对于有些新出的 Nand Flash,支持同时对多个片进行编程,比如上面提到的三星的 K9K8G08U0A,内部包含4片(Plane),分别叫做 Plane0,Plane1,Plane2,Plane3。由于硬件上,对于每一个 Plane,都有对应的大小是2048+64 = 2112 字节的页寄存器(Page Register),使得同时支持多个 Plane 编程成为可能。 K9K8G08U0A 支持同时对2个 Plane 进行编程。
不过要注意的是,只能对 Plane0 和 Plane1 或者 Plane2 和 Plane3,同时编程,而不支持 Plane0 和 Plane2 同时编程。
3.5 交错页编程(Interleave Page Program)
多片同时编程,是针对一个 chip 里面的多个 Plane 来说的,
而此处的交错页编程,是指对多个 chip 而言的。
可以先对一个 chip,假设叫 chip1,里面的一页进行编程,然后此时,chip1 内部就开始将数据一点点写到页里面,就出于忙的状态了,而此时可以利用这个时间,对出于就绪状态的 chip2,也进行页编程,发送对应的命令后,chip2 内部也就开始慢慢的写数据到存储单元里面去了,也出于忙的状态了。此时,再去检查 chip1,如果编程完成了,就可以开始下一页的编程了,然后发完命令后,就让其内部慢慢的编程吧,再去检查 chip2,如果也是编程完了,也就可以进行接下来的其他页的编程了。如此,交互操作 chip1 和 chip2,就可以有效地利用时间,使得整体编程效率提高近2倍,大大提高 Nand Flash 的编程/擦写速度了。
3.6 随机输出页内数据(Random Data Output In a Page)
在介绍此特性之前,先要说说,与 Random Data Output In a Page 相对应的是,普通的,正常的,sequential data output in a page。
正常情况下,我们读取数据,都是先发读命令,然后等待数据从存储单元到内部的页数据寄存器中后,我们通过不断地将RE# (Read Enale,低电平有效)置低,然后从我们开始传入的列的起始地址,一点点读出我们要的数据,直到页的末尾,当然有可能还没到页地址的末尾,就不再读了。所谓的顺序(sequential)读取也就是,根据你之前发送的列地址的起始地址开始,每读一个字节的数据出来,内部的数据指针就加1,移到下个字节的地址,然后你再读下一个字节数据,就可以读出来你要的数据了,直到读取全部的数据出来为止。
而此处的随机(random)读取,就是在你正常的顺序读取的过程中,先发一个随机读取的开始命令 0x05 命令,再传入你要将内部那个数据指针定位到具体什么地址,也就是2个 cycle 的列地址,然后再发随机读取结束命令 0xE0,然后,内部那个数据地址指针,就会移动到你所制定的位置了,你接下来再读取的数据,就是从那个制定地址开始的数据了。
而 Nand Flash 数据手册里面也说了,这样的随机读取,你可以多次操作,没限制的。
请注意,上面你所传入的地址,都是列地址,也就是页内地址,也就是说,对于页大小为 2K 的 Nand Flash 来说,所传入的地址,应该是小于 2048+64=2112 的。
不过,实际在 Nand Flash 的使用中,好像这种用法很少的。绝大多数,都是顺序读取数据。
4. Nand Flash 相关规范 – ONFI 和 LBA
4.1 ONFI 是什么
ONFI 规范,即 Open Nand Flash Interface specification。
ONFI 是 Intel 主导的,其他一些厂家(Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等)参与制定的,统一了 Nand Flash 的操作接口。
所谓操作接口,就是那些对 Nand Flash 操作的命令等内容。
而所谓统一,意思是之前那些 Nand Flash 的操作命令等,都是各自为政,虽然大多数常见的 Nand Flash 的操作,比如 page read 的命令是 0x00,0x30,page write 的命令是 0x80,0x10 等,但是有些命令相关的内容,很特别且很重要的一个例子就是,每个厂家的 Nand Flash 的 read id 的命令,虽然都是 0x90,但是读取出来的几个字节的含义,每个厂家定义的都不太一样。
因此,才有统一 Nand Flash 的操作接口这一说。
ONFI规范官网:http://onfi.org
图 4.1 ONFI中的Nand Flash的命令集合
可以看到,其中常见的一些命令,比如
- page read(0x00,0x30)
- page write(0x80,0x10)
- block erase(0x60,0xD0)
- Reset(0xFF)
等等命令,都是和普通的 Nand Flash 的命令是一样的,而额外多出一些命令,比如 Read Unique ID(0xED)等命令,是之前某些 Nand Flash 命令所不具有的。
如此,定义了 Nand Flash 的操作的命令的集合以及发送对应命令所遵循的时序等内容。
4.2 ONFI Block Abstracted NAND
ONFI 还定义了另外一个规范:
ONFI Block Abstracted Nand Specification
即 ONFI LBA Nand,简单说就是,逻辑块寻址的 Nand。其含义和 Toshiba 的 LBA,基本没有太多区别。
4.3 ONFI 的好处
ONFI 规范定义了之后,每家厂商的 Nand Flash,只要符合这个 ONFI 规范,然后上层 Nand Flash 的软件,就可以统一只用一种了,换句话说,我写了一份 Nand Flash 的驱动后,就可以操作所有和 ONFI 兼容的 Nand Flash 了,整个 Nand Flash 的兼容性,上层软件的兼容性,互操作性,就大大提高了。
而且,同样的,由于任何规范在定义的时候,都会考虑到兼容性和扩展性,ONFI 也不例外。针对符合 ONFI 规范的,写好的软件,除了可以操作多家与 ONFI 兼容的 Nand Flash 之外,而对于以后出现的新的技术,新制程的 Nand Flash,只要符合 ONFI 规范,也同样可以支持,可以在旧的软件下工作,而不需要由于 Nand Flash 的更新换代,而更改上层软件和驱动,这个优势,由于对于将 Nand Flash 芯片集成到自己系统中的相关开发人员来说,是个好消息。
4.4 LBA 规范是什么
LBA Nand Flash,Logical Block Address,逻辑块寻址的 Nand Flash,是 Nand Flash 大厂之一的 Toshiba,自己独立设计出来的新一代的 Nand Flash 的规范。
之所以叫做逻辑块寻址,是相对于之前常见的,普通的 Nand Flash 的物理块的寻址来说的。常见的 Nand Flash,如果要读取和写入数据,所用的对应的地址是对应的:block地址+block 内的 Page 地址+Page 内的偏移量 = 绝对的物理地址,
此物理块寻址,相对来说有个缺点,那就是,由于之前提到的 Nand Flash 会出现使用过程中出现坏块,所以,遇到这样的坏块,首先坏块管理要去将此坏块标记,然后将坏块的数据拷贝到另一个好的 block 中,再继续访问新的 block。
而且数据读写过程中,还要有对应的 ECC 校验,很多情况下,也都是软件来实现这部分的工作,即使是硬件的 ECC 校验,也要写少量的软件,去操作对应寄存器,读取 ECC 校验的结果,当然别忘了,还有对应的负载平衡等工作。
如此的这类的坏块管理工作,对于软件来说,很是繁重,而且整个系统实现起来也不是很容易,所以,才催生了一个想法,是否可以把 ECC 校验,负载平衡,坏块管理,全部都放到硬件实现上,而对于软件来说,我都不关心,只关心有多少个 Block 供我使用,用于数据读写。
针对于此需求,Toshiba 推出了 LBA 逻辑块寻址的 Nand Flash,在 Nand Flash 存储芯片之外,加了对应一个硬件控制权 Controller,实现了上述的坏块管理,ECC 校验,负载平衡等工作,这样使得人家想要用你 LBA 的 Nand Flash 的人,去开发对应的软件来驱动 LBA Nand Flash 工作,相对要做的事情,就少了很多,相对来说就是减轻了软件系统集成方面的工作,提高了开发效率,缩短了产品上市周期。
关于 Toshiba LBA Nand 规范,在此多说一点(参考附录中:lba-core.c):
LBA Nand 分为 PNP,VFP 和 MDP 三种分区:
- PNP 主要用于存放 Uboot 等启动代码
- VFP 主要用于存放 uImage 等内核代码
- MDP 主要用于存放用户的数据,以及 rootfs 等内容
4.5 ONFI 和 LBA 的区别和联系
总的来说,ONFI在对于旧的Nand Flash的兼容上,都是相对类似的。
- ONFI 规范,更注重对于 Nand Flash 的操作接口方面的定义 + ONFI LBA Nand 的定义
- 而 Toshiba LBA 规范,主要侧重于LBA的Nand的定义。
Toshiba 的 LBA 规范中,又多了些其他模式和应用类别。
总的来说,可以这么划分:
- ONFI = Nand Flash 操作接口的统一 + ONFI 的 LBA Nand
- Toshiba LBA = 等价于 ONFI 的 LBA Nand + 多种模式和对应的不同应用
5. 内存技术设备 MTD(Memory Technology Device)
MTD,是 Linux 的存储设备中的一个子系统。其设计此系统的目的是,对于内存类的设备,提供一个抽象层,一个接口,使得对于硬件驱动设计者来说,可以尽量少的去关心存储格式,比如 FTL,FFS2 等,而只需要去提供最简单的底层硬件设备的读/写/擦除函数就可以了。而对于数据对于上层使用者来说是如何表示的,硬件驱动设计者可以不关心,而 MTD 存储设备子系统都帮你做好了。
对于 MTD 子系统的好处,简单解释就是,他帮助你实现了,很多对于以前或者其他系统来说,本来也是你驱动设计者要去实现的很多功能。换句话说,有了 MTD,使得你设计 Nand Flash 的驱动,所要做的事情,要少很多很多,因为大部分工作,都由 MTD 帮你做好了。
当然,这个好处的一个“副作用”就是,使得我们不了解的人去理解整个 Linux 驱动架构,以及 MTD,变得更加复杂。但是,总的说,觉得是利远远大于弊,否则,就不仅需要你理解,而且还是做更多的工作,实现更多的功能了。
此外,还有一个重要的原因,那就是,前面提到的 Nand Flash 和普通硬盘等设备的特殊性:
有限的通过出复用来实现输入输出命令和地址/数据等的 IO 接口,最小单位是页而不是常见的 bit,写前需擦除等,导致了这类设备,不能像平常对待硬盘等操作一样去操作,只能采取一些特殊方法,这就诞生了 MTD 设备的统一抽象层。
MTD,将 Nand Flash,nor flash 和其他类型的 flash 等设备,统一抽象成 MTD 设备来管理,根据这些设备的特点,上层实现了常见的操作函数封装,底层具体的内部实现,就需要驱动设计者自己来实现了。具体的内部硬件设备的读/写/擦除函数,那就是你必须实现的了。
表 5.1 MTD设备和硬盘设备之间的区别
HARD drives | MTD device |
---|---|
连续的扇区 | 连续的可擦除块 |
扇区都很小(512B,1024B) | 可擦除块比较大 (32KB,128KB) |
主要通过两个操作对其维护操作:读扇区,写扇区 | 主要通过三个操作对其维护操作:从擦除块中读,写入擦除块,擦写可擦除块 |
坏快被重新映射,并且被硬件隐藏起来了(至少是在如今常见的LBA硬盘设备中是如此) | 坏的可擦除块没有被隐藏,软件中要处理对应的坏块问题 |
HDD 扇区没有擦写寿命超出的问题 | 可擦除块是有擦除次数限制的,大概是 104 - 105 次 |
5.1 读操作的硬件到软件的映射
下面这部分主要介绍一下,关于硬件的设计和规范,是如何映射到具体的软件实现的,看了这部分内容之后,你对如何根据硬件的规范去用软件代码实现对应的功能,就有了大概的了解了,然后去实现对应的某硬件的驱动,就有了大概的脉络了。
关于硬件部分的细节,前面其实已经介绍过了,但是为了方便说明,此处还是以读操作为例去讲解硬件到软件是如何映射的。
再次贴出上面的那个图:
Nand Flash数据读取操作的时序图
对于上面的从1到6,每个阶段所表示的含义,再简单解释一下:
- 此阶段,是读命令第一个周期,发送的命令为 0x00。
- 此阶段,依次发送列地址,关于这些行地址,列地址等是如何计算出来的,后面的内容会有详细解释。
- 此阶段是发送对应的行地址
- 此阶段是发送读命令第二周期 2nd cycle 所对应的命令,0x30
- 此阶段是等待时间,等待 Nand Flash 硬件上准备好对应的数据,以便后续读出。
- 此阶段,就是一点点地把所需要的数据读出来。