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;
}
}
}