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

 

 

转载于:https://www.cnblogs.com/liuchengchuxiao/p/4227454.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值