Nand Flash原理(二)
K9F2G08U0B的存储阵列
图 2-1 K9F2G08U0B的存储阵列
由图2-1,我们可以知道:K9F2G08U0B的一页为(2K+64)字节(2K 表示的是 main 区容量,64表示的是 spare 区容量),它的一块为 64 页,而整个设备包括了2048个块。这样算下来一共有 2112M 位容量,如果只算 main 区容量则有256M 字节(即 256M×8 位)。
要实现用 8 个 IO 口来要访问这么大的容量,如图 2-1 所示:K9F2G08U0A 规定了用 5 个周期来实现。第一个周期访问的地址为 A0-A7;第二个周期访问的地址为A8-A11,它作用在 IO0-IO3 上,而此时 IO4-IO7 必须为低电平;第三个周期访问的地址为 A12-A19;第四个周期访问的地址为 A20-A27;第五个周期访问的地址为 A28,它作用在
IO0上,而此时IO1~IO7 必须为低电平。前两个周期传输的是列地址,后三个周期传输的是行地址。
通过分析可知,
1、块寻址:K9F2G08U0B由2048个block组成,那么块寻址需要11位地址线进行寻址即A18-28(2^(28-18+1)=2048块)。
2、页寻址:一个block由64页组成,那么页寻址需要6位地址线进行寻址即A12-A17(2^6=64)
3、页内字节寻址(即上图中列地址) :页大小为2KB,只需要11位地址线进行寻址A0-10(2^11=2048个地址),A11作为页内地址扩展未使用。
由于所有的命令、地址和数据全部从8 位 IO 口传输,所以 Nand flash 定义了一个命令集来完成各种操作。
K9F2G08U0B的命令说明
图2-2 K9F2G08U0B命令表
图2-2是K9F2G08U0B芯片操作读写、擦除等操作的命令表。由于时序都有S3C2440的nand控制器控制。所以,这里的nand驱动。只要好好弄明白K9F2G08U0B这两个要点,就很容易掌握nand驱动。
二、硬件
在S3c2440中可通过NCON0、GPG13-15引脚来设置nand flash控制器所支持nand flash类型
图1实际芯片型号K9F2G08U0B 图2引脚配置
由上图可知
GPG13=3.3V(高电平),GPG14=3.3V(高电平)
GPG15=0.3V(按照图3的ARM芯片直流电气特性定义<0.8V,为低电平),NCON=3.3V(高电平)
根据GPG13-15、NCON引脚配置,配置nand flash存储器(配置表详见下面)
nand flash 存储器配置表
由上配置表可知:nand flash配置成:
1、先进nand(NCON0=1)
2、页容量2K字节(GPG13=1)
3、5个地址周期(GPG14=1)
4、8位宽(GPG15=0)
小结:上面nand flash配置结果与K9F2G08U0B一致。
三、软件
3.1初始化(时序图参数计算)
设置时序,其实是设置NFCONF 配置寄存器。S3C2440内部nand flash控制器时序图
TACLS:表示CLE/ALE的建立时间(setup time)。
TWRPH0:表示写控制信号nWE使能的持续时间。
TWRPH1:表示写控制信号new禁止到 CLE/ALE关闭的时间。
NFCONF配置寄存器
K9F2G08U0B下面的相关时序图
K9F2G08U0B对应时序参数
由上面两个时序图对比可知:TACLS就相当于tCLS或tALS参数,TWRPH0就相当于tWP,而TWRPH1就相当于tCLH或tALH。
其中:HCLK=100MHz(即10ns),TX2440开发板使用电源为3.3V,则tCLS=tALS=12ns(最小值),tWP=12ns(最小值),tCLH=tALH=5ns(最小值)。如果希望nand flash能正常读写操作,时序配置参数必须大于这些最小值。
验证程序中对nand flash的NFCONF寄存器 时序参数配置是否合适?
在nand flash的NFCONF寄存器中
1、TACLS=NFCONF[13:12]=3,即HCLK*TACLS=30ns >12ns(表中tCLSmin)
2、TWRPH0=NFCONF[10:8]=7 即HCLK*(TWRPH0+1)=80ns >12ns(表中tWPmin)
2、TWRPH1=NFCONF[6:4]=7 即HCLK*(TWRPH1+1)=80ns >5ns(表中tCLHmin),一般TWRPH1设置为0,也满足>5ns条件。
综上所述:设置nand flash寄存器的时序满足K9F2G08U0B要求。
3.2 读取ID
Nand芯片的每一个型号,都有固定的芯片ID和制造商ID。用户通过读取ID,确认是什么类型的nand芯片。
下表是K9F2G08U0B读取ID的代码。过程如下
1、 激活芯片片选
2、 写入复位命令,芯片复位(记得等待芯片内部操作完成)。复位命令不是必须的。
3、 写入读取芯片ID命令(0x90),然后写入地址0x00
4、 读取芯片ID(ID信息总共5个信息,每当从NF_RDDATA8读取一个信息后,NF_RDDATA8获取下一个的信息)
5、 关掉片
读取ID时序图
源码:
U32 ReadChipId(void)
{
U32 id;
unsigned char Makercode,Devcode,ID3rd,ID4rd,ID5rd;
NF_ChipEn(); //片选使能
NF_CMD(RdIDCMD); //写读nand flash IDC命令0x90
NF_ADDR(0);//写地址0x00
while(NFIsBusy());//判断nand flash是否busy?若busy,则继续等待。
Makercode = NF_RDDATA8();
Devcode = NF_RDDATA8();
ID3rd = NF_RDDATA8();
ID4rd = NF_RDDATA8();
ID5rd = NF_RDDATA8();
NF_ChipDs();//禁止片选使能
if((Makercode == 0xec) && (Devcode=0xda) && (ID3rd == 0x10) &&
(ID3rd == 0x10) && (ID4rd == 0x95) && (ID5rd == 0x44))
Uart_Printf("\nK9F2G08U0B\n");
Uart_Printf("Makercode=%x,Devcode=%x,ID3rd=%x,ID4rd=%x,ID5rd=%x\n",Makercode,Devcode,ID3rd,I
由上图可知读取ID信息与K9F2G08U0B中ID信息一致。
3.3块擦除
nandflash擦除操作以块为单位,对任何Flash闪存存储器进行写操作之前,都必须先进行擦除,然后写入。读写操作是页为单位
快擦除操作步骤(参考下面时序图):
1、nand falsh芯片使能
2、写擦除命令 0x60
3、写 需要擦除的块地址(块地址只需要行地址[28:13])
4、写擦除命令 0xD0
5、nand flash 忙检测,若处于busy,则等待至不忙ready。
6、写读取状态命令0x70
8、操作成功判断,首先通过IO[6]做忙状态检测,然后IO[0]位进行判断是否成功(为什么是IO0位,请参考下面读取70h状态的返回值定义)。
9、关闭芯片使能
块擦除时序图
读取70h状态后,返回值说明(由K9F2G08U0B的datasheet提供)
块擦除源码
/*********************************************************
**函数名称:U8 EraseBlock_2G08(U32 block_number)
**函数功能:nand flash的块擦除
**入口参数:block---块号
**出口参数:无
**返回值 : 擦除成功标志位
1、Earse_ok表示擦除成功
2、Earse_fail表示擦除失败
3、Markbad_fail表示标注失败
************************************************************/
U8 EraseBlock_2G08(U32 block)
{
char stat, temp;
temp = IsBadBlock_2G08( block); //判断该块是否为坏块 ,这些语句测试通过
if(temp == Isbad_ok){ return Isbad_ok;} //是坏块,返回
NF_ChipEn(); //打开片选
NF_CLEAR_RB(); //清RnB信号
/*擦除命令0x60*/
NF_CMD(ERASECMD0); //擦除命令周期1
//写入块地址的3个地址周期,从A18开始写起
NF_ADDR((block << 6) & 0xff); //行地址A18~A19
NF_ADDR((block >> 2) & 0xff); //行地址A20~A27
NF_ADDR((block >> 10) & 0xff); //行地址A28
/*擦除命令0xD0*/
NF_CMD(ERASECMD1); //擦除命令周期2
NF_DETECT_RB_my();//nand flahs的busy状态检测
/*读取状态0x70*/
NF_CMD(QUERYCMD); //读状态命令
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
NF_ChipDs(); //关闭nandflash片选
//判断状态值的第0位是否为0,为0则擦除操作正确,否则错误
if (stat & 0x1)
{
temp = MarkBadBlock_2G08(block); //标注该块为坏块
if (temp == Markbad_fail)
return Markbad_fail; //标注坏块失败
else
return Earse_fail; //擦除操作失败
}
else
return Earse_ok; //擦除操作成功
}
建议:在读取状态70h返回值时,加入忙状态检测(通过IO[6]位)
3.4 写入数据
nand falsh支持以页为单位写入、随机写入两种数据方式。
3.4.1 页写入
时序图
源码
/*********************************************************
**函数名称:static int WritePage_2G08(U32 block,U32 page,U8 *buffer)
**函数功能:在nand flash芯片中页读功能
**入口参数:
1、block---- 块号
2、page-----页号
3、*buffer----------存放读取整页的数据缓冲区
**出口参数:无
**返回值 : 写成功标志位
1、Write_ok表示写入成功
2、Write_fail表示写入失败
************************************************************/
static int WritePage_2G08(U32 block,U32 page,U8 *buffer)
{
int i;
U32 blockpage, Mecc, Secc;
U8 *bufPt=buffer,temp;
blockpage=(block<<6)+page;
temp = IsBadBlock_2G08(block); //判断该块是否为坏块
if(temp == Isbad_ok){return Isbad_ok ; } //是坏块,返回
NF_RSTECC(); // Initialize ECC
NF_MECC_UnLock();
NF_ChipEn();
NF_CMD(PROGCMD0); // Write 1st command
NF_ADDR(0); //Column (A[7:0]) = 0
NF_ADDR(0); // A[11:8]
NF_ADDR((blockpage)&0xff); // A[19:12]
NF_ADDR((blockpage>>8)&0xff); // A[27:20]
NF_ADDR((blockpage>>16)&0xff); //A[28]
for(i=0;i<2048;i++)
{
NF_WRDATA8(*bufPt++); // Write one page data from buffer
}
NF_CLEAR_RB();
NF_CMD(PROGCMD1); // Write 2nd command
NF_DETECT_RB();
NF_CMD(QUERYCMD); // Read status command
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
do{
temp = NF_RDDATA8();
}while(!(temp&0x40));
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
if (temp&0x1)
{
NF_ChipDs();
Uart_Printf("[PROGRAM_ERROR:block#=%d]\n",block);
MarkBadBlock_2G08(block);
return Write_fail;
}
else
{
NF_ChipDs();
return Write_ok;
}
3.4.2随机写入
时序图
源码:
/*********************************************************
**函数名称:RamdomWrite_2G08(U32 blockpage, U32 page_add, U8 data)
**函数功能:在nand flash芯片中随机写
**入口参数:
1、blockpage--页号(由块和页信息组成),blockpage=(block<<6)+page;
2、page_add-----页内地址
3、data----------写入的数据
**出口参数:无
**返回值 : 写成功标志位
1、Write_ok表示写入成功
2、Write_fail表示写入失败
************************************************************/
U8 RamdomWrite_2G08(U32 blockpage, U32 page_add, U8 data)
{
U8 temp,stat;
NF_ChipEn(); //打开nandflash片选
NF_CLEAR_RB(); //清RnB信号
//随机写命令80h
NF_CMD(PROGCMD0);
//写入5个地址周期
NF_ADDR(0x00); //列地址A0~A7
NF_ADDR(0x00); //列地址A8~A11
NF_ADDR((blockpage) & 0xff); //行地址A12~A19
NF_ADDR((blockpage >> 8) & 0xff); //行地址A20~A27
NF_ADDR((blockpage >> 16) & 0xff); //行地址A28
//随机写命令85h
NF_CMD(PROGCMD2);
//页内地址
NF_ADDR((U8)(page_add&0xff)); //列地址A0~A7
NF_ADDR((U8)((page_add>>8)&0x0f)); //列地址A8~A11
NF_WRDATA8(data); //写入数据
//写第二写命令10h
NF_CMD(PROGCMD1); //页写命令周期2
NF_DETECT_RB_my(); //busy检测
NF_CMD(QUERYCMD); //读状态命令
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
NF_ChipDs(); //关闭nandflash片选
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
if (stat & 0x1){return Write_fail; } //失败
else {return Write_ok; } //成功
}
3.5读取数据
3.5.1页读取
源码
/*********************************************************
**函数名称:static int ReadPage_2G08(U32 block,U32 page,U8 *buffer)
**函数功能:在nand flash芯片中页读功能
**入口参数:
1、block---- 块号
2、page-----页号
**出口参数:*buffer----------存放读取整页的数据缓冲区
**返回值 : 无
************************************************************/
static int ReadPage_2G08(U32 block,U32 page,U8 *buffer)
{
int i;
unsigned int blockpage;
U32 Mecc, Secc;
U8 *bufPt=buffer,temp;
U8 mainECC0, mainECC1, mainECC2, mainECC3,spareECC0,spareECC1;
blockpage=(block<<6)+page;//将block信息用页形式表示
NF_RSTECC(); // Initialize ECC
NF_MECC_UnLock();//解锁main区ECC
NF_ChipEn(); //打开nand flash片选使能
NF_CLEAR_RB();//清除RnB信号
NF_CMD(READCMD0); // Read command 0x00
NF_ADDR(0); // Column = 0
NF_ADDR(0);
NF_ADDR(blockpage&0xff); //行地址A12-A19
NF_ADDR((blockpage>>8)&0xff); // 行地址A20-A27
NF_ADDR((blockpage>>16)&0xff); //行地址A28
NF_CMD(READCMD3);//页读命令0x30
NF_DETECT_RB();//等到RnB信号变高,即不忙
for(i=0;i<2048;i++)
{
*bufPt++=NF_RDDATA8(); // Read one page
}
NF_ChipDs(); //关闭使能
}
3.5.2随机读取
时序图
源码
/*********************************************************
**函数名称:RamdomRead_2G08(blockpage, page_add)
**函数功能:在nand flash芯片中随机读
**入口参数:
1、blockpage--页号(由块和页信息组成),blockpage=(block<<6)+page;
2、page_add-----页内地址
**出口参数:无
**返回值 : 读取的数据
************************************************************/
U8 RamdomRead_2G08(U32 blockpage,U32 page_add)
{
NF_ChipEn(); //打开nandflash片选
NF_CLEAR_RB(); //清RnB信号
//随机读命令00h
NF_CMD(READCMD0);
//写入5个地址周期
NF_ADDR(0x00); //列地址A0~A7
NF_ADDR(0x00); //列地址A8~A11
NF_ADDR((blockpage) & 0xff); //行地址A12~A19
NF_ADDR((blockpage >> 8) & 0xff); //行地址A20~A27
NF_ADDR((blockpage >> 16) & 0xff); //行地址A28
//随机读命令30h
NF_CMD(READCMD3);
NF_DETECT_RB_my();
//随机读命令05h
NF_CMD(READCMD4);
//页内地址
NF_ADDR((U8)(page_add&0xff)); //列地址A0~A7
NF_ADDR((U8)((page_add>>8)&0x0f)); //列地址A8~A11
//随机读命令E0h
NF_CMD(READCMD5);
return NF_RDDATA8();
}
3.6坏块标记
Nand flash出厂的时候,厂商只保证第一块是绝对无问题。其他块,在使用中都可能出现问题,我们称其为坏块。K9F2G08U0B对坏块的处理是在一块的第一个扇区oob的第一个字节写入0。正常情况下,都是写入0xff。
试图写数据或擦除块操作时,如果操作结束时状态返回值判断失败,表示这是一个坏块。
源码:
/*********************************************************
**函数名称: MarkBadBlock_2G08(U32 block)
**函数功能:坏块的标记(通过向页内地址2054写入数据,为01表示该块是bad ;为0xff表
示非bad block)
**入口参数:block---块号
**出口参数:无
**返回值 :坏块标记结果
1、Markbad_ok表示成功标识了坏块
2、Markbad_fail表示未成功标识
************************************************************/
static int MarkBadBlock_2G08(U32 block)
{
U8 result;
result=RamdomWrite_2G08(block*64,2054,1);
if( result == Write_fail)
{
Uart_Printf("[block #%d is marked fail \n",block);
return Markbad_fail;
}
else
{
Uart_Printf("[block #%d is marked as a bad block]\n",block);
return Markbad_ok;
}
}
3.7坏块判断
源码
/*********************************************************
**函数名称: IsBadBlock_2G08(U32 block)
**函数功能:坏块的判断(通过读取页内地址2054存
储值判定,为01表示该块是bad ;为0xff表
示非bad block)
**入口参数:block---块号
**出口参数:无
**返回值 : 坏块判定结果
************************************************************/
static int IsBadBlock_2G08(U32 block)
{
U8 data,i;
U32 blockpage;
blockpage=block<<6;
data= RamdomRead_2G08(blockpage,2054);
if(data == 0x01) //
{
Uart_Printf("block %d is bad \n",block);
return Isbad_ok;
}
else
{
return Isbad_fail;
}
}
3.8ECC校验读写
ECC奇偶校验码如何产生?
s3c2440即可以产生main区的ECC校验码,也可以产生spare区的ECC校验码。因为K9F2G08U0A是8位IO口,因此s3c2440共产生4个字节的main区ECC码和2个字节的spare区ECC码。
在这里我们规定:在每一页的spare区的第0个地址到第3个地址存储main区ECC,第4个地址和第5个地址存储spare区ECC。
main区ECC码生成
1、在读取或写入main区的数据之前,先解锁main的ECC。通过写入IintECC(NFCONT[4])位为1并清除MainECClock(NFCONT[5])为0对main区ECC开锁。
2、读取或写入完数据之后,再设置MainECClock为1锁定该区的ECC,这样系统就会把产生的ECC码锁定在NFMECC0/1寄存器。
spare区ECC码生成
1、在读取或写入spare区的数据之前,先解锁spare的ECC。通过设SpareECClock(NFCONT[5])为0对ECC开锁。
2、读取或写入完数据之后,再设置SpareECClock为1锁定该区的ECC,这样系统就会把产生的ECC码锁定在NFSECC寄存器。
验证是否成功写入数据
我们在写入数据的时候,我们就计算这一页数据的ECC校验码,然后把校验码存储到spare区的特定位置中,在下次读取这一页数据的时候,同样我们也计算ECC校验码,然后与spare区中的ECC校验码比较,如果一致则说明读取的数据正确,如果不一致则不正确。
具体验证如下:
1、读取上次写的数据过程中,生成新的main区和spare区的ECC(分别存放于NFMECC0/1、NFSECC)
2、读取上次写数据时所存储的main区和spare区的ECC,并把这些数据分别放入NFMECCD0/1和NFSECCD的相应位置中。
3、最后通过比较新的ECC值和之前ECC值,判定是否成功写入。比较结果可以通过读取NFESTAT0/1(因为K9F2G08U0A是8位IO口,因此这里只用到了NFESTAT0)中的低4位来判断读取的数据是否正确,其中第0位和第1位为main区指示错误,第2位和第3位为spare区指示错误。
写源码
/*********************************************************
**函数名称:static int WritePage_2G08(U32 block,U32 page,U8 *buffer)
**函数功能:在nand flash芯片中页读功能
**入口参数:
1、block---- 块号
2、page-----页号
3、*buffer----------存放读取整页的数据缓冲区
**出口参数:无
**返回值 : 写成功标志位
1、Write_ok表示写入成功
2、Write_fail表示写入失败
************************************************************/
static int WritePage_2G08(U32 block,U32 page,U8 *buffer)
{
int i;
U32 blockpage, Mecc, Secc;
U8 *bufPt=buffer,temp;
blockpage=(block<<6)+page;
temp = IsBadBlock_2G08(block); //判断该块是否为坏块
if(temp == Isbad_ok){return Isbad_ok ; } //是坏块,返回
NF_RSTECC(); // Initialize ECC
NF_MECC_UnLock();
NF_ChipEn();
NF_CMD(PROGCMD0); // Write 1st command
NF_ADDR(0); //Column (A[7:0]) = 0
NF_ADDR(0); // A[11:8]
NF_ADDR((blockpage)&0xff); // A[19:12]
NF_ADDR((blockpage>>8)&0xff); // A[27:20]
NF_ADDR((blockpage>>16)&0xff); //A[28]
for(i=0;i<2048;i++)
{
NF_WRDATA8(*bufPt++); // Write one page to NFM from buffer
}
/*main 区ECC值生成及存储*/
NF_MECC_Lock();
// Get ECC data.
// Spare data for 8bit
// byte 0 1 2 3 4 5 6 7 8 9
// ecc [0] [1] [2] [3] x [Bad marking] SECC0 SECC1
Mecc = rNFMECC0;
Spare_Data_2G08[0]=(U8)(Mecc&0xff);
Spare_Data_2G08[1]=(U8)((Mecc>>8) & 0xff);
Spare_Data_2G08[2]=(U8)((Mecc>>16) & 0xff);
Spare_Data_2G08[3]=(U8)((Mecc>>24) & 0xff);
/*spare 区ECC值生成及存储*/
NF_SECC_UnLock();
//把main区的ECC值写入到spare区的前4个字节地址内,即第2048~2051地址
for(i=0;i<4;i++)
{
NF_WRDATA8(Spare_Data_2G08[i]); // Write spare array(Main ECC)
}
NF_SECC_Lock(); //锁定spare区的ECC值
Secc=rNFSECC; //读取spare区的ECC校验值
Spare_Data_2G08[4]=(U8)(Secc&0xff);
Spare_Data_2G08[5]=(U8)((Secc>>8) & 0xff);
//把spare区ECC值写入spare区的地址2052~2053内
for(i=4;i<6;i++)
{
NF_WRDATA8(Spare_Data_2G08[i]); // Write spare array(Spare ECC and Mark)
}
NF_CLEAR_RB();
NF_CMD(PROGCMD1); // Write 2nd command
NF_DETECT_RB();
NF_CMD(QUERYCMD); // Read status command
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
do{
temp = NF_RDDATA8();
}while(!(temp&0x40));
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
if (temp&0x1)
{
NF_ChipDs();
Uart_Printf("[PROGRAM_ERROR:block#=%d]\n",block);
MarkBadBlock_2G08(block);
return Write_fail;
}
else
{
NF_ChipDs();
return Write_ok;
}
}
读源码
/*********************************************************
**函数名称:static int ReadPage_2G08(U32 block,U32 page,U8 *buffer)
**函数功能:在nand flash芯片中页读功能
**入口参数:
1、block---- 块号
2、page-----页号
**出口参数:*buffer----------存放读取整页的数据缓冲区
**返回值 : 数据的ECC校验结果
1、ok表示写入的数据同读取数据一致
2、fail表示不一致
************************************************************/
static int ReadPage_2G08(U32 block,U32 page,U8 *buffer)
{
int i;
unsigned int blockpage;
U32 Mecc, Secc;
U8 *bufPt=buffer,temp;
U8 mainECC0, mainECC1, mainECC2, mainECC3,spareECC0,spareECC1;
blockpage=(block<<6)+page;//将block信息用页形式表示
NF_RSTECC(); // Initialize ECC
NF_MECC_UnLock();//解锁main区ECC
NF_ChipEn(); //打开nand flash片选使能
NF_CLEAR_RB();//清除RnB信号
NF_CMD(READCMD0); // Read command 0x00
NF_ADDR(0); // Column = 0
NF_ADDR(0);
NF_ADDR(blockpage&0xff); //行地址A12-A19
NF_ADDR((blockpage>>8)&0xff); // 行地址A20-A27
NF_ADDR((blockpage>>16)&0xff); //行地址A28
NF_CMD(READCMD3);//页读命令0x30
NF_DETECT_RB();//等到RnB信号变高,即不忙
for(i=0;i<2048;i++)
{
*bufPt++=NF_RDDATA8(); // Read one page
}
/* mian区和spare区的ECC的校验 */
NF_MECC_Lock();//锁定main区ECC值
NF_SECC_UnLock();//解锁spare区ECC
mainECC0=NF_RDDATA8() ;
mainECC1=NF_RDDATA8() ;
mainECC2=NF_RDDATA8() ;
mainECC3=NF_RDDATA8() ;
rNFMECCD0=(mainECC1<<16) |mainECC0;
rNFMECCD1=(mainECC3<<16) |mainECC2;
/* 上面语句与其功能等同
Mecc=NF_RDDATA();//
rNFMECCD0=((Mecc&0xff00)<<8)|(Mecc&0xff);
rNFMECCD1=((Mecc&0xff000000)>>8)|((Mecc&0xff0000)>>16);
*/
NF_SECC_Lock();
spareECC0=NF_RDDATA8() ;
spareECC1=NF_RDDATA8() ;
rNFSECCD=(spareECC1<<16)|spareECC0;
Spare_Data_2G08[6]=mainECC0;
Spare_Data_2G08[7]=mainECC1;
Spare_Data_2G08[8]=mainECC2;
Spare_Data_2G08[9]=mainECC3;
Spare_Data_2G08[10]=spareECC0;
Spare_Data_2G08[11]=spareECC1;
NF_ChipDs();
for(i=0;i<12;i++)
{
Uart_Printf("Spare_data_2G08[%d]=%x\n",i,Spare_Data_2G08[i]);
}
temp=rNFESTAT0;
Uart_Printf("rNFESTAT0=%x\n",temp);
if ((rNFESTAT0&0xf) == 0x0)
{
Uart_Printf("ECC OK!\n");
return OK;
}
else
{
Uart_Printf("ECC FAIL!\n");
return FAIL;
}
}
五、测试
程序中定义的页内数据存储(spare区中数据存放格式可由自己定义)
main区 spare区
地址: 0--2047
2048--2051 2052-2053
2054 其他
说明: main数据区
main区ECC spare区的ECC
坏块标记 保留
测试一、测试读写、擦除
1、向nand flash以页形式写入数据(向第2 block第2页写数据),写之前先读取该页信息。
2、以页读形式读取数据进行对比,是否正确。验证页读写。
3、以随机读形式读取第2 block中第2页地址2数据进行对,是否正确。验证随机读。
4、以随机写形式向第2 block中第2页地址2054写入01,然后用随机读验证。验证随机写
5、擦除第2 block数据。验证擦除。
6、以页形式读取2 block第2页数据;同时随机读地址2054数据是否擦除(验证后可知:擦除操作擦除该块64页的main区和spare区所以数据)。
测试界面
测试二、坏块检测、判定、标识
1、坏块检测 检测nand flash所有坏坏信息
2、人为设定坏块。使用随机写向(坏块标识通过随机写实现) blockpage首地址的2054写入01
3、坏块检测是否能检测到。若能检测到,表示坏块的检测和标识OK
4、坏块判定无法测试,因为坏块判定方法:写入不成功或擦除不成功,无法模拟。
测试三、ECC校验值
1、以页形式向 第0block 0页地址 写入一页数据并将main区的ECC写入spare区地址2048~2051 和spare区的ECC写入地址2052~2053
2、以页形式读取并ECC校验。
3、通过随机写来修改spare区中main的ECC值(将地址2048中数据0xFF修改为0x04)。
4、重新读取数据,来验证ECC是否有效。验证结果可知:ECC起到作用。
六、遇到问题
6.1 读取nand falsh的ID信息不准确
程序:
#define NF_RDDATA() (rNFDATA)
#define NF_RDDATA8() ((*(volatile unsigned char*)0x4E000010) )
U32 ReadChipId(void)
{
U32 id;
unsigned char Makercode,Devcode,ID3rd,ID4rd,ID5rd;
NF_ChipEn(); //片选使能
NF_CMD(RdIDCMD); //写读nand flash IDC命令0x90
NF_ADDR(0);//写地址0x00
while(NFIsBusy());//判断nand flash是否busy?若busy,则继续等待。
Makercode = NF_RDDATA();
Devcode = NF_RDDATA();
ID3rd = NF_RDDATA();
ID4rd = NF_RDDATA();
ID5rd = NF_RDDATA();
Uart_Printf("Makercode=%x\n",Makercode);
Uart_Printf("Devcode=%x\n",Devcode);
Uart_Printf("ID3rd=%x\n",ID3rd);
Uart_Printf("ID4rd=%x\n",ID4rd);
Uart_Printf("ID5rd=%x\n",ID5rd);
NF_ChipDs();//禁止片选使能
}
输出结果如下图:
原因:1、读取ID的rNFDATA的格式不合适,在2440DATASHEET中说明数据位都为32位,而读取ID时,每次的读取数据位8bit。所以需要定义#define NF_RDDATA8() ((*( unsigned char*)0x4E000010) ) ,定义后显示结果不正确。如下图
原因:编译器读取内存中已有NF_RDDATA8()数据,而不去读取真正的寄存器更新数据。
2、定义变量类型不对,这里必须使用volatile,应为该地址为寄存器,需要每次更新其中数据,不然编译器会读取内存中已有的数据而不去真正的寄存器更新数据。所以定义:#define NF_RDDATA8() ((*(volatile unsigned char*)0x4E000010)
)。修改后,结果如下:
正确读取K9F2G08U0B的ID中信息
扩展:如果每次读取16位,如何实现?因为在uboot中用到 将nand flash代码复制到sdram中,而SDRAM的位宽是16bit。
解决办法:第一步:#define NF_RDDATA16() ((*(volatile unsignedshort*)0x4E000010) )
第二步:将NF_RDDATA16()替换程序中NF_RDDATA8()
第三步:修改同读取数据宽度 相关的参数类型
经过测试:首先向第1 block第1page写入05开始,加1数据(即05 06 07 。。。。。)。通过read函数读取第1 block第1page数据 显示:0x605 。 nand flash能成成功读取16位。总结:读取的位数,取决于定义的变量类型。
6.2 nand flash写入新数据必须擦除
发现是没有先擦除在写入,无法正常写入。nand由于结构的特殊性,只能先充电,再放电,不能直接对每一位置高置低。因此规定,进行nand写入操作前,必须对nand进行擦除操作。擦除就是充电,对每一位置高,写入0xFF,而写入动作就是对特定位进行放电的操作了,这样才能得到正确的数据。在nand
flash第2块2页2060地址处写入0x01,之后在该地址写入0x09.,从原理上说,是行不通的。结果如下图:
成功写入0x01
写入0x09时失败
6.3 ECC寄存器设置
1、ECC值生成寄存器
NFMCC0/1存放mian区生成的ECC码
NFSECC存放spare区生成的ECC码
由于因为K9F2G08U0A是8位IO口,所以读取ECC码时使用I/O[7:0](即只使用NFMECC0寄存器)。详见
/*读取生成main区的ECC码*/
Mecc = rNFMECC0;
Spare_Data_2G08[0]=(U8)(Mecc&0xff);
Spare_Data_2G08[1]=(U8)((Mecc>>8) & 0xff);
Spare_Data_2G08[2]=(U8)((Mecc>>16) & 0xff);
Spare_Data_2G08[3]=(U8)((Mecc>>24) & 0xff);
2、ECC验证寄存器
ECC验证寄存器由NFMECCD0/1(用于存放读取的main区的ECC码))和NFSECCD(用于存放读取的spare区的ECC码)
/*读取main区的ECC码,对于8bit的nandflash 使用其I/O[7:0]*/
mainECC0=NF_RDDATA8() ;
mainECC1=NF_RDDATA8() ;
mainECC2=NF_RDDATA8() ;
mainECC3=NF_RDDATA8() ;
/*将ECC校验码赋值给rNFMECCD0/1,写入过程如上图标出1、2、3、4*/
rNFMECCD0=(mainECC1<<16) |mainECC0;
rNFMECCD1=(mainECC3<<16) |mainECC2;
七、小结
1、main区需要完整读写一页才有正确校验码,spare区读写生成校验码的时候不需要读写完整个spare区?
2.spare区锁定后,又向spare区写入了所得的ecc校验码,那这次写入不是使之前得到的校验码无效了吗?
3、想main区写入2048个数据后,若在写一个数据,是否存放在地址2048中?
解答:
1、main区和spare区都可以只读写一部分区域,就可以得到我们想要的校验码,只需要把校验的区域置于“解锁”和“锁定”之间即可;
2、在spare区,先写入的是main区的ecc,在写入这些ecc的同时,我们又得到了新的ecc,该ecc是spare区的ecc,也就是说spare区的ecc是“main区的ecc的ecc”(有些不好理解),所以校验码是有效的。
3、验证后可知:从页地址0开始连续写入2054个数据,通过随机读地址0x2050数据与写入值一致,说明可以连续写页地址0-2011(main区+spare区)