15.Nand-Flash操作实例

目录

1.Nand-Flash的原理图及其引脚功能

2.如何操作Nand Flash

3.Nand Flash 编程

1) .初始化Nand Flash控制器

2).读取芯片ID

3).Nand_FLash读取数据

       使程序能从Nand Flash启动

4).Nand Flash块擦除

5).Nand_Flash写数据 

源代码



1.Nand-Flash的原理图及其引脚功能

 问题1:Nand-Flash和2440之间只有数据线,如何传输地址呢?

答:在DATA0-DATA7 即传输数据,又传输地址,

当ALE为高电平时传输的是地址.

问题2:从Nand Flash芯片数据手册可知,要操作Nand Flash需要先发出命令,如何分辨是命令还是数据呢?

答: 在DATA0-DATA7 即传输数据,又传输地址,又传输命令,

当ALE为高电平时传输的是地址。

当CLE为高电平时传输的是命令。

当ALE和CLE都为低电平时传输的是数据。

问题3:假设烧写Nand Flash,把命令、地址、数据发送给它之后,Nand Flash如何判断烧写完成?

答:通过判断引脚Rnb:它为高电平表示就绪,低电平表示正忙状态。


2.如何操作Nand Flash

2.1读ID操作

查看时序图:

读ID对于Nand Flash做法和对比S3C2440的做法:

关于不同位宽的设备如何连接及操作方式:点我查看

操作方式Nand FlashS3C2440
发送命令

1.芯片片选拉低

2.CLE设为高电平

3.在DATA0-DATA7上写入命令

4.发出一个写脉冲WE拉低

 

把命令的值写到寄存器:NFCMMD

例子: NFCMD = 命令值
发送地址

1.芯片片选拉低

2.ALE设为高电平

3.在DATA0-DATA7上写入地址

4.发出一个写脉冲WE

 

把地址的值写到寄存器:NFADDR

例子: NFADDR = 地址值
发送数据

1.芯片片选拉低

2.ALE和CLE设为低电平

3.在DATA0-DATA7上写入数据

4.发出一个写脉冲WE

 

把数据的值写到寄存器:NFDATA

例子:NFDATA = 数据值
读取数据

1.芯片片选拉低

2.发出读脉冲RE拉低

3.读取在DATA0-DATA7上的数据

 

 

读取寄存器NFDATA的值

例子: val = NFDATA

备注:

在S3C2440中集成了一个Nand Flash控制器,它帮我们简化了这些读写的步骤:

我们需要做的就是对应寄存器写地址或数据,或读取数据就可以,时序是Nand Controller帮我们自动完成的。

 

读取ID的操作:

根据时序图可以知道:

 Nand FlashS3C2440
操作步骤

1.发出片选信号

2.发出命令:0x90

3.发出地址:0x00

4.第一次读取第一个字节数据得到:0xEC

5.第二次读取得到设备码:devide code

6.第三次读取得到:Internal Chip Number, Cell Type, Number                                 of Simultaneously Programmed Pages, Etc

7.第四次读取得到: Page Size, Block Size,Redundant Area                                  Size, Organization, Serial Access Minimum

8.第五次读取得到:Plane Number, Plane Size

1.设置NFCONT寄存器bit1=0

2.NFCMMD=0x90

3.NFADDR=0x00

4.val=NFDATA 

接着读取下一个信息

芯片手册描述:

S3C2440的Nand Flash Controller 

1).设置片选信号:配置NFCONT寄存器

2).发出命令,配置NFCMMD寄存器

3).发送地址:配置NFADDR寄存器

4).读数据,读取寄存器NFDATA寄存器

 


3.Nand Flash 编程

步骤:

1) .初始化Nand Flash控制器

2).读取芯片ID

3).Nand_FLash读取数据

       使程序能从Nand Flash启动

4).Nand Flash块擦除

5).Nand_Flash写数据

 


1) .初始化Nand Flash控制器

在S3C2440内部有一个Nand Flash控制器 ,因此当我们外部接有Nand Flash芯片的时候,很多命令发送控制器自动帮我们完成了,但是为了兼容不同类型的Nand Flash芯片,所以2440的时序是可以编程的,外部的时序是确定的,当我们知道了外部芯片的时序,就可以通过编程使Nand Flash控制器的时序能够满足外部Nand Flash芯片的时序,这样子2440就能够控制Nand Flash芯片了。

1).查看外部Nand Flash芯片数据手册的时序图。使用的芯片是:K9F2G08U0C

 

 

得到几条关键的信息:

1.CLE、CLE、WE的信号可以同时发出

2.WE的信号持续时间(tWP)最少为12ns

3.WE信号释放后,ALE和CLE信号至少再维持多久才可以变化时间最少为5ns


2).从上边已经知道了外部nand Flash的控制时序了,接下来就是让2440的nand Flash控制器的时序满足这个要求,打开2440数据手册,nand Flash控制器时序控制这一块:

 

从上边可知,可设置TACLS = 0, TWRPH0 = 12ns,TWRPH1=5ns,注意这个数值是最小值,设置的数值只可大不可小。

可以通过寄存器NFCONF设置这几个位的参数:

因此可以设置寄存器NFCONF的[13:12] =0b00,[10:8]=0b001,[6:4]=0b000.

注意:因为这里的Nand Falsh的位宽是8位的,因此发送地址,命令、数据都只能以一个字节的大小发送,因此需要重新设置一下一次性读写的字节数:

3).设置NFCONT寄存器,初始化ECC,使能Nand Flash控制器

编程如下: 


2).读取芯片ID

查看Nand Flash的读ID时序:

可以把读取ID的时序抽象成这几个时刻:

分析:

1).片选信号CS是一个总开关,只有当使能了片选信号,别的操作才有意义,所以一旦片选拉低了以后就可以开始传输读写命令了。

2).把上图的时间轴从t1往t2移动的过程中,可知需要先发出CLE命令信号,然后发送WE写信号(在上面分析时序可知外接的芯片允许CLE和WE同时发送),当CLE和WE有效时,代表的就是写命令,写的命令就是根据 I/Ox(数据总线来决定),其数值就是0x90(这个命令的含义就是读取ID)。到时间t2写的命令就结束了。

备注:

那么关于WE信号需要维持多久才可以释放,数据总线的数据需要维持多久,才能释放CLE信号等,都是通过查看外部所接的芯片手册来决定的,在上面的时序图也提到过,这里再贴一次:

所以结论就是:CLE信号和WE的信号可以同时发出(至少维持12ns),发出这个信号之后就代表是写命令信号,此时数据总线接受的就是命令的数据,tDS的含义是数据的建立时间,至少12ns(在发出CLE信号和WE信号维持的时间内,这个时间已经满足要求了,所以可以不用设置),也就是说至少经过这个时间,WE的信号才能够释放,然后至少再经过tCLH或tALH(5ns),CLE或ALE的信号才能释放。

 

如果说没有nand Flash控制器的话,我们就需要自己去实现如上所述时序的控制,而且必须掌控每个信号的时间,操作难度就会加大,性能也就随着降低,但是好在S3C2440有一个Nnad Flash控制器,通过前面对Nand Flash 控制器时序的初始化之后,我们写一个命令的时候,只需往寄存器:NFCMMD寄存器写一个命令的数值,也就是: NFCMMD = 命令值;   nand Flash控制器就会自动帮我们发送如上的时序,完成写命令操作。

3).同理t2-t3时刻就是一个发送地址的时序步骤,当有nand Flash 控制器,只需往,NFADDR寄存器写地址值,nand flash控制器自动帮我们完成上边的时序

4)t3-t4时刻就是读取一个字节的数据,当有nand flash控制器,只需读取NFDATA寄存器的值就可以得到数据总线传回来的数据,接着再读取一个字节的数据也是一样的步骤。


编写函数:

1).抽象出写地址,写命令,写数据,读数据,片选使能,片选失能的函数如下:

/*使能片选 CS*/
void nand_enable_cs(void)
{
	NFCONT &= ~(1<<1);
}
/*失能片选 CS*/
void nand_disable_cs(void)
{
	NFCONT |= (1<<1);
}

/*函数:nand_write_cmd
 *功能:往Nand Flash写一个字节的命令
 */
void nand_write_cmd(unsigned char cmd)
{
	volatile int i;
	NFCMMD = cmd;
	for(i=0;i<10;i++); //适当延时,保证稳定性

}
/*函数:nand_write_addr
 *功能:往Nand Flash写一个字节的地址
 */
void nand_write_addr(unsigned char addr)
{
	volatile int i;
	NFADDR = addr;
	for(i=0;i<10;i++); //适当延时,保证稳定性
}
/*函数:nand_writr_data_byte
 *功能:往Nand Flash写一个字节的数据
 */
void nand_writr_data_byte(unsigned char data)
{
	volatile int i;
	NFDATA = data;
	for(i=0;i<10;i++);
}
/*函数:nand_read_data_byte
 *功能:读取往Nand Flash一个字节的数据
 */

unsigned char nand_read_data_byte(void)
{
	return NFDATA;
}

2).根据读取ID的时序,进行读取操作,读取5个字节的数据,如下:

void nand_read_ID(void)
{
	int i;
	/*保存读取ID信息的数组*/
	unsigned char ID_info[5]={0}; 
	/*片选选中*/
	nand_enable_cs();
	/*发送命令:0x90*/
	nand_write_cmd(0x90);
	/*发送地址:0x00*/
	nand_write_addr(0x00);
	/*连续读取5个字节的数据,存储到数组中*/
	for(i=0;i<5;i++)
	{
		ID_info[i]=nand_read_data();
	}
	/*打印出数组信息:*/
	printf("Maker  Code : 0x%x\n\r",ID_info[0]);
	printf("Devide Code : 0x%x\n\r",ID_info[1]);
	printf("3rd   cycle : 0x%x\n\r",ID_info[2]);
	printf("4th   cycle : 0x%x\n\r",ID_info[3]);
	printf("5th   cycle : 0x%x\n\r",ID_info[4]);
	/*片选失能*/
	nand_disable_cs();
	
}

3).编写一个菜单选项功能函数,先只写读取ID的函数:

void nand_flash_test()
{
	while(1)
	{
		char c;
		printf("[s] Scan  nand flash\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            \n\r");
		printf("Enter after slection: ");

		c = getchar();
		printf("%c\r\n",c);
		/*打印菜单*/
		/*菜单选项
	     *1.识别nand_falsh,设备和容量
	     *2.读取某个地址的数据
	     *3.擦除nand_flash扇区数据
	     *4.往某个地址写数据
		 */
		 switch(c)
		 {
			case 'q':
			case 'Q':
				return;
				break;
			case 's':
			case 'S':
				nand_read_ID();
				break;
			
			case 'e':
			case 'E':
				
				break;
			case 'w':
			case 'W':
				
				break;
			case 'r':
			case 'R':
				
				break;
		 }
	}
}

4).在函数的入口,调用nand_flash初始化函数和测试函数:

int main()
{	
	

	/*初始化nand_flash*/
	nand_flash_init();
	/*调用nand_Flash操作菜单函数*/
	nand_flash_test();


	return 0;

}

5).修改Makefile内容,增加nand_flash.c进行编译链接:

all: start.o led.o uart.o sdram_init.o main.o execption.o interrupt.o timer.o lib1funcs.o my_printf.o nand_flash.o string_utils.o
	arm-linux-ld -T nand_flash.lds  $^ -o nand_falsh.elf
	arm-linux-objcopy -O binary -S nand_falsh.elf nand_falsh.bin	
	arm-linux-objdump -D nand_falsh.elf > nand_falsh.dis

%.o : %.c
	arm-linux-gcc -march=armv4 -c -o $@ $<

%.o : %.S
	arm-linux-gcc -march=armv4 -c -o $@ $<
clean:
	rm *.bin *.o *.elf *.dis

6).烧写到NOR_Flash,并从nor_Flash启动:

注意:如果此时烧写到Nand Flash,并从Nand Flash启动程序是不会成功的,因为这个bin文件大小已经超过了4K,且现在还没有实现nand flash的读函数。

打开串口:选择打印打印到的信息:

第一个字节的函数是Maker Code,第二个字节是设备ID,在数据手册,如下描述:

其中我们比较关系的是第4个字节的数据,有关于nand_flash块的大小,页的大小:

在上面我们读取到的数据是:0x95 = 0b10010101  ,因此,Page Size = 2KB,Block Size = 128KB。

我们也可以把这些关键信息打印出来,修改nand_read_ID函数如下:

void nand_read_ID(void)
{
	int i;
	/*保存读取ID信息的数组*/
	unsigned char ID_info[5]={0}; 
	/*片选选中*/
	nand_enable_cs();
	/*发送命令:0x90*/
	nand_write_cmd(0x90);
	/*发送地址:0x00*/
	nand_write_addr(0x00);
	/*连续读取5个字节的数据,存储到数组中*/
	for(i=0;i<5;i++)
	{
		ID_info[i]=nand_read_data();
	}
	/*打印出数组信息:*/
	printf("Maker  Code : 0x%x\n\r",ID_info[0]);
	printf("Devide Code : 0x%x\n\r",ID_info[1]);
	printf("3rd   cycle : 0x%x\n\r",ID_info[2]);
	printf("4th   cycle : 0x%x\n\r",ID_info[3]);
	printf("Page  Size = %d KB\r\n",1<<(ID_info[3]&0x03));
	printf("Block Size = %d KB\r\n",1<<((ID_info[3]>>4)&0x03)+6);
	printf("5th   cycle : 0x%x\n\r",ID_info[4]);
	/*片选失能*/
	nand_disable_cs();
	
}

再次烧写运行程序,就可以打印出页和块的大小了:

备注:此处代码命令为:nand_Flash读取ID 


3).Nand_FLash读取数据

  • 预备知识:

①nand_flash 的结构示意图如下:

从上面的结构图提取处如下的简图。

可以得到的信息有: 

1.这个nand_flash的数据线是8bit的,数据区总共有 2048(2K)列

2.总共有2048个block,每一个block的大小是 128K ,而每个block有64页(page),每个page的大小是2K,

3.这样算来,这个nand_flash的大小是:  2048 * 128 K = 256M

 

②读取数据时如何发送地址呢?

从数据手册中可以看出,需要发送5个周期的地址,才算发送一个完整的地址,有一些位是没有用到的,其目的也是以后兼容更大芯片的nand falsh.从结构图中计算出这个nand flash的大小是 256 Mbits,这个是如何得到:

分析:

首先对列进行寻址,从看表看出,需要发送两个字节的列地址,共有2048+64 列,即为(2K + 64B),使用二进制描述的话至少需要12位的二进制数(A0-A11),也就是2^12= 4096,方可描述所有的列数。因此一个page = (2k + 64B)bytes

规定在一个block中有64个page,因此一个block的大小是:(128K+4K)bytes

接着对行进行寻址,从看表看出,需要发送三个字节的行地址,但是只用到(A12 - A28),共用到17位,它可描述的最大数组是:2^17 = 131072个数字,如果一个block有64个page,那么17位地址一共可以描述 :131072 /64 = 2048个block,如果使用简图把上面的结构展开的话,大致如下图所示:

 


  • 读操作时序:

 

步骤:

①发出片选信号

②发出0x00命令.

③发送5个周期的地址(两个列地址,三个行地址(page))

④再发送0x30命令,然后就是接收所发送地址的数据了。

⑤禁止片选

  •  判断nand Flash读写忙标志位

通过判断NFSTAT的第0位来判断是否处于忙状态


使程序能从Nand Flash启动

nand_Flash读取ID 代码是没有办法从nand Flash启动的,因为上一个代码的长度大于4K了,当把程序烧写到nand Flash,2240硬件自动把nand Falsh前4K的代码复制到片内内存运行:

为什么从Nand Flash启动会失败?

如果从NOR启动,那么CPU的0地址就是NOR Flash的0地址。片内SRAM的起始地址就是0x40000000

如果从Nand启动,那么CPU的0地址就是SRAM的0地址。此时NOR FLASH(大小为2M)为不可见状态

然后再看一下,代码重定位函数:

void copy_to_sdram(void)
{

	/*从sdran.lds链接脚本的到代码的运行地址
	 *从外部得到_code_start,_bss_start
	 *接着从0地址复制数据到运行时地址
	**/
	//定义两个变量,_code_start 用于获取链接脚本的代码起始位置,_bss_star
	//用于获取代码结束位置,也是bss段的起始位置
	extern int _code_start,_bss_star;  
	//定义三个指针变量,分别是重定位代码的位置(运行时地址)
	//代码段结束位置
	//需要重定位的代码
	volatile unsigned int *des =(volatile unsigned int *)&_code_start;
	volatile unsigned int *end =(volatile unsigned int *)&_bss_star;
	volatile unsigned int *src =(volatile unsigned int *)0;
	//进行复制,把原地址代码复制到目的地址,代码重定位
	while(des<end)
	{
		*des++ = *src++;
	}
	
}

重定位代码函数是不区分是Nand Flash 启动还是NOR Flash启动的,而直接从0地址开始的代码复制到运行时地址,此处时SDRAM(0x30000000):

情况一:从NOR Flash启动,此时NOR Flash的0地址,对应的就是CPU的0地址,而NOR Flash是支持读取的,因此可以把所有的代码全部重定位到SDRAM.

情况二:从Nand Flash启动,此时片内SRAM的地址对应的就是CPU的0地址,如果从Nand Flash启动,2440硬件会把nand Flash前4K的数据复制到片内SRAM,如果Nand Flash上的程序大于4K,那也不理睬,接着还是重定位,就相当于只重定位了前4K的代码,这样子从Nand Flash就得不到想要的结果。

如何解决这个问题:

现在我们可以写读取Nand Flash数据的函数了,可接着以这样子做:

1.前提:已经能实现Nand Flash的读函数

2.代码烧写到Nand Flash,并从Nand Flash启动。

3.程序运行到重定位代码的位置判断一下,是从Nand Flash启动还是NOR  Flash启动(通过往0地址写数据,因为Nand是支持读取的,所以读出的结果和写的结果一样,而NOR Flash不能像内存一样读写,因此读写的内容是不一致的)

4.如果从NOR Flash启动,直接使用简单的重定位代码就行,如果是Nand Flash启动,那就是用Nnad Flash的读函数进行代码的重定位。

核心代码如下:

nand Flash读取数据的函数:

/*函数功能:读取nand_Falsh 数据
 *参数1:地址,参数2:读取到的数据,参数3:读取的字节数
 *
 */

void nand_read_data(unsigned int addr,unsigned char *data,unsigned int len)
{
	int i=0;
	/*因为读取数据的时候是一次性读出一页,因此当给出
	 *地址addr之后,每一页的数据大小是2K,因此我们可以
	 *根据地址知道我们读取的数据是哪一个页
	 */
	int page = addr/2048;
	/*page是定位到哪一个页,col变量定位的就是在这个
	 *页的偏移量
	 */
	int col = addr &(2048-1);       //addr%2048;
	/*打开片选*/
	nand_enable_cs();

	/*注意每次读取数据一次性只能读取一页,
	 *当读取的数据大于一页时就需要循环操作,因此
	 *加上死循环,当不满足条件再退出
	 */
	while(i<len)
	{
		
		/*发出0x00命令*/
		nand_write_cmd(0x00);
		/*发送5个周期的地址*/
			/*行地址,col addr*/
		       /*发出列地址的低8位*/
		nand_write_addr(col & 0xff);
				/*发送高8位,其实只用到4位*/
		nand_write_addr((col>>8) & 0xff);
			/*列地址。row/page addr*/
		nand_write_addr(page & 0xff);
		nand_write_addr((page>>8) & 0xff);
		nand_write_addr((page>>16) & 0xff);
		/*发送0x30命令*/
		nand_write_cmd(0x30);
		/*等待就绪*/
		nand_wait_ready();
		/*读取数据*/
		/*我们可以从地址分析出所需要读取的行和列,而列就是
		 *col,列地址最大为2047,因此如果读取的字节数大于(2047-col)
		 *那么就需要去读取下一个page的数据,依次类推
		 *for循环里面有两个条件:
		 *1.当读取到页尾,但是还没有读取完,说明需要读取下一页
		 *2.已经读取到指定的字节数了,已经读完
		 */
		for(;(col<2048)&&(i<len);col++)
		{
			data[i++] = nand_read_data_byte();
		}
		if(i==len)  //说明已经读取完成指定的字节数了
		{
			break;  //跳出循环
		}
		/*读取到一页中最后一个数据之后
		 *到下一页的第0个地址继续读取
		 */
		col = 0;
		page++;
	}
	/*禁止片选*/
	nand_disable_cs();
}

判断是从NOR Flash启动还是Nand Falsh启动:

int isBootFromNorFlash()
{
	volatile unsigned int *p=(volatile unsigned int *)0;
	int val = *p;  //读取0地址的值

	*p= 0x66666666;  //改变0地址的值

	if(*p== 0x66666666)
	{
		/*写入数据成功,说明从Nand Flash启动*/
		/*还原刚才改动的数据*/
		*p = val;
		return 0;
	}
	else  /*NOR Flash启动*/
	{  
		return 1;
	}

}

根据启动方式不同,使用不同的代码进行代码重定位:

/*把代码复制到SDRAM运行*/

void copy_to_sdram(void)
{

	/*从sdran.lds链接脚本的到代码的运行地址
	 *从外部得到_code_start,_bss_start
	 *接着从0地址复制数据到运行时地址
	**/
	//定义两个变量,_code_start 用于获取链接脚本的代码起始位置,_bss_star
	//用于获取代码结束位置,也是bss段的起始位置
	extern int _code_start,_bss_star;  
	//定义三个指针变量,分别是重定位代码的位置(运行时地址)
	//代码段结束位置
	//需要重定位的代码
	volatile unsigned int *des =(volatile unsigned int *)&_code_start;
	volatile unsigned int *end =(volatile unsigned int *)&_bss_star;
	volatile unsigned int *src =(volatile unsigned int *)0;
	int len;
	len = ((int)&_bss_star) -  ((int)&_code_start); //重定位代码的长度


	
	//进行复制,把原地址代码复制到目的地址,代码重定位
	if(isBootFromNorFlash()) //如果从NorFlash启动
	{
		while(des<end)
		{
			*des++ = *src++;
		}

	}
	else //否则从Nand Flash启动
	{
		/*需要使用到Nand Flash,先初始化Nand Flash*/
		nand_flash_init();
		/*调用nand flash读取数据函数
		 *从 src 复制到 des ,总共复制len字节,也就是重定位的代码
		 */
		nand_read_data(src,des,len);  
	}
	
	
}

这里还需要注意一点,需要修改Makefile使编译生成的bin文件,使代码重定位的代码放在前4K,如果在后面的话,nand Flash拷贝到SRAM数据丢失会导致程序重定位失败。

把这几个放在前面编译:

编译,下载到Nand Flash,从Nand Flash启动,如果说这个程序能正常运行,就说明,刚才那么Nand Flash数据读取函数是成功的:

下载复位,程序正常运行,说明数据读取成功:


现在代码可以烧写到nand flash并从nand flash启动,并已经写出了nand_read_dat()函数,现在抽象出一个函数,来读取指定地址的数据并打印出来:

代码如下:

/*函数功能:测试数据擦除函数,调用上面的nand_read_data 函数
			默认一次性读取160个字节数据
 */

unsigned short Read_nand_flash(void)
{
	//读取的基地址
	unsigned int addr;
	//定义一个无符号字符型变量p
	volatile unsigned char *p;
	int i,j;
	static unsigned int hex_addr=0;
	unsigned char c;
	unsigned short amount;
	/*保存数据*/
	unsigned char str[16];
	unsigned char data[160];
	
	/*获取读取地址*/
	printf("=================Enter Read Address: ");
	/*根据用户输入的地址,结果给回addr
	 *可输入16进制和10进制
	 */
	addr = get_uint();
	hex_addr = addr;
	/*从nand flash读取64个字节的数据*/
	nand_read_data(addr,data,160);
	
	/*p指向data,打印data数组的数据*/
	p = (volatile unsigned char *)data;
	//printf("Enter Read amount(16 times):  ");
	//amount = get_uint();
	printf("Read Data:\n\r");
	printf("            00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n\r");
	/*读取长度:160字节*/
	for(i=0;i<10;i++)
	{  
		printf("0x%08x  ",hex_addr);
		/*每行打印16字节数据*/
		for(j=0;j<16;j++)
		{
			/*先打印16进制*/
			c = *p++;    //读取第一个字节的数据,然后p指向下一个字节
			str[j] = c; //先保存数值
			printf("%02x ",c);  //打印出当前字节的16进制
			
		}
		printf(" ; ");
		for(j=0;j<16;j++)
		{
			/*打印对应的字符*/
			if(str[j]<0x20 || str[j]>0x7e) /*不可视字符,打印出一个点*/
			{
				putchar('.');
			}
			else
				putchar(str[j]);  //可视字符,直接打印
		}
		hex_addr+=16;
		printf("\n\r");
		
	}
}

现在下载到开发版,打印从0地址开始的160字节的数据,如果和烧写的bin文件数据一致,说明读取是成功的,否则读取失败:

1.bin文件内容如下:

2.调用Read_nand_flash()函数,打印0地址开始的内容如下:

对比可以看出数据时一模一样的,说明数据读取成功。


4).Nand Flash块擦除

块擦除时序:

1.选中nand flash片选

2.发送命令:0x60

3.发送行地址:三个字节

4.发送命令:0xD0,等待擦除完成

5.禁止片选

代码如下:

/*函数功能:擦除nand_Falsh 数据
 *参数1:地址,参数2:长度
 *
 */
int nand_erase(unsigned int addr,unsigned int len)
{
	/*注意:nand flash是以块位单位擦除的:一个block=128k
	 *比如len = 1,虽然len =1,但是此时扔擦除一个block
	 */
	int page = addr / 2048;
	/*擦除的起始地址不是128的整数倍*/
	if(addr & (0x1FFFF))  
	{
		printf("ERR addr!Please enter an integer multiple of 128K\n\r");
		return -1;
	
	}
	/*擦除的字节数不是128K的整数倍*/
	if(len & (0x1FFFF))
	{
		printf("ERR len!Please enter an integer multiple of 128K\n\r");
		return -1;
	}

	/*片选选中*/
	nand_enable_cs(void)
	while(1)
	{
		/*发送命令:0x60*/
		nand_write_cmd(0x60);
		/*发送行地址:page/row addr*/
		nand_write_addr(page & 0xff);
		nand_write_addr((page>>8) & 0xff);
		nand_write_addr((page>>16) & 0xff);
		/*发送0xD0命令*/
		nand_write_cmd(0xD0);
		/*等待擦除完成*/
		nand_wait_ready();

		/*此时已经完成擦除一个块的数据了,len减去128K*/
		len -= (128*1024);
		if(len <= 0) //说明擦除的数据小于一个块,擦除结束
		{
			break; //退出
		}
		addr+= (128*1024); //指向下一个block,继续擦除
	}
	/*取消片选*/	
	nand_disable_cs();
	return 0;
}

接着抽象出一个函数,来擦除指定的块,因为擦除是以块为单位的,所以在程序中干脆直接指定擦除哪一个块,防止擦除出错。

程序如下:

/*函数功能:测试数据擦除函数,调用上面的nand_erase 函数
			一次性最小擦除一个块。
 */
void Erase_nand_flash(void)
{
	int err=0;
	//擦除的地址
	unsigned int addr;
	unsigned int whichblock;
	/*从键盘获取删除地址*/
	printf("=================remarks:The size of each block is 128K======================\n\r");
	printf("=================Enter Erase Block number[0-2047]: ");
	/*输入需要删除的block*/
	whichblock = get_uint();
	while(whichblock > 2047 || whichblock < 0)
	{
		printf("!!!!!!!!!!!ERROR!!!!!!!!!!\n\r");
		printf("=================Please enter a correct number: ");
		whichblock = get_uint();
	}
	/*计算这个block对应的地址*/ 
	addr = whichblock *(128*1024);
	/*提示擦除数据的范围*/
	printf("=================Erase range : 0x%08x - 0x%08x\n\r",addr,(addr+(128*1024)));
	printf("=================erasing ...   ");
	
	/*擦除一个块(block)*/
	if(nand_erase(addr,128*1024)<0)
	{
		printf("!!!!!!!!!!!!!!!!Erase fail!!!!!!!!!!!!!\n\r");
	}
	printf("\n\r=================Erase finished!\n\r");
}

里面注释的很清楚,不做过多解释,直接调用函数Erase_nand_flash();指定擦除:0x500000 即:5M位置的数据(第40个block,计算方式:0x500000 /(1024*128)),擦除的范围是一个block,程序也会有提示:

擦除之前,先读取0x500000位置的数据不为空:

开始擦除:擦除第40个block的数据:


5).Nand_Flash写数据 

  • 查看芯片手册,nand Flash 写时序:

1.选中nand flash片选

2.发送命令:0x80

3.发送地址:5个字节

4.发送命令:0x10,等待写入完成

5.禁止片选

代码如下:

/*函数功能:往nand_Falsh 写数据
 *参数1:写地址,参数2:需要写的数据,参数3:写的字节数
 *
 */

void nand_write(unsigned int addr,unsigned char *data,unsigned int len)
{
	int i=0; 
	/*page是定位到哪一个页,col变量定位的就是在这个
	 *页的偏移量
	 */
	int page = addr / 2048;
	int col  = addr % 2048;       //addr%2048;
	/*片选选中*/
	nand_enable_cs();
	while(1)
	{
		/*发出0x00命令*/
		nand_write_cmd(0x80);
		/*发送5个周期的地址*/
			/*行地址,col addr*/
		       /*发出列地址的低8位*/
		nand_write_addr(col & 0xff);
				/*发送高8位,其实只用到4位*/
		nand_write_addr((col>>8) & 0xff);
			/*列地址。row/page addr*/
		nand_write_addr(page & 0xff);
		nand_write_addr((page>>8) & 0xff);
		nand_write_addr((page>>16) & 0xff);
		/*发出数据*/
		/*for循环里面有两个条件:
		 *1.当写到页尾,但是还没有写完,说明需要写下一页
		 *2.已经写到指定的字节数了,已经写完*/
		for(;(col<2048) && (i<len);col++)
		{
			/*一次写一个字节的数据*/
			nand_write_data_byte(data[i++]);
		}
		/*发出0x10命令*/
		nand_write_cmd(0x10);
		/*等待烧写完成*/
		nand_wait_ready();
		if(i==len)
		{
			break; //退出
		}
		else /*还没有写完,继续写下一页*/
		{
			col = 0;
			page ++;
		}

	}

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

抽象出一个函数,用于指定输入写入的地址,以及写入的数据,为了擦除的方便,程序中会提示你所写入的数据的地址是属于哪一页和哪一个block的。

代码如下:

/*函数功能:测试数据擦除函数,调用上面的nand_write 函数
 */

void Write_nand_flash(void)
{
	//写数据的地址
	unsigned int addr;
	unsigned char write_data[100];
	unsigned int whichpage,whichblock;
	/*从键盘获取写数据起始地址*/
	printf("=================Enter Write Address: ");
	addr = get_uint();
	/*提示你所输入的地址是否正确
	 *如果正确,则告知你所写的是属于哪个block以及哪个page
	 */
	if(addr >= 0x10000000 || addr < 0)
	{
		printf("!!!!!!!!!!!ERROR!!!!!!!!!!\n\r");
		printf("=================Please enter a correct addr: ");
		addr = get_uint();
	}
	whichpage = addr/2048;
	whichblock= whichpage/64;
	printf("=================This address belongs to: page-->%d, block--> %d\n\r",whichpage,whichblock);
	printf("=================Enter Write data: ");
	gets(write_data);
	printf("=================writing........   ");
	nand_write(addr,write_data,strlen(write_data)+1);
	printf("\n\r=================writie Finished!  \n\r");

}

此处注意:

一般在烧写数据之前需要对数据进行擦除操作,除非原本的数据全f,否则都需要进行擦除。

调用函数:Write_nand_flash()函数,写入指定地址:0x500000 数据 hello world!

然后再读取数据:

写入成功,读取0x500000的数据,如下:

如果想擦除这个数据的话,通过上面的提示,知道需要擦除的是第40个block,擦除后再读取数据:

备注:此处代码命名为:nand_Flash读写擦除数据。

源代码

 nand_Flash读取ID          :https://download.csdn.net/download/qq_36243942/10943840

nand_Flash读写擦除数据:https://download.csdn.net/download/qq_36243942/10947506

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值