主题 : mini2440 nand flash 操作(C语言版) 复制链接 | 浏览器收藏 | 打印
此生无意行高远,浪迹行书弹指间!
级别: 新手上路
UID: 32708
精华: 2
发帖: 4
金钱: 120 两
威望: 24 点
贡献值: 2 点
综合积分: 48 分
注册时间: 2010-11-19
最后登录: 2011-11-09
楼主  发表于: 2011-11-08 19:40

 mini2440 nand flash 操作(C语言版)

管理提醒: 本帖被 kasim 执行加亮操作(2011-12-11)
         Mini2440 nand flash 操作总结
首先要感谢CSDN论坛的赵春江老师的文章。这篇文章基本上是他文章的内容。
百度上搜 “CSDN 赵春江” 就能找到。

一:硬件连接

    Nand flash操作对于自己编写mini2440启动代非常关键。因为一般来说,嵌入式系统都有一片nand flash用来作为存储器。所以掌握nand flash 的操作非常的必要和关键。
    在操作之前,首先应该对nand flash 的硬件上有一定的了解。我的mini2440板子自带的是K9F1G08U0A。大小是128M。其芯片上的重要引脚如下:
    I/O0~I/O7:数据总线,用于nandflash的所有命令,地址的输入,和数据的双向传输.
    CE :芯片使能引脚
    CLE (Command Latch Enable):命令锁存允许引脚
    ALE(Address Latch Enable):地址锁存允许引脚
    WE/ : 写芯片允许引脚
    RE/ : 读芯片允许引脚
    RB : 状态读取引脚(低电平表示忙;高电平表示空闲)
    其他引脚:诸如电源之类
    
    因为S3C2440硬件自带有nand flash控制器,所以具有相对应的引脚可以和nand flash芯片连接。
    系统总线引脚DATA0~DATA7 :直接与I/O0~I/O7相连接
    nFCE/GPA22  :直接与CE引脚相连
    CLE/GPA17 :直接与CLE引脚相连
    ALE/GPA18 :直接与ALE引脚相连
    nFWE/GPA19 :直接与WE/引脚相连
    nFRE/GPA20 :直接与WE/引脚相连
    FRnB :直接与RB引脚相连
    
    除此之外,S3C2440还有另外几个引脚用于配置nandflash控制器。比如页大小,地址周期数等等。这些引脚主要有如下:
    OM[1:0] ; NCON ; GPG13 ; GPG14 ; GPG15。 他们的配置方式在S3C2440说明书上已经很详细。
    
所以综上所述,S3C2440 与 nandflash(K9F1G08U0A)的硬件连接图如下所示:

  


二:nand flsah 工作原理简介

    Nand flash 主要是用于大容量的存储。它的优点是性价比高,容量大,体积小;缺点是不能随机访问(这是为什么程序不能直接在nandflash中运行的根本原因),容易产生坏块,一般要进行ECC校验。另外nand flash智能把0写成1,不能把1写成0,所以每次往里面写东西,都必须先进行擦除操作。
    Nand flash 的内存分为 “块” 和 “页”。一个页里面有两部分,一部分是Main区,另一部分是spare区。Main区主要用于存放信息数据,spare区主要用来存放ECC校验等。不同的芯片其页的大小不同,块的大小也不同。K9F1G08U0A的页大小为 (2K+64)Byte ,其中64字节就是spare区的大小。那么128M(不包含spare区)的大小,一共就有65536个页。它的一个块包含64个页,所以一共就有1024个块。
    因为该芯片的页大小是2K+64 字节,所以寻址任意一页内的字节需要12根地址总线;
另外,根据芯片总大小(包括spare区)可以算出需要的地址总线一共是 28根。但是为了兼容1G(如果包括spare区,大于1G),地址总线一共是31根。前面提到的12根地址总线表示的是列地址,即寻址的是每一页里面的内容。除了列地址总线,剩余的地址总线称为行地址,用于寻址页。因为nand flash 为了缩小体积,只有8根I/O口,所以为了区分不同的操作,需要事先写入相关的命令来实现。下面是说明书上的一些命令值:

    


    Nand flash 的常用操作一般有
    初始化操作
    复位操作
    读取芯片ID ;读取任意页的整页内容;读取指定页的任意字节;
    写任意页的整页内容;写指定页的任意地址字节

三:S3C2440  nand flash 控制器的相关寄存器介绍

    主要可以分为三类。
第一类:配置、控制寄存器
    NFCONF(配置寄存器):主要用来配置操作时序的长短。
    NFCONT(控制寄存器):第一位用来控制nFCE/GPA22,即芯片的是能引脚。
                           第四位用来复位ECC。
                           第五位main区ECC解锁
                           第六位spare区ECC解锁
第二类:数据传输寄存器
    NFCMMD:用于IO口传送(低8位可用)命令
    NFDATA ,  用于IO口传送(32位可用)数据。注意:因为是32位的,所以对该寄存器进行一次读,硬件会自动对flash进行四次读操作,并把四个字节的内容依次填入NFDAT寄存器。所以最好定义一个该地址的8位寄存器。写入数据也是一样的。
    #define  rNFDATA8  *((char *)0X4E000010)
    NFADDR  用于IO口传送(低8位可用)地址
第三类:ECC校验相关寄存器
    NFMECCD0/1 :nandflash的main区ECC寄存器(用来装载读取到得main区的ECC校
                   验码)
    NFSECCD :   nandflash的spare 区ECC寄存器(用来装载读取到的spare区的ECC
                   校验码)
    NFMECC0/1 :nandflash用于自动保存硬件产生的main区ECC寄存器
    NFSECC   :nandflash用于自动保存硬件产生的spare区的ECC寄存器
第四类:状态寄存器
    NFSTAT :nandflash状态寄存器 。
              第0位可以用于判断nandflash是否在忙。
              第2位对应RnB引脚信号。注意硬件上只能使RnB从低变高,所以每次操
                  作时,都应该先向这位写1,来清除RnB信号。
    NFESTAT0/1:nandflash的ECC状态寄存器
    
    
四:基于S3C2440的nandflash操作函数(C语言版)的编写

    首先宏定义S3C2440的相关寄存器。因为这个工作很简单,这里省略。但是千万要注意下面这个宏的定义
    #define  rNFDATA8  *((char *)0X4E000010)
    
    接着对于nand flash的一些命令进行宏定义
    #define READ_1                 0x00              //页读命令周期1
    #define READ_2                 0x30              //页读命令周期2
    #define READ_ID                0x90              //读ID命令
    #define WRITE_1                0x80              //页写命令周期1
    #define WRITE_2                0x10              //页写命令周期2
    #define ERASE_1                0x60              //块擦除命令周期1
    #define ERASE_2                0xd0              //块擦除命令周期2
    #define READ_STATUS            0x70              //读状态命令
    #define RESET                  0xff              //复位
    #define RANDOM_READ_1          0x05              //随意读命令周期1
    #define RANDOM_READ_2          0xE0              //随意读命令周期2
    #define RANDOM_WRITE           0x85              //随意写命令
    
    下面再宏定义一些简单的操作
    #define ENABLE_NANDFLASH   rNFCONT &= ~(1<<1)        //使能芯片EN
    #define DISABLE_NANDFLASH  rNFCONT |= (1<<1)         //不使能芯片EN
    #define RESET_RNB          rNFSTAT |= (1<<2)         //RnB信号复位(清零)
    #define WAIT_RNB_HIGH      while(!(rNFSTAT&(1<<2)))  //等待RnB信号变高。即
                                                         //等待芯片空闲
    #define ECC_INIT           rNFCONT |= (1<<4)         //复位ECC
    #define MAIN_ECC_UNLOCK    rNFCONT &= ~(1<<5)        //解锁main区的ECC
    #define MAIN_ECC_LOCK      rNFCONT |= (1<<5)         //锁定main区的ECC
    #define SPARE_ECC_UNLOCK   rNFCONT &= ~(1<<6)        //解锁spare区的ECC
    #define SPARE_ECC_LOCK     rNFCONT |= (1<<6)         //锁定spare区的ECC
    
    /*******************************
    **nandflash 初始化函数
    **功能:配置相应的IO引脚
    **设置一些时序相关的时间
    *******************************/
    void NandFlash_Init ( void )
    {
        rGPACON = 0x3f<<17;                     //配置芯片控制引脚
        rNFCONF = (1<<12)|(2<<8)|(0<<4)|(0<<0); //这里的时间配置。要注意和芯片的
                                               //对应性!!!TACLS=1、TWRPH0=2、
                                               //TWRPH1=0,8位IO
    
        rNFCONT=(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<
                 1)|(1<<0);                    
    //非锁定,屏蔽nandflash中断,初
                                                //始化ECC及锁定main区和spare
                                                //区ECC,使能nandflash片选及控
                                                //制器
    }
    
    /*****************************
    **nandflash 的复位函数
    **貌似nandflash使用前
    **必须复位一次
    *****************************/
    void NandFlash_Reset()
    {
          ENABLE_NANDFLASH;       //使能芯片EN
          RESET_RNB;               //RnB信号复位(清零)
          rNFCMD = RESET;         //写入nandflash的复位命令
          WAIT_RNB_HIGH ;         //等待RnB信号变高,即不忙
          DISABLE_NANDFLASH;      //不使能芯片EN
    }
    
    
    /***********************************
    **写入一页函数
    ***********************************/
    unsigend char NandFlash_Write_Page(unsigned int pages)
    {
          unsigned int  i;
          unsigned int  main_ecc, spare_ecc;
          unsigned char stat, temp;
          
            temp = rNF_IsBadBlock(page_number>>6); //判断该块是否为坏块
          if(temp == 0x33)
                 return 0x42;                    //是坏块,返回
    
          ECC_INIT ;                             //复位ECC
          MAIN_ECC_UNLOCK;                       //解锁main区ECC
    
          ENABLE_NANDFLASH;                      //使能芯片EN
          RESET_RNB;                             //RnB信号复位(清零)
    
          rNFCMD=WRITE_1                         //页读命令周期1
    
                                                 //写入5个地址周期
          rNFADDR=(0x00);                         //列地址A0~A7
          rNFADDR=(0x00);                         //列地址A8~A11
          rNFADDR=(pages & 0xff);                 //行地址A12~A19
          rNFADDR=((pages >> 8) & 0xff);          //行地址A20~A27
          rNFADDR=((pages >> 16) & 0xff);         //行地址A28
          
          //写入一页数据
          for (i = 0; i < 2048; i++)
          {
                 rNFDATA8=(INbuffer);         //INbuffer[2048]为全局数组
          }
          
          MAIN_ECC_LOCK;                        //锁定main区的ECC值
          
          main_ecc=rNFMECC0;                     //读取main区的ECC校验码
          //把ECC校验码由字型转换为字节型,并保存到全局变量数组ECCBuf中
          ECCBuf[0]=(U8)(main_ecc&0xff);
          ECCBuf[1]=(U8)((main_ecc>>8) & 0xff);
          ECCBuf[2]=(U8)((main_ecc>>16) & 0xff);
          ECCBuf[3]=(U8)((main_ecc>>24) & 0xff);
    
          SPARE_ECC_UNLOCK;                      //解锁spare区的ECC
          //把main区的ECC值写入到spare区的前4个字节地址内,即第2048~2051地址
          for(i=0;i<4;i++)
          {
                 rNFDATA8=ECCBuf;
            }
    
          SPARE_ECC_LOCK;                      //锁定spare区的ECC值
          spare_ecc=rNFSECC;                  //读取spare区的ECC校验码
          //把ECC校验码保存到全局变量数组ECCBuf中
          ECCBuf[4]=(U8)(spare_ecc&0xff);
          ECCBuf[5]=(U8)((spare_ecc>>8) & 0xff);
          //把spare区的ECC值继续写入到spare区的第2052~2053地址内
          for(i=4;i<6;i++)
          {
                 rNFDATA8=ECCBuf;
          }
    
          rNFCMD=WRITE_2                       //页写命令周期2
    
            delay(1000);                        //延时一段时间,以等待写操作完成
      
        
             rNFCMD=READ_STATUS;                 //读状态命令
      
             //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
             do{
                stat = NF_RDDATA8();
               }while(!(stat&0x40));
          
        
             DISABLE_NANDFLASH;                   //关闭nandflash片选
        
             //判断状态值的第0位是否为0,为0则写操作正确,否则错误
        if (stat & 0x1)
        {
              temp = rNF_MarkBadBlock(page_number>>6);        //标注该页所在的块为坏块
              if (temp == 0x21)
                     return 0x43           //标注坏块失败
              else
                    return 0x44;         //写操作失败
        }
        else
        return 0x66;               //写操作成功
    }
    

注释:对于写入一页内容思路上不难理解。关键在于ECC校验码部分有点难懂。当向nand flash写入或者读取数据时,s3c2440的nandflash控制器会自动产生ECC校验码,并把它保存在NFMECC0/1和NFSECC寄存器中。当然前提是要解锁main区和spare区的ECC。解锁的意思就是允许硬件产生新的ECC校验码。所以在写一页数据时,解锁main区的ECC校验码,写完之后锁定main区的ECC校验码。然后这一过程由硬件产生的ECC校验码就被保存在了NFMECC0/1寄存器中。然后读取该寄存器的值,将其写入到对应页的spare区的前面四个字节中。当然在写之前要首先解锁spare区的ECC,等把mian区的ECC校验码写完之后,锁定spare区的ECC,然后就可以读取NFSECC寄存器里面产生的spare区的ECC校验码了,再把它写入到spare区的第5,6字节处即可。当然在这一过程中,会涉及到对坏块的判断,这在后面会介绍。





    /**********************************
    **读取芯片ID函数
    **返回一个32位数据,最低8位表示
    **的是设备ID,次低8位是厂商ID
    **********************************/
    unsigned int NandFlash_Read_ID()
    {
          unsigned char FID;      //厂商ID,factory_id
          unsigned char DID;      //设备ID,device_id
          unsigned char ID_detail1,ID_detail2,ID_detail3;  //详细id信息
    
          ENABLE_NANDFLASH;           //使能芯片EN
          RESET_RNB;                    //RnB信号复位(清零)
          rNFCMD = READ_ID;             //读ID命令
          rNFADDR = 0x00;                 //写0x00地址
    
          //读五个周期的ID
          FID = rNFDATA8;                 //厂商ID:0xEC
          DID = rNFDATA8;                //设备ID:0xDA
          ID_detail1 = rNFDATA8;           //更加详细ID信息
          ID_detail1 = rNFDATA8;           //更加详细ID信息
          ID_detail1 = rNFDATA8;           //更加详细ID信息
    
          DISABLE_NANDFLASH;             //不使能芯片EN
          return ((unsined int)FID*0x10000+DIV);    //返回FID和DID
    }
    
    /****************************
    **读取一页内容函数
    **读一页函数
    ****************************/
    unsigned char NandFlash_Read_Page(unsigned int pages)
    {
          unsigned int  i;
          unsigned int  main_ecc, spare_ecc;
    
          ECC_INIT ;                             //复位ECC
          MAIN_ECC_UNLOCK;                       //解锁main区ECC
    
          
          ENABLE_NANDFLASH;                      //使能芯片EN
          RESET_RNB;                             //RnB信号复位(清零)
    
          rNFCMD=READ_1                          //页读命令周期1
    
                                                  //写入5个地址周期
          rNFADDR=(0x00);                        //列地址A0~A7
          rNFADDR=(0x00);                        //列地址A8~A11
          rNFADDR=(pages & 0xff);                 //行地址A12~A19
          rNFADDR=((pages >> 8) & 0xff);          //行地址A20~A27
          rNFADDR=((pages >> 16) & 0xff);         //行地址A28
    
          rNFCMD=READ_2                          //页读命令周期2
    
          WAIT_RNB_HIGH ;                        //等待RnB信号变高,即不忙
          
          //读取一页数据内容
          for (i = 0; i < 2048; i++)             //1页是2KB。所以i取到2048
          {
                 buffer = rNFDATA8;       //注意,这里的buffer【2048】数组是一个全局的数组
          }
          
          MAIN_ECC_LOCK();                    //锁定main区ECC值
          
          SPARE_ECC_UNLOCK();                 //解锁spare区ECC
    
          main_ecc=rNFDATA;                   //读spare区的前4个地址内容,即第2048~2051地址,这4个字节为main区的ECC
                                              //这里的4个字节应当理解成硬件自动完成的。虽然总线只有8跟
          
          //把读取到的main区的ECC校验码放入NFMECCD0/1的相应位置内
          rNFMECCD0=((main_ecc&0xff00)<<8)|(main_ecc&0xff);
          rNFMECCD1=((mecc0&0xff000000)>>8)|((mecc0&0xff0000)>>16);
              
          SPARE_ECC_LOCK();              //锁定spare区的ECC值
    
          spare_ecc=rNFDATA();          //继续读spare区的4个地址内容,即第2052~2055地址,其中前2个字节为spare区的ECC值
          //把读取到的spare区的ECC校验码放入NFSECCD的相应位置内
          rNFSECCD=((spare_ecc&0xff00)<<8)|(spare_ecc&0xff);
    
          DISABLE_NANDFLASH;             //不使能芯片EN
          
          //判断所读取到的数据是否正确
          if ((rNFESTAT0&0xf) == 0x0)
                 return 0x66;                 //正确
           else
                 return 0x44;                 //错误
          
    }
    
这一过程页并不难以理解。还是ECC部分有一点难懂。其思想就是,在读取main区的时候,解锁mian区的ECC,那么读取的过程就会产生mian区的ECC校验码,并自动保存到NFMECC0/1寄存器中。然后再把spare区中的前四个字节(写该页时产生的main区ECC校验码)读取出来,和现在读取的ECC校验码作对比即可。而这个作对比的过程也可以有硬件完成,只要把读取到的ECC校验码装载进NFMECCD0/1寄存器,然后再根据NFESTAT状态寄存器的相应位即可判断。

/***************************************
***在固定页内任意地址读字节函数
***************************************/
unsigned char NANDFLASH_Ramdom_Read(unsigend int pages, unsigend int add)
{
      ENABLE_NANDFLASH;                      //使能芯片EN
      RESET_RNB;                             //RnB信号复位(清零)

      rNFCMD=READ_1                          //页读命令周期1
  
                                            //写入5个地址周期
      NF_ADDR=(0x00);                         //列地址A0~A7
      NF_ADDR=(0x00);                         //列地址A8~A11
      NF_ADDR=(pages & 0xff);                 //行地址A12~A19
      NF_ADDR=((pages >> 8) & 0xff);          //行地址A20~A27
      NF_ADDR=((pages >> 16) & 0xff);         //行地址A28

      rNFCMD=READ_2;                         //页读命令周期2

      WAIT_RNB_HIGH ;                       //等待RnB信号变高,即不忙

      rNFCMD=RANDOM_READ_1                //随意读命令周期1
                                            //页内地址
      rNFADDR=((char)(add&0xff));                        //列地址A0~A7
      rNFADDR=((char)((add>>8)&0x0f));                //列地址A8~A11
      rNFCMD=RANDOM_READ_2;               //随意读命令周期2
      
      return rNFDATA8;                    //读取数据并返回
}

/***************************************
***在固定页内任意地址写字节函数
***************************************/
unsigned char NANDFLASH_Ramdom_Write(unsigned int pages, unsigned int add, unsigned char dat)
{
      unsigned char temp,stat;

      ENABLE_NANDFLASH;                      //使能芯片EN
      RESET_RNB;                             //RnB信号复位(清零)

      rNFCMD=WRITE_1;               //页写命令周期1

                                            //写入5个地址周期
      NF_ADDR=(0x00);                         //列地址A0~A7
      NF_ADDR=(0x00);                         //列地址A8~A11
      NF_ADDR=(pages & 0xff);                 //行地址A12~A19
      NF_ADDR=((pages >> 8) & 0xff);          //行地址A20~A27
      NF_ADDR=((pages >> 16) & 0xff);         //行地址A28

      rNFCMD=RANDOM_WRITE;    ;                //随意写命令
      //页内地址
      NF_ADDR=((char)(add&0xff));                 //列地址A0~A7
      NF_ADDR=((char)((add>>8)&0x0f));         //列地址A8~A11

      rNFDATA8=dat;                            //写入数据

      rNFCMD = WRITE_2;                        //页写命令周期2
      
      delay(1000);                             //延时一段时间
  
        rNFCMD=READ_STATUS;                      //读状态命令
  
      //判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
       do{
          stat = NF_RDDATA8();
       }while(!(stat&0x40));
  
       DISABLE_NANDFLASH;                   //关闭nandflash片选
    
       //判断状态值的第0位是否为0,为0则写操作正确,否则错误
       if (stat & 0x1)
          return 0x44;                //失败
       else
       return 0x66;                //成功
}

//下面介绍上文中提到的判断坏块以及标注坏块的那两个程序:
//rNF_IsBadBlock和rNF_MarkBadBlock。在这里,我们定义在
//spare区的第6个地址(即每页的第2054地址)用来标注坏块,
//0x44表示该块为坏块。要判断坏块时,利用随意读命令来读
//取2054地址的内容是否为0x33,要标注坏块时,利用随意写
//命令来向2054地址写0x33。下面就给出这两个程序,它们的
//输入参数都为块地址,也就是即使仅仅一页出现问题,我们
//也标注整个块为坏块。

unsigned char rNF_IsBadBlock(unsigend int block)
{
      return NANDFLASH_Ramdom_Read(block*64, 2054);
}

unsigend char rNF_MarkBadBlock(unsigend int block)
{
     unsigend char result;
   result = NANDFLASH_Ramdom_Write(block*64, 2054, 0x33);
   if(result == 0x44)
     return 0x21;                 //写坏块标注失败
      else
   return 0x60;                 //写坏块标注成功
}
    
                                                                         中国计量学院  郑星
                                                                         newstar111@163.com
级别: 骑士
UID: 14419
精华: 1
发帖: 183
金钱: 995 两
威望: 199 点
贡献值: 1 点
综合积分: 386 分
注册时间: 2010-02-08
最后登录: 2014-04-13
1楼  发表于: 2011-11-08 20:21
沙发!!有图就更好了。。。
级别: 新手上路
UID: 52806
精华: 0
发帖: 4
金钱: 20 两
威望: 4 点
贡献值: 0 点
综合积分: 8 分
注册时间: 2011-07-25
最后登录: 2012-09-23
2楼  发表于: 2011-12-16 21:24
推推
级别: 新手上路
UID: 61185
精华: 0
发帖: 22
金钱: 110 两
威望: 22 点
贡献值: 0 点
综合积分: 44 分
注册时间: 2011-12-24
最后登录: 2012-12-15
3楼  发表于: 2011-12-29 13:32
顶起来
级别: 新手上路
UID: 68257
精华: 0
发帖: 2
金钱: 10 两
威望: 2 点
贡献值: 0 点
综合积分: 4 分
注册时间: 2012-04-20
最后登录: 2012-04-20
4楼  发表于: 2012-04-20 16:03
刚好需要
级别: 新手上路
UID: 83336
精华: 0
发帖: 16
金钱: 80 两
威望: 16 点
贡献值: 0 点
综合积分: 32 分
注册时间: 2012-12-05
最后登录: 2017-09-13
5楼  发表于: 2012-12-06 10:13
非常感谢!!!
级别: 新手上路
UID: 85337
精华: 0
发帖: 2
金钱: 10 两
威望: 2 点
贡献值: 0 点
综合积分: 4 分
注册时间: 2013-01-04
最后登录: 2013-01-07
6楼  发表于: 2013-01-04 11:45
非常感谢!!!
级别: 新手上路
UID: 145733
精华: 0
发帖: 11
金钱: 55 两
威望: 11 点
贡献值: 0 点
综合积分: 22 分
注册时间: 2021-01-22
最后登录: 2021-01-29
7楼  发表于: 2021-01-23 10:48
卡好短杰卡斯基多拉到家了卡死了的科技爱上了点几点击拉三等奖阿圣诞节快乐静安寺断开连接阿萨德加拉手机端克拉斯断开连接

股指可以做多做空,去哪里开户?股指期货怎么开户?可以运用最低本金1万,即可操作股指期货配资公司提供的账户操作获利。期货配资是一种提高资金使用的方式。通过融航系统操作,可实现反锁仓代替平今,降低交易手续费,把原本平今500+的手续费降低到400,威可搜  gz5185186线下模式,有融航系统和文华财经系统欢迎来聊
安徽省抵抗力静安寺