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区)

时间: 2024-08-02 11:03:57

Nand Flash原理(二)的相关文章

NAND FLASH 原理

NAND FLASH 原理 http://www.360doc.com/content/12/0522/21/21412_212888167.shtml 闪存保存数据的原理: 与DRAM以电容作为存储元件不同,闪存的存储单元为三端器件,与场效应管有相同的名称:源极.漏极和栅极.栅极与硅衬底之间有二氧化硅绝缘层,用 来保护浮置栅极中的电荷不会泄漏.采用这种结构,使得存储单元具有了电荷保持能力,就像是装进瓶子里的水,当你倒入水后,水位就一直保持在那里,直到你再 次倒入或倒出,所以闪存具有记忆能力.

nand Flash原理

6.1.3) Block 块 一个 Nand Flash (的 chip,芯片) 由很多个块(Block)组成,块的大小一般是 128KB, 256KB, 512KB,此处是 128KB.其他的小于 128KB 的,比如 64KB,一般都是下面将要介绍到的small block的Nand Flash. 块 Block,是Nand Flash的擦除操作的基本/最小单位. 6.1.4) Page 页 每个块里面又包含了很多页(page) .每个页的大小,对于现在常见的Nand Flash多数是2KB

Nand Flash 和Nor Flash的区别详解

写在前面: Flash存储器在嵌入式开发实施中有着重要位置,文本介绍一些关于Flash存储器的知识. 本文内容如下: (一)Nand Flash和Nor Flash存储器简介 (二)Nand Flash和Nor Flash原理 (2-1)存储数据的原理 (2-2)浮栅的重放电 (2-3)0和1 (2-4)连接和编码方式 (三)Nand Flash和Nor Flash的区别 (3-1)Nand Flash 和Nor Flash的性能比较 (3-2)Nand Flash 和Nor Flash的接口差

Nand Flash与Nor Flash的区别

区别:http://zhidao.baidu.com/question/1068445.html?qbl=relate_question_0&word=Serial%20Flash%20%D3%EBNand%C7%F8%B1%F0 Nand Flash原理:http://blog.chinaunix.net/uid-22731254-id-3622773.html

Uboot(二)支持NAND Flash和NOR Flash

 四.自动识别从NAND Flash启动还是从Nor flash启动       原理:在启动的时候,用程序将0x40000000-0x40001000中的某些位置清零,然后回读0x00000000-0x00001000中的相应位置, 为零说明是NAND boot,如果是原来的数据就是Nor boot. 判断完后如果是NAND boot,还要恢复被改动的数据,再进入自拷贝阶段. 选择了在start.S文件开头,全局中断向量之后第57行的变量:.balignl 16,0xdeadbeef . 理由

ECC校验原理以及在Nand Flash中的应用

     本篇文章主要介绍ECC基本原理以及在Nand Flash中的应用,本文记录自己对ECC校验原理的理解和学习. ECC介绍      ECC,全称为Error Correcting Code,错误纠正码,这是一种编码方式,用于在于可以在一定程度上自行发现和纠正传输过程中发生的错误.      香农在1948年发表的<通信的数学理论>中的信道编码定理指出:主要采取适当的纠错码,就可以在多类信道上传输消息,其误码率可以任意小.经过历代人们的持续努力,找出了许多好的信道编码方法,满足许多实用

Nand Flash 控制器工作原理

对 Nand Flash 存储芯片进行操作, 必须通过 Nand Flash 控制器的专用寄存器才能完成.所以,不能对 Nand Flash 进行总线操作.而 Nand Flash 的写操作也必须块方式进行.对 Nand Flash 的读操作可以按字节读取.   Nand Flash 控制器特性 1. 支持对 Nand Flash 芯片的读.检验.编程控制 2. 如果支持从 Nand Flash 启动, 在每次重启后自动将前 Nand Flash 的前 4KB 数据搬运到 ARM 的内部 RAM

嵌入式Linux驱动学习之路(二十三)NAND FLASH驱动程序

NAND FLASH是一个存储芯片. 在芯片上的DATA0-DATA7上既能传输数据也能传输地址. 当ALE为高电平时传输的是地址. 当CLE为高电平时传输的是命令. 当ALE和CLE都为低电平时传输的是数据. 将数据发给nand Flash后,在发送第二次数据之前还要判断芯片是否处于空闲状态.一般是通过引脚RnB来判断,一般是高电平代表就绪,低电平代表正忙. 操作Nand Flash的一般步骤是: 1. 发命令 选中芯片 CLE设置为高电平 在DATA0-DATA7上输出命令值 发出一个写脉冲

u-boot-2014.10移植第20天----添加nand flash命令支持(二)

继前一天移植的情况,运行后发现: Flash: 2 MiBNAND:  0 MiB 说明Nand flash没有移植成功,在文件drivers/mtd/nand/s3c2440_nand.c 中发现board_nand_init函数中: nand->select_chip = NULL 选择芯片函数为NULL,我们在board_nand_init函数上面加上s3c2440_nand_select函数,代码如下: static void s3c2440_nand_select(struct mtd