一,nand flash简介
1,相关名词的解释
1.1 (bad)Block Management(坏)块管理
Nand Flash由于其物理特性,只有有限的擦写次数,超过那个次数,基本上就是坏了。在使用过程中,有些Nand Flash的block会出现被用坏了,当发现了,要及时将此block标注为坏块,不再使用。于此相关的管理工作,属于Nand Flash的坏块管理的一部分工作。
1.2 Wear-Leveling 负载平衡
Nand Flash的block的管理,还包括负载平衡。正是由于 Nand Flash的block,都是有一定寿命限制的,所以如果你每次都往同一个block擦除然后写入数据,那么那个block就很容易被用坏了,所以我们要去管理一下,将这么多次的对同一个block的操作,平均分布到其他一些block上面,使得在block的使用上,相对较平均,这样相对来说,可以更
能充分利用Nand Flash。关于wear-leveling这个词,再简单解释一下,wear就是穿(衣服)等,用(东西)导致磨损,而leveling就是使得均衡,所以放在一起就是,使得对于Nand Flash的那么多的block的使用磨损,相对均衡一些,以此延长Nand Flash的使用寿命或者说更加充分利用Nand Flash。
1.3 ECC错误校验码
Nand Flash物理特性上使得其数据读写过程中会发生一定几率的错误,所以要有个对应的错误检测和纠正的机制,于是才有此ECC,用于数据错误的检测与纠正。Nand Flash的ECC,常见的算法有海明码和BCH,这类算法的实现,可以是软件也可以是硬件。不同系统,根据自己的需求,采用对应的软件或者是硬件。
相对来说,硬件实现这类ECC算法,肯定要比软件速度要快,但是多加了对应的硬件部分,所以成本相对要高些。如果系统对于性能要求不是很高,那么可以采用软件实现这类ECC算法,但是由于增加了数据读取和写入前后要做的数据错误检测和纠错,所以性能相对要降低一些,即Nand Flash的读取和写入速度相对会有所影响。(使用软件的话,可以参照yaffs2中ecc算法);
1.4 OOB/Spare Area
每个页,相应另一块区域。叫做空暇区域(SpareArea)。在Linux系统中。一般叫做OOB(Out of Band)。
数据在读写的时候相对easy错误,所以为了保证数据的正确性。必需要有相应的检測和纠正机制,此机制叫做ECC/EDC,所以设计了多余的区域,用于存放数据的校验值。OOB的读写是随着随着页的操作一起完毕的。
OOB的详细用途包含下面几个方面:
► 标记所处的block是否为坏块
► 存储ECC数据
► 存储一些和文件系统相关的数据。如jaffs2就会用到这些空间存储一些特定信息。而yaffs2文件系统。会在oob中存放非常多和自己文件系统相关的信息
1.5容量计算
NAND芯片内部分为die, plane,block, page
Chip是指芯片,一个封装好的芯片就是一个chip
Die(internal chip)是晶圆上的小方块,一个芯片里可能封装若干个die,因为flash的工艺不一样,技术不一样。由此产生了die的概念。常见的有Mono Die,a Die。 b die等,一个chip包括N个die。
Plane是NAND可以依据读、写、擦除等命令进行操作的最小单位,一个plane就是一个存储矩阵。包括若干个Block。
Block是NANDFlash的最小擦除单位,一个Block包括了若干个Page。
Page是NANDFlash的最小读写单位。一个Page包括若干个Byte。
Chip >Die > plane > block > page
pageSZblockPNplaneBNdiePNdieNumber=ChipSZ;
已华邦的W29N02GVSIAA为例:
2K64102421= 256MB;
1.6 芯片ID
每个厂家的ID内容都有一些不同,但是关键参数基本是一致的,所以在做兼容的时候需要以芯片厂家为单位做区分和自动识别;
nandflash 有两个ID地址 00h(device identifier code) 20h(ONFI identifier code);一般只看00h地址中的ID,它包含了芯片组成结构的基本信息,用于初始化nandflash的驱动;00h可以读出5个字节(通常只使用4个字节)
1.7 读写地址计算
假设,我们要访问其中的第100个块中的第64页中的1208字节处的地址,此时,我们就要先把具体的
地址算出来:
物理地址
=块大小×块号 + 页大小×页号 + 页内地址
=128K×100 + 2K×64 + 1208
=0xCA04B8
但是nandflash地址需要做一些转换,因每个页里面都有oob区域,读数据时需要跳过这些地址,要读oob时则需要选择到oob的页内偏移,所以nand把地址分为了页内地址和页地址
对应的就是:
Column address(列地址,页内地址)=1208=0x4b8
RowAddress(行地址,页地址)=(128K/2K)100 + 64 = 0x1940
所以发送的地址为0x194004b8。
oob如何访问呢?oob是连续在main区域的后面所以只需要知道页地址就可以了即:
oob区域的地址:
Column address(列地址,页内地址)=page size=2048=0x800
RowAddress(行地址,页地址)=(128K/2K)100 + 64 = 0x1940
1.8 ECC校验
ECC的全称是Error Checking and Correction,是一种用于Nand的差错检测和修正算法。如果操作时序和电路稳定性不存在问题的话,NAND Flash出错的时候一般不会造成整个Block或是Page不能读取或是全部出错,而是整个Page(例如512Bytes)中只有一个或几个bit出错。ECC能纠正1个比特错误和检测2个比特错误,而且计算速度很快,但对1比特以上的错误无法纠正,对2比特以上的错误不保证能检测。
ECC一般每256字节原始数据生成3字节ECC校验数据,包含列校验和行校验。对每个待校验的bit位求异或,若结果为0,则表示有偶数个1,反则有奇数个1。这三字节共24比特分成两部分:6比特的列校验和16比特的行校验,多余的两个比特置1,如下图所示:
ECC的列校验、行校验和生成规则如下图所示:
列校验用数学表达式表示为:
P1=D6(+)D4(+)D2(+)D0 表示第0、2、4、6列的异或操作 P1=D7(+)D5(+)D3(+)D1表示第1、3、5、7列的异或操作 P2=D5(+)D4(+)D1(+)D0 表示第 0、1、4、5列的异或操作
P2=D7(+)D6(+)D3(+)D2表示2、3、6、7列的异或操作
P4`=D3(+)D2(+)D1(+)D0表示0、1、2、3列的异或操作
P4=D7(+)D6(+)D5(+)D4表示4、5、6、7列的异或操作
这里(+)表示“位异或”操作
行验算用数学表达式表示为:
P8’=BYTE0+BYTE2+BYTE4….+BYTE254 表示对第0、2、4、6、8、….254字节进行异或操作
P8=BYTE1+BYTE3+BYTE5….+BYTE255 表示对第1、3、5、7、9、….255字节进行异或操作
P16’=BYTE0+BYTE1+BYTE4+BYTE5….BYTE252+BYTE253 表示对第0字节开始间隔2字节进行异或操作
P16=BYTE2+BYTE3+BYTE6+BYTE7….BYTE254+BYTE255 表示对第2字节开始间隔2字节进行异或操作
P32 表示间隔4字节进行异或
P64 表示间隔8字节进行异或
P128 表示间隔16字节进行异或
P256 表示间隔32字节进行异或
P512 表示间隔64字节进行异或
P1024 表示间隔128字节进行异或
二,MTD层
MTD是Memory Technology Device的缩写,它是底层硬件和上层软件之间的桥梁。对底层来说,它无论对NOR型或是NAND型都有很好的驱动支持;对上层来说,它抽象出文件系统所需要的接口函数。rt-thread环境中没有那么复杂,去掉了Linux MTD中很多无法使用的属性,只剩下了必要的与硬件相关的参数:
在mtd_nand.h中定义了这些属性:
struct rt_mtd_nand_device
{
struct rt_device parent;
rt_uint16_t page_size; /* The Page size in the flash /
rt_uint16_t oob_size; / Out of bank size /
rt_uint16_t oob_free; / the free area in oob that flash driver not use /
rt_uint16_t plane_num; / the number of plane in the NAND Flash /
rt_uint32_t pages_per_block; / The number of page a block /
rt_uint16_t block_total;
/ Only be touched by driver /
rt_uint32_t block_start; / The start of available block*/
rt_uint32_t block_end; /* The end of available block /
/ operations interface */
const struct rt_mtd_nand_driver_ops *ops;
};
struct rt_mtd_nand_driver_ops
{
rt_err_t (*read_id)(struct rt_mtd_nand_device *device);
rt_err_t (*read_page)(struct rt_mtd_nand_device *device,
rt_off_t page,
rt_uint8_t *data, rt_uint32_t data_len,
rt_uint8_t *spare, rt_uint32_t spare_len);
rt_err_t (*write_page)(struct rt_mtd_nand_device *device,
rt_off_t page,
const rt_uint8_t *data, rt_uint32_t data_len,
const rt_uint8_t *spare, rt_uint32_t spare_len);
rt_err_t (*move_page)(struct rt_mtd_nand_device *device, rt_off_t src_page, rt_off_t dst_page);
rt_err_t (*erase_block)(struct rt_mtd_nand_device *device, rt_uint32_t block);
rt_err_t (*check_block)(struct rt_mtd_nand_device *device, rt_uint32_t block);
rt_err_t (*mark_badblock)(struct rt_mtd_nand_device *device, rt_uint32_t block);
};
在底层驱动中实现这些函数和填写这些变量即可。
三、STM32 FMC时序设置
FMC中操作nandflash的时序参数名能和nandflsh中的参数一一对应,需要做一些调整。
tCLR CLE to #RE Delay
tAR ALE to #RE Delay
FMC还有两个不同时序空间的时序控制:
通用存储器空间的时序寄存器: FMC_PMEM ,这个寄存器是我们读写nand常用的时序空间
特性存储器空间的时序寄存器: FMC_PATT,这个是一些特殊nand需要设置某些参数时需要用到时序空间;
他们有4个值分别是:
MEM_SET, ATT_SET
MEM_HIZ, ATT_HIZ
MEM_WAIT, ATT_WAIT
MEM_HOLD, ATT_HOLD
时序解释如下图所示:
CE无关访问中的tWB=ATT_HOLD;其他的两个空间可以保持一致;
数据手册的原话:要克服该时序限制,可使用特性存储器空间,将其时序寄存器编程为满足 tWB(WE HIGH to Busy) 时序的ATTHOLD 值,并将 MEMHOLD 保持为其最小值。
MEM_SET:建立时间(地址或者数据建立时间);定义使能命令(NWE,NOE)前建立地址所需的时间,可以等价与nandflash中的tWHR(WE HIGH to #RE LOW)
MEM_HIZ:数据总线高阻态时间(只对写入时序有效);定义在对 NAND Flash 的通用存储空间开始执行写访问之后,数据总线保持高阻态所持续的 时间,可以等价与nandflash中TADL(ALE to Data Loading Time);
MEM_WAIT:等待时间;定义使能命令(NWE、NOE)所需的 持续时间的最小值,可以等价于nandflash中tWP(WE Pulse Width),tRP(RE Pulse Width);
MEM_HOLD:保持时间;定义禁止命令(NWE、NOE)之后保持地址(和写访问数据)的写入访问的时间和读取访问的时间;可以等价与nandflash中tCH (CE Hold Time),tDH(Data Hold Time),如果为了兼容CE无关操作,控制器自动等待NWAIT,则使用tWB(WE hight to busy)。
四,FMC 硬件ECC
软件ECC纠错算法生成的字节表为 :byte0(rp0rp7);byte1(rp8rp15);byte2(11 cp0p5)(见1.8的表)
经过实际验证FMC 硬件ECC生成数据(ECC页长度为256,一共只有21位)
FMC ECC tab:bit0bit5:cp0cp5;bit6bit13:rp0rp7;bit14bit21:rp8~rp15;
软件ecc与硬件ECC是取反的关系,且排布顺序不一致,因此需要做相应的修改才能兼容软件ECC的校验值。软件ECC为什么要取反呢,因为Nandflash写入0是有效的,写入1是无效,所以在全是0xff的位置硬件ECC的校验值为0x000000,;如果把这个值接接写入nand的oob区,那么这个区域就对应的oob区就无法修改了(因为全是0了),下次写入校验时肯定就报ECC错误。所以要把ECC校验值取反在存储。
FMC的ECC值需要做以下变化才能和软件ECC兼容:变化过程 HECC = ~HECC;HECC=(HECC <<2)|0x03;HECC= (HECC<<16)|(HECC>>16)(byte0与byte2交换位置,伪代码);
五, nandflash芯片识别
最开始的设想也是想着使用ID号去做芯片参数的自识别,然后多看了几家nandflash厂家的芯片 datasheet,发现某些位不同厂家定义不完全相同,不同工艺的nand相同的位置代表的意思也有出入,所以用ID号去自识别是一个不靠谱的做法。最后只能参照uboot做法将要支持的nand写入配置表中:
const struct nand_flash_dev nand_flash_id[]=
{
{"F59L1G81MB 1Gb 3.3v 8bit",{.id={0xC8,0xD1,0x80,0x95}},2048,128,1281024,4,64},
{"F59L2G81XA 2Gb 3.3v 8bit",{.id={0x2C,0xDA,0x90,0x95}},2048,256,1281024,4,128},
{"F59L4G81XB 4Gb 3.3v 8bit",{.id={0x2C,0xDC,0x80,0xA6}},4096,512,2561024,4,256},
{"W29N01HV 1Gb 3.3v 8bit",{.id={0xEF,0xF1,0x00,0x95}},2048,128,1281024,4,64},
{"W29N02KV 2Gb 3.3v 8bit",{.id={0xEF,0xDA,0x10,0x95}},2048,256,1281024,4,128},
{"W29N04GV 4Gb 3.3v 8bit",{.id={0xEF,0xDC,0x90,0x95}},4096,512,1281024,4,64},
};
结构体如下代码:
struct nand_flash_dev {
char *name; //nandflash名称
_nand_id_t u_nand_id; //nandflash ID
unsigned long pagesize; //页大小
unsigned long chipsize; //器件总容量(MB)
unsigned long erasesize; //擦除大小(块大小)
//unsigned long options; //设置量,单片机只提供一种读写方法,不会在做读写上的优化,不适用;
uint16_t id_len; //ID长度
uint16_t oobsize; //oob区大小
// struct {
// uint16_t strength_ds;
// uint16_t step_ds;
// } ecc; //ECC格式 For example, the "4bit ECC for each 512Byte",单片机不需要
// int onfi_timing_mode_default; //the default ONFI timing mode entered after a NAND reset. Should be deduced from timings described in the datasheet.
};
/nand属性结构体/
struct nand_attriute
{
uint8_t u8IdInfIndex; //id 消息索引
uint32_t u32ChipSize; //容量,单位MB
uint32_t u32PageSize; //页大小
uint32_t u32BlockSzie; //块大小
uint32_t u32BlockPageNum;//块包含的页数
uint32_t u32BlockNum; //块的数量
uint32_t u32oobSize; //oob区大小
};
在初始化完FMC接口后,去读取nand的ID号,与自己的tab比较就知道现在焊接的是什么nand芯片了。
原作者:yoyotansa