S3C2440內部集成了一個Nand flash控制器。S3C2440的Nand flash控制器包含了如下的特性:
- 一個引導啟動單元
- Nand Flash存儲器接口,支持8位或16位的每頁大小為256字,512字節,1K字和2K字節的Nand flash
- 軟件模式:用戶可以直接訪問Nand Flash存儲器,此特性可以用于Nand Flash存儲器的讀、擦除和編程。
- ?S3C2440支持8/16位的Nand Flash存儲器接口總線
- 硬件ECC生成,檢測和指示(軟件糾錯)。
- Steppingstone接口,支持大/小端模式的按字節/半字/字訪問。
我用的開發板是天嵌的TQ2440,板子用到的Nand Flash是Samsung公司的K9F2G08U0A,它是8位的Nand flash。本文只介紹Nand Flash的電路原理和Nand Flash的讀、寫、擦除等基本操作,暫不涉及Nand Flash啟動程序的問題。
Nand Flash的電路連接如圖 1所示:
圖 1 Nand Flash電路原理
上圖的左邊為K9F2G08U0A與2440的連接圖,原理方面就不多介紹,去看看datasheet估計就懂得了,右邊的部分是S3C2440的Nand控制器的配置。配置引腳NCON,GPG13,GPG14和GPG15用來設置Nand Flash的基本信息,Nand控制器通過讀取配置引腳的狀態獲取外接的Nand Flash的配置信息,圖 2是這四個配置引腳的定義:
圖 2 Nand控制配置引腳信息
由于K9F2G08U0A的總線寬度為8位,頁大小為2048字節,需要5個尋址命令,所以NCON、GPG13和GPG14應該接高電平,GPG15應該接低電平。
K9F2G08U0A沒有地址或數據總線,只有8個IO口,這8個IO口用于傳輸命令、地址和數據。K9F2G08U0A主要以page(頁)為單位進行讀寫,以block(塊)為單位進行擦除。每一頁中又分為main區和spare區,main區用于正常數據的存儲,spare區用于存儲一些附加信息,如塊好壞的標記、塊的邏輯地址、頁內數據的ECC校驗和等。K9F2G08U0A的存儲陣列如圖 3所示:
圖 3 K9F2G08U0A內部存儲陣列
由上圖,我們可以知道:K9F2G08U0A的一頁為(2K+64)字節(2K表示的是main區容量, 64表示的是spare區容量),它的一塊為64頁,而整個設備包括了2048個塊。這樣算下來一共有2112M位容量,如果只算main區容量則有256M字節(即256M×8位)。
圖 4 K9F2G08U0A地址序列
要實現用8個IO口來要訪問這么大的容量,如圖 4所示:K9F2G08U0A規定了用5個周期來實現。第一個周期訪問的地址為A0~A7;第二個周期訪問的地址為A8~A11,它作用在IO0~IO3上,而此時IO4~IO7必須為低電平;第三個周期訪問的地址為A12~A19;第四個周期訪問的地址為A20~A27;第五個周期訪問的地址為A28,它作用在IO0上,而此時IO1~IO7必須為低電平。前兩個周期傳輸的是列地址,后三個周期傳輸的是行地址。通過分析可知,列地址是用于尋址頁內空間,行地址用于尋址頁,如果要直接訪問塊,則需要從地址A18開始。由于所有的命令、地址和數據全部從8位IO口傳輸,所以Nand flash定義了一個命令集來完成各種操作。有的操作只需要一個命令(即一個周期)即可,而有的操作則需要兩個命令(即兩個周期)來實現。K9F2G08U0A的命令說明如圖 5所示:
圖 5 K9F2G08U0A命令表
為了方便使用,我們宏定義了K9F2G08U0A的常用命令
#define CMD_READ1 0x00 //頁讀命令周期1
#define CMD_READ2 0x30 //頁讀命令周期2
#define CMD_READID 0x90 //讀ID命令
#define CMD_WRITE1 0x80 //頁寫命令周期1
#define CMD_WRITE2 0x10 //頁寫命令周期2
#define CMD_ERASE1 0x60 //塊擦除命令周期1
#define CMD_ERASE2 0xd0 //塊擦除命令周期2
#define CMD_STATUS 0x70 //讀狀態命令
#define CMD_RESET 0xff //復位
#define CMD_RANDOMREAD1 0x05 //隨意讀命令周期1
#define CMD_RANDOMREAD2 0xE0 //隨意讀命令周期2
#define CMD_RANDOMWRITE 0x85 //隨意寫命令
接下來介紹幾個Nand Flash控制器的寄存器。Nand Flash控制器的寄存器主要有NFCONF(Nand Flash配置寄存器),NFCONT(Nand Flash控制寄存器),NFCMMD(Nand Flash命令集寄存器),NFADDR(Nand Flash地址集寄存器),NFDATA(Nand Flash數據寄存器),NFMECCD0/1(Nand Flash的main區ECC寄存器),NFSECCD(Nand Flash的spare區ECC寄存器),NFSTAT(Nand Flash操作狀態寄存器),NFESTAT0/1(Nand Flash的ECC狀態寄存器),NFMECC0/1(Nand Flash用于數據的ECC寄存器),以及NFSECC(Nand Flash用于IO的ECC寄存器)。
(1)NFCONF:2440的NFCONF寄存器是用來設置NAND Flash的時序參數TACLS、TWRPH0、TWRPH1。配置寄存器的[3:0]是只讀位,用來指示外部所接的Nand Flash的配置信息,它們是由配置引腳NCON,GPG13,GPG14和GPG15所決定的(比如說K9F2G08U0A的配置為NCON、GPG13和GPG14接高電平,GPG15接低電平,所以[3:0]位狀態應該是1110)。
(2)NFCONT:用來使能/禁止NAND Flash控制器、使能/禁止控制引腳信號nFCE、初始化ECC。它還有其他功能,在一般的應用中用不到,比如鎖定NAND Flash。
(3)NFCMMD:對于不同型號的Flash,操作命令一般不一樣。參考前面介紹的K9F2G08U0A命令序列。
(4)NFADDR:當寫這個寄存器時,它將對Flash發出地址信號。只用到低8位來傳輸,所以需要分次來寫入一個完整的32位地址,K9F2G08U0A的地址序列在圖4已經做了詳細說明。
(5)NFDATA:只用到低8位,讀、寫此寄存器將啟動對NAND Flash的讀數據、寫數據操作。
(6)NFSTAT:只用到位0,用來檢測NAND是否準備好。0:busy,1:ready。
NFCONF寄存器使用TACLS、TWRPH0、TWRPH1這3個參數來控制NAND Flash信號線CLE/ALE與寫控制信號nWE的時序關系,它們之間的關系如圖6和圖7所示:
圖6 CLE/ALE時序圖
圖7 nWE和nRE時序圖
TACLS為CLE/ALE有效到nWE有效之間的持續時間,TWRPH0為nWE的有效持續時間,TWRPH1為nWE無效到CLE/ALE無效之間的持續時間,這些時間都是以HCLK為單位的。通過查閱K9F2G08U0A的數據手冊,我們可以找到并計算與S3C2440相對應的時序:K9F2G08U0A中的Twp與TWRPH0相對應,Tclh與TWRPH1相對應, TACLS應該是與Tcls相對應。K9F2G08U0A給出的都是最小時間, 2440只要滿足它的最小時間即可。TACLS、TWRPH0、TWRPH1這三個變量取值大一些會更保險,在這里,這三個值分別取1,2和0。
下面就開始詳細介紹K9F2G08U0A的基本操作,包括復位,讀ID,頁讀、寫數據,隨意讀、寫數據,塊擦除等。
為了更好地應用ECC和使能Nand Flash片選,我們還需要一些宏定義:
#define NF_nFCE_L() {rNFCONT &= ~(1《《1); }
#define NF_CE_L() NF_nFCE_L() //打開nandflash片選
#define NF_nFCE_H() {rNFCONT |= (1《《1); }
#define NF_CE_H() NF_nFCE_H() //關閉nandflash片選
#define NF_RSTECC() {rNFCONT |= (1《《4); } //復位ECC
#define NF_MECC_UnLock() {rNFCONT &= ~(1《《5); } //解鎖main區ECC
#define NF_MECC_Lock() {rNFCONT |= (1《《5); } //鎖定main區ECC
#define NF_SECC_UnLock() {rNFCONT &= ~(1《《6); } //解鎖spare區ECC
#define NF_SECC_Lock() {rNFCONT |= (1《《6); } //鎖定spare區ECC
NFSTAT是另一個比較重要的寄存器,它的第0位可以用于判斷nandflash是否在忙,第2位用于檢測RnB引腳信號:
#define NF_WAITRB() {while(!(rNFSTAT&(1《《0)));} //等待Nand Flash不忙
#define NF_CLEAR_RB() {rNFSTAT |= (1《《2); } //清除RnB信號
#define NF_DETECT_RB() {while(!(rNFSTAT&(1《《2)));}
//等待RnB信號變高,即不忙
NFCMMD,NFADDR和NFDATA分別用于傳輸命令,地址和數據,為了方便起見,我們可以定義一些宏定義用于完成上述操作:
#define NF_CMD(data) {rNFCMD = (data); } //傳輸命令
#define NF_ADDR(addr) {rNFADDR = (addr); } //傳輸地址
#define NF_RDDATA() rNFDATA) //讀32位數據
#define NF_RDDATA8() (rNFDATA8) //讀8位數據
#define NF_WRDATA(data) {rNFDATA = (data); } //寫32位數據
#define NF_WRDATA8(data) {rNFDATA8 = (data); } //寫8位數據
首先,是初始化操作
void rNF_Init(void)
{
rNFCONF = (TACLS《《12)|(TWRPH0《《8)|( TWRPH1《《4)|(0《《0);//初始化時序參數
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片選及控制器
rNF_Reset();//復位芯片
}
復位操作,寫入復位命令
static void rNF_Reset()
{
NF_CE_L(); //打開nandflash片選
NF_CLEAR_RB(); //清除RnB信號
NF_CMD(CMD_RESET); //寫入復位命令
NF_DETECT_RB(); //等待RnB信號變高,即不忙
NF_CE_H(); //關閉nandflash片選
}
讀取K9F2G08U0A芯片ID的操作如下:時序圖在datasheet的figure18。首先需要寫入讀ID命令(0x90),然后再寫入0x00地址,并等待芯片就緒,就可以讀取到一共五個周期的芯片ID,第一個周期為廠商ID,第二個周期為設備ID,第三個周期至第五個周期包括了一些具體的該芯片信息,函數如下
static char rNF_ReadID()
{
char pMID;
char pDID;
char cyc3, cyc4, cyc5;
NF_nFCE_L(); //打開nandflash片選
NF_CLEAR_RB(); //清RnB信號
NF_CMD(CMD_READID); //讀ID命令
NF_ADDR(0x0); //寫0x00地址
for ( i = 0; i 《 100; i++ );等一段時間
//讀五個周期的ID
pMID = NF_RDDATA8(); //廠商ID:0xEC
pDID = NF_RDDATA8(); //設備ID:0xDA
cyc3 = NF_RDDATA8(); //0x10
cyc4 = NF_RDDATA8(); //0x95
cyc5 = NF_RDDATA8(); //0x44
NF_nFCE_H(); //關閉nandflash片選
return (pDID);
}
下面介紹Nand Flash讀操作,讀操作是以頁為單位進行的。如果在讀取數據的過程中不進行ECC校驗判斷,則讀操作比較簡單,在寫入讀命令的兩個周期之間寫入要讀取的頁地址,然后讀取數據即可。如果為了更準確地讀取數據,則在讀取完數據之后還要進行ECC校驗判斷,以確定所讀取的數據是否正確。
在上文中已經介紹過,Nand Flash的每一頁有兩區:main區和spare區,main區用于存儲正常的數據,spare區用于存儲其他附加信息,其中就包括ECC校驗碼。當我們在寫入數據的時候,我們就計算這一頁數據的ECC校驗碼,然后把校驗碼存儲到spare區的特定位置中,在下次讀取這一頁數據的時候,同樣我們也計算ECC校驗碼,然后與spare區中的ECC校驗碼比較,如果一致則說明讀取的數據正確,如果不一致則不正確。ECC的算法較為復雜,好在S3C2440能夠硬件產生ECC校驗碼,這樣就省去了不少的麻煩事。S3C2440既可以產生main區的ECC校驗碼,也可以產生spare區的ECC校驗碼。因為K9F2G08U0A是8位IO口,因此S3C2440共產生4個字節的main區ECC碼和2個字節的spare區ECC碼。在這里我們規定,在每一頁的spare區的第0個地址到第3個地址存儲main區ECC,第4個地址和第5個地址存儲spare區ECC。
產生ECC校驗碼的過程為:在讀取或寫入哪個區的數據之前,先解鎖該區的ECC,以便產生該區的ECC。在讀取或寫入完數據之后,再鎖定該區的ECC,這樣系統就會把產生的ECC碼保存到相應的寄存器中。main區的ECC保存到NFMECC0/1中(因為K9F2G08U0A是8位IO口,因此這里只用到了NFMECC0),spare區的ECC保存到NFSECC中。對于讀操作來說,我們還要繼續讀取spare區的相應地址內容,以得到上次寫操作時所存儲的main區和spare區的ECC,并把這些數據分別放入NFMECCD0/1和NFSECCD的相應位置中。最后我們就可以通過讀取NFESTAT0/1(因為K9F2G08U0A是8位IO口,因此這里只用到了NFESTAT0)中的低4位來判斷讀取的數據是否正確,其中第0位和第1位為main區指示錯誤,第2位和第3位為spare區指示錯誤。
下面是一段具體的頁讀操作程序:
U8 rNF_ReadPage( U32 page_number )
{
U32 i, mecc0, secc;
NF_RSTECC(); //復位ECC
NF_MECC_UnLock(); //解鎖main區ECC
NF_nFCE_L();//使能芯片
NF_CLEAR_RB();//清除RnB
NF_CMD(CMD_READ1); //頁讀命令周期1,0x00
//寫入5個地址周期
NF_ADDR(0x00); //列地址A0-A7
NF_ADDR(0x00); //列地址A8-A11
NF_ADDR((addr) & 0xff); //行地址A12-A19
NF_ADDR((addr 》》 8) & 0xff); //行地址A20-A27
NF_ADDR((addr 》》 16) & 0xff); //行地址A28
NF_CMD(CMD_READ2); //頁讀命令周期2,0x30
NF_DETECT_RB(); ////等待RnB信號變高,即不忙
for (i = 0; i 《 2048; i++)
{
buf[i] = NF_RDDATA8();//讀取一頁數據內容
}
NF_MECC_Lock(); //鎖定main區ECC值
NF_SECC_UnLock(); //解鎖spare區ECC
mecc0=NF_RDDATA(); //讀spare區的前4個地址內容,即第2048~2051地址,這4個字節為main區的ECC
//把讀取到的main區的ECC校驗碼放入NFMECCD0/1的相應位置內
rNFMECCD0=((mecc0&0xff00)《《8)|(mecc0&0xff);
rNFMECCD1=((mecc0&0xff000000)》》8)|((mecc0&0xff0000)》》16);
NF_SECC_Lock(); //鎖定spare區的ECC值
secc=NF_RDDATA(); //繼續讀spare區的4個地址內容,即第2052~2055地址,其中前2個字節為spare區的ECC值
//把讀取到的spare區的ECC校驗碼放入NFSECCD的相應位置內
rNFSECCD=((secc&0xff00)《《8)|(secc&0xff);
NF_nFCE_H(); //關閉nandflash片選
//判斷所讀取到的數據是否正確
if ((rNFESTAT0&0xf) == 0x0)
return 0x66; //正確
else
return 0x44; //錯誤
}
這段程序是把某一頁的內容讀取到全局變量數組buffer中。該程序的輸入參數直接就為K9F2G08U0A的第幾頁,例如我們要讀取第128064頁中的內容,可以調用該程序為:rNF_ReadPage(128064)。由于第128064頁是第2001塊中的第0頁(128064=2001×64+0),所以為了更清楚地表示頁與塊之間的關系,也可以寫為:rNF_ReadPage(2001*64)。
頁寫操作的大致流程為:在兩個寫命令周期之間分別寫入頁地址和數據,當然如果為了保證下次讀取該數據時的正確性,還需要把main區的ECC值和spare區的ECC值寫入到該頁的spare區內。然后我們還需要讀取狀態寄存器,以判斷這次寫操作是否正確。下面就給出一段具體的頁寫操作程序,其中輸入參數也是要寫入數據到第幾頁:
U8 rNF_WritePage(U32 page_number)
{
U32 i, mecc0, secc;
U8 stat, temp;
temp = rNF_IsBadBlock(page_number》》6); //判斷該塊是否為壞塊
if(temp == 0x33)
return 0x42; //是壞塊,返回
NF_RSTECC(); //復位ECC
NF_MECC_UnLock(); //解鎖main區的ECC
NF_nFCE_L(); //打開nandflash片選
NF_CLEAR_RB(); //清RnB信號
NF_CMD(CMD_WRITE1); //頁寫命令周期1
//寫入5個地址周期
NF_ADDR(0x00); //列地址A0~A7
NF_ADDR(0x00); //列地址A8~A11
NF_ADDR((page_number) & 0xff); //行地址A12~A19
NF_ADDR((page_number 》》 8) & 0xff); //行地址A20~A27
NF_ADDR((page_number 》》 16) & 0xff); //行地址A28
for (i = 0; i 《 2048; i++)//寫入一頁數據
{
NF_WRDATA8((char)(i+6));
}
NF_MECC_Lock(); //鎖定main區的ECC值
mecc0=rNFMECC0; //讀取main區的ECC校驗碼
//把ECC校驗碼由字型轉換為字節型,并保存到全局變量數組ECCBuf中
ECCBuf[0]=(U8)(mecc0&0xff);
ECCBuf[1]=(U8)((mecc0》》8) & 0xff);
ECCBuf[2]=(U8)((mecc0》》16) & 0xff);
ECCBuf[3]=(U8)((mecc0》》24) & 0xff);
NF_SECC_UnLock(); //解鎖spare區的ECC
//把main區的ECC值寫入到spare區的前4個字節地址內,即第2048~2051地址
for(i=0;i《4;i++)
{
NF_WRDATA8(ECCBuf[i]);
}
NF_SECC_Lock(); //鎖定spare區的ECC值
secc=rNFSECC; //讀取spare區的ECC校驗碼
//把ECC校驗碼保存到全局變量數組ECCBuf中
ECCBuf[4]=(U8)(secc&0xff);
ECCBuf[5]=(U8)((secc》》8) & 0xff);
//把spare區的ECC值繼續寫入到spare區的第2052~2053地址內
for(i=4;i《6;i++)
{
NF_WRDATA8(ECCBuf[i]);
}
NF_CMD(CMD_WRITE2); //頁寫命令周期2
delay(1000); //延時一段時間,以等待寫操作完成
NF_CMD(CMD_STATUS); //讀狀態命令
//判斷狀態值的第6位是否為1,即是否在忙,該語句的作用與NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
NF_nFCE_H(); //關閉Nand Flash片選
//判斷狀態值的第0位是否為0,為0則寫操作正確,否則錯誤
if (stat & 0x1)
{
temp = rNF_MarkBadBlock(page_number》》6);//標注該頁所在的塊為壞塊
if (temp == 0x21)
return 0x43 //標注壞塊失敗
else
return 0x44; //寫操作失敗
}
else
return 0x66; //寫操作成功
}
該段程序先判斷該頁所在的壞是否為壞塊,如果是則退出。在最后寫操作失敗后,還要標注該頁所在的塊為壞塊,其中所用到的函數rNF_IsBadBlock和rNF_MarkBadBlock,我們在后面介紹。我們再總結一下該程序所返回數值的含義,0x42:表示該頁所在的塊為壞塊;0x43:表示寫操作失敗,并且在標注該頁所在的塊為壞塊時也失敗;0x44:表示寫操作失敗,但是標注壞塊成功;0x66:寫操作成功。
擦除是以塊為單位進行的,因此在寫地址周期是,只需寫三個行周期,并且要從A18開始寫起。與寫操作一樣,在擦除結束前還要判斷是否擦除操作成功,另外同樣也存在需要判斷是否為壞塊以及要標注壞塊的問題。下面就給出一段具體的塊擦除操作程序:
U8 rNF_EraseBlock(U32 block_number)
{
char stat, temp;
temp = rNF_IsBadBlock(block_number); //判斷該塊是否為壞塊
if(temp == 0x33)
return 0x42; //是壞塊,返回
NF_nFCE_L(); //打開片選
NF_CLEAR_RB(); //清RnB信號
NF_CMD(CMD_ERASE1); //擦除命令周期1
//寫入3個地址周期,從A18開始寫起
NF_ADDR((block_number 《《 6) & 0xff); //行地址A18~A19
NF_ADDR((block_number 》》 2) & 0xff); //行地址A20~A27
NF_ADDR((block_number 》》 10) & 0xff); //行地址A28
NF_CMD(CMD_ERASE2); //擦除命令周期2
delay(1000); //延時一段時間
NF_CMD(CMD_STATUS); //讀狀態命令
//判斷狀態值的第6位是否為1,即是否在忙,該語句的作用與NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
NF_nFCE_H(); //關閉Nand Flash片選
//判斷狀態值的第0位是否為0,為0則擦除操作正確,否則錯誤
if (stat & 0x1)
{
temp = rNF_MarkBadBlock(page_number》》6); //標注該塊為壞塊
if (temp == 0x21)
return 0x43 //標注壞塊失敗
else
return 0x44; //擦除操作失敗
}
else
return 0x66; //擦除操作成功
}
該程序的輸入參數為K9F2G08U0A的第幾塊,例如我們要擦除第2001塊,則調用該函數為:rNF_EraseBlock(2001)。
K9F2G08U0A除了提供了頁讀和頁寫功能外,還提供了頁內地址隨意讀、寫功能。頁讀和頁寫是從頁的首地址開始讀、寫,而隨意讀、寫實現了在一頁范圍內任意地址的讀、寫。隨意讀操作是在頁讀操作后輸入隨意讀命令和頁內列地址,這樣就可以讀取到列地址所指定地址的數據。隨意寫操作是在頁寫操作的第二個頁寫命令周期前,輸入隨意寫命令和頁內列地址,以及要寫入的數據,這樣就可以把數據寫入到列地址所指定的地址內。下面兩段程序實現了隨意讀和隨意寫功能,其中隨意讀程序的輸入參數分別為頁地址和頁內地址,輸出參數為所讀取到的數據,隨意寫程序的輸入參數分別為頁地址,頁內地址,以及要寫入的數據。
U8 rNF_RamdomRead(U32 page_number, U32 add)
{
NF_nFCE_L(); //打開Nand Flash片選
NF_CLEAR_RB(); //清RnB信號
NF_CMD(CMD_READ1); //頁讀命令周期1
//寫入5個地址周期
NF_ADDR(0x00); //列地址A0~A7
NF_ADDR(0x00); //列地址A8~A11
NF_ADDR((page_number) & 0xff); //行地址A12~A19
NF_ADDR((page_number 》》 8) & 0xff); //行地址A20~A27
NF_ADDR((page_number 》》 16) & 0xff); //行地址A28
NF_CMD(CMD_READ2); //頁讀命令周期2
NF_DETECT_RB(); //等待RnB信號變高,即不忙
NF_CMD(CMD_RANDOMREAD1); //隨意讀命令周期1
//頁內地址
NF_ADDR((char)(add&0xff)); //列地址A0~A7
NF_ADDR((char)((add》》8)&0x0f)); //列地址A8~A11
NF_CMD(CMD_RANDOMREAD2); //隨意讀命令周期2
return NF_RDDATA8(); //讀取數據
}
U8 rNF_RamdomWrite(U32 page_number, U32 add, U8 dat)
{
U8 temp,stat;
NF_nFCE_L(); //打開Nand Flash片選
NF_CLEAR_RB(); //清RnB信號
NF_CMD(CMD_WRITE1); //頁寫命令周期1
//寫入5個地址周期
NF_ADDR(0x00); //列地址A0~A7
NF_ADDR(0x00); //列地址A8~A11
NF_ADDR((page_number) & 0xff); //行地址A12~A19
NF_ADDR((page_number 》》 8) & 0xff); //行地址A20~A27
NF_ADDR((page_number 》》 16) & 0xff); //行地址A28
NF_CMD(CMD_RANDOMWRITE); //隨意寫命令
//頁內地址
NF_ADDR((char)(add&0xff)); //列地址A0~A7
NF_ADDR((char)((add》》8)&0x0f)); //列地址A8~A11
NF_WRDATA8(dat); //寫入數據
NF_CMD(CMD_WRITE2); //頁寫命令周期2
delay(1000); //延時一段時間
NF_CMD(CMD_STATUS); //讀狀態命令
//判斷狀態值的第6位是否為1,即是否在忙,該語句的作用與NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
NF_nFCE_H(); //關閉Nand Flash片選
//判斷狀態值的第0位是否為0,為0則寫操作正確,否則錯誤
if (stat & 0x1)
return 0x44; //失敗
else
return 0x66; //成功
}
下面介紹上文中提到的判斷壞塊以及標注壞塊的那兩個程序:rNF_IsBadBlock和rNF_MarkBadBlock。在這里,我們定義在spare區的第6個地址(即每頁的第2054地址)用來標注壞塊,0x44表示該塊為壞塊。要判斷壞塊時,利用隨意讀命令來讀取2054地址的內容是否為0x44,要標注壞塊時,利用隨意寫命令來向2054地址寫0x33。下面就給出這兩個程序,它們的輸入參數都為塊地址,也就是即使僅僅一頁出現問題,我們也標注整個塊為壞塊。
U8 rNF_IsBadBlock(U32 block)
{
return rNF_RamdomRead(block*64, 2054);
}
U8 rNF_MarkBadBlock(U32 block)
{
U8 result;
result = rNF_RamdomWrite(block*64, 2054, 0x33);
if(result == 0x44)
return 0x21; //寫壞塊標注失敗
else
return 0x60; //寫壞塊標注成功
}
關于Nand Flash的基本操作就介紹到這吧
評論
查看更多