第十课JZ2440裸板开发之nor flash

一、flash介绍

常用的Flash类型有Nor Flash和NAND Flash两种,区别大致如下:
在这里插入图片描述
Flash存储器件由擦除单元(也称为块)组成,当要写某个块时,需要确保这个块己经被擦除。Nor Flash的块大小范围为64kB、128kB:NAND Flash的块大小范围为8kB,64kB,擦/写一个Nor Flash块需4s,而擦/写一个NAND Flash块仅需2ms。Nor Flash的块太大,不仅增加了擦写时间,对于给定的写操作,Nor Flash也需要更多的擦除操作——特别是小文件,比如一个文件只有IkB,但是为了保存它却需要擦除人小为64kB—128kB的Nor Flash块。

Nor Flash支持XIP,即代码可以直接在Nor Flash上执行,无需复制到内存中。这是由于NorF lash的接口与RAM完全相同,可以随机访问任意地址的数据。而NAND Flash的接口仅仅包含几个I/O引脚,需要串行地访问。NAND Flash一般以512字节为单位进行读写。这使得Nor Flash适合于运行程序,而NAND Flash更适合于存储数据。容量相同的情况下,NAND Flash的体积更小,对于空间有严格要求的系统,NAND Flash可以节省更多空间。市场上Nor Flash的容量通常为IMB~4MB(也有32MB的Nor Flash),NAND Flash的容量为8MB~512MB。容量的差别也使得Nor Flash多用于存储程序,NAND Flash多用于存储数据。

对于Flash存储器件的可靠性需要考虑3点:位反转、坏块和可擦除次数。所有Flash器件都遭遇位反转的问题:由于Flash固有的电器特性,在读写数据过程中,偶然会产生一位或几位数据错误(这种概率很低),而NAND Flash出现的概率远大于Nor Flash,当位反转发生在关键的代码、数据上时,有可能导致系统崩溃。当仅仅是报告位反转,重新读取即可:如果确实发生了位反转,则必须有相应的错误检测/恢复措施。在NAND Flash上发生位反转的概率史高,推荐使用EDC/ECC进行错误检测和恢复。NAND Flash上面会有坏块随机分布在使用前需要将坏块扫描出来,确保不再使用它们,否则会使产品含有严重的故障。NAND Flash每块的可擦除次数通常在100000次左右,是Nor Flash的10倍。另外,因为NAND Flash的块大小通常是NorF lash的1/8,所以NAND Flash的寿命远远超过Nor Flash。

嵌入式Linux对Nor、NAND Flash的软件支持都很成熟。在Nor Flash上常用jffs2文件系统,而在NAND Flash常用yaffs文件系统。在更底层,有MTD驱动程序实现对它们的读、写、擦除操仵,它也实现了EDC/ECC校验。

二、uboot下操作flash

2.1 uboot下操作flash命令

2.1.1读内存指令md

md命令即memory display,内存显示,用法如下:
md [.b, .w, .l] address [# of objects]
md之后可以跟三个参数,表示读取给定内存地址处要操作多少个字节:
b:8位
w:16位
l:32位(默认)
第一个参数address表示要读取的内存地址(十六进制),第二个参数可选,表示从address开始读取几个数据(十六进制)。

2.1.2 写内存指令mw

同样,mw指令即memory write,向内存中写入数据,用法格式如下:
mw [.b, .w, .l] address value [count]
mw之后可以跟三个参数,表示读取给定内存地址处要操作多少个字节:
b:8位
w:16位
l:32位(默认)
第一个参数address表示要写入的内存地址(十六进制),value是要写入的值(十六进制),count表示从address开始读取几个数据(十六进制)。

2.2 S3C2410nor flash

JZ2440开发板上板载了一个Nor Flash,型号为MX29LV160DBTI-70G,连接在S3C2440内存控制器上的BANK0,原理图如下:
在这里插入图片描述

2.3 uboot下读取芯片信息

在这里插入图片描述
在这里插入图片描述

2.3.1读取Manifacture ID

由上图可知,我们能读取
① 向Nor Flash的0x555地址处写入数据0xAA;
② 向Nor Flash的0x2aa地址处写入数据0x55;
③ 向Nor Flash的0x555地址处写入数据0x90(进入读ID模式);
④ 从Nor Flash的0x00地址处读取数据,读取出的值就是Manifacture ID,为0xC2;
⑤退出读ID模式:向任意地址写0xF0;

因为S3C2440的A1接在Nor Flash的A0上,所以CPU发出的地址应该是左移一位之后的地址,所以uboot中应该使用如下几条命令进行测试:

mw.w aaa aa
mw.w 554 55
mw.w aaa 90
md.w 0 1

在这里插入图片描述

2.3.2 读取Nor Flash ID( Device ID)

从数据手册中可知,读取Device ID的流程如下:
① 向Nor Flash的0x555地址处写入数据0xAA;
② 向Nor Flash的0x2aa地址处写入数据0x55;
③ 向Nor Flash的0x555地址处写入数据0x90(进入读ID模式);
④ 从Nor Flash的0x02地址处读取数据,读取出的值就是Device ID,为实际ID;
⑤ 退出读ID模式:向任意地址写0xF0;

因为S3C2440的A1接在Nor Flash的A0上,所以CPU发出的地址应该是左移一位之后的地址,所以uboot中应该使用如下几条命令进行测试:

mw.w aaa aa
mw.w 554 55
mw.w aaa 90
md.w 2 1

在这里插入图片描述

2.3.3读取CFI信息

通常内核里面要识别一个 Nor Flash 有两种方法:
一种是 jedec 探测,就是在内核里面事先定义一个数组,该数组里面放有不同厂家各个芯片的一些参数,探测的时候将 flash 的 ID 和数组里面的 ID 一一比较,如果发现相同的,就使用该数组的参数。
jedec 探测的优点就是简单,缺点是如果内核要支持的 flash 种类很多,这个数组就会很庞大。内核里面用 jedec 探测一个芯片时,是先通过发命令来获取 flash 的 ID,然后和数组比较,但是 flash.c 中连 ID 都是自己通过宏配置的。

一种是 CFI(common flash interface)探测,就是直接发各种命令来读取芯片的信息,比如 ID、容量等,芯片本身就包含了电压有多大,容量有有多少等信息,我们这里采用CFI的方式。
(1)进入CFI模式
由上面第二张图可知,进入CFI模式,需要向地址55写入98

mw.w aa 98

(2)读取信息
在这里插入图片描述
首先测试一下读取“”QRY“”

md.w 20 3

在这里插入图片描述
接着读取flash容量信息:
在这里插入图片描述

md.w 4e 1

在这里插入图片描述
读出来的是十六进制0x15,所以芯片大小为2^21=2M,跟数据手册一致。

(3)读取CFI信息完毕,退出读CFI模式:向任意地址写0xF0(复位)。

mw.w 00 f0

三、编写程序

我们今天编写程序主要是实现nor flash的识别及读写跟擦除。
先创建一个nor_flash.c的文件,内容包括下面所有代码:

3.1 nor flash测试程序编写

首先先在nor_flash.c文件中编写一个测试程序,如下:

void nor_flash_test(void)
{
	char c;

	while (1)
	{
		/* 打印菜单, 供我们选择测试内容 */
		printf("[s] Scan nor flash\n\r");
		printf("[e] Erase nor flash\n\r");
		printf("[w] Write nor flash\n\r");
		printf("[r] Read nor flash\n\r");
		printf("[q] quit\n\r");
		printf("Enter selection: ");

		c = getchar();
		printf("%c\n\r", c);

		/* 测试内容:
		 * 1. 识别nor flash
		 * 2. 擦除nor flash某个扇区
		 * 3. 编写某个地址
		 * 4. 读某个地址
		 */
		switch (c)		 
		{
			case 'q':
			case 'Q':
				return;
				break;
			case 's':
			case 'S':
				do_scan_nor_flash();
				break;
			case 'e':
			case 'E':
				do_erase_nor_flash();
				break;
			case 'w':
			case 'W':
				do_write_nor_flash();
				break;
			case 'r':
			case 'R':
				do_read_nor_flash();
				break;
			default:
				break;
		}
	}
}

3.2 通用函数编写

其次我们还要写几个通用的写数据,读数据的函数。

//因为nor flash是16位,所以2440的A0不跟NOR flash链接,所以如果我们向往flash地址A写入数据或者读取该地址的数据时,我们站在2440的角度,应该给该地址向左偏移1位,这样nor flash收到的地址就是原来的地址。
void nor_write_word(unsigned int base, unsigned int offset, unsigned int val)
{
	volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
	*p = val;
}

void nor_cmd(unsigned int offset, unsigned int cmd)
{
	nor_write_word(NOR_FLASH_BASE, offset, cmd);
}

unsigned int nor_read_word(unsigned int base, unsigned int offset)
{
	volatile unsigned short *p = (volatile unsigned short *)(base + (offset << 1));
	return *p;
}

unsigned int nor_dat(unsigned int offset)
{
	return nor_read_word(NOR_FLASH_BASE, offset);
}

3.3芯片的识别

之前我们在uboot下进行了芯片信息的读取,这里我们使用代码实现函数:

void do_scan_nor_flash(void)
{
	char str[4];
	unsigned int size;
	int regions, i;
	int region_info_base;
	int block_addr, blocks, block_size, j;
	int cnt;

	int vendor, device;
	
	/* 打印厂家ID、设备ID */
	nor_cmd(0x555, 0xaa);    /* 解锁 */
	nor_cmd(0x2aa, 0x55); 
	nor_cmd(0x555, 0x90);    /* read id */
	vendor = nor_dat(0);     /*读厂家ID*/
	device = nor_dat(1);	/*读设备ID*/
	nor_cmd(0, 0xf0);        /* reset */
	
	nor_cmd(0x55, 0x98);  /* 进入cfi模式 */
	
	/*读QRY*/
	str[0] = nor_dat(0x10);
	str[1] = nor_dat(0x11);
	str[2] = nor_dat(0x12);
	str[3] = '\0';
	printf("str = %s\n\r", str);

	/* 打印容量 */
	size = 1<<(nor_dat(0x27)); //读容量,取出来左移一位,相当于2的幂
	printf("vendor id = 0x%x, device id = 0x%x, nor size = 0x%x, %dM\n\r", vendor, device, size, size/(1024*1024));

	/* 打印各个扇区的起始地址 */
	/* 名词解释:
	 *    erase block region : 里面含有1个或多个block, 它们的大小一样
	 * 一个nor flash含有1个或多个region
	 * 一个region含有1个或多个block(扇区)

	 * Erase block region information:
	 *    前2字节+1    : 表示该region有多少个block 
	 *    后2字节*256  : 表示block的大小
	 */

	regions = nor_dat(0x2c);
	region_info_base = 0x2d;
	block_addr = 0;
	printf("Block/Sector start Address:\n\r");
	cnt = 0;
	for (i = 0; i < regions; i++)
	{
		blocks = 1 + nor_dat(region_info_base) + (nor_dat(region_info_base+1)<<8);
		block_size = 256 * (nor_dat(region_info_base+2) + (nor_dat(region_info_base+3)<<8));
		region_info_base += 4;

//		printf("\n\rregion %d, blocks = %d, block_size = 0x%x, block_addr = 0x%x\n\r", i, blocks, block_size, block_addr);

		for (j = 0; j < blocks; j++)
		{
			/* 打印每个block的起始地址 */
			//printf("0x%08x ", block_addr);
			printHex(block_addr);
			putchar(' ');
			cnt++;
			block_addr += block_size;
			if (cnt % 5 == 0)
				printf("\n\r");
		}
	}
	printf("\n\r");
	/* 退出CFI模式 */
	nor_cmd(0, 0xf0);
}

3.4 擦除flash

由之前的芯片手册可知,我们erase chip的办法如下:
在这里插入图片描述
等待烧写完成 : 读数据, Q6无变化时表示结束 (参考芯片手册),

void wait_ready(unsigned int addr)
{
	unsigned int val;
	unsigned int pre;

	pre = nor_dat(addr>>1);
	val = nor_dat(addr>>1);
	while ((val & (1<<6)) != (pre & (1<<6)))
	{
		pre = val;
		val = nor_dat(addr>>1);		
	}
}

擦除:

void do_erase_nor_flash(void)
{
	unsigned int addr;

		/* 获得地址 */
	printf("Enter the address of sector to erase: ");
	addr = get_uint();

	printf("erasing ...");
	nor_cmd(0x555, 0xaa);    /* 解锁 */
	nor_cmd(0x2aa, 0x55); 
	nor_cmd(0x555, 0x80);	 /* erase sector */

	nor_cmd(0x555, 0xaa);    /* 解锁 */
	nor_cmd(0x2aa, 0x55); 
	nor_cmd(addr>>1, 0x30);	 /* 发出扇区地址 */
	wait_ready(addr);
}

3.5 写数据到flash

在这里插入图片描述
写数据代码:

void do_write_nor_flash(void)
{
	unsigned int addr;
	unsigned char str[100];
	int i, j;
		unsigned int val;

	/* 获得地址 */
	printf("Enter the address of sector to write: ");
	addr = get_uint();

	printf("Enter the string to write: ");
	gets(str);

	printf("writing ...\n\r");

	/* str[0],str[1]==>16bit 
	 * str[2],str[3]==>16bit 
	*/
	i = 0;
	j = 1;
	while (str[i] && str[j])
	{
		val = str[i] + (str[j]<<8);
	
		/* 烧写 */
		nor_cmd(0x555, 0xaa);	 /* 解锁 */
		nor_cmd(0x2aa, 0x55); 
		nor_cmd(0x555, 0xa0);	 /* program */
		nor_cmd(addr>>1, val);
		/* 等待烧写完成 : 读数据, Q6无变化时表示结束 */
		wait_ready(addr);

		i += 2;
		j += 2;
		addr += 2;
	}

	val = str[i];
	/* 烧写 */
	nor_cmd(0x555, 0xaa);	 /* 解锁 */
	nor_cmd(0x2aa, 0x55); 
	nor_cmd(0x555, 0xa0);	 /* program */
	nor_cmd(addr>>1, val);
	/* 等待烧写完成 : 读数据, Q6无变化时表示结束 */
	wait_ready(addr);
}

3.6 读flash数据

由于nor flash可以跟SDRAM一样读写,这里读数据就跟我们平常读内存一样就行

void do_read_nor_flash(void)
{
	unsigned int addr;
	volatile unsigned char *p;
	int i, j;
	unsigned char c;
	unsigned char str[16];
	
	/* 获得地址 */
	printf("Enter the address to read: ");
	addr = get_uint();

	p = (volatile unsigned char *)addr;

	printf("Data : \n\r");
	/* 长度固定为64 */
	for (i = 0; i < 4; i++)
	{
		/* 每行打印16个数据 */
		for (j = 0; j < 16; j++)
		{
			/* 先打印数值 */
			c = *p++;
			str[j] = c;
			printf("%02x ", c);
		}

		printf("   ; ");

		for (j = 0; j < 16; j++)
		{
			/* 后打印字符 */
			if (str[j] < 0x20 || str[j] > 0x7e)  /* 不可视字符 */
				putchar('.');
			else
				putchar(str[j]);
		}
		printf("\n\r");
	}
}

3.7这里我们再修改main.c

12	int main(void)
13	{
14		led_init();
15
18	
19		puts("\n\rg_A = ");
20		printHex(g_A);
21		puts("\n\r");
22
23		nor_flash_test();
24	
25		return 0;
26	}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值