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

 

 

posted @ 2015-01-15 22:42  来杯绿茶  阅读(2276)  评论(0编辑  收藏  举报