欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

十三.ARM裸机学习之NandFlash详解

程序员文章站 2022-07-14 10:19:11
...

一、Nand Flash简介

2017/12/22 23:19

*1.Flash:NandFlash、NorFlash

*
● 优点:容量较大,改写速度快等优点,适用于大量数据的存储
● 缺点:时序复杂,无坏块处理机制,接口不统一
● NandFlash:MLC(可靠性差,容量大)、SLC(可靠性高、容量小)
● 现在基本都在发展MLC技术

二、Nand Flash存储器结构

NandFlash存储器结构如下(以K9F2G16为例):
十三.ARM裸机学习之NandFlash详解

1.NAND Flash存储器由block (块) 构成, block的基本单元是page (页)。

在K9F2G08芯片中,Page的大小是2KB+64B。也就是说我们要读写K9F2G08,每次至少要读写2KB或者n*2KB。(为什么是2KB+64B,参考四.NandFlash的结构详解
页往上还有个Block(块)的概念,1个块等于若干个页(譬如在K9F2G08中1个块等于64页)。
块往上就是整个Nand芯片了,叫做Device。一个Device是若干个Block,譬如K9F2F08一个Device有2028个block。所以整个Device大小为:2048×64×2K = 256MB

2.块设备分page、block有什么意义?

● NAND Flash的读取和烧录以页为基础,NAND Flash的擦除操作是基于block (块)的,这些规则是由Nand的物理原理和限制要求的!
● 在NAND Flash上有三种基本的操作:读取一个页, 烧录一个页和擦除一个块

三、NandFlash接口

Nand的功能框图如下(以K9F2G16为例):
十三.ARM裸机学习之NandFlash详解

1.Nand功能框图中主要包含2部分:Nand存储器+Nand接口电路。

● Nand存储器中的存储颗粒就是纯粹的Nand原理的存储单元,类似于仓库; Nand接口电路是用来管理存储颗粒,并且给外界提供一个统一的Nand接口规格的访问接口的。
● Nand芯片内部接口电路可以接收外部SoC发送的命令,与SoC交互。外部SoC对Nand芯片的操作都必须按照NandFlash规定的时序进行。(时序由Nand存储器内部处理,不需要自己处理,了解即可)
● Nand芯片内部通过接口电路,向外部提供统一的Nand接口的访问规则,然后外部的SoC可以使用Nand接口时序来读写这个Nand存储芯片。Nand接口是一种公用标准接口,理论上来说外部SoC可以直接模拟Nand接口来读写Nand芯片,但是实际上因为nand接口对时序要求非常严格,而且时序很复杂,所以一般的SoC都是通过专用的硬件的Nand控制器(这些控制器一般是作为SoC的内部外设来存在的)来操控Nand芯片的。

四、NandFlash的结构详解,了解即可

1.Nand的单元组织:block块与page页(大页Nand与小页Nand)

● 一个block等于多少page也是不定的,不同的Nand也不同。一个Nand芯片有多少block也是不定的,不同的Nand芯片也不同。
● Nand的组织架构挺乱的,接口时序也不同,造成结构就是不同厂家的Nand芯片,或者是同一个厂家的不同系列型号存储容量的nand接口也不一样。所以nand有一个很大的问题就是一旦升级容量或者换芯片系列则硬件要重新做、软件要重新移植。

2.带内数据和带外数据(ECC与坏块标记),块设备基础理论

● Nand的每个页由2部分组成,这2部分各自都有一定的存储空间。譬如K9F2G08中为2K+64字节。其中的2K字节属于带内数据,是我们真正的存储空间,将来存储在Nand中的有效数据就是存在这2K范围内的(我们平时计算nand的容量时也是只考虑这2KB);64字节的带外数据不能用来存储有效数据,是作为别的附加用途的(譬如用来存储ECC数据、用来存储坏块标志等····)
● ECC:(error correction code,错误校验码)。因为nand存储本身出错(位反转)概率高(Nand较Nor最大的缺点就是稳定性),所以当我们将有效信息存储到Nand中时都会同时按照一定算法计算一个ECC信息(譬如CRC16等校验算法),将ECC信息同时存储到Nand这个页的带外数据区。然后等将来读取数据时,对数据用同样的算法再计算一次ECC,并且和从带外数据区读出的ECC进行校验。如果校验通过则证明Nand的有效数据可信,如果校验不通过则证明这个数据已经被损坏(只能丢弃或者尝试修复)
● 坏块标志:Nand芯片用一段时间后,可能某些块会坏掉(这些块无法擦除了,或者无法读写了),nand的坏块非常类似于硬盘的坏道。坏块是不可避免的,而且随着Nand的使用坏块会越来越多。当坏块还不算太多时这个Nand都是可以用的,除非坏块太多了不划算使用了才会换新的。所以我们为了管理Nand发明了一种坏块标志机制。Nand的每个页的64字节的带外数据中,我们(一般是文件系统)定义一个固定位置(譬如定位第24字节)来标记这个块是好的还是坏的。文件系统在发现这个块已经坏了没法用了时会将这个块标记为坏块,以后访问nand时直接跳过这个块即可。

3.Nand的地址时序

● (1)nand的地址有多位,分4/5周期通过IO引脚发送给Nand芯片来对Nand进行寻址。寻址的最小单位是字节,但是读写的最小单位是页。
● (2)nand的地址在写代码时要按照Nand要求的时序和顺序去依次写入。

4.Nand的命令码

十三.ARM裸机学习之NandFlash详解
● (1)外部SoC要想通过Nand控制器来访问Nand(实质就是通过Nand接口),就必须按照Nand接口给nand发送命令、地址、数据等信息来读写Nand。
● (2)Nand芯片内部的管理电路本身可以接收外部发送的命令,然后根据这些命令来读写Nand内容与外部SoC交互。所以我们对nand进行的所有操作(擦除、读、写···)都要有命令、地址、数据的参与才能完成,而且必须按照Nand芯片规定的流程来做。

五、NandFlash的常见操作及流程分析

参考博客:http://blog.51cto.com/9291927/1787752,侵权联系删。
发送烧录命令、烧录地址、烧录数据的命令如下
十三.ARM裸机学习之NandFlash详解

1、坏块检查

● NandFlash使用前需要统一擦除(按块擦除),擦除后填充1。所以擦干净之后读出来的值是0xFF。
● 擦除块,将块中的每个字节与0xFF比较,如果不是0xFF,是坏块。
十三.ARM裸机学习之NandFlash详解

2、页写(program)操作

(1)、正常页写入过程:
● 页烧录前需要擦除,如果页烧录之前没有擦除,烧录的数据将是错误的。
● 烧录时,SoC通过命令线、IO线依次发送烧录命令、烧录地址、烧录数据等进入NandFlash。
NandFlash芯片接口电路接收页数据到缓冲区,再集中烧录到Nand Flash的存储颗粒。烧录过程需要一定时间,SoC的NandFlash控制器需要等待NandFlash烧录完,等待过程中SoC的NandFlash控制器将会不断读取页烧录状态。SoC的NandFlash控制器收到正确的状态响应时,确认页烧录已经成功,如果一直未收到正确的状态,则认为烧录页所在的块是坏块。
(2)正常情况下经过以上的过程已经完了。但是因为Nand的读写有不靠谱情况,因此我们为了安全会去做ECC校验
*

ECC校验有硬件式校验和软件式校验2种

*。
● 软件式校验可以采用的策略有很多,其中之一(Nand芯片手册上推荐的方式是):将刚才写入的1页数据读出来,和写入的内容进行逐一对比。如果读出的和写入的完全一样,说明刚才的写入过程正确完成了;如果读出来的和写入的不完全一样那就说明刚才的写入有问题
● 硬件式ECC:SoC的Nand控制器可以提供硬件式ECC(这个也是比较普遍的情况)。硬件式ECC就是在Nand的控制器中有个硬件模块专门做ECC操作。当我们操作Nand芯片时,只要按照SoC的要求使硬件模块工作即可,内部的ECC会自动处理,我们需要做的就是对生成的数据进行处理即可。
十三.ARM裸机学习之NandFlash详解

3、块擦除、页读取

● (1)擦除时必须给块对齐的地址。如果给了不对齐的地址,结果是不可知的(有些Nand芯片没关系,它内部会自动将其对齐,而有些Nand会返回地址错误)。
● (2)读写时给的地址也是一样,要求是页对齐地址。如果给了不对齐的,也是有可能对有可能错。
十三.ARM裸机学习之NandFlash详解

六、Nand Flash控制器

结构框图如下:
十三.ARM裸机学习之NandFlash详解
结构框图中关键点:SFR(编程时就是通过读写SFR来产生Nand接口时序以读写Nand芯片的) + Nand interface(硬件接口,将来和Nand芯片的相应引脚进行连接) + ECC生成器
NFCONF:NandFlash配置寄存器,配置nandflash的寄存器
NFCONT:NandFlash控制寄存器,控制片选信号等
NFCMMD:NandFlash命令寄存器,发命令需要的寄存器
NFADDR:NandFlash地址寄存器,发列地址和行地址需要用到的寄存器
NFDATA:NandFlash数据寄存器,读取2k带内数据需要用到
NFSBLK:烧录块起始地址
NFEBLK:烧录块结束地址
NFSTAT:状态寄存器,在擦除块,页写入流程中的待就绪这个过程就需要该寄存器

七、Nand Flash代码分析

要结合SoC的数据手册、Nand芯片的数据手册、和示例代码三者来理解

在六中,我们已经知道Nand Flash控制器的主要寄存器功能,这里,结合Nand Flash的常见操作流程和SoC中Nand Flash控制器的寄存器说明来解剖原厂提供的示例代码。

1.发地址

Nand读写时地址传递是通过IO线发送的,因为地址有30位而IO只有8位,所以需要多个cycle才能发送完毕。一般的Nand都是4cycle或者5cycle发送地址(从这里把Nand分为了4cycle Nand和5cycle Nand)。
十三.ARM裸机学习之NandFlash详解
十三.ARM裸机学习之NandFlash详解

2.块擦除

十三.ARM裸机学习之NandFlash详解
通过以上代码的分析,可以得出
● 块擦除编程流程如下:
A、获取擦除块的地址(块对齐)
B、发出片选信号
C、发送擦除命令,第一个周期发命令0x60,第二个周期发块地址(3个周期的行地址),第三个周期发命令0xd0 。
D、清除状态,等待状态。
E、读取状态,擦除成功则取消片选,擦除失败取消片选。

3.页读取

十三.ARM裸机学习之NandFlash详解
● 页读取编程流程:
A、发出片选信号
B、发送页读取命令,第一个周期发命令0x00,第二个周期发送页地址,第三个周期发送命令0x30
C、等待状态
D、发送页读取第一个周期命令0x05
E、写入页内偏移地址
F、发送页读取第二个周期命令0xE0
G、读取数据
H、检查状态,如果读取成功,取消片选信号,读取失败取消片选信号。

4.页写入

十三.ARM裸机学习之NandFlash详解
● 页写入编程流程如下:
A、发出片选信号
B、发送页烧录命令第一个周期命令0x80
C、随机写页内某个地址的值
D、发送页写命令0x85
E、写入页内偏移地址
F、写入数据
G、发送页烧录命令第二个周期命令0x10
H、等待状态
I、读取状态,页烧录成功取消片选信号,失败取消片选信号

最后贴上原厂官方整体代码:

#include "nand.h"
#include "stdio.h"

#define rNFCONF             ( *((volatile unsigned long *)0xB0E00000) )
#define rNFCONT             ( *((volatile unsigned long *)0xB0E00004) )
#define rNFCMMD             ( *((volatile unsigned long *)0xB0E00008) )
#define rNFADDR             ( *((volatile unsigned long *)0xB0E0000C) )
#define rNFDATA             ( *((volatile unsigned long *)0xB0E00010) )
#define rNFDATA8            ( *((volatile unsigned char *)0xB0E00010) )
#define rNFSTAT             ( *((volatile unsigned long *)0xB0E00028) )

#define rMP0_1CON       ( *((volatile unsigned long *)0xE02002E0) )
#define rMP0_2CON       ( *((volatile unsigned long *)0xE0200300) )
#define rMP0_3CON       ( *((volatile unsigned long *)0xE0200320) )

#define MAX_NAND_BLOCK                8192          /*定义nand最大块数:8192块  */
#define NAND_PAGE_SIZE                2048          /*定义一页的容量:2048 byte     */
#define NAND_BLOCK_SIZE               64            /*定义block大小:64页     */

#define TACLS                         1     // 12ns     /* 时序相关的设置          */
#define TWRPH0                        4
#define TWRPH1                        1

#define NAND_CMD_READ_1st             0x00          /* 命令                       */
#define NAND_CMD_READ_2st             0x30

#define NAND_CMD_READ_CB_1st          0x00
#define NAND_CMD_READ_CB_2st          0x35

#define NAND_CMD_RANDOM_WRITE         0x85
#define NAND_CMD_RANDOM_READ_1st      0x05
#define NAND_CMD_RANDOM_READ_2st      0xe0

#define NAND_CMD_READ_ID              0x90
#define NAND_CMD_RESET                0xff
#define NAND_CMD_READ_STATUS          0x70

#define NAND_CMD_WRITE_PAGE_1st       0x80
#define NAND_CMD_WRITE_PAGE_2st       0x10

#define NAND_CMD_BLOCK_ERASE_1st      0x60
#define NAND_CMD_BLOCK_ERASE_2st      0xd0


#define ECC_EN                        (1<<4)
#define CONTROL_EN                    (1<<0)


static void nand_reset(void);
static void nand_wait_idle(void);
static void nand_select_chip(void);
static void nand_deselect_chip(void);
static void nand_send_cmd(unsigned long cmd);
static void nand_send_addr(unsigned long addr);
static unsigned char nand_read8(void);
static void nand_write8(unsigned char data);
static unsigned int nand_read32(void);
static void nand_write32(unsigned int data);

typedef struct nand_id_info
{
    //marker code
    unsigned char IDm; 
    //device code
    unsigned char IDd; 
    unsigned char ID3rd;
    unsigned char ID4th;
    unsigned char ID5th;
}nand_id_info;

// 复位  
void nand_reset(void)
{
    nand_select_chip();
    nand_send_cmd(NAND_CMD_RESET);
    nand_wait_idle();
    nand_deselect_chip();
}

// 等待就绪  
void nand_wait_idle(void)
{
    unsigned long i;
    while( !(rNFSTAT & (1<<4)) )
        for(i=0; i<10; i++);
}

// 发片选  
void nand_select_chip(void)
{
    unsigned long i;
    rNFCONT &= ~(1<<1);
    for(i=0; i<10; i++);
}

// 取消片选  
void nand_deselect_chip(void)
{
    unsigned long i = 0;
    rNFCONT |= (1<<1);
    for(i=0; i<10; i++);
}

// 发命令  
void nand_send_cmd(unsigned long cmd)
{
    unsigned long i = 0;

    rNFCMMD = cmd;
    for(i=0; i<10; i++);
}

// 发地址  
void nand_send_addr(unsigned long addr)
{
    unsigned long i;
    unsigned long col, row;

    // 列地址,即页内地址  
    col = addr % NAND_PAGE_SIZE;        
    // 行地址,即页地址             
    row = addr / NAND_PAGE_SIZE;

    // Column Address A0~A7  
    rNFADDR = col & 0xff;           
    for(i=0; i<10; i++);        

    // Column Address A8~A11  
    rNFADDR = (col >> 8) & 0x0f;        
    for(i=0; i<10; i++);

    // Row Address A12~A19 
    rNFADDR = row & 0xff;           
    for(i=0; i<10; i++);

    // Row Address A20~A27 
    rNFADDR = (row >> 8) & 0xff;
    for(i=0; i<10; i++);

    // Row Address A28~A30 
    rNFADDR = (row >> 16) & 0xff;
    for(i=0; i<10; i++);
}

unsigned int nand_read32(void)
{
    return rNFDATA;
}

void nand_write32(unsigned int data)
{
    rNFDATA = data;
}

// 读一个字节的数据  
unsigned char nand_read8(void)
{
    return rNFDATA8;
}

// 写一个字节的数据  
void nand_write8(unsigned char data)
{
    rNFDATA8 = data;
}

unsigned char nand_read_status(void)
{
    unsigned char ch;
    int i;

    // 1. 发出片选信号  
    nand_select_chip();

    // 2. 读状态  
    nand_send_cmd(NAND_CMD_READ_STATUS);
    for(i=0; i<10; i++);
    ch = nand_read8();

    // 3. 取消片选  
    nand_deselect_chip();
    return ch;
}

// nandflash 初始化  
void nand_init(void)
{

    // 1. 配置nandflash  
    rNFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<3)|(0<<2)|(1<<1)|(0<<0);
    rNFCONT = (0<<18)|(0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(0<<7)|(0<<6)|(0x3<<1)|(1<<0);

    // 2. 配置引脚  
    rMP0_1CON = 0x22333322;
    rMP0_2CON = 0x00002222;
    rMP0_3CON = 0x22222222;

    // 3. 复位  
    nand_reset();
}

// 读芯片ID  
void nand_read_id(void)
{
    nand_id_info nand_id;
    // 1. 发片选  
    nand_select_chip();

    // 2. 读ID  
    nand_send_cmd(NAND_CMD_READ_ID);
    nand_send_addr(0x00);
    nand_wait_idle();

    nand_id.IDm =   nand_read8();
    nand_id.IDd =   nand_read8();
    nand_id.ID3rd = nand_read8();
    nand_id.ID4th = nand_read8();
    nand_id.ID5th = nand_read8();

    printf("nandflash: makercode = %x\r\n devicecode = %x\r\n ID3rd = %x\r\n ID4rd = %x\r\n ID5rd = %x\r\n", nand_id.IDm, nand_id.IDd, nand_id.ID3rd, nand_id.ID4th, nand_id.ID5th);
    nand_deselect_chip();
}

// 擦除块,参数为块号(0 ~ MAX_NAND_BLOCK-1)  
int nand_block_erase(unsigned long block_num)
{
    unsigned long i = 0;
    // 获得row地址,即页地址  块号*大小
    unsigned long row = block_num * NAND_BLOCK_SIZE;

    // 1. 发出片选信号  
    nand_select_chip();
    // 2. 擦除:第一个周期发命令0x60,第二个周期发块地址,第三个周期发命令0xd0  
    nand_send_cmd(NAND_CMD_BLOCK_ERASE_1st);
    for(i=0; i<10; i++);

    // Row Address A12~A19 
    rNFADDR = row & 0xff;                           
    for(i=0; i<10; i++);
    // Row Address A20~A27  
    rNFADDR = (row >> 8) & 0xff;
    for(i=0; i<10; i++);
    // Row Address A28~A30  
    rNFADDR = (row >> 16) & 0xff;   

    rNFSTAT |= (1<<4);          // clear RnB bit 

    nand_send_cmd(NAND_CMD_BLOCK_ERASE_2st);
    for(i=0; i<10; i++);
    // 3. 等待就绪  
    nand_wait_idle();

    // 4. 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )
    {
        // statas[0] = 1,表示擦除失败,详见NAND Flash数据手册中 READ STATUS一节的描述
        // 取消片选信号  
        nand_deselect_chip();                       
        printf("masking bad block %d\r\n", block_num);
        return -1;
    }
    else
    {
        // status[0] = 0,表示擦除成功,返回0
        nand_deselect_chip();
        return 0;
    }
}

int nand_erase(unsigned long block_addr)
{
    int i = 0;
    if((nand_read_status() & 0x80) == 0) 
    {
        printf("Write protected.\n");
        return -1;
    }

    unsigned long row = block_addr >> 18;
    // 1. 发出片选信号  
    nand_select_chip();
    // 2. 擦除:第一个周期发命令0x60,第二个周期发块地址,第三个周期发命令0xd0  
    nand_send_cmd(NAND_CMD_BLOCK_ERASE_1st);
    for(i=0; i<10; i++);

    // Row Address A12~A19 
    rNFADDR = row & 0xff;                           
    for(i=0; i<10; i++);
    // Row Address A20~A27  
    rNFADDR = (row >> 8) & 0xff;
    for(i=0; i<10; i++);
    // Row Address A28~A30  
    rNFADDR = (row >> 16) & 0x01;   // 只要最低1bit为 A28
    for(i=0; i<10; i++);

    rNFSTAT |= (1<<4);          // clear RnB bit 

    nand_send_cmd(NAND_CMD_BLOCK_ERASE_2st);
    for(i=0; i<10; i++);
    // 3. 等待就绪  
    nand_wait_idle();

    // 4. 读状态  
    unsigned char status = nand_read_status();
    if (status & 1)
    {
        // statas[0] = 1,表示擦除失败,详见NAND Flash数据手册中 READ STATUS一节的描述
        // 取消片选信号  
        nand_deselect_chip();                       
        printf("masking bad block %d\r\n", block_addr);
        return -1;
    }
    else
    {
        // status[0] = 0,表示擦除成功,返回0
        nand_deselect_chip();
        return 0;
    }
}

// 从nand中读数据到sdram  
int copy_nand_to_sdram(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long length)
{
    unsigned long i = 0;

    // 1. 发出片选信号  
    nand_select_chip();

    // 2. 从nand读数据到sdram,第一周期发命令0x00,第二周期发地址nand_addr,第三个周期发命令0x30,可读一页(2k)的数据  
    while(length)
    {
        nand_send_cmd(NAND_CMD_READ_1st);
        nand_send_addr(nand_addr);
        rNFSTAT = (rNFSTAT)|(1<<4);
        nand_send_cmd(NAND_CMD_READ_2st);
        nand_wait_idle();

        // 列地址,即页内地址  
        unsigned long col = nand_addr % NAND_PAGE_SIZE;
        i = col;
        // 读一页数据,每次拷1byte,共拷2048次(2k),直到长度为length的数据拷贝完毕 
        for(; i<NAND_PAGE_SIZE && length!=0; i++,length--)
        {
            *sdram_addr = nand_read8();
            sdram_addr++;
            nand_addr++;
        }
    }

    // 3. 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )
    {
        // 取消片选信号  
        nand_deselect_chip();
        printf("copy nand to sdram fail\r\n");
        return -1;
    }
    else
    {
        nand_deselect_chip();
        return 0;
    }
}

// 从sdram中写数据到nand  
int copy_sdram_to_nand(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long length)
{
    unsigned long i = 0;

    // 1. 发出片选信号  
    nand_select_chip();

    // 2. 从sdram读数据到nand,第一周期发命令0x80,第二周期发地址nand_addr,第三个周期写一页(2k)数据,第四周期发0x10  
    while(length)
    {
        nand_send_cmd(NAND_CMD_WRITE_PAGE_1st);
        nand_send_addr(nand_addr);
        // 列地址,即页内地址  
        unsigned long col = nand_addr % NAND_PAGE_SIZE;
        i = col;
        // 写一页数据,每次拷1byte,共拷2048次(2k),直到长度为length的数据拷贝完毕 
        for(; i<NAND_PAGE_SIZE && length!=0; i++,length--)
        {
            nand_write8(*sdram_addr);
            sdram_addr++;
            nand_addr++;
        }
        rNFSTAT = (rNFSTAT)|(1<<4);
        nand_send_cmd(NAND_CMD_WRITE_PAGE_2st);
        nand_wait_idle();
    }
    // 3. 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )
    {
        // 取消片选信号  
        nand_deselect_chip();
        printf("copy sdram to nand fail\r\n");
        return -1;
    }
    else
    {
        nand_deselect_chip();
        return 0;
    }
}

int nand_page_read(unsigned int pgaddr, unsigned char *buf, unsigned int length)
{
    int i = 0;

    // 1 发出片选信号  
    nand_select_chip();

    // 2 写页读命令1st  
    nand_send_cmd(NAND_CMD_READ_1st);

    // 3 写入页地址
    rNFADDR = 0;                                        
    rNFADDR = 0;
    rNFADDR = pgaddr&0xff;
    rNFADDR = (pgaddr>>8)&0xff;
    rNFADDR = (pgaddr>>16)&0xff;

    // 4 clear RnB
    rNFSTAT |= (1<<4);

    // 5  写页读命令2st 
    nand_send_cmd(NAND_CMD_READ_2st);

    // 6 等待空闲
    nand_wait_idle();

    // 7 连续读取2KB的Page main区数据 (继续读取可读出64B的spare area数据)
    for (i=0; (i<NAND_PAGE_SIZE) && (length!=0); i++,length--)
        *buf++ = nand_read8();

    // 8 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )
    {
        // 读出错,取消片选信号,返回错误码-1  
        nand_deselect_chip();
        printf("nand random read fail\r\n");
        return -1;
    }
    else
    {
        // 读正确,取消片选,返回0
        nand_deselect_chip();
        return 0;
    }
}


int nand_page_read32(unsigned int pgaddr, unsigned int *buf, unsigned int lengthB)
{
    int i = 0;

    // 1 发出片选信号  
    nand_select_chip();

    // 2 写页读命令1st  
    nand_send_cmd(NAND_CMD_READ_1st);

    // 3 写入页地址
    rNFADDR = 0;                                        
    rNFADDR = 0;
    rNFADDR = pgaddr&0xff;
    rNFADDR = (pgaddr>>8)&0xff;
    rNFADDR = (pgaddr>>16)&0xff;

    // 4 clear RnB
    rNFSTAT |= (1<<4);

    // 5  写页读命令2st 
    nand_send_cmd(NAND_CMD_READ_2st);

    // 6 等待空闲
    nand_wait_idle();

    // 7 连续读取2KB的Page main区数据 (继续读取可读出64B的spare area数据)
    for (i=0; (i<NAND_PAGE_SIZE/4) && (lengthB!=0); i++,lengthB--)
        *buf++ = nand_read32();

    // 8 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )
    {
        // 读出错,取消片选信号,返回错误码-1  
        nand_deselect_chip();
        printf("nand random read fail\r\n");
        return -1;
    }
    else
    {
        // 读正确,取消片选,返回0
        nand_deselect_chip();
        return 0;
    }
}


int nand_page_write(unsigned int pgaddr, const unsigned char *buf, unsigned int length)
{
    int i = 0;

    // 1 发出片选信号  
    nand_select_chip();

    // 2 write cmd 1st  
    nand_send_cmd(NAND_CMD_WRITE_PAGE_1st);

    // 3 write page addr
    rNFADDR = 0;
    rNFADDR = 0;
    rNFADDR = pgaddr&0xff;
    rNFADDR = (pgaddr>>8)&0xff;
    rNFADDR = (pgaddr>>16)&0xff;

    // 4 写入一页内容
    for(; i<NAND_PAGE_SIZE && length!=0; i++,length--)
        nand_write8(*buf++);

    // 5 clear RnB
    rNFSTAT = (rNFSTAT)|(1<<4);

    // 6 write cmd 2
    nand_send_cmd(NAND_CMD_WRITE_PAGE_2st);

    // 7 wait idle
    nand_wait_idle();

    // 8 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )
    {
        // 取消片选信号  
        nand_deselect_chip();
        printf("nand random write fail\r\n");
        return -1;
    }
    else
    {
        nand_deselect_chip();
        return 0;
    }

}

/*
 * 函数功能:    随机读数据 
 * 参数:      pgaddr 为页地址, offset为页内偏移地址,data为返回值
 * 返回值:     0表示读取成功,1表示读取失败
*/ 
int nand_random_read(unsigned long pgaddr,unsigned short offset, unsigned char *data)
{
    unsigned char readdata;
  // 1. 发出片选信号  
    nand_select_chip();

    // 2. 随机读页内某个地址的值  
    nand_send_cmd(NAND_CMD_READ_1st);
    //写入页地址
    rNFADDR = 0;                                        
    rNFADDR = 0;
    rNFADDR = pgaddr&0xff;
    rNFADDR = (pgaddr>>8)&0xff;
    rNFADDR = (pgaddr>>16)&0xff;
    rNFSTAT |= (1<<4);
    nand_send_cmd(NAND_CMD_READ_2st);
    nand_wait_idle();

    nand_send_cmd(NAND_CMD_RANDOM_READ_1st);
    //写入页内偏移地址
    rNFADDR = offset&0xff;                          
    rNFADDR = (offset>>8)&0xff;
    rNFSTAT = (rNFSTAT)|(1<<4);
    nand_send_cmd(NAND_CMD_RANDOM_READ_2st);

    readdata = nand_read8();

    // 3. 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )// 取消片选信号  
    {

        nand_deselect_chip();
        printf("nand random read fail\r\n");
        return -1;
    }
    else //读取失败取消片选信号
    {
        nand_deselect_chip();
        *data = readdata;
        return 0;
    }
}/*
 * 函数功能:    随机写数据
 * 参数:      pgaddr 为页地址, offset为页内偏移地址,wrdata为要写入的数据
 * 返回值:     0表示写入成功,1表示写入失败
 * 测试结论:    1、random write一次只能写入一个字节,因此内部只能使用 nand_write8,使用nand_write32就会出错
*/ 
int nand_random_write(unsigned long pgaddr,unsigned short offset,unsigned char wrdata)
{
    // 1. 发出片选信号  
    nand_select_chip();
    // 2. 随机写页内某个地址的值  
    nand_send_cmd(NAND_CMD_WRITE_PAGE_1st);
    rNFADDR = 0;
    rNFADDR = 0;
    rNFADDR = pgaddr&0xff;
    rNFADDR = (pgaddr>>8)&0xff;
    rNFADDR = (pgaddr>>16)&0xff;
    nand_send_cmd(NAND_CMD_RANDOM_WRITE);
    //写入页内偏移地址
    rNFADDR = offset&0xff;                  
    rNFADDR = (offset>>8)&0xff;
    nand_write8(wrdata);
    rNFSTAT = (rNFSTAT)|(1<<4);
    nand_send_cmd(NAND_CMD_WRITE_PAGE_2st);
    nand_wait_idle();

    // 3. 读状态  
    unsigned char status = nand_read_status();
    if (status & 1 )// 取消片选信号  
    {
        // 取消片选信号  
        nand_deselect_chip();
        printf("nand random write fail\r\n");
        return -1;
    }
    else //失败取消片选信号
    {
        nand_deselect_chip();
        return 0;
    }
}


// nand_test 使用的是BLOCK_NO块中的首页
#define BLOCK_NO    10
#define PAGE_NO     (BLOCK_NO * NAND_BLOCK_SIZE)
#define PAGE_ADDR   (PAGE_NO * NAND_PAGE_SIZE)


#define OFFSET0 4
#define OFFSET1 5
#define OFFSET2 6
#define OFFSET3 7

void nand_test(void)
{
    int ret = 0;
    unsigned char data1, data2, data3, data4;
    unsigned char buf[8];
    unsigned int bufInt[2];

    nand_init();
    nand_read_id();


    #if 0
    // 得先擦除才能写啊
    if ((ret = nand_erase(PAGE_ADDR)) == 0)
        printf("success to erase block %d\r\n", BLOCK_NO);
    else
        printf("fail to erase block %d\r\n", BLOCK_NO);
    #endif


    // 先给丫random写4个byte
    #if 1
    nand_random_write(PAGE_ADDR, OFFSET0, 'a');
    nand_random_write(PAGE_ADDR, OFFSET1, 'b');
    nand_random_write(PAGE_ADDR, OFFSET2, 'c');
    nand_random_write(PAGE_ADDR, OFFSET3, 'd');
    #endif

    #if 0
    nand_random_write(PAGE_ADDR, OFFSET0, 0xba);
    nand_random_write(PAGE_ADDR, OFFSET1, 0xde);
    nand_random_write(PAGE_ADDR, OFFSET2, 0xc0);
    nand_random_write(PAGE_ADDR, OFFSET3, 0xde);

    #endif

    // 然后再用三种方法,读出来看看对不对得上
    nand_random_read(PAGE_ADDR, OFFSET0, &data1);
    nand_random_read(PAGE_ADDR, OFFSET1, &data2);
    nand_random_read(PAGE_ADDR, OFFSET2, &data3);
    nand_random_read(PAGE_ADDR, OFFSET3, &data4);

    printf("PAGE_ADDR: \r\n", PAGE_ADDR);
    printf("4 byte data from nand_random_read: %x, %x, %x, %x\r\n", data1, data2, data3, data4);

    ret = nand_page_read(PAGE_ADDR, buf, sizeof(buf));
    if (ret != 0)
        printf("nand_page_read error!\r\n");
    else
        printf("4 byte data form nand_page_read: %x, %x, %x, %x\r\n", buf[OFFSET0], buf[OFFSET1], buf[OFFSET2], buf[OFFSET3]);

    ret = nand_page_read32(PAGE_ADDR, bufInt, sizeof(bufInt)/sizeof(unsigned int));
    if (ret != 0)
        printf("nand_page_read32 error!\r\n");
    else    
        printf("1 word data form nand_page_read32: %x\r\n", bufInt[OFFSET0]);


}