新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > ARM9(2440)對nand flash的讀寫操作

ARM9(2440)對nand flash的讀寫操作

作者: 時(shí)間:2016-11-20 來源:網(wǎng)絡(luò) 收藏
s3c2440nandflash的操作(K9F2G08)

nandflash在對大容量的數(shù)據(jù)存儲(chǔ)中發(fā)揮著重要的作用。相對于norflash,它具有一些優(yōu)勢,但它的一個(gè)劣勢是很容易產(chǎn)生壞塊,因此在使用nandflash時(shí),往往要利用校驗(yàn)算法發(fā)現(xiàn)壞塊并標(biāo)注出來,以便以后不再使用該壞塊。nandflash沒有地址或數(shù)據(jù)總線,如果是8位nandflash,那么它只有8個(gè)IO口,這8個(gè)IO口用于傳輸命令、地址和數(shù)據(jù)。nandflash主要以page(頁)為單位進(jìn)行讀寫,以block(塊)為單位進(jìn)行擦除。每一頁中又分為main區(qū)和spare區(qū),main區(qū)用于正常數(shù)據(jù)的存儲(chǔ),spare區(qū)用于存儲(chǔ)一些附加信息,如塊好壞的標(biāo)記、塊的邏輯地址、頁內(nèi)數(shù)據(jù)的ECC校驗(yàn)和等。

本文引用地址:http://butianyuan.cn/article/201611/319025.htm

三星公司是最主要的nandflash供應(yīng)商,因此在它所開發(fā)的各類處理器中,實(shí)現(xiàn)對nandflash的支持就不足為奇了。s3c2440不僅具有nandflash的接口,而且還可以利用某些機(jī)制實(shí)現(xiàn)直接從nandflash啟動(dòng)并運(yùn)行程序。本文只介紹如何對nandflash實(shí)現(xiàn)讀、寫、擦除等基本操作,不涉及nandflash啟動(dòng)程序的問題。

在這里,我們使用的nandflash為K9F2G08U0A,它是8位的nandflash。不同型號(hào)的nandflash的操作會(huì)有所不同,但硬件引腳基本相同,這給產(chǎn)品的開發(fā)擴(kuò)展帶來了便利。因?yàn)椴煌吞?hào)的PCB板是一樣的,只要更新一下軟件就可以使用不同容量大小的nandflash。

K9F2G08U0A的一頁為(2K+64)字節(jié)(加號(hào)前面的2K表示的是main區(qū)容量,加號(hào)后面的64表示的是spare區(qū)容量),它的一塊為64頁,而整個(gè)設(shè)備包括了2048個(gè)塊。這樣算下來一共有2112M位容量,如果只算main區(qū)容量則有256M字節(jié)(即256M×8位)。要實(shí)現(xiàn)用8個(gè)IO口來要訪問這么大的容量,K9F2G08U0A規(guī)定了用5個(gè)周期來實(shí)現(xiàn)。第一個(gè)周期訪問的地址為A0~A7;第二個(gè)周期訪問的地址為A8~A11,它作用在IO0~IO3上,而此時(shí)IO4~IO7必須為低電平;第三個(gè)周期訪問的地址為A12~A19;第四個(gè)周期訪問的地址為A20~A27;第五個(gè)周期訪問的地址為A28,它作用在IO0上,而此時(shí)IO1~IO7必須為低電平。前兩個(gè)周期傳輸?shù)氖橇械刂?,后三個(gè)周期傳輸?shù)氖切械刂?。通過分析可知,列地址是用于尋址頁內(nèi)空間,行地址用于尋址頁,如果要直接訪問塊,則需要從地址A18開始。

(2的12次方是4096,并不是2048,這是為什么?個(gè)人理解:為了兼容以后更大的NAND,NAND的地址序列可以把“列地址的長度”預(yù)留出來,雖說送的是A0-A7,A8-A11,實(shí)際取得是A0-A7,A8-A10;(頁內(nèi)地址空間))··········一共是2048*64=131072頁,2的17次方

由于所有的命令、地址和數(shù)據(jù)全部從8位IO口傳輸,所以nandflash定義了一個(gè)命令集來完成各種操作。有的操作只需要一個(gè)命令(即一個(gè)周期)即可,而有的操作則需要兩個(gè)命令(即兩個(gè)周期)來實(shí)現(xiàn)。下面的宏定義為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 //讀狀態(tài)命令

#define CMD_RESET 0xff //復(fù)位

#define CMD_RANDOMREAD1 0x05 //隨意讀命令周期1

#define CMD_RANDOMREAD2 0xE0 //隨意讀命令周期2

#define CMD_RANDOMWRITE 0x85 //隨意寫命令

在這里,隨意讀命令和隨意寫命令可以實(shí)現(xiàn)在一頁內(nèi)任意地址地讀寫。讀狀態(tài)命令可以實(shí)現(xiàn)讀取設(shè)備內(nèi)的狀態(tài)寄存器,通過該命令可以獲知寫操作或擦除操作是否完成(判斷第6位),以及是否成功完成(判斷第0位)。

引腳配置:

OM[1:0] = 00: Enable NAND flash memory boot
NCON : NAND flash memory selection(Normal / Advance)
0: Normal NAND flash(256Words/512Bytes page size, 3/4 address cycle)
1: Advance NAND flash(1KWords/2KBytes page size, 4/5 address cycle)
GPG13 : NAND flash memory page capacitance selection
0: Page=256Words(NCON = 0) or Page=1KWords(NCON = 1)
1: Page=512Bytes(NCON = 0) or Page=2KBytes(NCON = 1)
GPG14: NAND flash memory address cycle selection
0: 3 address cycle(NCON = 0) or 4 address cycle(NCON = 1)
1: 4 address cycle(NCON = 0) or 5 address cycle(NCON = 1)

(注:GPG13 GPG14 read only)
GPG15 : NAND flash memory bus width selection
0: 8-bit bus width
1: 16-bit bus width

與s3c2440的硬件連接:

下面介紹s3c2440的nandflash控制器。s3c2440支持8位或16位的每頁大小為256字,512字節(jié),1K字和2K字節(jié)的nandflash,這些配置是通過系統(tǒng)上電后相應(yīng)引腳的高低電平來實(shí)現(xiàn)的。s3c2440還可以硬件產(chǎn)生ECC校驗(yàn)碼,這為準(zhǔn)確及時(shí)發(fā)現(xiàn)nandflash的壞塊帶來了方便。nandflash控制器的主要寄存器有NFCONF(nandflash配置寄存器),NFCONT(nandflash控制寄存器),NFCMMD(nandflash命令集寄存器),NFADDR(nandflash地址集寄存器),NFDATA(nandflash數(shù)據(jù)寄存器),NFMECCD0/1(nandflash的main區(qū)ECC寄存器),NFSECCD(nandflash的spare區(qū)ECC寄存器),NFSTAT(nandflash操作狀態(tài)寄存器),NFESTAT0/1(nandflash的ECC狀態(tài)寄存器),NFMECC0/1(nandflash用于數(shù)據(jù)的ECC寄存器),以及NFSECC(nandflash用于IO的ECC寄存器)。

NFCMMD,NFADDR和NFDATA分別用于傳輸命令,地址和數(shù)據(jù),為了方便起見,我們可以定義一些宏定義用于完成上述操作:

#define NF_CMD(data) {rNFCMD = (data); } //傳輸命令

#define NF_ADDR(addr) {rNFADDR = (addr); } //傳輸?shù)刂?/p>

#define NF_RDDATA() (rNFDATA) //讀32位數(shù)據(jù)

#define NF_RDDATA8() (rNFDATA8) //讀8位數(shù)據(jù)

#define NF_WRDATA(data) {rNFDATA = (data); } //寫32位數(shù)據(jù)

#define NF_WRDATA8(data) {rNFDATA8 = (data); } //寫8位數(shù)據(jù)

其中rNFDATA8的定義為(*(volatile unsigned char *)0x4E000010) //0x4E000010此地址是NFDATA寄存器的地址

NFCONF主要用到了TACLS、TWRPH0、TWRPH1,這三個(gè)變量用于配置nandflash的時(shí)序。s3c2440的數(shù)據(jù)手冊沒有詳細(xì)說明這三個(gè)變量的具體含義,但通過它所給出的時(shí)序圖,我們可以看出,TACLS為CLE/ALE有效到nWE有效之間的持續(xù)時(shí)間,TWRPH0為nWE的有效持續(xù)時(shí)間,TWRPH1為nWE無效到CLE/ALE無效之間的持續(xù)時(shí)間,這些時(shí)間都是以HCLK為單位的(本文程序中的HCLK=100MHz)。通過查閱K9F2G08U0A的數(shù)據(jù)手冊,我們可以找到并計(jì)算該nandflash與s3c2440相對應(yīng)的時(shí)序:K9F2G08U0A中的tWP與TWRPH0相對應(yīng),tCLH與TWRPH1相對應(yīng),(tCLS-tWP)與TACLS相對應(yīng)。K9F2G08U0A給出的都是最小時(shí)間,s3c2440只要滿足它的最小時(shí)間即可,因此TACLS、TWRPH0、TWRPH1這三個(gè)變量取值大一些會(huì)更保險(xiǎn)。在這里,這三個(gè)值分別取1,2和0。

NFCONF的第0位表示的是外接的nandflash是8位IO還是16位IO,這里當(dāng)然要選擇8位的IO。NFCONT寄存器是另一個(gè)需要事先初始化的寄存器。它的第13位和第12位用于鎖定配置,第8位到第10位用于nandflash的中斷,第4位到第6位用于ECC的配置,第1位用于nandflash芯片的選取,第0位用于nandflash控制器的使能。另外,為了初始化nandflash,還需要配置GPACON寄存器,使它的第17位到第22位與nandflash芯片的控制引腳相對應(yīng)。下面的程序?qū)崿F(xiàn)了初始化nandflash控制器:

void NF_Init ( void )

{

rGPACON = (rGPACON &~(0x3f<<17)) | (0x3f<<17); //配置芯片引腳

//TACLS=1、TWRPH0=2、TWRPH1=0,8位IO,

rNFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<0);

//非鎖定,屏蔽nandflash中斷,初始化ECC及鎖定main區(qū)和spare區(qū)ECC,使能nandflash片選及控制器

rNFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0);

}

為了更好地應(yīng)用ECC和使能nandflash片選,我們還需要一些宏定義:

#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() //關(guān)閉nandflash片選

#define NF_RSTECC() {rNFCONT |= (1<<4); } //復(fù)位ECC

#define NF_MECC_UnLock() {rNFCONT &= ~(1<<5); } //解鎖main區(qū)ECC

#define NF_MECC_Lock() {rNFCONT |= (1<<5); } //鎖定main區(qū)ECC

#define NF_SECC_UnLock() {rNFCONT &= ~(1<<6); } //解鎖spare區(qū)ECC

#define NF_SECC_Lock() {rNFCONT |= (1<<6); } //鎖定spare區(qū)ECC

NFSTAT是另一個(gè)比較重要的寄存器,它的第0位可以用于判斷nandflash是否在忙,第2位用于檢測RnB引腳信號(hào):

#define NF_WAITRB() {while(!(rNFSTAT&(1<<0)));} //等待nandflash不忙

#define NF_CLEAR_RB() {rNFSTAT |= (1<<2); } //清除RnB信號(hào)

#define NF_DETECT_RB() {while(!(rNFSTAT&(1<<2)));} //等待RnB信號(hào)變高,即不忙

下面就詳細(xì)介紹K9F2G08U0A的基本操作,包括復(fù)位,讀ID,頁讀、寫數(shù)據(jù),隨意讀、寫數(shù)據(jù),塊擦除等。

復(fù)位操作最簡單,只需寫入復(fù)位命令即可:

static void rNF_Reset()

{

NF_CE_L(); //打開nandflash片選

NF_CLEAR_RB(); //清除RnB信號(hào)

NF_CMD(CMD_RESET); //寫入復(fù)位命令

NF_DETECT_RB(); //等待RnB信號(hào)變高,即不忙

NF_CE_H(); //關(guān)閉nandflash片選

}

讀取K9F2G08U0A芯片ID操作首先需要寫入讀ID命令,然后再寫入0x00地址,就可以讀取到一共五個(gè)周期的芯片ID,第一個(gè)周期為廠商ID,第二個(gè)周期為設(shè)備ID,第三個(gè)周期至第五個(gè)周期包括了一些具體的該芯片信息,這里就不多介紹:

static char rNF_ReadID()

{

char pMID;

char pDID;

char cyc3, cyc4, cyc5;

NF_nFCE_L(); //打開nandflash片選

NF_CLEAR_RB(); //清RnB信號(hào)

NF_CMD(CMD_READID); //讀ID命令

NF_ADDR(0x0); //寫0x00地址

//讀五個(gè)周期的ID

pMID = NF_RDDATA8(); //廠商ID:0xEC

pDID = NF_RDDATA8(); //設(shè)備ID:0xDA

cyc3 = NF_RDDATA8(); //0x10

cyc4 = NF_RDDATA8(); //0x95

cyc5 = NF_RDDATA8(); //0x44

NF_nFCE_H(); //關(guān)閉nandflash片選

return (pDID);

}

下面介紹讀操作,讀操作是以頁為單位進(jìn)行的。如果在讀取數(shù)據(jù)的過程中不進(jìn)行ECC校驗(yàn)判斷,則讀操作比較簡單,在寫入讀命令的兩個(gè)周期之間寫入要讀取的頁地址,然后讀取數(shù)據(jù)即可。如果為了更準(zhǔn)確地讀取數(shù)據(jù),則在讀取完數(shù)據(jù)之后還要進(jìn)行ECC校驗(yàn)判斷,以確定所讀取的數(shù)據(jù)是否正確。

在上文中我們已經(jīng)介紹過,nandflash的每一頁有兩區(qū):main區(qū)和spare區(qū),main區(qū)用于存儲(chǔ)正常的數(shù)據(jù),spare區(qū)用于存儲(chǔ)其他附加信息,其中就包括ECC校驗(yàn)碼。當(dāng)我們在寫入數(shù)據(jù)的時(shí)候,我們就計(jì)算這一頁數(shù)據(jù)的ECC校驗(yàn)碼,然后把校驗(yàn)碼存儲(chǔ)到spare區(qū)的特定位置中,在下次讀取這一頁數(shù)據(jù)的時(shí)候,同樣我們也計(jì)算ECC校驗(yàn)碼,然后與spare區(qū)中的ECC校驗(yàn)碼比較,如果一致則說明讀取的數(shù)據(jù)正確,如果不一致則不正確。ECC的算法較為復(fù)雜,好在s3c2440能夠硬件產(chǎn)生ECC校驗(yàn)碼,這樣就省去了不少的麻煩事。s3c2440即可以產(chǎn)生main區(qū)的ECC校驗(yàn)碼,也可以產(chǎn)生spare區(qū)的ECC校驗(yàn)碼。因?yàn)镵9F2G08U0A是8位IO口,因此s3c2440共產(chǎn)生4個(gè)字節(jié)的main區(qū)ECC碼和2個(gè)字節(jié)的spare區(qū)ECC碼。在這里我們規(guī)定,在每一頁的spare區(qū)的第0個(gè)地址到第3個(gè)地址存儲(chǔ)main區(qū)ECC,第4個(gè)地址和第5個(gè)地址存儲(chǔ)spare區(qū)ECC。產(chǎn)生ECC校驗(yàn)碼的過程為:在讀取或?qū)懭肽膫€(gè)區(qū)的數(shù)據(jù)之前,先解鎖該區(qū)的ECC,以便產(chǎn)生該區(qū)的ECC。在讀取或?qū)懭胪陻?shù)據(jù)之后,再鎖定該區(qū)的ECC,這樣系統(tǒng)就會(huì)把產(chǎn)生的ECC碼保存到相應(yīng)的寄存器中。main區(qū)的ECC保存到NFMECC0/1中(因?yàn)镵9F2G08U0A是8位IO口,因此這里只用到了NFMECC0),spare區(qū)的ECC保存到NFSECC中。對于讀操作來說,我們還要繼續(xù)讀取spare區(qū)的相應(yīng)地址內(nèi)容,已得到上次寫操作時(shí)所存儲(chǔ)的main區(qū)和spare區(qū)的ECC,并把這些數(shù)據(jù)分別放入NFMECCD0/1和NFSECCD的相應(yīng)位置中。最后我們就可以通過讀取NFESTAT0/1(因?yàn)镵9F2G08U0A是8位IO口,因此這里只用到了NFESTAT0)中的低4位來判斷讀取的數(shù)據(jù)是否正確,其中第0位和第1位為main區(qū)指示錯(cuò)誤,第2位和第3位為spare區(qū)指示錯(cuò)誤。

下面就給出一段具體的頁讀操作程序:

U8 rNF_ReadPage(U32 page_number)

{

U32 i, mecc0, secc;

NF_RSTECC(); //復(fù)位ECC

NF_MECC_UnLock(); //解鎖main區(qū)ECC

NF_nFCE_L(); //打開nandflash片選

NF_CLEAR_RB(); //清RnB信號(hào)

NF_CMD(CMD_READ1); //頁讀命令周期1

//寫入5個(gè)地址周期

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信號(hào)變高,即不忙

//讀取一頁數(shù)據(jù)內(nèi)容

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

{

buffer[i] = NF_RDDATA8();

}

NF_MECC_Lock(); //鎖定main區(qū)ECC值

NF_SECC_UnLock(); //解鎖spare區(qū)ECC

mecc0=NF_RDDATA(); //讀spare區(qū)的前4個(gè)地址內(nèi)容,即第2048~2051地址,這4個(gè)字節(jié)為main區(qū)的ECC

//把讀取到的main區(qū)的ECC校驗(yàn)碼放入NFMECCD0/1的相應(yīng)位置內(nèi)

rNFMECCD0=((mecc0&0xff00)<<8)|(mecc0&0xff);

rNFMECCD1=((mecc0&0xff000000)>>8)|((mecc0&0xff0000)>>16);

NF_SECC_Lock(); //鎖定spare區(qū)的ECC值

secc=NF_RDDATA(); //繼續(xù)讀spare區(qū)的4個(gè)地址內(nèi)容,即第2052~2055地址,其中前2個(gè)字節(jié)為spare區(qū)的ECC值

//把讀取到的spare區(qū)的ECC校驗(yàn)碼放入NFSECCD的相應(yīng)位置內(nèi)

rNFSECCD=((secc&0xff00)<<8)|(secc&0xff);

NF_nFCE_H(); //關(guān)閉nandflash片選

//判斷所讀取到的數(shù)據(jù)是否正確

if ((rNFESTAT0&0xf) == 0x0)

return 0x66; //正確

else

return 0x44; //錯(cuò)誤

}

這段程序是把某一頁的內(nèi)容讀取到全局變量數(shù)組buffer中。該程序的輸入?yún)?shù)直接就為K9F2G08U0A的第幾頁,例如我們要讀取第128064頁中的內(nèi)容,可以調(diào)用該程序?yàn)椋簉NF_ReadPage(128064);。由于第128064頁是第2001塊中的第0頁(128064=2001×64+0),所以為了更清楚地表示頁與塊之間的關(guān)系,也可以寫為:rNF_ReadPage(2001*64);。

頁寫操作的大致流程為:在兩個(gè)寫命令周期之間分別寫入頁地址和數(shù)據(jù),當(dāng)然如果為了保證下次讀取該數(shù)據(jù)時(shí)的正確性,還需要把main區(qū)的ECC值和spare區(qū)的ECC值寫入到該頁的spare區(qū)內(nèi)。然后我們還需要讀取狀態(tài)寄存器,以判斷這次寫操作是否正確。下面就給出一段具體的頁寫操作程序,其中輸入?yún)?shù)也是要寫入數(shù)據(jù)到第幾頁:

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(); //復(fù)位ECC

NF_MECC_UnLock(); //解鎖main區(qū)的ECC

NF_nFCE_L(); //打開nandflash片選

NF_CLEAR_RB(); //清RnB信號(hào)

NF_CMD(CMD_WRITE1); //頁寫命令周期1

//寫入5個(gè)地址周期

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

//寫入一頁數(shù)據(jù)

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

{

NF_WRDATA8((char)(i+6));

}

NF_MECC_Lock(); //鎖定main區(qū)的ECC值

mecc0=rNFMECC0; //讀取main區(qū)的ECC校驗(yàn)碼

//把ECC校驗(yàn)碼由字型轉(zhuǎn)換為字節(jié)型,并保存到全局變量數(shù)組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區(qū)的ECC

//把main區(qū)的ECC值寫入到spare區(qū)的前4個(gè)字節(jié)地址內(nèi),即第2048~2051地址

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

{

NF_WRDATA8(ECCBuf[i]);

}

NF_SECC_Lock(); //鎖定spare區(qū)的ECC值

secc=rNFSECC; //讀取spare區(qū)的ECC校驗(yàn)碼

//把ECC校驗(yàn)碼保存到全局變量數(shù)組ECCBuf中

ECCBuf[4]=(U8)(secc&0xff);

ECCBuf[5]=(U8)((secc>>8) & 0xff);

//把spare區(qū)的ECC值繼續(xù)寫入到spare區(qū)的第2052~2053地址內(nèi)

for(i=4;i<6;i++)

{

NF_WRDATA8(ECCBuf[i]);

}

NF_CMD(CMD_WRITE2); //頁寫命令周期2

delay(1000); //延時(shí)一段時(shí)間,以等待寫操作完成

NF_CMD(CMD_STATUS); //讀狀態(tài)命令

//判斷狀態(tài)值的第6位是否為1,即是否在忙,該語句的作用與NF_DETECT_RB();相同

do{

stat = NF_RDDATA8();

}while(!(stat&0x40));

NF_nFCE_H(); //關(guān)閉nandflash片選

//判斷狀態(tài)值的第0位是否為0,為0則寫操作正確,否則錯(cuò)誤

if (stat & 0x1)

{

temp = rNF_MarkBadBlock(page_number>>6); //標(biāo)注該頁所在的塊為壞塊

if (temp == 0x21)

return 0x43 //標(biāo)注壞塊失敗

else

return 0x44; //寫操作失敗

}

else

return 0x66; //寫操作成功

}



關(guān)鍵詞: ARM9(2440)nandflash讀寫操

評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉