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