天天看点

Nand Flash介绍和Nand Flash控制器使用

一、flash介绍

    常用的flash类型有nor flash 和nand flash 两种;

    (1)nor flash

    1、nor flash的接口和ram完全相同,可以随机访问任意地址的数据,在其上进行读操作的效率非常高,但是擦除和写操作的效率很低,另外,nor flash的容量一般比较小,通常,nor flash用于存储程序;

    2、nor flash的块大小范围为64kb—128kb;

    3、擦写一个nor flash块需要4s,

    4、市场上nor flash 的容量通常为1mb—4mb

    (2)nand flash

    1、nand flash的接口仅仅包含几个i/o引脚,需要串行地访问,nand flash进行擦除和写操作的效率很高,容量较大,

通常nand flash用于存储数据;

    2、nand flash的块大小范围为8kb—64kb;

    3、擦写一个nand flash块需要2ms;

    4、nand flash 一般以512字节为单位进行读写

    5、 市场上 nand flash 的容量一般为 8m—512m

二、nand  flash的物理结构

    以三星公司生产的 k9f1208u0m  为例:

    1、容量:64mb,

                      一共4个层;

                      每层1024个块(block);

                      1块包含32页

                      1页包含 512 + 16  = 528个字节

    2、外部接口:8个i/o口,5个使能信号(ale、cle、nwe、nre、nce),1个状态引脚(rdy/b),1个写保护引脚(nwe);            

    3、命令、地址、数据都通过8个i/o口输入输出;

    4、写入命令、地址、数据时,都需要将nwe、nce信号同时拉低;数据在we上升沿被锁存;

    5、cle、ale用来区分i/o引脚上传输的是数据还是地址;

    6、64mb的空间需要26位地址,因此以字节为单位访问flash时需要4个地址序列;

    7、读/写页在发出命令后,需要4个地址序列,而擦除块在发出擦除命令后仅需要3个地址序列;

三、nand flash访问方法

 1 特殊功能寄存器定义 

#define rnfconf  (*(volatile unsigned int *)0x4e000000) 

#define rnfcmd   (*(volatile unsigned char *)0x4e000004) 

#define rnfaddr  (*(volatile unsigned char *)0x4e000008) 

#define rnfdata  (*(volatile unsigned char *)0x4e00000c) 

#define rnfstat  (*(volatile unsigned int *)0x4e000010) 

#define rnfecc   (*(volatile unsigned int *)0x4e000014) 

#define rnfecc0 (*(volatile unsigned char  *)0x4e000014) 

#define rnfecc 1 (*(volatile unsigned char *)0x4e000015) 

#define rnfecc2 (*(volatile unsigned char *)0x4e000016) 

2 操作的函数实现 

1. 发送命令 

#define nf_cmd(cmd)    {rnfcmd=cmd; } 

2. 写入地址 

#define nf_addr(addr)  {rnfaddr=addr;} 

3. nand flash 芯片选中 

#define nf_nfce_l()    {rnfconf&=~(1<<11);} 

4. nand flash 芯片不选中 

#define nf_nfce_h()    {rnfconf|=(1<<11);} 

5. 初始化 ecc 

#define nf_rstecc()    {rnfconf|=(1<<12);}

6. 读数据 

#define nf_rddata()         (rnfdata) 

7. 写数据 

#define nf_wrdata(data) {rnfdata=data;} 

8. 获取nand flash 芯片状态 

#define nf_waitrb()         {while(!(rnfstat&(1<<0)));} 

0/假: 表示nand flash 芯片忙状态 

1/真:表示nand flash 已经准备好 

3.nandflash读写擦具体实现。

      操作nand  flash时,先传输命令,然后传输地址,最后读、写数据,期间要检查flash的状态;

      k9f1208u0m  一页大小为528字节,而列地址a0——a7可以寻址的范围是256字节,所以将一页分为a、b、c三个区:

                 a区:0—255字节

                 b区:256—511字节

                 c区:512—527字节

 (0)nand flash 初始化 

void nf_init(void) 

    /* 设置 nand flash 配置寄存器, 每一位的取值见1.3 节 */ 

    rnfconf=(1<< 15)|(1<<14)|(1<< 13)|(1<<12)|(1<< 11)|(tacls<<8)|(twrph0<<4)|(twrph1<<0); 

    /* 复位外部 nand flash 芯片 */ 

    nf_reset(); 

}

(1)复位

          命令:ffh

          步骤:发出命令即可复位nand flash芯片;

 static void nf_reset(void) 

    int i; 

    nf_nfce_l();         /* 片选 nand flash 芯片*/ 

    nf_cmd(0xff);        /* 复位命令          */ 

    for(i=0;i< 10;i++); /* 等待twb = 100ns.       */ 

    nf_waitrb();        /* wait 200~500us;      */ 

    nf_nfce_h();        /* 取消nand flash 选中*/ 

(2)读操作

          命令:

                 00h——读a区

                 01h——读b区

                 50h——读c区

           操作步骤:

              1、发出命令 00h、01h 或50h,  00h将地址位a8设为0,  01h将a8设为1 ;

              2、依次发出4个地址序列;

              3、检测r/nb,待其为高电平时,就可以读取数据了;

参数说明:block :块号 

          page :页号 

          buffer :指向将要读取到内存中的起始位置 

返回值:1:读成功 

        0 :读失败 

static int nf_readpage(unsigned int block, unsigned int page, unsigned char *buffer) 

    unsigned int blockpage; 

    unsigned char ecc0, ecc1, ecc2; 

    unsigned char *bufpt=buffer; 

    unsigned char se[16]; 

    page=page&0x1f; 

    blockpage=(block<<5)+page; 

    nf_rstecc();      /* 初始化 ecc              */ 

    nf_nfce_l();       /* 片选 nand flash 芯片*/ 

    nf_cmd(0x00);      /* 从a 区开始读      */ 

    nf_addr(0);       /*  a0~a7 位(column address)      */ 

    nf_addr(blockpage&0xff);        /* a9-a 16, (page address) */ 

    nf_addr((blockpage>>8)&0xff);        /* a17-a24, (page address) */ 

    nf_addr((blockpage>> 16)&0xff);  /* a25,     (page address) */ 

    /* 等待nand flash 处于再准备状态 */ 

    for(i=0;i< 10;i++); 

    nf_waitrb();       /*等待 tr(max 12us) */ 

    /* 读整个页, 512 字节             */ 

    for(i=0;i<512;i++) 

    { 

         *bufpt++=nf_rddata(); 

    } 

    /* 读取 ecc 码 */ 

    ecc0=rnfecc0; 

    ecc 1=rnfecc 1; 

    ecc2=rnfecc2; 

/* 读取该页的oob 块 */ 

    for(i=0;i< 16;i++) 

         se[i]=nf_rddata(); 

    /* 校验 ecc 码, 并返回 */ 

    if(ecc0==se[0] && ecc 1==se[1] && ecc2==se[2]) 

           return 1; 

    else 

          return 0; 

(3)flash编程

            命令:

                   80h——10h :写单页;

                   80h——11h :对多个层进行些页操作;    

             操作步骤:

                 1、写单页步骤:

                            【1】发出80h命令后;

                            【2】发送4个地址序列;

                            【3】向flash发送数据;

                            【4】发出命令10h启动写操作,flash内部自动完成写、校验操作;

                            【5】通过命令70h读取状态位,查询写操作是否完成;

                  2、多页写

                            【1】发出80h、4个地址序列、最多528字节的数据;

                            【2】发出11h命令;

                            【3】接着在相邻层执行【1】、【2】两步操作;

                            【4】第四页的最后使用10h代替11h,启动flash内部的写操作;

                            【5】可以通过71h查询写操作是否完成;

以页为单位写入. 

参数说明:block        块号 

          page   页号 

          buffer  指向内存中待写入nand flash 中的数据起始位置 

返回值:      0 :写错误 

         1:写成功 

static int nf_writepage(unsigned int block, unsigned int page, unsigned char *buffer) 

    unsigned int blockpage = (block<<5)+page; 

    unsigned char *bufpt = buffer; 

    nf_rstecc();      /* 初始化 ecc            */ 

    nf_nfce_l();     /* 片选 nand flash 芯片*/ 

    nf_cmd(0x0);      /* 从a 区开始写      */ 

    nf_cmd(0x80);  /* 写第一条命令      */ 

    nf_addr(0);      /* a0~a7 位(column address)     */ 

    nf_addr(blockpage&0xff);      /* a9-a 16, (page address) */ 

    nf_addr((blockpage>>8)&0xff);      /* a17-a24, (page address) */ 

    nf_addr((blockpage>> 16)&0xff);  /* a25,    (page address) */ 

        nf_wrdata(*bufpt++);            /* 写一个页512 字节到 nand flash 芯片 */ 

    /* 

    * oob 一共16 bytes, 每一个字节存放什么由程序员自己定义, 通常, 

    * 我们在 byte0-byte2 存 ecc 检验码. byte6 存放坏块标志. 

    */ 

    sebuf[0]=rnfecc0;  /* 读取 ecc 检验码 0 */ 

    sebuf[1]=rnfecc 1;  /* 读取 ecc 检验码 1 */ 

    sebuf[2]=rnfecc2;  /* 读取 ecc 检验码 2 */ 

    sebuf[5]=0xff;       /* 非坏块标志       */ 

        nf_wrdata(sebuf [i]); /* 写该页的oob 数据块  */ 

    nf_cmd(0x10);     /* 结束写命令 */ 

    /* 等待nand flash 处于准备状态 */ 

    nf_waitrb(); 

    /* 发送读状态命令            nand flash */ 

    nf_cmd(0x70); 

    for(i=0;i<3;i++); 

  if (nf_rddata()&0x1) 

    {   /*如果写有错, 则标示为坏块    */ 

        nf_nfce_h();  /* 取消nand flash 选中*/ 

        nf_markbadblock(block); 

        return 0; 

    } else { /* 正常退出 */ 

        nf_nfce_h(); /* 取消nand flash 选中*/ 

        return 1; 

(4)复制

               命令:

                     00h——8ah——10h  :单层页内复制

                     03h——8ah——11h  :多层页内复制

               操作步骤:

                   1、单层页内复制步骤:

                            【1】发出命令00h;

                            【2】4个源地址序列;

                            【2】接着发出8ah;

                            【4】发出4个目的地址序列;

                            【5】发出10h命令,启动写操作;

                            【6】通过70h命令读取状态查询操作是否完成;

                   2、多层页内复制步骤:

                             【1】发出命令00h(第一层)、4个源页地址序列;

                             【2】以后各层依次发出命令03h、4个源页地址序列;

                             【3】发出命令8ah、目的地址、命令11h;

                             【4】各层依次执行【3】,在最后一页的地址后,用10h代替11h,启动写操作;

                             【5】通过71h命令读取状态查询操作是否完成;

(5)擦除

               命令:

                     60h——d0h  :单层内块擦除

                     60h-60h ——d0h  :多层内块擦除

               操作步骤:

                  1、单层内块擦除:

                              【1】发出命令字60h;

                              【2】发出块(block)地址,仅需3个地址序列;

                              【3】发出d0h,启动擦除操作;

                              【4】发出70h命令查询状态,是否完成擦除;

                   2、多层内块擦除:

                              【1】发出命令字60h,3个块地址序列;

                              【2】对每个层执行【1】;

                              【3】发出命令d0h,启动擦除操作;

                              【4】发出71h命令查询状态,检查是否完成擦除;

(6)读取芯片id

                 命令:90h

                 操作步骤:

                     1、发出命令90h;

                     2、发出4个地址序列(均设为0);

                     3、连续读入5个数据,分别表示:厂商代码、设备代码、保留字节、多层操作代码;

返回值为nand flash 芯片的 id 号 

unsigned short  nf_checkid(void) 

    unsigned short id; 

    nf_nfce_l();            /* 片选 nand flash 芯片*/ 

    nf_cmd(0x90);           /* 发送读id 命令到 nand flash 芯片 */ 

    nf_addr(0x0);           /* 指定地址 0x0 ,芯片手册要求    */ 

    for(i=0;i< 10;i++);      /* 等待twb = 100ns.           */ 

    id=nf_rddata()<<8;  /* 厂商id(k9s 1208v:0xec)          */ 

    id|=nf_rddata();         /* 设备 id(k9s 1208v:0x76)  */ 

    nf_nfce_h();             /* 取消nand flash 选中*/ 

    return id; 

(7)读状态

                 命令:

                       70h——单层状态

                       71h——多层状态

                 操作步骤:写入命令字之后,然后启动读操作即可读入此寄存器。

(8)nand flash 标记坏块 

如果是坏块, 通过写 oob 块的byte6 把该块标记为坏块。 

参数说明:block 块号 

返回值:1:ok ,成功完成标记。 

        0 :表示写oob 块正确. 

static int nf_markbadblock(unsigned int block) 

    unsigned int blockpage=(block<<5); 

    sebuf[0]=0xff; 

    sebuf[1]=0xff; 

    sebuf[2]=0xff; 

    sebuf[5]=0x44;      /* 设置坏块标记 */ 

    nf_cmd(0x50);         /* 从c 区开始写    */ 

    nf_cmd(0x80);         /* 发送编程命令, 让nand flash 处理写状态 */ 

    nf_addr(0x0);  /*        a0~a7 位(column address)         */ 

    nf_addr(blockpage&0xff);           /* a9-a 16, (page address) */ 

    nf_addr((blockpage>>8)&0xff);            /* a17-a24, (page address) */ 

    nf_addr((blockpage>> 16)&0xff);  /* a25,           (page address) */ 

    /* 写oob 数据块 */ 

         nf_wrdata(sebuf [i]); 

    nf_cmd(0x10);         /* 结束写命令 */ 

    /* 等待nandflash 准备好 */ 

    for(i=0;i< 10;i++);  /* twb = 100ns.     */ 

    nf_waitrb();

/*读nandflash 的写状态 */ 

    for(i=0;i<3;i++);  /* twhr=60ns  */ 

    if (nf_rddata()&0x1) 

                nf_nfce_h(); /* 取消nand flash 选中*/ 

                return 0; 

     } else { 

    return 1; 

(9)nand flash 检查坏块 

检查指定块是否是坏块. 

返回值:1:指定块是坏块 

        0 :指定块不是坏块。 

static int nf_isbadblock(u32 block) 

    u8 data; 

    blockpage=(block<<5); 

    nf_nfce_l();        /* 片选 nand flash 芯片*/ 

    nf_cmd(0x50);       /* read oob 数据块   */ 

    nf_addr(517&0xf);  /* a0~a7 位(column address)         */ 

    nf_addr(blockpage&0xff); /* a9-a 16, (page address) */ 

    nf_addr((blockpage>>8)&0xff);       /* a17-a24, (page address) */ 

    for(i=0;i< 10;i++); /* wait twb(100ns)  */ 

    /* 读取读出值 */ 

    data=nf_rddata(); 

    nf_nfce_h();     /* 取消nand flash 选中*/ 

    /* 如果data 不为0xff 时, 表示该块是坏块 */ 

    if(data != 0xff) 

(10)擦除指定块中数据 

返回值:0 :擦除错误。(若是坏块直接返回0 ;若擦除出现错误则标记为坏块然后返回0) 

1 :成功擦除。 

 static int nf_eraseblock(unsigned int block) 

 { 

     unsigned int blockpage=(block<<5); 

     int i; 

     /* 如果该块是坏块, 则返回 */ 

     if(nf_isbadblock(block)) 

         return 0; 

     nf_nfce_l();      /* 片选 nand flash 芯片*/ 

     nf_cmd(0x60);     /* 设置擦写模式      */ 

     nf_addr(blockpage&0xff);       /* a9-a 16, (page address) , 是基于块擦*/ 

     nf_addr((blockpage>>8)&0xff);        /* a17-a24, (page address) */ 

     nf_addr((blockpage>> 16)&0xff);  /* a25, (page address) */ 

     nf_cmd(0xd0);     /* 发送擦写命令, 开始擦写 */ 

     /* 等待nandflash 准备好 */ 

     for(i=0;i< 10;i++); /* twb(100ns) */ 

     nf_waitrb(); 

     /* 读取操作状态         */ 

     nf_cmd(0x70); 

     if (nf_rddata()&0x1) 

     { 

                 nf_nfce_h(); /* 取消nand flash 选中*/ 

                 nf_markbadblock(block); /* 标记为坏块 */ 

                 return 0; 

     }  else  { 

                 return 1; 

     } 

 }