NAND flash基础。

千里之行始于足下——只有基础的东西学扎实了,才能做出更好的东西。在这个基础上才能进行改进以及创新,就像俗话所说:基础不牢、地动山摇。下面介绍nandflash的基础知识,由于NAND具有容量大、价格便宜、操作快(擦除和写),引脚少等特点,现在越来越多的设备使用NAND,对于位反转的缺陷,随着现代工艺技术的发展,以及坏块保护及纠错(oob)等技术,基本上都可以处理好。

下面简单介绍下NAND的硬件知识和操作流程以及编程要点,想要深入了解的同学最好仔细看官方的数据手册。数据手册永远是最好的学习资料。下面的介绍是基于K9F2G08U0C这款芯片。

NAND的数据存储结构:

从图中可以知道nand存储单元是page,每页的大小是2KB,还提供了64字节的OOB(out of blank),oob用来存放坏块以及前2K数据的校验码。oob对CPU是不可以视的,CPU读写数据时只操作前2K的数据。进行访问时先输入列地址(即在这页中的第几列)在输入行地址(即页的地址,第几个页)。

NAND的引脚及其作用:

NAND flash是个存储芯片,所以最基本的是使用存储功能,读取A地址的数据,以及把数据C写入B地址。但是从原理图上面发现NAND的引脚很少,没有地址引脚。

问:只有DATA0~DATA7这8根数据引脚,如何实现传输地址、数据以及命令?

答:显然这是通过引脚的复用,DATA0~DATA7既可以用来传输地址、也可以用来传输命令以及数据。在同一时刻只能传输一种,这个由谁来决定此时传输的是地址还是数据以及命令呢?可以通过引脚来控制芯片,让其有序的工作,ALE为高电平时,DATA0~DATA7上面传输的是地址(ALE:address LATCH enable),当CLE为高电平时,DATA0~DATA7上面传输的是命令(CLE:command LATCH enable),当CLE和ALE都为低电平时DATA0~DATA7上面传输的是数据。

问:芯片上的CE有什么功能?

答:CE是片选引脚,当CE为低电平的时候选上nand flash,此时才能操作这个芯片。为啥要片选呢?因为很多芯片都接在有DATA0~DATA7,所以需要让SOC能够区分操作的是什么芯片。

问:往flash中写入数据后,芯片不能立刻把数据写入,需要时间。那什么时候能写或者是什么时候能读取数据?

答:R/B(READY/BUSY)引脚用来表示nand的状态,要是为高时表示准备好了,可以往里面写入数据。

问:如何区分SOC对nand进行的读写操作?

答:可以通过RE/WE引脚来控制。

所以处理器操作NAND的时候(很多处理器上面都会集成NAND控制器,只要操作控制就可以控制nand),只需要进行以下步骤:

  • 通过设置片选引脚把NAND选上。
  • 发出操作指令(同时发出写脉冲)。
  • 发出操作地址(同时发出写脉冲)。
  • 读取数据(或者是写入数据)

比如读取数据,参考时序图,别的操作也是一样的,比如擦除、编写、读取ID等操作。

  • 通过设置CE为低电平选上此nand。
  • 设置CLE为高电平,发送00到nand(发送写脉冲)
  • 设置ALE为高电平,依次发送地址(先发列地址在发行地址)到nand(发送写)。
  • 设置CLE为高电平,发送30到nand(发送些脉冲)
  • 监测是否繁忙,等数据准备好了,发送读脉冲开始读数据。
  • 退出读状态,设置CLE为高电平,发送0xff到nand(发送时需要发送写脉冲)

要是按照上面的时序来逐个操作引脚很是麻烦,也很难保证同时做到。所以很多处理器上面都集成了NAND控制器,帮忙来处理这些繁琐的操作,咱们只需要操作相应的寄存器即可以实现发送命令、发送地址、发送数据。下图是s3c2440的NAND控制器操作流程图

  • 同样的nand和nor一样。操作完成后需要退出读写模式时,使用reset(复位)退出。
  • flash的特性是只有由1变为0,而不能从0变为1。所以给flash写数据时需要先擦除(即全部设置为0xff)。

存储芯片的编程基本都是类似的,初始化、识别、实现读、实现写、实现擦除等操作。对于NAND的编程流程也是一样的,如下:

  • 初始化主控芯片的NAND flash控制器,主要设置NAND的时序。
  • 读取ID识别芯片的信息。
  • 实现读,一次读一个页(page)。
  • 实现写,一次写一页(page)。
  • 实现擦除,一次擦除一个块(block)。

NAND的时序设置完全有外接的flash来决定,S3C2440中的时序如下:

  • TACLS表示的是发出地址或者是命令锁存信号后多久才能发送写脉冲。
  • TWRPH0表示的是读写脉冲信号的时间长度(周期)
  • TWRPH1表示的是读写信号结束后多久才能释放命令锁存或者是地址锁存信号。

NAND中的时序图:

从图中可以得知:TACLS = Tcls-Twp = 0       TWRPH0 = Twp=12     TWRPH1 = Tclh = 5。

下面就是S3C中有关NAND的编程代码:



#define PAGE_SIZE	(0x800)
#define BLOCK_SIZE	(64*PAGE_SIZE)


unsigned char nand_read(void)
{
	volatile unsigned char *NFDATA = (volatile unsigned char *)0x4E000010;
	return *NFDATA&0xff;
}

/*发送指令函数,往寄存器中写入值,控制器会自动发出指令*/
void nand_cmd(unsigned char cmd)
{	
	volatile int i = 0;
	volatile unsigned char *NFCMMD = (volatile unsigned char *)0x4E000008;
	*NFCMMD = cmd;
	for(i = 0;i < 100;i++);
	
}
/*发送地址函数,往寄存器中写入值,控制器会自动发出地址*/
void nand_addr(unsigned char addr)
{
	volatile int i = 0;
	volatile unsigned char *NFADDR = (volatile unsigned char *)0x4E00000C;
	*NFADDR = addr;
	for(i = 0;i < 100;i++);

}
/*nand flash 片选*/
void nand_select(void)
{
	volatile unsigned int *NFCONT = (volatile unsigned int*)0x4E000004;
	*NFCONT &= ~(1<<1);
}
/*nand flash取消片选*/
void nand_disselect(void)
{
	volatile unsigned int *NFCONT = (volatile unsigned int*)0x4E000004;
	*NFCONT |= (1<<1);
}

void nand_init(void)
{
	#define TACLS 		0
	#define TWRPH0 		1
	#define TWRPH1		0
	volatile unsigned int *NFCONF = (volatile unsigned int*)0x4E000000;
	volatile unsigned int *NFCONT = (volatile unsigned int*)0x4E000004;

	/*设置NAND控制器的时序,参考手册时序图*/
	*NFCONF = (TACLS<<14) | (TWRPH0<<8) |(TWRPH1<<4);
	/*使能NAND控制器*/
	*NFCONT = (1<<0) | (1<<1) |(1<<4);
}

void nand_reset(void)
{
	nand_cmd(0xff);
}

void read_nand_id(void)
{
	unsigned int buffer[5] = {0};
	volatile int i;
	/*发送指令0x90*/
	nand_cmd(0x90);
	/*发送地址00*/
	nand_addr(0);
	/*读数据总线获取数据*/
	for(i =0 ;i <5;i++){
		buffer[i] = nand_read();
		putHex_(buffer[i]);
	}
	/*退出读取ID状态*/
	nand_cmd(0xff);
}

void nand_wait_ready(void)
{
	volatile unsigned int *NFSTAT = (volatile unsigned int *)0x4E000020;
	/*等待nand进入就绪状态*/
	while(!(*NFSTAT&1));
}

#if 0
void nand_read_dat(unsigned int addr,unsigned int length,char* buf)
{
	unsigned short offset;
	unsigned int page;
	int i;

	page = addr / PAGE_SIZE;
	offset = addr % PAGE_SIZE;

	nand_cmd(0x00);
	nand_addr(offset&0xff);
	nand_addr(offset>>8 &0xff);
	nand_addr(page&0xff);
	nand_addr(page>>8&0xff);
	nand_addr(page>>16&0xff);
	nand_cmd(0x30);

	nand_wait_ready();
		
	for(i = 0;i<length;i++){
		*buf++ =nand_read();
	}

	nand_cmd(0xff);
	
}
#endif

void nand_read_dat(unsigned int addr,unsigned int length,char*buf)
{
	/*参考nand flash芯片手册读时序图*/
	unsigned short col = addr % PAGE_SIZE;
	unsigned int page = addr / PAGE_SIZE;
	int i = 0;
	while(i <length){
		/*发出0x00命令*/
		nand_cmd(0x00);
		/*发出列地址column低8bit*/
		nand_addr(col&0xff);
		/*发出列地址column高8bit*/
		nand_addr(col>>8&0xff);
		/*发出行地址(page)低8bit*/
		nand_addr(page&0xff);
		/*发出行地址(page)第二字节*/
		nand_addr(page>>8&0xff);
		/*发出行地址(page)的第三个字节*/
		nand_addr(page>>16&0xff);
		/*发出0x30命令,开始读取数据*/
		nand_cmd(0x30);

		/*等待nand就绪*/
		nand_wait_ready();
		
		for(; ((col<PAGE_SIZE)&&(i<length));i++,col++){
			*buf++ = nand_read();		
		}
		if(i == length){break;}
		col = 0;
		page++;
		
	}
	nand_reset();
	
}

int erase_block(unsigned int addr,int len)
{
	
	//unsigned char val = 0; 
	/*参考nand flash芯片手册擦除时序图*/
	unsigned int page = addr / PAGE_SIZE;
	if(addr % BLOCK_SIZE){return -1;}
	
	while(1){
		/*发出命令0x60*/
		nand_cmd(0x60);
		/*发出行地址1*/
		nand_addr(page&0xff);
		/*发出行地址2*/
		nand_addr(page>>8&0xff);
		/*发出行(page)地址3*/
		nand_addr(page>>16&0xff);
		/*发出命令0xd0*/
		nand_cmd(0xD0);
		/*等待nand就绪*/

		nand_wait_ready();
		len -= BLOCK_SIZE;
		if(len<=0){break;}
		page += 64;

	}
#if 0	
	nand_read_dat(0x70,1,&val);
	putHex_(val);
	if(val & 1){
		puts_("erase error\n\r");
	}else{
		puts_("erase success\n\r");
	}
#endif
	nand_reset();
 	return 0;
}

void nand_program_dat(unsigned int addr,unsigned int length,unsigned char* buf)
{
	/*参考nand flash芯片page编程时序图*/
	unsigned int page = addr / PAGE_SIZE;
	unsigned short col = addr % PAGE_SIZE;
	volatile unsigned char* NFDATA = (volatile unsigned char*)0x4E000010;
	int i = 0;
	unsigned char val = 0;
	
	while(i<length){
		/*发送命令0x80*/
		nand_cmd(0x80);
		/*发送地址*/
		nand_addr(col&0xff);
		nand_addr(col>>8&0xff);
		nand_addr(page &0xff);
		nand_addr(page>>8&0xff);
		nand_addr(page>>16&0xff);
		/*发送数据*/
		for(; ((col <PAGE_SIZE) &&i<length); i++,col++){
			*NFDATA = *buf++;
		}
		/*发送命令0x10*/
		nand_cmd(0x10);
		/*等待芯片处于就绪状态*/
		nand_wait_ready();
		/*校验是否编程(写)成功*/
#if 0
		nand_read_dat(0x70,1,&val);
		putHex_(val);
		if(val & 1){
			puts_("program error\n\r");
		}else{
			puts_("program success\n\r");
		}
#endif		
		if(i ==length){break;}
		col = 0;
		page++;
		
	}
	nand_reset();
}

void nand_flash_test(void)
{
	char c;
	int i;
	char buffer[PAGE_SIZE];
	/*初始化NAND控制器*/
	nand_init();
	/*设置片选信号、选择nand*/
	nand_select();
	
	while(1){
		puts_("[s] Scan nand flash\n\r");
		puts_("[e] erase nand flash\n\r");
		puts_("[w] write nand flash\n\r");
		puts_("[r] read nand flash\n\r");
		puts_("[q] quit \n\r");
		puts_("please enter selection: ");
		c=getchar_();
		puts_("\n\r");

		switch(c){
			case 'S':
			case 's':
				read_nand_id();
				break;
			case 'e':
			case 'E':
				erase_block(0,BLOCK_SIZE);
				break;
			case 'w':
			case 'W':
				for(i = 0;i<PAGE_SIZE;i++){
					buffer[i] = i;
				}
				nand_program_dat(0, PAGE_SIZE, buffer);
				break;
			case 'r':
			case 'R':
				for(i = 0;i<PAGE_SIZE;i++)
				buffer[i] = 0;
				nand_read_dat(0,PAGE_SIZE,buffer);
				for(i = 0;i < PAGE_SIZE;i++){
					putHex_(buffer[i]);
				}
				break;
			case 'q':
			case 'Q':
				return;
				break;
			case 't':
			case 'T':
				//do_test_serial();
				break;
			default:
				puts_("please enter  the correct instruction\n\r");

		}

	}
		/*取消片选*/
	nand_disselect();
}

代码中的读写擦除以及读取芯片的ID都是参考了nand的数据手册以及s3c2440的nand控制器章节。

发布了35 篇原创文章 · 获赞 1 · 访问量 1870

猜你喜欢

转载自blog.csdn.net/lzj_linux188/article/details/101762378