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

猜你喜欢

转载自blog.csdn.net/qq_28816873/article/details/105000143