201007141414[轉載]Nand flash結構以及讀寫經典分析


NAND Flash 的數據是以bit 的方式保存在memory cell,一般來說,一個cell 中只能存儲一個bit。這些cell 以8 個或者16 個為單位,連成bit line,形成所謂的byte(x8)/word(x16),這就是NAND Device 的位寬。這些Line 會再組成Page.
(Nand Flash 有多種結構,我使用的Nand Flash 是K9F1208,下面內容針對三星的K9F1208U0M),每頁528Byte,每32 個page 形成一個Block, Sizeof(block)=16kByte 。
1 block="16kbyte",512Mbit=64Mbyte,Numberof(block)=4096 1block=32page, 1page=528byte=512byte(Main Area)+16byte(Spare Area)
Nand flash 以頁為單位讀寫數據,而以塊為單位擦除數據。
按照這樣的組織方式可以形成所謂的三類地址:
--Block Address -- Page Address  --Column Address
對於NAND Flash 來講,地址和命令只能在I/O[7:0]上傳遞,數據寬度是8 位。
512byte需要9bit來表示,對於528byte系列的NAND,這512byte被分成1st half和2nd half,各自的訪問由地址指針命令來選擇,A[7:0]就是所謂的column address。
32 個page 需要5bit 來表示,佔用A[13:9],即該page 在塊內的相對地址。 Block的地址是由A14 以上的bit 來表示,例如512Mb 的NAND,共4096block,因此,需要12 個bit 來表示,即A[25:14],如果是1Gbit 的528byte/page的NAND Flash,則block address用A[26:24]表示。而page address就是blcok address|page address in block

NAND Flash 的地址表示為:
 Block Address|Page Address in block|halfpage pointer|Column Address
地址傳送順序是Column Address,Page Address,Block Address。
由於地址只能在I/O[7:0]上傳遞,因此,必須採用移位的方式進行。例如,對於512Mbit x8 的NAND flash,地址範圍是0~0x3FF_FFFF,只要是這個範圍內的數值表示的地址都是有效的。以NAND_ADDR 為例: 第1 步是傳遞column address,就是NAND_ADDR[7:0],不需移位即可傳遞到I/O[7:0]上,而halfpage pointer 即bit8 是由操作指令決定的,即指令決定在哪個halfpage 上進行讀寫。而真正的bit8 的值是don't care 的。第2 步就是將NAND_ADDR 右移9 位,將NAND_ADDR[16:9]傳到I/O[7:0]上第3 步將NAND_ADDR[24:17]放到I/O 上第4 步需要將NAND_ADDR[25]放到I/O 上因此,整個地址傳遞過程需要4 步才能完成,即4-step addressing。如果NAND Flash 的容量是256Mbit 以下,那麼,block adress 最高位只到bit24,因此尋址只需要3 步。下面,就x16 的NAND flash 器件稍微進行一下說明。由於一個page 的main area 的容量為256word,仍相當於512byte。但是,這個時候沒有所謂的1st halfpage 和2nd halfpage 之分了,所以,bit8就變得沒有意義了,也就是這個時候bit8 完全不用管,地址傳遞仍然和x8 器件相同。除了,這一點之外,x16 的NAND使用方法和x8 的使用方法完全相同。

 

正如硬盤的盤片被分為磁道,每個磁道又分為若干扇區,一塊nand flash也分為若干block,每個block分為如乾page。一般而言,block、page之間的關係隨著芯片的不同而不同,典型的分配是這樣的:
1block = 32page
1page = 512bytes(datafield) + 16bytes(oob)


需要注意的是,對於flash的讀寫都是以一個page開始的,但是在讀寫之前必須進行flash的擦寫,而擦寫則是以一個block為單位的。同時必須提醒的是,512bytes理論上被分為1st half 和2sd half,每個half各佔256個字節。

我們討論的K9F1208U0B總共有4096 個Blocks,故我們可以知道這塊flash的容量為4096 *(32 *528)= 69206016 Bytes = 66 MB  但事實上每個Page上的最後16Bytes是用於存貯檢驗碼和其他信息用的,並不能存放實際的數據,所以實際上我們可以操作的芯片容量為4096 *(32 *512) = 67108864 Bytes = 64 MB由上圖所示,1個Page總共由528 Bytes組成,這528個字節按順序由上而下以列為單位進行排列(1列代表一個Byte。第0行為第0 Byte ,第1行為第1 Byte,以此類推,每個行又由8個位組成,每個位表示1個Byte裡面的1bit)。這528Bytes按功能分為兩大部分,分別是Data Field和Spare Field,其中Spare Field佔528Bytes裡的16Bytes,這16Bytes是用於在讀寫操作的時候存放校驗碼用的,一般不用做普通數據的存儲區,除去這16Bytes,剩下的512Bytes便是我們用於存放數據用的Data Field,所以一個Page上雖然有528個Bytes,但我們只按512Bytes進行容量的計算。

讀命令有兩個,分別是Read1,Read2其中Read1用於讀取Data Field的數據,而Read2則是用於讀取Spare Field的數據。對於Nand Flash來說,讀操作的最小操作單位為Page,也就是說當我們給定了讀取的起始位置後,讀操作將從該位置開始,連續讀取到本Page的最後一個Byte為止(可以包括Spare Field)

Nand Flash的尋址
Nand Flash的地址寄存器把一個完整的Nand Flash地址分解成Column Address與Page Address.進行尋址。
Column Address: 列地址。 Column Address其實就是指定Page上的某個Byte,指定這個Byte其實也就是指定此頁的讀寫起始地址。
Paage Address:頁地址。由於頁地址總是以512Bytes對齊的,所以它的低9位總是0。確定讀寫操作是在Flash上的哪個頁進行的。
Read1命令
當我們得到一個Nand Flash地址src_addr時我們可以這樣分解出Column Address和Page Address
column_addr=src_addr%512;                         // column address
page_address=(src_addr>>9);                       // page address
也可以這麼認為,一個Nand Flash地址的A0~A7是它的column_addr,A9~A25是它的Page Address。 (注意地址位A8並沒有出現,也就是A8被忽略,在下面你將了解到這是什麼原因)
Read1 命令的操作分為4個Cycle,發送完讀命令00h或01h(00h與01h的區別請見下文描述)之後將分4個Cycle發送參數,1st.Cycle是發送Column Address。 2nd.Cycle ,3rd.Cycle和4th.Cycle則是指定Page Address(每次向地址寄存器發送的數據只能是8位,所以17位的Page Address必須分成3次進行發送
Read1的命令裡面出現了兩個命令選項,分別是00h和01h。這裡出現了兩個讀命是否令你意識到什麼呢?是的,00h是用於讀寫1st half的命令,而01h是用於讀取2nd half的命令。現在我可以結合上圖給你說明為什麼K9F1208U0B的DataField被分為2個half了。
如上文我所提及的,Read1的1st.Cycle是發送Column Address,假設我現在指定的Column Address是0,那麼讀操作將從此頁的第0號Byte開始一直讀取到此頁的最後一個Byte (包括Spare Field),如果我指定的Column Address是127,情況也與前面一樣,但不知道你發現沒有,用於傳遞Column Address的數據線有8條(I/O0~I/O7,對應A0 ~A7,這也是A8為什麼不出現在我們傳遞的地址位中),也就是說我們能夠指定的Column Address範圍為0~255,但不要忘了,1個Page的DataField是由512個Byte組成的,假設現在我要指定讀命令從第256個字節處開始讀取此頁,那將會發生什麼情景?我必須把Column Address設置為256,但Column Address最大隻能是255,這就造成數據溢出。 。 。正是因為這個原因我們才把Data Field分為兩個半區,當要讀取的起始地址(Column Address)在0~255內時我們用00h命令,當讀取的起始地址是在256 ~511時,則使用01h命令.假設現在我要指定從第256個byte開始讀取此頁,那麼我將這樣發送命令串
column_addr=256;
NF_CMD=0x01; ?                                       從2nd half開始讀取
NF_ADDR=column_addr&0xff;                       1st Cycle
NF_ADDR=page_address&0xff;                      2nd.Cycle
NF_ADDR=(page_address>>8)&0xff;             3rd.Cycle
NF_ADDR=(page_address>>16)&0xff;           4th.Cycle
其中NF_CMD和NF_ADDR分別是NandFlash的命令寄存器和地址寄存器的地址解引用,我一般這樣定義它們,
#define rNFCMD        (*(volatile unsigned char *)0x4e000004)        //NADD Flash command
#define rNFADDR        (*(volatile unsigned char *)0x4e000008)        //NAND Flash address
事實上,當NF_CMD=0x01時,地址寄存器中的第8位(A8)將被設置為1(如上文分析,A8位不在我們傳遞的地址中,這個位其實就是硬件電路根據01h或是00h這兩個命令來置高位或是置低位),這樣我們傳遞column_addr的值256隨然由於數據溢出變為1,但A8位已經由於NF_CMD =0x01的關係被置為1了,所以我們傳到地址寄存器裡的值變成了

A0  A1  A2  A3  A4  A5  A6  A7  A8
1     0     0     0     0     0    0     0     1

這8個位所表示的正好是256,這樣讀操作將從此頁的第256號byte(2nd half的第0號byte)開始讀取數據。 nand_flash.c中包含3個函數
void nf_reset(void);
void nf_init(void);
void nf_read(unsigned int src_addr,unsigned  char *desc_addr,int size);
nf_reset()將被nf_init()調用。 nf_init()是nand_flash的初始化函數,在對nand flash進行任何操作之前,nf_init()必須被調用。
nf_read(unsigned int src_addr,unsigned  char *desc_addr,int size);為讀函數,src_addr是nand flash上的地址,desc_addr是內存地址,size是讀取文件的長度。
在nf_reset和nf_read函數中存在兩個宏
NF_nFCE_L();
NF_nFCE_H();
你可以看到當每次對Nand Flash進行操作之前NF_nFCE_L()必定被調用,操作結束之時NF_nFCE_H()必定被調用。這兩個宏用於啟動和關閉Flash芯片的工作(片選/取消片選)。至於nf_reset()中的
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4) |(TWRPH1<<0);
這一行代碼是對NandFlash的控制寄存器進行初始化配置,rNFCONF是Nand Flash的配置寄存器,各個位的具體功能請參閱s3c2410數據手冊。
現在舉一個例子,假設我要從Nand Flash中的第5000字節處開始讀取1024個字節到內存的0x30000000處,我們這樣調用read函數
nf_read(5000, 0x30000000,1024);
我們來分析5000這個src_addr.
根據
column_addr=src_addr%512;       
page_address=(src_addr>>9);      
我們可得出column_addr=5000%512=392
page_address=(5000>>9)=9
於是我們可以知道5000這個地址是在第9頁的第392個字節處,於是我們的nf_read函數將這樣發送命令和參數
column_addr=5000%512;
>page_address=(5000>>9);
NF_CMD=0x01;                                           從2nd half開始讀取
NF_ADDR= column_addr &0xff;                     1st Cycle
NF_ADDR=page_address&0xff;                      2nd.Cycle
NF_ADDR=(page_address>>8)&0xff;             3rd.Cycle
NF_ADDR=(page_address>>16)&0xff;           4th.Cycle
向NandFlash的命令寄存器和地址寄存器發送完以上命令和參數之後,我們就可以從rNFDATA寄存器(NandFlash數據寄存器)讀取數據了.
我用下面的代碼進行數據的讀取.
for(i=column_addr;i<512;i++)
{
        *buf++=NF_RDDATA();
}
每當讀取完一個Page之後,數據指針會落在下一個Page的0號Column(0號Byte).
下面是源代碼:
/*
    http://www.another-prj.com/   
    author: caiyuqing   
本代碼只屬於交流學習,不得用於商業開發
*/
#include "s3c2410.h"
#include "nand_flash.h"
static unsigned char seBuf[16]={0xff};
//------------------------------------------------ --------------------------------------
unsigned short nf_checkId(void)
{
    int i;
    unsigned short id;
    NF_nFCE_L();        //chip enable
   
    NF_CMD(0x90);        //Read ID
    NF_ADDR(0x0);
    for(i=0;i<10;i++);    //wait tWB(100ns)
   
id="NF"_RDDATA()<<8;    // Maker code(K9S1208V:0xec)
id|=NF_RDDATA();    // Devide code(K9S1208V:0x76)
   
    NF_nFCE_H();        //chip enable
    return id;
}
//------------------------------------------------ --------------------------------------
static void nf_reset(void)
{
    int i;
    NF_nFCE_L();        //chip enable
    NF_CMD(0xFF);        //reset command
    for(i=0;i<10;i++);      //tWB = 100ns.
NF_WAITRB();         //wait 200~500us;
    NF_nFCE_H();        //chip disable
}
//------------------------------------------------ --------------------------------------
void nf_init(void)
{
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4) |(TWRPH1<<0);   
//         1      1        1         1       1      xxx     r xxx,      r xxx       
//         En     r    r       ECCR    nFCE="H" tACLS   tWRPH0      tWRPH1
    nf_reset();
}
//------------------------------------------------ --------------------------------------

void nf_read(unsigned int src_addr,unsigned  char *desc_addr,int size)
{
    int i;
unsigned int column_addr = src_addr % 512;            // column address
unsigned int page_address = (src_addr >> 9);        // page addrress
    unsigned char *buf = desc_addr;
while((unsigned int)buf < (unsigned int)(desc_addr) + size)
    {
NF_nFCE_L();                    // enable chip
       
/*NF_ADDR和NF_CMD為nand_flash的地址和命令寄存器的解引用*/
if(column_addr > 255)                // 2end halft   
NF_CMD(0x01);                // Read2 command.   cmd 0x01: Read command(start from 2end half page)       
        else
NF_CMD(0x00);                // 1st halft?
       
NF_ADDR(column_addr & 0xff);                // Column Address
NF_ADDR(page_address & 0xff);            // Page Address
NF_ADDR((page_address >> 8) & 0xff);        // ...
NF_ADDR((page_address >> 16) & 0xff);        // ..
for(i = 0; i < 10; i++);                // wait tWB(100ns)/////??????
NF_WAITRB();                    // Wait tR(max 12us)
   
        // Read from main area
        for(i = column_addr; i < 512; i++)
        {
            *buf++= NF_RDDATA();
        }
NF_nFCE_H();                    // disable chip
        column_addr = 0;
        page_address++;
    }
    return ;
}



本文来自: (www.91linux.com) 详细出处参考:http://www.91linux.com/html/article/qianrushiyingyong/20090131/15574.html

平均分數:0 顆星    投票人數:0
我要評分:
回應
關鍵字
    沒有新回應!





Powered by Xuite