最 近一直在致力于基于ARM920T的FLASH的读写,虽然这部分一般没有多少人屑于研究,认为比较简单(U-boot和VIVI中都有现成的支持程 序),但对于初学的我,还是尽心尽力地学习一下,目的在于自己编写一个通用flash驱动程序,以下是前段时间对主要两种flash的认识,大致整理了一 下
FLASH
所谓Flash , 是内存(Memory )的一种,但兼有RAM 和ROM 的优点,是一种可在系统(In-System ) 进行电擦写,掉电后信息不丢失的存储器,同时它的高集成度和低成本使它成为市场主流。
Flash 芯片是由内部成千上万个存储单元组成的,每个单元存储一个bit 。 具有低功耗、大容量、擦写速度快、可整片或分扇区在系统编程(烧写)、擦除等特点,并且可由内部嵌入的算 法完成对芯片的操作,因而在各种嵌入式系统中得到了广泛的应用。
作为一种非易失性存储器,Flash 在 系统中通常用于存放程序代码、常量表以及一些在系统掉电后需要保存的用户数据等。
常用的Flash 为8 位或16 位的数据宽度,编程电压为单3.3V 。主要的生产厂商为INTEL 、ATMEL 、AMD 、HYUNDAI 等。Flash 技术根据不同的应用场合 也分为不同的发展方向,有擅长存储代码的NOR Flash 和擅长存储数据的NAND Flash 。一下对NOR Flash 和NAND Flash 的技术分别作了相应的介绍。
一.NOR Flash
1. 市场介绍
随着技术的发展,愈来愈多的电子产品需要更多 的智能化,这也对这些产品的程序存储提出了更高的要求。Flash 作为一种低成本、高集成度的存 储技术在电子产品领域的应用非常广泛。今天90% 的PC 、 超过90% 的手机、超过50% 的Modem ,都是用了Flash ,如今Flash 市场规模已经超过了100 亿美元。
如此巨大的市场规模,也导致市场上的Flash 品牌层出不穷。在NOR Flash 市场 中,Intel 公司是非常重要的一家生产厂商。Intel 公 司生产的Flash 芯片多年来占据着市场的很大份额,而它的芯片封装形式和接口也成为业界标准,从 而为不同品牌的Flash 带来了兼容的方便。
2. NOR Flash 的硬件设计和调试
首先,Flash 要通过系统总线接在处理器上, 即保持一个高速的数据交换的通道。那么就必须了解一下Flash 在系统总线上的基本操作。
1 ) 先了解一下处理器存储空间BANK 的概念。以32 位处理器S3C2410 为 例,理论上可以寻址的空间为4GB ,但其中有3GB 的 空间都预留给处理器内部的寄存器和其他设备了,留给外部可寻址的空间只有1GB ,也就是0X00000000~0X3fffffff ,总共应该有30 根 地址线。这1GB 的空间,2410 处理器又 根据所支持的设备的特点将它分为了8 份,每份空间有128MB , 这每一份的空间又称为一个BANK 。为方便操作,2410 独 立地给了每个BANK 一个片选信号(nGCS7~nGCS0 )。 其实这8 个片选信号可以看作是2410 处理 器内部30 根地址线的最高三位所做的地址译码的结果。正因为这3 根地址线所代表的地址信息已经由8 个片选信号来传递 了,因此2410 处理器最后输出的实际地址线就只有A26~A0 (如 下图1 )
图1 2410 内 存BANK 示意图
2 )以图2( 带nWAIT 信号) 为例,描述一下处理器的总线的读操作 过程,来说明Flash 整体读、写的流程。第一个时钟周期开始,系统地址总线给出需要访问的存储空 间地址,经过Tacs 时间后,片选信号也相应给出(锁存当前地址线上地址信息),再经过Tcso 时间后,处理器给出当前操作是读(nOE 为 低)还是写(new 为低),并在Tacc 时 间内将数据数据准备好放之总线上,Tacc 时间后(并查看nWAIT 信 号,为低则延长本次总线操作),nOE 拉高,锁存数据线数据。这样一个总线操作就基本完成
图2 带nWAIT 信号的总线读操作
3 ) NOR Flash 的接口设计(现代的29LV160 芯 片)
29LV160 存储容量为8M 字节,工作 电压为3.3V ,采用56 脚TSOP 封装或48 脚FBGA 封装,16 位数据宽度。 29LV160 仅需单3.3V 电 压即可完成在系统的编程与擦除操作,通过对其内部的命令寄存器写入标准的命令序列,可对Flash 进 行编程(烧写)、整片擦除、按扇区擦除以及其他操作。引脚信号描述和接口电路分别如图3 和图4 所示。
图3 29LV160 引脚信号描 述
图4 FLASH (29LV160 )接口电路
可 以从信号引脚图3 和总线操作图2 看出,NOR Flash 的接口和系统总线接口完全匹配,可以很容易地接到系统总线上。
3. NOR Flash 的软件设计
Flash 的命令很多,但常用到的命令就3 种:识别、擦除、编程命令 。以下就对3 种命令作分别的简要介绍:
1) NOR Flash 的识别
29lv160_CheckId()
{
U8 tmp;
U16 manId,devId;
int i;
_RESET();
_WR(0x555,0xaa);
_WR(0x2aa,0x55);
_WR(0x555,0x90);
manId=_RD(0x0);
devId=_RD(0x1);
_RESET();
printf("Manufacture ID(0x22C4)=%4x, Device ID(0x2249)=%4x/n",manId,devId);
if(manId == 0x22C4 && devId == 0x2249)
return 1;
else
return 0;
}
NOR Flash 的识别程序由四个读写周期就可以完成,在Flash 的相关命令表中可以查到相应ID 识别的命令。
2) NOR Flash 的擦除
要对NOR Flash 进行写操作,就一定要先进性擦除操作。NOR Flash 的擦除都是以块(sector )为 单位进行的,但是每一种型号的Flash 的sector 的 大小不同,即使在同一片的Flash 内,,不同sector 的 大小也是不完全一样的。
void 29lv160db_EraseSector(int targetAddr)
{
printf("Sector Erase is started!/n");
_RESET();
_WR(0x555,0xaa);
_WR(0x2aa,0x55);
_WR(0x555,0x80);
_WR(0x555,0xaa);
_WR(0x2aa,0x55);
_WR(BADDR2WADDR(targetAddr),0x30);
return _WAIT(BADDR2WADDR(targetAddr);
}
图5 Erase Operation
/**************
如上图5 所示,擦除操作时还要有一个关键的操作擦除查询算法,即等待Flash 擦 除的过程,并返回擦除是否成功的结果。算法如右图6 所示
****************/
Int _WAIT(void)
{
unsigned int state ,flashStatus ,old;
old=_RD(BADDR2WADDR(0x0));
while(1)
{
flashStatus=_RD(BADDR2WADDR(0x0));
if( (old&0x40) == (flashStatus&0x40) )
break;
if( flashStatus&0x20 )
{
//printf("[DQ5=1:%x]/n",flashStatus);
old=_RD(BADDR2WADDR(0x0));
flashStatus=_RD(BADDR2WADDR(0x0));
if( (old&0x40) == (flashStatus&0x40) )
return 0;
else return 1;
}
//printf(".");
old=flashStatus;
} //printf("!/n");
return 1;
}
图6 Toggle Bit Algorithm
以 上的方法为查询数据线上的一个特定位 Toggle 位。此外还有 2 种检测方法,一种为提供额外的 Busy 信号,处理器通过不断查询 Busy 信号来得知 Flash 的擦除操作是否完成,一般较少应用;一 种为查询 Polling 位。
3) NOR Flash 的编程操作
int 29lv160db_ProgFlash(U32 realAddr,U16 data)
{
_WR(0x555,0xaa);
_WR(0x2aa,0x55);
_WR(0x555,0xa0);
_WR(BADDR2WADDR(realAddr),data);
return _WAIT(BADDR2WADDR( realAddr ) ;
}
对擦除过的Flash 进行编程比较简单,但仍然用到以上提到的查询算法,速度比较慢,一般为20uS ,最长的达到500uS 。
二.NAND FLASH
NAND FLASH 在对大容量的数据存储需要中日益发展,到现今,所有 的数码相机、多数MP3播放器、各种类型的U盘、很多PDA 里面都有NAND FLASH 的身影。
1. Flash 的简介
NOR Flash :
u 程序和数据可存放在同一片芯片上,拥有独立的数据总线和地址总线,能快速随机地读取,允许系统直接从Flash 中读取代码执行,而无需先将代码下载至RAM中再执行
u 可以单字节或单字编程,但不能单字节擦除,必须以块为单位或对整片执行擦除操作,在对存储器进行编程之前需要对块或整片进行预编程 和擦除操作。
NAND FLASH
u 以页为单位进行读写操作,1 页为256B 或512B ;以块为单位进行擦除操作,1 块为4KB 、8KB 或16KB 。具有快编程和快擦除的功能
u 数据、地址采用同一总线,实现串行读取。随机读取速度慢且不能按字节随机编程
u 芯片尺寸小,引脚少,是位成本(bit cost )最低的固态存储器
u 芯片存储位错误率较高,推荐使用 ECC 校验,并包含有冗余块,其数目大概 占1% ,当某个存储块发生错误后可以进行标注,并以冗余块代替
u Samsung 、TOSHIBA 和Fujistu 三家公司支持采用NAND 技术NAND Flash 。目前,Samsung 公司推出 的最大存储容量可达8Gbit 。NAND 主 要作为SmartMedia 卡、Compact Flash 卡、PCMCIA ATA 卡、固态盘的存储介质,并正成为Flash 磁盘技术的核心。
2. NAND FLASH 和NOR FLASH 的比较
1) 性能比较
flash 闪存是非易失存储器,可以对称为块的存储器 单元块进行擦写和再编程。任何flash 器件的写入操作只能在空或已擦除的单元内进行,所以大多数 情况下,在进行写入操作之前必须先执行擦除。NAND 器件执行擦除操作是十分简单的,而NOR 则要求在进行擦除前先要将目标块内所有的位都写为0 。
由于擦除NOR 器 件时是以64 ~128KB 的块进行的,执行 一个写入/ 擦除操作的时间为5s ,与此相 反,擦除NAND 器件是以8 ~32KB 的块进行的,执行相同的操作最多只需要4ms 。
执行擦除时块尺寸的不同进一步拉大了NOR 和NADN 之间的性能差距,统计表明,对于给定的 一套写入操作( 尤其是更新小文件时) ,更多 的擦除操作必须在基于NOR 的单元中进行。这样,当选择存储解决方案时,设计师必须权衡以下的各项 因素。
● NOR 的读速度比NAND 稍快一些。
● NAND 的写入速度比NOR 快很多。
● NAND 的4ms 擦除速度远比NOR 的5s 快。
● 大 多数写入操作需要先进行擦除操作。
● NAND 的擦除单元更小,相应的擦除电路更少。
2) 接口差别
NOR flash 带有SRAM 接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。
NAND 器件使用复杂的I/O 口来串行地存取数据,共用8 位总线(各个产品或厂 商的方法可能各不相同)。8 个引脚用来传送控制、地址和数据信息。NAND 读和写操作采用512 字节的页和32KB 的块为单位,这一点有点像硬盘管理此类操作,很自然地,基于NAND 的 存储器就可以取代硬盘或其他块设备。
3) 容量和成本
NAND flash 的单元尺寸几乎是NOR 器件的一半,由于生产过程更为简单,NAND 结 构可以在给定的模具尺寸内提供更高的容量,也就相应地降低了价格,大概只有NOR 的十分之一。
NOR flash 占据了容量为1 ~16MB 闪存市场的大部分,而NAND flash 只是用在8 ~128MB 的产品当中,这也说明NOR 主要应用在代码存 储介质中,NAND 适合于数据存储,NAND 在CompactFlash 、Secure Digital 、PC Cards 和MMC 存储卡市场上所占份额最大。
4) 可靠性和耐用性
采用flahs 介质时一个需要重点考虑的问题是可 靠性。对于需要扩展MTBF 的系统来说,Flash 是 非常合适的存储方案。可以从寿命( 耐用性) 、 位交换和坏块处理三个方面来比较NOR 和NAND 的 可靠性。
在NAND 闪存中每个块的最大擦写次数是一百万 次,而NOR 的擦写次数是十万次。NAND 存 储器除了具有10 比1 的块擦除周期优势,典 型的NAND 块尺寸要比NOR 器件小8 倍,每个NAND 存储器块在给定的时间内的删除次数 要少一些。
5) 位交换(错误率)
所有flash 器件都受位交换现象的困扰。在某些 情况下( 很少见,NAND 发生的次数要比NOR 多) ,一个比特位会发生反转或被报告反转了。一 位的变化可能不很明显,但是如果发生在一个关键文件上,这个小小的故障可能导致系统停机。如果只是报告有问题,多读几次就可能解决了。当然,如果这个位真 的改变了,就必须采用错误探测/ 错误更正(EDC/ECC) 算 法。位反转的问题更多见于NAND 闪存,NAND 的 供应商建议使用NAND 闪存的时候,同时使用EDC/ECC 算 法。
这个问题对于用NAND 存储多媒体信息时倒不是致 命的。当然,如果用本地存储设备来存储操作系统、配置文件或其他敏感信息时,必须使用EDC/ECC 系 统以确保可靠性。
6) 坏块处理
NAND 器件中的坏块是随机分布的。以前也曾有过 消除坏块的努力,但发现成品率太低,代价太高,根本不划算。NAND 器件需要对介质进行初始化扫描 以发现坏块,并将坏块标记为不可用。现在的FLSAH 一般都提供冗余块来代替坏块如发现某个块的数 据发生错误(ECC 校验),则将该块标注成坏块,并以冗余块代替。这导致了在NAND Flash 中,一般都需要对坏块进行编号管理,让每一个块都有自己的逻辑地址。
7) 易于使用
可以非常直接地使用基于NOR 的闪存,可以像其他 存储器那样连接,并可以在上面直接运行代码。由于需要I/O 接口,NAND 要复杂得多。各种NAND 器件的存取方法因厂家 而异。在使用NAND 器件时,必须先写入驱动程序,才能继续执行其他操作。向NAND 器件写入信息需要相当的技巧,因为设计师绝不能向坏块写入,这就意味着在NAND 器件上自始至终都必须进行虚拟映射。
8) 软件支持
当讨论软件支持的时候,应该区别基本的读/ 写/ 擦操作和高一级的用于磁盘仿真和闪存管理算法的软件,包括性能优化。在NOR 器件上运行代码不需要任何的软件支持,在NAND 器 件上进行同样操作时,通常需要驱动程序,也就是内存技术驱动程序(MTD) ,NAND 和NOR 器件在进行写入和擦除操作时都需要MTD 。使用NOR 器件时所需要的MTD 要相对少一些,许多厂商都提供用于NOR 器件的更 高级软件,这其中包括M-System 的TrueFFS 驱 动,该驱动被Wind River System 、Microsoft 、QNX Software System 、Symbian 和Intel 等厂商所采用。驱动还用于对DiskOnChip 产 品进行仿真和NAND 闪存的管理,包括纠错、坏块处理和损耗平衡。
在掌上电脑里要使用NAND FLASH 存储数 据和程序,但是必须有NOR FLASH 来启动。除了SAMSUNG 处 理器,其他用在掌上电脑的主流处理器还不支持直接由NAND FLASH 启动程序。因此,必须先 用一片小的NOR FLASH 启动机器,在把OS 等 软件从NAND FLASH 载入SDRAM 中 运行才行。
9) 主要供应商
NOR FLASH 的主要供应商是INTEL ,MICRO 等厂商,曾经是FLASH 的主 流产品,但现在被NANDFLASH 挤的比较难受。它的优点是可以直接从FLASH 中运行程序,但是工艺复杂,价格比较贵。
NAND FLASH 的主要供应商是SAMSUNG 和东芝,在U 盘、各种存储卡、MP3 播放器里面的都是这种FLASH ,由于工艺上的 不同,它比NORFLASH 拥有更大存储容量,而且便宜。但也有缺点,就是无法寻址直接运行程序, 只能存储数据。另外NAND FLASH 非常容易出现坏区,所以需要有校验的算法。
3 .NAND Flash 的 硬件设计
NAND FLASH 是采用与非门结构技术的非易失存储器, 有8 位和16 位 两种组织形式, 下面以8 位的NAND FLASH 进行讨论。
1) 接口信号
与NOR Flash 相比较,其数据线宽度只有8bit , 没有地址总线,I/O 接口可用于控制命令和地址的输入,也可用于数据的输入和输出,多了CLE 和ALE 来区分总线上的数据类别。
信号 | 类型 | 描述 |
CLE | O | 命 令锁存使能 |
ALE | O | 地 址锁存使能 |
nFCE | O | NAND Flash 片选 |
NFRE | O | NAND Flash 读使 能 |
nFWE | O | NAND Flash 写使 能 |
NCON | I | NAND Flash 配置 |
R/nB | I | NAND Flash Ready/Busy |
2) 地址结构
NAND FLASH 主要以页(page ) 为单位进行读写,以块(block) 为单位进行擦除。FLASH 页 的大小和块的大小因不同类型块结构而不同,块结构有两种:小块(图7 )和大块(图8 ),小块NAND FLASH 包含32 个页,每页512+16 字节;大块NAND FLASH 包含64 页,每页2048+64 字节。
图7 小块类型NAND FLASH
图8 大块类型NAND FLASH
其中,512B (或1024B )用于存放数据,16B (64B )用于存放其他信息(包括:块好坏的标记、块 的逻辑地址、页内数据的ECC 校验和等) 。NAND 设 备的随机读取得效率很低,一般以页为单位进行读操作。系统在每次读一页后会计算其校验和,并和存储在页内的冗余的16B 内 的校验和做比较,以此来判断读出的数据是否正确。
大块和小块NAND FLASH 都有与页大小相同的页寄存器,用于数据缓 存。当读数据时,先从NAND FLASH 内存单元把数据读到页寄存器,外部通过访问NAND FLASH I/O 端口获得页寄存器中数据(地址自动累加);当写数据时,外部通过NAND FLASH I/O 端口输入的数据首先缓存在页寄存器,写命令发出后才写入到内存单元中。
3) 接口电路设计(以下以2410 和K9F1208U 为 例)
2410 处理器拥有专门针对 NAND 设备的接口,可以 很方便地和NAND 设备对接,如图9 所示。 虽然NAND 设备的接口比较简单,容易接到系统总线上,但2410 处 理器针对NAND 设备还集成了硬件ECC 校 验,这将大大提高NAND 设备的读写效率。当 没有处理器的ECC 支持时,就需要由软件来完成ECC 校 验,这将消耗大量的CPU 资源,使读写速度下降。
图9 S3C2410 与NAND FLASH 接口电路示意图
3 .NAND FLASH 的 软件编写和调试
NAND 设备的软件调试一般分为以下几个步骤:设置相关 寄存器、NAND 设备的初始化、NAND 设 备的识别、NAND 设备的读擦写(带ECC 校 验 )
NAND 设备的操作都是需要通过命令来完成,不同厂家的 命令稍有不同,以下一Samsung 公司的K9F1208U0M 命令表为例介绍NAND 设备的软件编写。
表2 K9F1208U0M Command Sets
1) 根据2410 寄存器定义如下的命令宏
#define NF_CMD(cmd) {rNFCMD=cmd;}
#define NF_ADDR(addr) {rNFADDR=addr;}
#define NF_nFCE_L() {rNFCONF&=~(1<<11);}
#define NF_nFCE_H() {rNFCONF|=(1<<11);}
#define NF_RSTECC() {rNFCONF|=(1<<12);}
#define NF_RDDATA() (rNFDATA)
#define NF_WRDATA(data) {rNFDATA=data;}
#define NF_WAITRB() {while(!(rNFSTAT&(1<<0)));}
//wait tWB and check F_RNB pin.
2) NAND 设备的初始化
static void NF_Init(void) //Flash 初始化
{
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0); // 设置NAND 设备的相关寄存器
// 1 1 1 1, 1 xxx, r xxx, r xxx
// En 512B 4step ECCR nFCE="H" tACLS tWRPH0 tWRPH1
NF_Reset();
}
static void NF_Reset(void) //Flash 重置
{
int i;
NF_nFCE_L();
NF_CMD(0xFF); //reset command
for(i=0;i<10;i++); //tWB = 100ns
NF_WAITRB(); //wait 200~500us;
NF_nFCE_H();
}
3) NAND 设备的识别 //#define ID_K9F1208U0M 0xec76
static U16 NF_CheckId(void) //Id 辨别
{
int i;
U16 id;
NF_nFCE_L();
NF_CMD(0x90);
NF_ADDR(0x0);
for(i=0;i<10;i++); //wait tWB(100ns)
id=NF_RDDATA()<<8; // Maker code(K9F1208U:0xec )
id|=NF_RDDATA(); // Devide code(K9F1208U:0x76 )
NF_nFCE_H();
return id;
}
4) NAND 的擦操作
static int NF_EraseBlock(U32 block)
{
U32 blockPage=(block<<5);
int i;
NF_nFCE_L();
NF_CMD( 0x60 [q1] ); // Erase one block 1st command
NF_ADDR(blockPage&0xff); // Page number="0"
NF_ADDR((blockPage>>8)&0xff);
NF_ADDR((blockPage>>16)&0xff);
NF_CMD( 0xd0 [q2] ); // 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);
return 0;
}
else
{
NF_nFCE_H();
return 1;
}
}
5) NAND 的读操作
static int NF_ReadPage(U32 block,U32 page,U8 *buffer) // 读Flash
{
int i;
unsigned int blockPage;
U8 ecc0,ecc1,ecc2;
U8 *bufPt=buffer;
U8 se[16];
page=page&0x1f; //32 页
blockPage=(block<<5)+page; //1Bolck 包含32 页
NF_RSTECC(); // Initialize ECC
NF_nFCE_L();
NF_CMD(0x00); // Read command
NF_ADDR(0); // Column = 0
NF_ADDR(blockPage&0xff); //
NF_ADDR((blockPage>>8)&0xff); // Block & Page num.
NF_ADDR((blockPage>>16)&0xff); //
for(i=0;i<10;i++); //wait tWB(100ns)
NF_WAITRB(); // Wait tR(max 12us)
for(i=0;i<512;i++)
{
*bufPt++=NF_RDDATA(); // Read one page
}
/************************ECC 校验 ***************************/
ecc0=rNFECC0; // 利用2410 自带的硬件ECC 校验
ecc1=rNFECC1;
ecc2=rNFECC2;
[q3] for(i=0;i<16;i++)
{
se[i]=NF_RDDATA(); // Read spare array
// 读页内冗余的16B
}
NF_nFCE_H();
if(ecc0==se[0] && ecc1==se[1] && ecc2==se[2]) // 未知使用哪一种软件规 范?
{ // 比较数据结果是否正确
Uart_Printf("[ECC OK:%x,%x,%x]/n",se[0],se[1],se[2]);
return 1;
}
else
{
Uart_Printf("[ECC ERROR(RD):read:%x,%x,%x, reg:%x,%x,%x]/n",
se[0],se[1],se[2],ecc0,ecc1,ecc2);
return 0;
}
}
6) NAND 的写操作
static int NF_WritePage(U32 block,U32 page,U8 *buffer) // 写Flash
{
int i;
U32 blockPage=(block<<5)+page;
U8 *bufPt=buffer;
NF_RSTECC(); // Initialize ECC
NF_nFCE_L();
NF_CMD( 0x0 [q4] ); //?????//Read Mode 1
NF_CMD(0x80); // Write 1st command, 数 据输入
NF_ADDR(0); // Column 0
NF_ADDR(blockPage&0xff);
NF_ADDR((blockPage>>8)&0xff); // Block & page num.
NF_ADDR((blockPage>>16)&0xff);
for(i=0;i<512;i++)
{
NF_WRDATA(*bufPt++); // Write one page to NFM from buffer
}
seBuf[0]=rNFECC0;
seBuf[1]=rNFECC1;
seBuf[2]=rNFECC2;
seBuf[5]=0xff; // Marking good block
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]); // Write spare array(ECC and Mark)
}
NF_CMD(0x10); // Write 2nd command
for(i=0;i<10;i++); //tWB = 100ns. ??????
NF_WAITRB(); //wait tPROG 200~500us;
NF_CMD(0x70); // Read status command
for(i=0;i<3;i++); //twhr=60ns
if (NF_RDDATA()&0x1) // Page write error
{
NF_nFCE_H();
Uart_Printf("[PROGRAM_ERROR:block#=%d]/n",block);
return 0;
}
else
{
NF_nFCE_H();
#if (WRITEVERIFY==1)
//return NF_VerifyPage(block,page,pPage);
#else
return 1;
#endif
}
}
以下讨论一下NAND 设备上所支持的文件系统,大概现在有以下几种:
A. JFFS2 (没有坏块处理,支持大容量存储的时候需要消耗大量的内存,大量的 随机访问降低了NAND 设备的读取效率)和YAFFS (速 度快,但不支持文件的压缩和解压)
B. 支持DiskOnChip 设备的TRUEFFS (True Flash File System ). TRUEFFS 是M-Systems 公司为其产品DiskOnChip 开 发的文件系统,其规范并不开放。
C. 由SSFDC (Solid State Floppy Disk Card )论坛定义的支持SM 卡的DOS-FAT 。SM 卡的DOS-FAT 文件系统是由SSFDC 论坛定义的,但它 必须用在标准的块设备上。
对于大量用在各类存储卡上的NAND 设备而言,他们几乎都采用FAT 文件系统,而在嵌入式操作系统下,还没有驱动程序可以直接让NAND 设 备采用文件系统,就技术角度来说,FAT 文件系统不是很适合NAND 设 备,因为FAT 文件系统的文件分区表需要不断地擦写,而NAND 设 备的只能有限次的擦写。
在上面已经很明显的提到,NAND 设备 存在坏块,为和上层文件系统接口,NAND 设备的驱动程序必须给文件系统提供一个可靠的存储空间, 这就需要ECC (Error Corection Code )校验,坏块标注、地址映射等一系列的技术手段来达到可靠存储目的。
SSFDC 软件规范中,详细定义了如何利用NAND 设备每个页中的冗余信息来实现上述功能。这个软件规范中,很重要的一个概念就是块的逻辑地址,它将在物 理上可能不连续、不可靠的空间分配编号,为他们在逻辑空间上给系统文件提供一个连续可靠的存储空间。
表3 给出了SSFDC 规 范中逻辑地址的标注方法。在系统初始化的时候,驱动程序先将所有的块扫描一遍,读出他们所对应的逻 辑地址,并把逻辑地址和虚拟地址的映射表建好。系统运行时,驱动程序通过查询映射表,找到需要访问的逻辑地址所对应的物理地址然后进行数据读写。
表3 冗余字节定义
字节序号 | 内容 | 字节序号 | 内容 |
512 | 用户定义数据 | 520 | 后256BECC 校验和 |
513 | 521 | ||
514 | 522 | ||
515 | 523 | 块逻辑地址 | |
516 | 数据状态 | 524 | |
517 | 块状态 | 525 | 前256BECC 校验和 |
518 | 块逻辑地址1 | 526 | |
519 | 527 |
表4 给出了块逻辑地址的存放格式,LA 表 示逻辑地址,P 代表偶校验位。逻辑地址只有10bit , 代表只有1024bit 的寻址空间。而SSFDC 规 范将NAND 设备分成了多个zone ,每个zone 内有1024 块,但这物理上的1024 块映射到逻辑空间只有1000 块,其他的24 块就作为备份使用,当有坏块存在时,就可以以备份块将其替换。
表4 逻辑地址格式
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|
0 | 0 | 0 | 1 | 0 | LA9 | LA8 | LA7 | 第518 523 字节 |
LA6 | LA5 | LA4 | LA3 | LA2 | LA1 | LA0 | P | 第519 524 字节 |
有了以上的软件规范,就可以对NAND 设备写出较标准的ECC 校验,并可以编写检测坏块、标记坏块、建立物理地址和逻辑地址的映射表的程序了。
static int NF_IsBadBlock(U32 block) // 检测坏块
{
int i;
unsigned int blockPage;
U8 data;
blockPage=(block<<5); // For 2'nd cycle I/O[7:5]
NF_nFCE_L();
NF_CMD(0x50); // Spare array read command
NF_ADDR(517&0xf); // Read the mark of bad block in spare array(M addr="5") [q6]
NF_ADDR(blockPage&0xff); // The mark of bad block is in 0 page
NF_ADDR((blockPage>>8)&0xff); // For block number A[24:17]
NF_ADDR((blockPage>>16)&0xff); // For block number A[25]
for(i=0;i<10;i++); // wait tWB(100ns) //?????
NF_WAITRB(); // Wait tR(max 12us)
data=NF_RDDATA();
NF_nFCE_H();
if(data!=0xff)
{
Uart_Printf("[block %d has been marked as a bad block(%x)]/n",block,data);
return 1;
}
else
{
return 0;
}
}
static int NF_MarkBadBlock(U32 block) // 标记坏块
{
int i;
U32 blockPage=(block<<5);
seBuf[0]=0xff;
seBuf[1]=0xff;
seBuf[2]=0xff;
seBuf[5]=0x44; // Bad blcok mark="0" [q7]
NF_nFCE_L();
NF_CMD(0x50); //????
NF_CMD(0x80); // Write 1st command
NF_ADDR(0x0); // The mark of bad block is
NF_ADDR(blockPage&0xff); // marked 5th spare array
NF_ADDR((blockPage>>8)&0xff); // in the 1st page.
NF_ADDR((blockPage>>16)&0xff);
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]); // Write spare array
}
NF_CMD(0x10); // Write 2nd command
for(i=0;i<10;i++); //tWB = 100ns. ///???????
NF_WAITRB(); // Wait tPROG(200~500us)
NF_CMD(0x70);
for(i=0;i<3;i++); //twhr=60ns??????
if (NF_RDDATA()&0x1) // Spare arrray write error
{
NF_nFCE_H();
Uart_Printf("[Program error is occurred but ignored]/n");
}
else
{
NF_nFCE_H();
}
Uart_Printf("[block #%d is marked as a bad block]/n",block);
return 1;
}
int search_logic_block(void) // 建立物理地址到逻
// 辑地址的映射表
{
unsigned int block,i,blockPage,logic_no,zone,zone_i;
U8 SE[16];
for(i=0;i<BLOCK_NR;i++) // 初始化全局变量
lg2ph[i]=space_block[i]=0xffff;
logic_number=0;
space_nr=0;
NF_nFCE_L();
zone=BLOCK_NR/1024; // 确定NAND 设备中zone
// 的个数
for(zone_i=0;zone_i<zone;zone_i++)
{
// 搜索每 个zone 内逻辑地址和物理地址的映射关系
for(block=0;block<1024;block++)
{
blockPage=((block+zone_i*1024)<<BLOCK_ADDRERSS_SHIFT);
NF_WATIRB(); // 等待R/B# 信号有效
NF_CMD(0x50); // 读取每个block 内部第
//0 个Page 内冗余的16 个字节
NF_ADDR(0); // Column 0
NF_ADDR(blockPage&0xff);
NF_ADDR((blockPage>>8)&0xff); // Block & page num.
NF_ADDR((blockPage>>16)&0xff);
NF_WATIRB(); // 等待R/B# 信号有效
for(i=0;i<16;i++) se[i]=NF_RDDATA(); // Write spare array
NF_WATIRB();
if( se[5]!=0xff) [q8] // 检测是否存在坏块
printk("/n/rphysic block %d is bad block/n/r",block);
else if( se[7]!=se[12] [q9] )
printk("block address1:%d!=block address2 %d/n/r",se[7],se[12]);
else if(( se[6] [q10] &0xf8)==0x10)
{
// 计 算该block 对应的逻辑地址
logic_no=((0x7&se[6])<<7)+(se[7]>>1)+zone_i*1000;
if(lg2ph[logic_no]!=0xffff) // 说明有2 个block 拥 有相
// 同的逻辑地址
printk("physical block %d and block %d have the same logic number %d/n",lg2ph[logic_no],block,logic_no);
else lg2ph[logic_no]=block; // 将该block 的逻辑地址
// 关系记入lg2ph 表
logic_number++;
}
else if(se[7]==0xff) // 说明 该block 尚未编号
{space_block[space_nr]=block;
space_nr++;
}
}
}
printk("there are totally %d logic blocks/n/r",logic_number);
NF_nFCE_H();
return logic_number;
}
这段代码的主要作用就是产生数组lg2ph[], 这个数组的含义就是“块物 理地址=lg2ph[ 逻辑地址] ”。