14.Nor-Flash操作实例

目录

1.基础知识

2.使用u-boot体验Nor Flash的操作

3.编写NOR-Flash测试程序

源代码

数据手册

 


1.基础知识

1).Nor Flash 和Nand Flash 的区别?

NOR FLASH 和NAND FLASH 的区别
  NOR FLASH NAND FLASH
结构 NORflash采用内存的随机读取技术。各单元之间是并联的,对存储单元进行统一编址,所以可以随机访问任意一个字,应用程序可直接在flash内运行,而无需先拷贝到RAM。 NANDflash数据线和地址线共用I/O线,需额外联接一些控制的输入输出
读写速度 NOR flash有更快的读取速度 NAND flash有更快的写、擦除速度
可靠性 NOR的擦写次数是10万次

NAND的擦写次数是100万次

(NAND器件的坏块是随机分布的)

成本和容量 在面积和工艺相同的情况下,NAND的容量比NOR大的多,成本更低
优缺点 无位翻转,无坏块,稳定(存储关键性的程序) 位反转,有坏块
易用性 NOR flash有专用的地址引脚来寻址,较容易和其他芯片联接,还支持本地执行 NAND flash的IO端口采用复用的数据线和地址线,必须先通过寄存器串行地进行数据存取。各厂商对信号的定义不同,增加了应用的难度
编程

NOR flash采用统一编址(有独立地址线),可随机读取每个“字”,但NOR flash不能像RAM以字节改写数据,只能按“页”写,故NOR flash不能代替RAM。擦除既可整页擦除,也可整块擦除

注意:

flash进行写操作时,只能将相应的位由1变0,而擦除才能把块内所有位由0变1。所有写入数据时,如果该页已经存在数据,必须先擦除再写。

 NAND flash共用地址线和数据线,页是读写数据的最小单元,块是擦除数据的最小单元

NOR FLASH 的特点是:

可以像内存一样的读取,但是不能直接写。

例子: 在Nor Flash中有以下指令:

mov R0,#0      --R0赋值为0

LDR  R1 ,[R0]    --读取地址R0的值到寄存器R1

STR  R1 ,[R0]    --把R1的值写入R0寄存器

当执行STR  R1 ,[R0] 不会成功,被视为无效操作。

这样子如果程序烧写在NOR FLASH 中会出现什么问题呢?

程序中含有需要更改的全局变量/静态变量(变量存储在栈中),但是如果现在bin文件烧写到NOR FLASH 上,那么意味着不能修改变量的值了,程序就会达不到我们预期的效果。所以如果想要的到想要的结果,就需要把这些全局变量/静态变量,重定位放到SDRAM(可读可写)中。


2).以S3C2440为例,设为NOR Flash 或 Nand Flash 启动时,启动地址的差别

  • .基本框架

S3C2440是一个SOC,在一块芯片上面集成了有CPU、GPIO控制器、Nand控制器、Nor控制器、以及4K的SRAM。

  • 启动过程(大多数ARM芯片从0地址启动)

1.NoR启动,NoR Flash 基地址为0(片内RAM地址为:0x40000000),CPU读出NOR读出上的第一个指令(前四个字节)执行,CPU继续读出其他指令执行(CPU可以在Nor Flash取指令直接执行)。

2.Nand启动,片内4K SRAM 基地之为0(Nor Falsh 不可访问),硬件2240把NAND前4K内容复制到片内内存SRAM中,然后CPU从0地址取出第一条指令(前四个字节)执行。



 3).不同位宽内存设备之间的连接

内存控制器地址线和片外内存设备的接法:

可以发现他们的接法好像都是不同的,他们的规律是什么?

这时需要去查看一下芯片手册。

8-bit ROM

16-bit ROM

32-bit ROM

规律:那么就是说外接芯片的位宽有变化,那地址线的接法也有变化。

大致如下简图所示:

那为什么是这样呢?需要了解数据内部的保存结构,简图如下:

假如现在执行指令:

mov  R0 ,#3    --把3的数值赋值给R0

LDRB R1, [R0]  --读取地址为3的位置读取1个字节的数据

那么CPU就会把地址3发出去,也就是:0x00000011  ,那么此时地址线 A0  =1, A1 =1 ,其他(A2-A27)为0.

  • 如果外界是8-bit ROM,因为A0接A0,A1连接A1,此时他收到的就是0x3
  • 如果外界是16-bit ROM,因为A1接A0,A2连接A1,此时他收到的就是0x1
  • 如果外界是32-bit ROM,因为A2接A0,A3连接A1,此时他收到的就是0x0

我们的任务是读取第3byte的数据:

当为8-bit ROM,那我收到的正好也是0x3这个数值,没有问题。

当为16-bit ROM,我收到的是0x1,这个数值,也就是十进制的1,读取地址编号为1的地址的值,很巧,发现我们需要第3字节的数据刚好在地址1的位置,但是这个位置有两个字节啊,怎么办?CPU不用管,它只需要发出地址就行,所以这也是内存控制器做的事,内存控制器根据收到的CPU发送的 A0 = 1 ,那么读取的就是地址编号为1,右边的那个字节(第3字节)的数据。

当为32-bit ROM,我收到的是0x0,这个数值,也就是十进制的0,读取地址编号为0的地址的值,同样,发现我们需要第3字节的数据刚好在地址0的位置,但是这个位置有四个字节啊,怎么办?CPU不用管,它只需要发出地址就行,所以这也是内存控制器做的事,内存控制器根据收到的  [A1:A0]  =  11 ,那么读取的就是地址编号为0,最右边的那个字节(第3字节)的数据。

这个过程可以使用一张图来表示,就可以简单明了。


再举个例子:

假如现在执行指令:

mov  R0 ,#4    --把4的数值赋值给R0

LDR R1, [R0]  --读取地址为43的位置读取4个字节的数据

那他的过程可以简化为下表:

所以CPU就是一个大BOSS,它只负责发出指令(地址)给内存控制器,而关于你数据是如何拆分和组装的,CPU并不关心,我只需要发出指令,然后就收数据就好了,那剩下的事情就是手下(内存控制器)做的,它负责数据的组装和拆分,最终把正确的结果返回给CPU。


2.使用u-boot体验Nor Flash的操作

例子2:读数据

烧写u-boot,到Nor Flash 上,开发版设为Nor Flash启动、

使用OpenJTAG:输入命令读取:md.b 0 读取Nor Flash 0地址的数据:

结果如下:

查看一下uboot.bin文件的数据和读取出来是一样:

说明Nor Flash可以直接读取数据。


例子2:读取ID

查看Nor Flash的数据手册,在此开发版使用的芯片型号是:MX29LV160DBTI

步骤:

①往地址 0x555写入数据0xAA

②往地址 0x2AA写入数据0x55

③往地址 0x555写入数据0x90

④读取地址0x00得到厂家ID:0xC2

⑤读取地址0x01得到设备ID:MX29LV160DT: 22C4; MX29LV160DB: 2249

注意:设备位宽连接的问题,因为NOR Flash连接的是16位的,所以连接的简图如下

因此如果CPU需要往NOR Flash的0x555的写入数据0xAA,那么需要在2440发送地址的时候需要把0x555左移一位,再发送给Nor Flash才是正确的地址。

那么此时UBOOT怎么操作呢?

如果CPU需要左移一位,才是正确的地址,也就是是说,发送的地址需要乘上2,如下:

步骤:

①往地址 0xAAA写入数据0xAA      (mw.w aaa aa)

②往地址 0x554写入数据0x55        (mw.w 554 55)

③往地址 0xAAA写入数据0x90       (mw.w aaa 90)

④读取地址0x00得到厂家ID:0xC2   (md.w 0 1)

⑤读取地址0x02得到设备ID:MX29LV160DT: 22C4; MX29LV160DB: 2249 ( md.w 2 1)

结果如下:

如何退出读ID状态?

往任意地址写入:0xF0

结果如下:


例子3:读取CFI信息

查看芯片手册的描述:

进入CFI(common flash interface)模式:

往地址0x55 写入 0x98 进入CFI模式

查询芯片容量:

从0x10 读取一个字节数据得到0x51(Q)

从0x11 读取一个字节数据得到0x51(R)

从0x12 读取一个字节数据得到0x51(Y)

从0x27 读取一个字节数据得到内存大小。

由于设备位宽不同,所以在OpenJTAG的操作是:

往地址0xAA 写入 0x98 进入CFI模式        (mw.w aa 98)

从0x20 读取一个字节数据得到0x51(Q)     (md.w 20 1)

从0x22读取一个字节数据得到0x51(R)      (md.w 22 1)

从0x24 读取一个字节数据得到0x51(Y)     (md.w 24 1)

从0x4E 读取一个字节数据得到内存大小。(md.w 4e 1)

结果如下:

退出CFI模式,往任意地址写0xf0

 


例子4:写数据

试一下在地址1M以外的地方写入数据0x1234,然后读出来

 

那如何写数据呢?需要一定的格式,如下手册所描述:

步骤:

①往地址 0x555写入数据0xAA

②往地址 0x2AA写入数据0x55

③往地址 0x555写入数据0xA0

④往指定Addr写Data

那么此时UBOOT怎么操作呢?

如果CPU需要左移一位,才是正确的地址,也就是是说,发送的地址需要乘上2,如下:

步骤:

①往地址 0xAAA写入数据0xAA      (mw.w aaa aa)

②往地址 0x554写入数据0x55        (mw.w 554 55)

③往地址 0xAAA写入数据0xA0       (mw.w aaa A0)

④往地址 0x100000 写入数据 0x1234 (mw.w 100000 1234)

⑤读取地址0x100000的数据是否写入成功 (md.w 100000 1)

结果如下:


例子5:当原来的数据不是全f的时候

解锁后能写入成功的前提,必须是这个位置的数据时全f,也就是:ffff

再次往0x100000这个地址 ,写入一个新数据,是否能成功?

那如何写呢?如果原来的数据不是全f,那么需要先对这个位置进行擦除操作 

芯片数据手册的描述如下:

擦除扇区0x100000数据的步骤步骤:

①往地址 0x555写入数据0xAA

②往地址 0x2AA写入数据0x55

③往地址 0x555写入数据0x80

④往地址 0x555写入数据0xAA

⑤往地址 0x2AA写入数据0x55

⑥往扇区0x100000写0x30

那么此时UBOOT怎么操作呢?

如果CPU需要左移一位,才是正确的地址,也就是是说,发送的地址需要乘上2,如下:

步骤:

①往地址 0xAAA写入数据0xAA      (mw.w aaa aa)

②往地址 0x554写入数据0x55        (mw.w 554 55)

③往地址 0xAAA写入数据0x80       (mw.w aaa 80)

④往地址 0xAAA写入数据0xAA      (mw.w aaa aa)

⑤往地址 0x554写入数据0x55        (mw.w 554 55)

④往地址 0x100000 写入数据 0x30 (mw.w 100000 30)

如下:

擦除成功后就可以正常的写数据了,和上面写的步骤是一样的。

 


3.编写NOR-Flash测试程序

在这个测试例程中有这几个功能如下:

  /*菜单选项
         *1.识别nor_falsh,设备和容量
         *2.读取某个地址的数据
         *3.擦除nor_flash扇区数据
         *4.往某个地址写数据
         */

1).识别nor_falsh打印出各个扇区的起始地址,读取设备ID和厂家ID以及设备容量

由数据手册可知,需要查询设备容量,厂家ID和设备ID之前,需要进入CFI模式

那么进入CFI模式的方式如下所描述:往0x55的地址写入0x98

退出CFI模式,如下所描述:往任意地址写0xF0

进入CFI模式之后,可以查询的信息如下描述:

读取设备容量:读取地址0x27可以得到设备容量

在CFI模式下,可以查询 erase region的有关信息:

其中region的描述需要参考:CFI publication 100 规范,如下:

 代码如下:

/*进入NOR_FLASH的CFI模式,读取设备信息*/
void Scan_nor_flash(void)
{
	char  qry[4]; //存储读取CFI模式下的QRY
	int  Manifacture_ID,Devide_ID;
	int n,size;
	int regions; //NOR_Flash的region个数
	int i,j,count=0;
	/*变量含义:
	 *	1.region的起始地址 2.block的个数
	 *	3.每个block的大小  4.block的起始扇区基地址
	 */
	int region_Bank_Area,blocks,block_size,block_base_addr;
	/*读取QRY字符到数组qry*/
	nor_cmd(0x55,0x98);/*进入CFI模式,往0x55写0x98*/
	qry[0] = nor_read(0x10);
	qry[1] = nor_read(0x11);
	qry[2] = nor_read(0x12);
	qry[3] = '\0';
	printf("******************msg = %s\n\r" ,qry); //打印出QRY
	nor_cmd(0,0xf0); //复位,退出CFI模式
	/*打印nor_flash的厂家ID,设备ID*/
	nor_cmd(0x555,0xaa);
	nor_cmd(0x2AA,0x55); //解锁
	nor_cmd(0x555,0x90); //读取厂家ID命令
	Manifacture_ID = nor_read(0x00); //读取厂家ID
	Devide_ID	   = nor_read(0x01); //读取设备ID
	printf("******************The Manifacture_ID is: 0x%x\r\n",Manifacture_ID);
	printf("******************The Devide_ID      is: 0x%x\r\n",Devide_ID);
	nor_cmd(0,0xf0); //复位
	/*打印设备容量*/
	nor_cmd(0x55,0x98);/*进入CFI模式,往0x55写0x98*/
	n = nor_read(0x27);
	size = 1<<n;  //表示 2^n
	printf("******************Device size:0x%x = %d bytes = %d M \r\n",size,size,size/(1024*1024));
	/*打印各个扇区的起始地址*/
	regions = nor_read(0x2c); //从0x2c的地址读出region个数
	region_Bank_Area =0x2d;
	block_base_addr=0; 
	printf("Block/Sector start Address:\r\n");
	for(i=0;i<regions;i++)
	{
		/*读出这个region总共有多少个block*/
		blocks=nor_read(region_Bank_Area)+(nor_read(region_Bank_Area+1)<<8)+1;
		/*读取每个block的大小*/
		block_size=(nor_read(region_Bank_Area+2)+(nor_read(region_Bank_Area+3)<<8))*256;
		
		printf("================Region %d==============\n\r",i+1);
		printf("number_of_blocks=%d,  block_size=0x%x=%dk\n\r",blocks,block_size,block_size/1024);
		/*打印每个block的起始地址*/
		
		for(j=0;j<blocks;j++)
		{
			
			puthex(block_base_addr);
			printf("\t");
			block_base_addr +=block_size;
			count++;
			if(count % 5 == 0)
			{
				printf("\r\n");
			}
		}
		printf("\n\r");
		/*region地址自加4读取下一个region的信息*/
		region_Bank_Area +=4; 
		
	}

	
	nor_cmd(0,0xf0); //复位,退出CFI模式
	printf("\n\r");
}

注意事项:进入CFI模式之后,退出函数时需要退出CFI模式,才能正常访问NOR_FALSH。

程序运行结果如下:

注意事项:

编译程序加上选项:-march=armv4 ,

否则像

     volatile unsigned short *p =xxx;    
    *p=data

的操作会被分成两个strb步骤(我们需要的是strh),最后导致读取CFI设备ID和厂家ID的时候出错。


2).读取nor_flash某个地址的数据

首先从键盘输入要读取的起始地址,接着输入读取的字节,为了方便起见,读取的字节数是16的整数倍。

代码如下:

unsigned short Read_nor_flash(void)
{
	//读取的基地址
	unsigned int addr;
	//定义一个无符号字符型变量p
	volatile unsigned char *p;
	int i,j;
	unsigned char c;
	unsigned short amount,hex_addr=0;
	/*保存数据*/
	unsigned char str[16];
	/*获取读取地址*/
	printf("Enter Read Address: ");
	/*根据用户输入的地址,结果给回addr
	 *可输入16进制和10进制
	 */
	addr = get_uint();
	/*把addr强制转换成unsigned char 类型,表明*p只读取一个字节的数据*/
	p = (volatile unsigned char *)addr;
	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");
	/*读取长度:64字节*/
	for(i=0;i<amount;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字节的数据,那么从键盘输入的地址就是0,读取数量就是10,结果如下:

我们读取的nor_flash上前160字节的数据,对比一下nor_flash.bin文件的数据是否一致:

可以看出来结果完全一致,说明读取nor_flash的数据时成功的。

注意:

ascii的可打印字符:ASCII字符集由95个可打印字符(0x20-0x7E)


3).擦除nor_flash扇区数据

查看数据手册中如何擦除数据:

代码如下:

void Erase_nor_flash(void)
{
	//擦除的地址
	unsigned int addr;
	/*从键盘获取删除地址*/
	printf("Enter Erase Address: ");
	addr = get_uint();
	printf("erasing ...   ");
	nor_cmd(0x555,0xaa); /*解锁*/
	nor_cmd(0x2aa,0x55);
	/*注意此处擦除的是一整个扇区,擦除的最小单位就是
	 *一个扇区的大小,此处总共有35个扇区
	 */
	nor_cmd(0x555,0x80); /*擦除扇区*/
	nor_cmd(0x555,0xaa); /*解锁*/
	nor_cmd(0x2aa,0x55);
	/*因为nor_Flash是16bit的,所以发送地址,需要右移一位*/
	nor_cmd(addr>>1,0x30);
	wait_ready(addr); /*等待擦除完成*/
	printf("Erase finished!\n\r");
}

4)往某个地址写数据

查看数据手册如何写数据:

需要注意一个点,写入的数据是16位的,也就是两个字节,需要一次性写入一个字节,所以在写入之前需要对数据进行整合。

如何判断烧写完成,以便于进行下一次的烧写工作:

代码如下:

void Write_nor_flash(void)
{
	//写数据的地址
	unsigned int addr;
	unsigned char write_data[100];
	int i=0,j=1;
	unsigned int data;
	/*从键盘获取写数据起始地址*/
	printf("Enter Write Address: ");
	addr = get_uint();
	printf("Enter Write data: ");
	gets(write_data);
	printf("writing...   ");
	/*把write_data的数组构造16bit的数据*/
		/*如果数组有数据*/
	while(write_data[i] && write_data[j]) 
	{
		//构造一个16bit的数据
		data = write_data[i]+ (write_data[j]<<8);
		/*写入数据*/
		nor_cmd(0x555,0xaa); /*解锁*/
		nor_cmd(0x2aa,0x55);
		/*注意只有当数据为f时才能直接些,否则必须先擦除*/
		nor_cmd(0x555,0xa0); /*写命令*/
		/*往地址写数据*/
		nor_cmd(addr>>1,data);
		/*等待烧写完成:读数据Q6无变化则烧写完成*/
		wait_ready(addr);
		i +=2;
		j +=2;
		addr +=2;
	}
	
	/*IF write_data[i]!=0 write_data[j]=0
	 * 说明还有数据,此时:
	 * data =  write_data[i]+ (write_data[j]<<8);
	 */
	 /*IF write_data[i]==0 write_data[j]!=0
	 * 说明已经结束了,0是结束符‘\0’
	 * data =  write_data[i]+ (0<<8); 此时data =0
	 */
	 /*IF write_data[i]==0 write_data[j]==0
	 * 说明已经结束了,0是结束符‘\0’
	 * data =  write_data[i]+ (0<<8); 此时data =0
	 */
	 /*已上三种情况都需要再写一个数据
	  *为了方便和保险起见,无论是三种的哪一种
	  *都让 data = write_data[i]; 把结束符写进去
	  */
	//构造一个16bit的数据
	data = write_data[i];
	/*写入数据*/
	nor_cmd(0x555,0xaa); /*解锁*/
	nor_cmd(0x2aa,0x55);
	/*注意只有当数据为f时才能直接些,否则必须先擦除*/
	nor_cmd(0x555,0xa0); /*写命令*/
	/*往地址写数据*/
	nor_cmd(addr>>1,data);
	/*等待烧写完成:读数据Q6无变化则烧写完成*/
	wait_ready(addr);
	printf("Write finished!\n\r");	
}

此处注意还需要加一个判断数据是否写完成的函数,当一次行写入16bit时,等待写入完成再写下一个16bit,依次类推:

函数如下:

void wait_ready(unsigned int addr)
{
	unsigned int pre_val;
	unsigned int current_val;
	pre_val    = nor_read(addr>>1); //先读取一次
	current_val= nor_read(addr>>1); //再次读取
	/*两次读取的结果不一致,说明数据还在变化,继续等待*/
	while((pre_val&(1<<6)) != (current_val&(1<<6)))
	{
		pre_val=current_val; //当前的值等于上一次的值
		/*再次读取:Q6 位*/
		current_val = nor_read(addr>>1);
	}
	

}

上面的代码已经完成了所有功能了,接下来就是写数据进行测试了。

通过上面的例子已经知道,这个NOR_flash的大小是2M,共有4个region,而且每一个region的block数量以及block的大小是不同的。如下所示:其中地址是每个block的起始地址

注意:在写数据时,只有当数据为全f时,才能写入,因为规定,数据的bit只能从1变成0,而不能从0变成1,所以当数据不为全f时,需要对擦除对应的block,而且每次擦除的最小单位是block,比如,擦除region1的block,那么一次性擦除16k的数据,擦除region2的block,一次性擦除8k的数据。

现在读取地址:0x001D0000  开始,160 byte的数据,如下:

直接写入,写入数据:hello world ! 

往地址:0x001Daaaa 的位置写入,hello yuan (也就是在这个block的末尾的位置)

现在擦除地址:0x001D0000的数据,查看数据是否还在:

这也证明了,擦除操作是针对一个扇区。


源代码

地址:https://download.csdn.net/download/qq_36243942/10940801

数据手册

地址:https://download.csdn.net/download/qq_36243942/10941195

发布了91 篇原创文章 · 获赞 247 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_36243942/article/details/86604329