NandFlash 驅動分析與基礎功能實現

一、寫作目的及參考來源說明

NandFlash 儘管本身時序複雜,但是經過這麼多年的發展,早已形成了一種專用的接口,作爲普通使用者來說,我們無需直接去編寫配置複雜的時序,只需按照CPU芯片手冊和NAND手冊的要求去配置SOC的nand控制器就可以了 ,即便是這樣,有些流程和細節仍需我們注意,本篇以三星公司生產的 K9F2G08U0C爲例,特此記錄,但本人求忘記的時候“”有章可循“”,也希望能夠幫助更多的人

本篇所寫,如有錯誤,歡迎評論區批評指出

本篇參考了以下博客的內容和韋東山老師的講解,特此感謝
NAND_FLASH(K9F1208U0C)驅動分析
Nand Flash基礎知識與壞塊管理機制的研究
物聯網:關於Nand flash讀寫範圍的問題

二、NandFlash的簡介

Nand flash成本相對低,說白了就是便宜,缺點是使用中數據讀寫容易出錯,所以一般都需要有對應的軟件或者硬件的數據校驗算法,統稱爲ECC。但優點是,相對來說容量比較大,現在常見的Nand Flash都是1GB,2GB,8GB,更大的128GB的都有了,相對來說,價格便宜,因此適合用來存儲大量的數據。其在嵌入式系統中的作用,相當於PC上的硬盤,用於存儲大量數據。
(圖片來源於網絡,如有侵權請告知)
在這裏插入圖片描述
在這裏插入圖片描述

三、引腳功能

在這裏插入圖片描述
注意:“#” 表示第低電平有效

標號 功能
I/O 0~7 命令/地址/數據 複用
CLE 命令鎖存使能
ALE 地址鎖存使能
CE# 片選(芯片使能)
RE# 讀使能
WE# 寫使能
WP# 寫保護
R/B# 待續/忙狀態
Vcc 電源
Vss
N.C 無連接

四、Array Organization(組織陣列)

在這裏插入圖片描述

1 . 最小讀寫單元

如圖所示,nandflash 的最小的讀寫單元是一個page,一個 page 由 2K Bytes 大小的數據存儲區和64Bytes的oob區組成,數據存儲區用於存放我們要存儲的數據,而剩下的oob區用於存放數據的校驗值,所以我們一般說 Page Size都指的是2 KBytes 的數據存儲區。

2 . 最小擦除單元

nandflash 的最小擦除單元是一個block,一個block由 64 個 page 組成,也就是 64 x 2 x 1024 + 64 x 64 = 128KBytes + 4KBytes
最終 2048 個 block 組成了一個device,即nandflash塊設備
前面說了,nandflash 的缺點是使用中數據讀寫容易出錯,所以對於每個page的數據區爲2KB的nandflash,其壞塊標記是一次標記一個block到該block的第一個page的oob區的第一個和第二個字節,讀取校驗的的時候需要block對齊

3 . Nand Flash控制器與Nand Flash芯片

摘自: Nand Flash基礎知識與壞塊管理機制的研究
我們寫驅動,是寫Nand Flash 控制器的驅動,而不是Nand Flash 芯片的驅動,因爲獨立的Nand Flash芯片,一般來說,是很少直接拿來用的,多數都是硬件上有對應的硬件的Nand Flash的控制器,去操作和控制Nand Flash,包括提供時鐘信號,提供硬件ECC校驗等等功能,我們所寫的驅動軟件,是去操作Nand Flash的控制器

然後由控制器去操作Nand Flash芯片,實現我們所要的功能。

由於Nand Flash讀取和編程操作來說,一般最小單位是頁,所以Nand Flash在硬件設計時候,就考慮到這一特性,對於每一片(Plane),都有一個對應的區域專門用於存放,將要寫入到物理存儲單元中去的或者剛從存儲單元中讀取出來的,一頁的數據,這個數據緩存區,本質上就是一個緩存buffer,但是隻是此處datasheet裏面把其叫做頁寄存器page register而已,實際將其理解爲頁緩存,更貼切原意。

而正是因爲有些人不瞭解此內部結構,才容易產生之前遇到的某人的誤解,以爲內存裏面的數據,通過Nand Flash的FIFO,寫入到Nand Flash裏面去,就以爲立刻實現了實際數據寫入到物理存儲單元中了,而實際上只是寫到了這個頁緩存中,只有當你再發送了對應的編程第二階段的確認命令,即0x10,之後,實際的編程動作纔開始,纔開始把頁緩存中的數據,一點點寫到物理存儲單元中去。

3 . SLC和MLC

SLC 和MLC分別是是Single-Level Cell 單層單元和Multi-Level Cell多層單元的縮寫,SLC的特點是成本高、容量小、速度快,而MLC的特點是容量大成本低,但是速度慢。MLC的每個單元是2bit的,相對SLC來說整整多了一倍。不過,由於每個MLC存儲單元中存放的資料較多,結構相對複雜,出錯的機率會增加,必須進行錯誤修正,這個動作導致其性能大幅落後於結構簡單的SLC閃存。所以DIY固態U盤可以儘量選擇SLC
那麼軟件如何識別系統上使用過的SLC還是MLC呢?
在這裏插入圖片描述
Nand Flash設計中,有個命令叫做Read ID,讀取ID,讀取好幾個字節,一般最少是4個,新的芯片,支持5個甚至更多。以K9F2G08U0C爲例,支持5cyc。
在這裏插入圖片描述
在這裏插入圖片描述
從這些字節中,可以解析出很多相關的信息,比如此Nand Flash內部是幾個芯片(chip)所組成的,每個chip包含了幾片(Plane),每一片中的頁大小Page Size,Block Size 塊大小,等等。
3rd ID Data 的 Cell Type 從2起步,表明 K9F2G08U0C 是 MLC
在這裏插入圖片描述
在這裏插入圖片描述

4 . oob / Redundant Area / Spare Area

每一個頁,對應還有一塊區域,叫做空閒區域(spare area)/冗餘區域(redundant area),而Linux系統中,一般叫做OOB(Out Of Band),這個區域,是最初基於Nand Flash的硬件特性:數據在讀寫時候相對容易錯誤,所以爲了保證數據的正確性,必須要有對應的檢測和糾錯機制,此機制被叫做EDC(Error Detection Code)/ECC(Error Code Correction, 或者 Error Checking and Correcting),所以設計了多餘的區域,用於放置數據的校驗值。

Oob的讀寫操作,一般是隨着頁的操作一起完成的,即讀寫頁的時候,對應地就讀寫了oob。

關於oob具體用途,總結起來有:

標記是否是壞快
存儲ECC數據
存儲一些和文件系統相關的數據。如jffs2就會用到這些空間存儲一些特定信息,而yaffs2文件系統,會在oob中,存放很多和自己文件系統相關的信息。

五、基於 CPU NandFlash 控制器的操作

在這裏插入圖片描述
前面說了,nand已經發展爲一種行業通用的接口,我們使用nand,只需要按照CPU芯片手冊和NAND手冊的要求去配置SOC的nand控制器就可以了。

1 . NAND FL ASH CONFIGURATION REGISTER 的時序時間計算

下面我們來看一下手冊中的相關內容:
2440中nand時序:
在這裏插入圖片描述
然後我們在nand手冊中找一個時序圖,來計算一下CPU 的 nand 控制器的TACLS、TWRPH0、TWRPH1 的時間:
在這裏插入圖片描述
計算好時間之後我就可以寫相應的2440 的nandflash控制器(NAND FL ASH CONFIGURATION REGISTER)了:
在這裏插入圖片描述

之前我們進行過CPU的時鐘配置,HCLK = 100M 如下:

/* 設置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

所以,可以分別計算出TACLS、TWRPH0、TWRPH1的值 0,1,0,見上圖 NAND FL ASH CONFIGURATION REGISTER中的紅色字

2 . CONTROL REGISTER

在這裏插入圖片描述

3 . 初始化2440 的 nandflash 控制器

首先,在s3c2440_soc.h中地址有如下宏定義

/*NAND Flash*/

#define     NFCONF                   __REG(0x4E000000)  //NAND flash configuration             
#define     NFCONT                   __REG(0x4E000004)  //NAND flash control                   
#define     NFCMD                    __REG_BYTE(0x4E000008)  //NAND flash command                   
#define     NFADDR                   __REG_BYTE(0x4E00000C)  //NAND flash address                   
#define     NFDATA                   __REG_BYTE(0x4E000010)  //NAND flash data                      
#define     NFMECC0                  __REG(0x4E000014)  //NAND flash main area ECC0/1          
#define     NFMECC1                  __REG(0x4E000018)  //NAND flash main area ECC2/3          
#define     NFSECC                   __REG(0x4E00001C)  //NAND flash spare area ECC            
#define     NFSTAT                   __REG_BYTE(0x4E000020)  //NAND flash operation status          
#define     NFESTAT0                 __REG(0x4E000024)  //NAND flash ECC status for I/O[7:0]   
#define     NFESTAT1                 __REG(0x4E000028)  //NAND flash ECC status for I/O[15:8]  
#define     NFMECC0_STATUS           __REG(0x4E00002C)  //NAND flash main area ECC0 status     
#define     NFMECC1_STATUS           __REG(0x4E000030)  //NAND flash main area ECC1 status     
#define     NFSECC_STATUS            __REG(0x4E000034)  //NAND flash spare area ECC status     
#define     NFSBLK                   __REG(0x4E000038)  //NAND flash start block address       
#define     NFEBLK                   __REG(0x4E00003C)  //NAND flash end block address       

代碼配置如下:

#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0

void nand_init(void)
{

	/*設置NAND FLASH 的時序 */
	NFCONF = (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4);

	/* 設置NAND FLASH 控制器 */
	NFCONT = (1 << 4) | (1 << 1) | (1 << 0);
}

4 . 基於nandflash 控制器的操作

有了nandflash 控制器,我們的讀寫操作就變得相對簡單了,只需要讀寫相應的CPU的nand相關的寄存器,2440就可以自動按照讀寫時序讀取或發出數據。
下面以 2440 和 K9F2G08U0C 爲例來進一步說明:

使能NAND Flash

前面的引腳 CE# 片選(芯片使能)低電平時有效,寫MODE 位爲1, 2440的nFCE引腳就會爲低電平,則與其連接的 CE# 爲低電平 ,使能nand flash 。
在這裏插入圖片描述同理,禁止片選只要將MODE位寫0就好了
代碼配置如下:

void nand_delay(void)
{
	volatile unsigned int i;
	for (i = 0; i < 10; i++);
}
void nand_select(void)
{
	/*使能片選 bit 1 爲 1 */
	NFCONT &= ~(1 << 1);
	nand_delay();
}
void nand_deselect(void)
{
	/*禁止片選 bit 1 爲 1 */
	NFCONT |= (1 << 1);
}

5 . I/O 0~7 地址/命令/數據

我們只需要讀或者寫相應的2440寄存器,nandflash控制器就可以自動完成對nandflash的操作
來看一下2440中的這三個寄存器
在這裏插入圖片描述
所以代碼編寫如下:

//寫命令
void nand_cmd(unsigned char cmd)
{
	NFCMD = cmd;
	nand_delay();
}
//寫地址
void nand_addr_byte(unsigned char addr)
{
	NFADDR = addr;
	nand_delay();
}
//讀數據
unsigned char nand_data(void)
{
	return NFDATA;
}
//寫數據
void nand_w_data(unsigned char val)
{
	NFDATA = val;
}

我們還需要判斷nandflash的狀態,以判斷操作是否完成:
在這裏插入圖片描述

void nand_wait_ready(void)
{
	while (!(NFSTAT & 1));
}

地址發送的形式:
在這裏插入圖片描述
進一步封裝寫地址的函數,封裝成三類;如下:

void nand_addr(unsigned int addr)
{
	unsigned int col = addr % 2048;
	unsigned int page = addr / 2048;

	NFADDR = col & 0xff;
	nand_delay();
	NFADDR = (col >> 8) & 0xff;
	nand_delay();

	NFADDR = page & 0xff;
	nand_delay();
	NFADDR = (page >> 8) & 0xff;
	nand_delay();
	NFADDR = (page >> 16) & 0xff;
	nand_delay();
}

void nand_page(unsigned int page)
{

	NFADDR = page & 0xff;
	nand_delay();
	NFADDR = (page >> 8) & 0xff;
	nand_delay();
	NFADDR = (page >> 16) & 0xff;
	nand_delay();
}

void nand_col(unsigned int col)
{

	NFADDR = col & 0xff;
	nand_delay();
	NFADDR = (col >> 8) & 0xff;
	nand_delay();
}

6 . 壞塊檢測

對於每個page的數據區爲2KB的nandflash,其壞塊標記是一次標記一個block到該block的第一個page的oob區的第一個和第二個字節,0xff表示正常,其它值表示錯誤。讀取校驗的的時候需要block對齊,在每次讀寫數據或者擦除操作前都需要判斷當前所在塊的好壞

int nand_bad(unsigned int addr)
{
	unsigned int col = 2048;
	unsigned int page = addr / (2048 * 1024);
	unsigned char val;

	/* 1. 選中 */
	nand_select();

	/* 2. 發出讀命令00h */
	nand_cmd(0x00);

	/* 3. 發出地址(分5步發出) */
	nand_col(col);
	nand_page(page);

	/* 4. 發出讀命令30h */
	nand_cmd(0x30);

	/* 5. 判斷狀態 */
	nand_wait_ready();

	/* 6. 讀數據 */
	val = nand_data();

	/* 7. 取消選中 */
	nand_deselect();

	if (val != 0xff)
		return 1; /* bad blcok */
	else
		return 0;
}

7 . 數據讀取

nandflsh 時序如下:
在這裏插入圖片描述
在每次讀數據前都需要判斷當前所在塊的好壞:

void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
	/* 定位當前待讀取的列地址 */
	unsigned int column = addr % 2048;
	unsigned int cnt = 0; //累計已讀字節長度

	/*cnt沒讀夠len時,每發出一遍命令後,column一次最多隻能遍歷一個page的data區 ,當超出一個page時需要再發一遍讀命令*/
	while (cnt < len)
	{
		if (nand_bad(addr)) /* 一個block只判斷一次 */
		{
			addr += (128 * 1024); /* 跳過當前block */
			continue;			  /* 之所以加continue,是因爲跳過當前block的下一個block也需要讀oob區來判斷好壞,continue結束當前循環,進入下一次去判斷 */
		}
		/* 使能片選 */
		nand_select();

		/*發出00命令*/
		nand_cmd(0x00);

		/* 發出地址(分5步發出) */
		nand_addr(addr);

		/*發出30命令*/
		nand_cmd(0x30);

		/* 等待就緒 */
		nand_wait_ready();

		/*在累計讀取次數cnt不夠目標次數len的前提下(while),column只要未超出單個page的data區就可以繼續讀 */
		for (column = 0; (column < 2048) && (cnt < len); column++)
		{
			buf[cnt++] = nand_data();
			addr++; //addr累加
		}
		//跳出for後的addr只能是2048的倍數,在下一個nand_addr(addr);會自動回車換行指向下一個page的起始地址
		/*禁止片選*/
		nand_deselect();
	}
}

爲了方便調用和信息交互,我們進一步對其進行封裝:每次讀取並打印64個字符

void do_read_nand_flash(void)
{
	unsigned int i = 0, j = 0;
	unsigned char read_buf[64] = {0};
	volatile unsigned char *p = (volatile unsigned char *)read_buf;

	unsigned int addr = 0;
	printf("Enter the start address to read:");
	addr = get_uint();

	nand_read(addr, read_buf, 64);

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 16; j++)
		{
			printf("%02x ", *p++);
		}
		printf("   ;");

		for (p -= 16, j = 0; j < 16; j++, p++)
		{
			/* 後打印字符 */
			if (*p < 0x20 || *p > 0x7e) /* 不可視字符 */
			{
				putchar('.');
			}
			else
				putchar(*p);
		}
		printf("\n\r");
	}
}

8 . 數據寫入

nandflsh 時序如下:
在這裏插入圖片描述
每次寫前,需要判斷當前要寫入的快是否爲好塊

char nand_write(unsigned int addr, unsigned char *write_buf, unsigned int len)
{
	if (nand_bad(addr)) /* 一個block只判斷一次 */
	{
		printf("this block is bad !\n\r");
		return -1;
	}

	unsigned int page = addr / 2048;
	unsigned int column = addr % 2048;
	unsigned int cnt_Byte = 0;

	while (cnt_Byte < len)
	{
		nand_select();
		nand_cmd(0x80);

		/* 發出地址(分5步發出) */
		nand_addr(addr);

		/* 發出數據 */
		for (; (column < 2048) && (cnt_Byte < len);)
		{
			nand_w_data(write_buf[cnt_Byte++]);
		}

		nand_cmd(0x10);
		nand_wait_ready();
		nand_deselect();

		cnt_Byte += 2048;
		if (cnt_Byte == len)
			break;
		else
		{
			column = 0; //類似於回車
			page++;		//類似於換行
		}
	}

	return 0;
}

爲了方便調用和信息交互,我們進一步對其進行封裝:最多一次支持寫100個字符

void do_write_nand_flash(void)
{
	unsigned int addr;
	unsigned char str[100];
	/* 獲得地址 */
	printf("Enter the address of sector to write: \n\r");
	addr = get_uint();
	printf("Please enter less than 100 characters to write: ");
	gets(str);
	nand_write(addr, str, strlen(str) + 1); //+1,保留'\0'
}

9 . block擦除過程

擦除過程是將0變成1的過程,即充電的過程(比如SLC中,當低於某個電壓值表示0,高於這個電壓值則表示1;而對於MLC來說可以有多個閾值,所以可以保存更多bit)。擦除過程是按塊進行的,但啓始地址是頁地址,不過擦除過程在內部是有邊界對齊的,也就是說當擦除啓始地址不是塊對齊時,只能擦除本塊,而不能垮越到第二個塊繼續擦除,也就是無論我們給的地址是否頁對齊,本塊都將擦除,不會有任何保留。
需要注意的是:塊擦除時每一頁的oob區也同時被擦除掉了,所以一般擦除前先讀取塊的第一頁的兩個字節看是否爲0xff,不是的話就不要擦除,0xff表示正常,其它值表示錯誤,否則將會擦掉所有壞塊信息,尤其是出廠時寫入的。

nandflsh 時序如下:
在這裏插入圖片描述

char nand_erase_block(unsigned int addr, unsigned int len)
{
	unsigned int page = 0;
	unsigned int cnt_Byte = 0;
	page = addr / 2048;

	if (nand_bad(addr)) /* 一個block只判斷一次 */
	{
		printf("this block is bad !\n\r");
		return -1;
	}
	/* 如果page或者len不是block的整數倍,則提醒並返回 */
	if (page % 64 || len % (2 * 64 * 1024))
	{
		printf("nand_erase err, addr is not block align\n\r");
		return -1;
	}
	/* 即便是對齊也再強制對齊一遍 */
	else
	{
		page = (page >> 6) << 6; //保證起始擦除地址是64page即block對齊的(二的六次方)
	}

	while (cnt_Byte < len)

	{
		nand_select();

		nand_cmd(0x60);

		nand_page(page);

		nand_cmd(0xd0);

		nand_wait_ready();

		nand_deselect();

		cnt_Byte += (64 * 2 * 1024);

		if (cnt_Byte == len) //如果查出的長度達到len,則停止下一次除
			break;
		else //否則,繼續下一次的擦除
			page += 64;
	}

	return 0;
}

爲了方便調用和信息交互,我們進一步對其進行封裝:每次調用一次擦除一個block

void do_erase_nand_flash(void)
{
	unsigned int addr;
	/* 獲得地址 */
	printf("Enter the address of block to erase: \n\r");
	addr = get_uint();
	if (nand_erase_block(addr, (64 * 2 * 1024)) == 0)
	{
		printf("erase is ok\n\r");
	}
	else
		printf("erase is fail\n\r");
	//最小擦除單位爲一個block
}

10 . 軟件獲取芯片ID

在前面 SLC和MLC 部分,我們說到了read ID 命令,現在具體實現一下:
我們主要關心nandflash第四個數據中包含的信息:
在這裏插入圖片描述

void nand_chip_id(void)
{
	unsigned char id_data[5] = {0};
	unsigned char i;

	nand_select();

	nand_cmd(0x90);

	nand_addr_byte(0x00);

	for (i = 0; i < 5; i++)
	{
		id_data[i] = nand_data();
		nand_delay();
	}

	nand_deselect();

	printf("Maker Code:   0x%x\n\r", id_data[0]);
	printf("Device Code:  0x%x\n\r", id_data[1]);
	printf("3th cyc:      0x%x\n\r", id_data[2]);
	printf("4th cyc:      0x%x\n\r", id_data[3]);
	printf("page  size:   %d KBytes\n\r", 1 << (id_data[3] & 0x03));
	printf("block size:   %d KBytes\n\r", 64 << ((id_data[3] >> 4) & 3));
	printf("5th cyc       0x%x\n\r", id_data[4]);
}

經測試串口返回信息如下:
在這裏插入圖片描述

六、NAND操作菜單彙總及串口輸出測試

有了以上這些函數,我們可以進一步封裝調用,實現菜單操作:

void nand_flash_test(void)
{
	/* 打印菜單,供我們選擇測試內容*/

	/* 測試內容:
    * 1.識別nand flash
    * 2.擦除nand flash 摸個扇區
    * 3.編寫某個地址
    * 4.讀某個地址
    */
	while (1)
	{
		char c;

		printf("[s] Scan nand flash id\n\r");
		printf("[e] Erase nand flash\n\r");
		printf("[w] Write nand flash\n\r");
		printf("[r] Read nand flash\n\r");
		printf("[q] Quit nand flash\n\r");
		printf("Enter selection\n\r");

		c = getchar();
		/* 回車 換行 回顯*/
		printf("%c\n\r", c);
		switch (c)
		{
		case 'q':
		case 'Q':
			return;
			break;
		case 's':
		case 'S':
			nand_chip_id();
			break;
		case 'e':
		case 'E':
			do_erase_nand_flash();
			break;

		case 'w':
		case 'W':
			do_write_nand_flash();
			break;
		case 'r':
		case 'R':
			do_read_nand_flash();
			break;
		}
	}
}

測試順序如下:
依次:
read ID
read data
erase block
read data
write data
read data
測試效果如圖:
在這裏插入圖片描述

七、代碼彙總

爲了方便大家的測試和使用,將nandflash.c 的代碼整理如下,
來源:韋東山老師
SOC: 2440
NandFlash : K9F2G08U0C

#include "s3c2440_soc.h"
#include "my_printf.h"
#include "nand_flash.h"
#include "include/string.h"
#include "string_utils.h"

#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0

void nand_delay(void)
{
	volatile unsigned int i;
	for (i = 0; i < 10; i++)
		;
}
void nand_init(void)
{

	/*設置NAND FLASH 的時序 */
	NFCONF = (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4);

	/* 設置NAND FLASH 控制器 */
	NFCONT = (1 << 4) | (1 << 1) | (1 << 0);
}
void nand_select(void)
{
	/*使能片選 bit 1 爲 1 */
	NFCONT &= ~(1 << 1);
	nand_delay();
}
void nand_deselect(void)
{
	/*禁止片選 bit 1 爲 1 */
	NFCONT |= (1 << 1);
}

void nand_cmd(unsigned char cmd)
{
	NFCMD = cmd;
	nand_delay();
}
void nand_addr_byte(unsigned char addr)
{
	NFADDR = addr;
	nand_delay();
}
unsigned char nand_data(void)
{
	return NFDATA;
}
void nand_w_data(unsigned char val)
{
	NFDATA = val;
}
void nand_wait_ready(void)
{
	while (!(NFSTAT & 1))
		;
}

void nand_addr(unsigned int addr)
{
	unsigned int col = addr % 2048;
	unsigned int page = addr / 2048;

	NFADDR = col & 0xff;
	nand_delay();
	NFADDR = (col >> 8) & 0xff;
	nand_delay();

	NFADDR = page & 0xff;
	nand_delay();
	NFADDR = (page >> 8) & 0xff;
	nand_delay();
	NFADDR = (page >> 16) & 0xff;
	nand_delay();
}

void nand_page(unsigned int page)
{

	NFADDR = page & 0xff;
	nand_delay();
	NFADDR = (page >> 8) & 0xff;
	nand_delay();
	NFADDR = (page >> 16) & 0xff;
	nand_delay();
}

void nand_col(unsigned int col)
{

	NFADDR = col & 0xff;
	nand_delay();
	NFADDR = (col >> 8) & 0xff;
	nand_delay();
}

int nand_bad(unsigned int addr)
{
	unsigned int col = 2048;
	unsigned int page = addr / (2048 * 1024);
	unsigned char val;

	/* 1. 選中 */
	nand_select();

	/* 2. 發出讀命令00h */
	nand_cmd(0x00);

	/* 3. 發出地址(分5步發出) */
	nand_col(col);
	nand_page(page);

	/* 4. 發出讀命令30h */
	nand_cmd(0x30);

	/* 5. 判斷狀態 */
	nand_wait_ready();

	/* 6. 讀數據 */
	val = nand_data();

	/* 7. 取消選中 */
	nand_deselect();

	if (val != 0xff)
		return 1; /* bad blcok */
	else
		return 0;
}


void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
	/* 定位當前待讀取的列地址 */
	unsigned int column = addr % 2048;
	unsigned int cnt = 0; //累計void nand_chip_id(void)
{
	unsigned char id_data[5] = {0};
	unsigned char i;

	nand_select();

	nand_cmd(0x90);

	nand_addr_byte(0x00);

	for (i = 0; i < 5; i++)
	{
		id_data[i] = nand_data();
		nand_delay();
	}

	nand_deselect();

	printf("Maker Code:   0x%x\n\r", id_data[0]);
	printf("Device Code:  0x%x\n\r", id_data[1]);
	printf("3th cyc:      0x%x\n\r", id_data[2]);
	printf("4th cyc:      0x%x\n\r", id_data[3]);
	printf("page  size:   %d KBytes\n\r", 1 << (id_data[3] & 0x03));
	printf("block size:   %d KBytes\n\r", 64 << ((id_data[3] >> 4) & 3));
	printf("5th cyc       0x%x\n\r", id_data[4]);
}

	/*cnt沒讀夠len時,每發出一遍命令後,column一次最多隻能遍歷一個page的data區 ,當超出一個page時需要再發一遍讀命令*/
	while (cnt < len)
	{
		if (nand_bad(addr)) /* 一個block只判斷一次 */
		{
			addr += (128 * 1024); /* 跳過當前block */
			continue;			  /* 之所以加continue,是因爲跳過當前block的下一個block也需要讀oob區來判斷好壞,continue結束當前循環,進入下一次去判斷 */
		}
		/* 使能片選 */
		nand_select();

		/*發出00命令*/
		nand_cmd(0x00);

		/* 發出地址(分5步發出) */
		nand_addr(addr);

		/*發出30命令*/
		nand_cmd(0x30);

		/* 等待就緒 */
		nand_wait_ready();

		/*在累計讀取次數cnt不夠目標次數len的前提下(while),column只要未超出單個page的data區就可以繼續讀 */
		for (column = 0; (column < 2048) && (cnt < len); column++)
		{
			buf[cnt++] = nand_data();
			addr++; //addr累加
		}
		//跳出for後的addr只能是2048的倍數,在下一個nand_addr(addr);會自動回車換行指向下一個page的起始地址
		/*禁止片選*/
		nand_deselect();
	}
}

char nand_erase_block(unsigned int addr, unsigned int len)
{
	unsigned int page = 0;
	unsigned int cnt_Byte = 0;
	page = addr / 2048;

	if (nand_bad(addr)) /* 一個block只判斷一次 */
	{
		printf("this block is bad !\n\r");
		return -1;
	}
	/* 如果page或者len不是block的整數倍,則提醒並返回 */
	if (page % 64 || len % (2 * 64 * 1024))
	{
		printf("nand_erase err, addr is not block align\n\r");
		return -1;
	}
	/* 即便是對齊也再強制對齊一遍 */
	else
	{
		page = (page >> 6) << 6; //保證起始擦除地址是64page即block對齊的(二的六次方)
	}

	while (cnt_Byte < len)

	{
		nand_select();

		nand_cmd(0x60);

		nand_page(page);

		nand_cmd(0xd0);

		nand_wait_ready();

		nand_deselect();

		cnt_Byte += (64 * 2 * 1024);

		if (cnt_Byte == len) //如果查出的長度達到len,則停止下一次除
			break;
		else //否則,繼續下一次的擦除
			page += 64;
	}

	return 0;
}
char nand_write(unsigned int addr, unsigned char *write_buf, unsigned int len)
{
	if (nand_bad(addr)) /* 一個block只判斷一次 */
	{
		printf("this block is bad !\n\r");
		return -1;
	}

	unsigned int page = addr / 2048;
	unsigned int column = addr % 2048;
	unsigned int cnt_Byte = 0;

	while (cnt_Byte < len)
	{
		nand_select();
		nand_cmd(0x80);

		/* 發出地址(分5步發出) */
		nand_addr(addr);

		/* 發出數據 */
		for (; (column < 2048) && (cnt_Byte < len);)
		{
			nand_w_data(write_buf[cnt_Byte++]);
		}

		nand_cmd(0x10);
		nand_wait_ready();
		nand_deselect();

		cnt_Byte += 2048;
		if (cnt_Byte == len)
			break;
		else
		{
			column = 0; //類似於回車
			page++;		//類似於換行
		}
	}

	return 0;
}
void do_read_nand_flash(void)
{
	unsigned int i = 0, j = 0;
	unsigned char read_buf[64] = {0};
	volatile unsigned char *p = (volatile unsigned char *)read_buf;

	unsigned int addr = 0;
	printf("Enter the start address to read:");
	addr = get_uint();

	nand_read(addr, read_buf, 64);

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 16; j++)
		{
			printf("%02x ", *p++);
		}
		printf("   ;");

		for (p -= 16, j = 0; j < 16; j++, p++)
		{
			/* 後打印字符 */
			if (*p < 0x20 || *p > 0x7e) /* 不可視字符 */
			{
				putchar('.');
			}
			else
				putchar(*p);
		}
		printf("\n\r");
	}
}
void do_write_nand_flash(void)
{
	unsigned int addr;
	unsigned char str[100];
	/* 獲得地址 */
	printf("Enter the address of sector to write: \n\r");
	addr = get_uint();
	printf("Please enter less than 100 characters to write: ");
	gets(str);
	nand_write(addr, str, strlen(str) + 1); //+1,保留'\0'
}

void do_erase_nand_flash(void)
{
	unsigned int addr;
	/* 獲得地址 */
	printf("Enter the address of block to erase: \n\r");
	addr = get_uint();
	if (nand_erase_block(addr, (64 * 2 * 1024)) == 0)
	{
		printf("erase is ok\n\r");
	}
	else
		printf("erase is fail\n\r");
	//最小擦除單位爲一個block
}
void nand_flash_test(void)
{
	/* 打印菜單,供我們選擇測試內容*/

	/* 測試內容:
    * 1.識別nand flash
    * 2.擦除nand flash 摸個扇區
    * 3.編寫某個地址
    * 4.讀某個地址
    */
	while (1)
	{
		char c;

		printf("[s] Scan nand flash id\n\r");
		printf("[e] Erase nand flash\n\r");
		printf("[w] Write nand flash\n\r");
		printf("[r] Read nand flash\n\r");
		printf("[q] Quit nand flash\n\r");
		printf("Enter selection\n\r");

		c = getchar();
		/* 回車 換行 回顯*/
		printf("%c\n\r", c);
		switch (c)
		{
		case 'q':
		case 'Q':
			return;
			break;
		case 's':
		case 'S':
			nand_chip_id();
			break;
		case 'e':
		case 'E':
			do_erase_nand_flash();
			break;

		case 'w':
		case 'W':
			do_write_nand_flash();
			break;
		case 'r':
		case 'R':
			do_read_nand_flash();
			break;
		}
	}
}

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