zoukankan      html  css  js  c++  java
  • 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(" K9F2G08U0B ");
        Uart_Printf("Makercode=%x,Devcode=%x,ID3rd=%x,ID4rd=%x,ID5rd=%x ",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] ",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  ",block);
            return Markbad_fail;
        }
        else
        {
            Uart_Printf("[block #%d is marked as a bad block] ",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   ",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] ",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 ",i,Spare_Data_2G08[i]);
        }
        temp=rNFESTAT0;
        Uart_Printf("rNFESTAT0=%x ",temp);
        if ((rNFESTAT0&0xf) == 0x0)
        {
            Uart_Printf("ECC OK! ");
            return OK;
        }
        else
        {
            Uart_Printf("ECC FAIL! ");
               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 ",Makercode);
        Uart_Printf("Devcode=%x ",Devcode);        
        Uart_Printf("ID3rd=%x ",ID3rd);
        Uart_Printf("ID4rd=%x ",ID4rd);
        Uart_Printf("ID5rd=%x ",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区)

     

  • 相关阅读:
    js设计模式 -- 拷贝模式
    超简单的SpringBoot整合mybatis
    使用JS调用手机本地摄像头或者相册图片识别二维码/条形码
    SpringBootsad整合EhCache做缓存处理
    调用微信的扫一扫功能详解说明---(java 排坑版)
    获取系统文件的创建日期
    在windows上获取文件创建时间
    简单计算器
    lambda-基于谓词筛选值序列
    IEnumerable<T>和IQueryable<T>区别
  • 原文地址:https://www.cnblogs.com/liuchengchuxiao/p/4227454.html
Copyright © 2011-2022 走看看