Nand Flash數據存儲方式和數據讀寫方法!

   本站已經有很多文章談到Nand Flash的數據存儲方式,但關於NAND Flash的數據讀寫方法方面的文章不多,這篇文章詳細講述了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).

    下面是源代碼:
/*
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 ;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章