您尚未登录。

楼主 #1 2018-06-03 16:43:43

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

NOR Flash 原理及读写擦除操作

《单片机小白转嵌入式Linux学习记录,基于S3C2440----目录》


NOR Flash 原理及硬件操作

NOR Flash 是内存类接口,可以像读内存一样读,但不能像内存一样写数据。

写数据需要发送对应的解锁指令才能进行操作,写的时候必须保证当前地址是0xff,每次写都是以16位数据为一次操作进行。

如果要写的地址不为空,需要擦除,擦除是以块为单位进行的操作

相同区域内的块大小相同,NOR flash 内有不同的区域。具体要看芯片手册 或 CFI 接口读取。


--------------------------------
uboot 体验 NOR Flash的读写擦除

1.先烧写uboot到NOR Flash , 并设置为NOR 启动

uboot启动时按空格进入uboot,然后输入q 进入命令模式

md.b 0 40                从0地址开始读取64字节数据
mw.w 0 1234                往0地址写入0x1234            0地址为NOR Flash的地址
md.b 0 40                从0地址开始读取64字节数据

md.b 30000000 40        从0x30000000地址开始读取64字节数据
mw.w 30000000 1234        往0x30000000地址写入0x1234    0x30000000 地址为 SDRAM的地址
md.b 30000000 40        从0x30000000地址开始读取64字节数据

对比发现,NOR可以像内存一样“读”,但不可以像内存一样“写”入。

-------------------------------
2. 读取NOR Flash ID:
参考资料:MX29LV160DBTI-70G(NOR FLASH).pdf

NOR Flash 指令操作方式:

解锁->发送操作指令->进行相关操作->退出

芯片手册 P20
Addr    Data    word
0x555    0xAA    //解锁
0x2AA    0x55

0x555    0x90    //读ID命令

X00                //从X00读取厂商ID
X01                //从X01读取设备ID

XXX        0xF0    //任意地址写入0xF0 复位退出 解锁模式

注意事项:
a. 操作过程中解锁,读ID 这些操作的Addr 对应的是NOR Flash的看到的地址 由于CPU是16bit访问,CPU地址线与NOR Flash的地址线相错1位,所以我们要左移一位。
b. 对扇区的擦除,烧写,对应的Addr是内存地址(CPU看到的地址),所以发出的Addr不变。

所以我们正确的读ID方式是:

往地址AAAH写AAH                      mw.w aaa aa    0x555<<1 = 0xAAA 下同
往地址554写55H                       mw.w 554 55
往地址AAAH写90H                      mw.w aaa 90
读0地址得到厂家ID: C2H               md.w 0 1
读2地址得到设备ID: 22DAH或225BH      md.w 2 1
退出读ID状态:                        mw.w 0 f0

3. NOR有两种规范, jedec, cfi(common flash interface)
参考资料:CFI publication 100
https://wenku.baidu.com/view/cd1c1e22482fb4daa58d4b42.html

CFI 命令查看芯片手册 P27

进入CFI模式    往AAH写入98H            mw.w AA 98
读数据:        读20H得到0051           md.w 20 1
               读22H得到0052           md.w 22 1
               读24H得到0059           md.w 24 1
               读4EH得到0015           md.w 4E 1    2^21=2MB
               读58H得到0004           md.w 58 1    说明有4个区域 Number of erase regions within device 说明:每个区域内的BANK块大小相等
               //区域1
               读5AH得到0000           md.w 5A 1    //获取区域1 的块个数 0x0000+1=1 BANk    低位在低地址(读出的16bit数据低8位有效)
               读5CH得到0000           md.w 5C 1
               
               读5EH得到0040           md.w 5E 1    //每个BANk的容量 0x0040    =    64*256 = 16k
               读60H得到0000           md.w 60 1
               
               ....
               //区域4
               读72H得到001E           md.w 72 1    //获取区域4 的块个数 0x001E+1=31 BANk    低位在低地址
               读74H得到0000           md.w 74 1
               
               读76H得到0000           md.w 76 1    //每个BANk的容量 0x0100    =    256*256 = 64k 低位在低地址(读出的16bit数据低8位有效)
               读78H得到0001           md.w 78 1
               
               退出CFI模式             mw.w 0 f0

4. 写数据
写数据操作步骤:解锁->发出写数据指令->写数据->复位
写入的数据必须是以16bit;且起始地址必须是偶数16bit对齐,否则重启。

往地址AAAH写AAH               mw.w aaa aa        解锁
往地址554H写55H               mw.w 554 55

往地址AAAH写A0H               mw.w aaa a0        写数据指令

往地址0x100000写1234h         mw.w 100000 1234    写数据    写数据 16bit对齐 当一个地址的数据不是0xFF 时需要重新擦除才能再写

5. 数据擦除

擦除操作步骤:解锁->发出擦除指令->解锁->发出扇区地址->启动擦除

mw.w aaa aa
mw.w 554 55

mw.w aaa 80

mw.w aaa aa
mw.w 554 55

mw.w 100000 30

------------------------------------------------
C 程序编写

写之前需要修改Makefile文件,添加编译选项 -march=armv4 ,否则对NOR的一些操作指令会被拆分成两条,导致访问出错。
%.o : %.c
    arm-linux-gcc -march=armv4 -c -o $@ $<
%.o : %.S
    arm-linux-gcc -march=armv4 -c -o $@ $<
擦除块时,发出的地址只要在某一个块的范围内就可以。

对于nor flash 启动,  由于中断向量表是存在于NOR flash 的起始地址部分, 而对nor flash 进行解锁操作寄存器时,cpu 操作对应的 nor flash 地址段是访问不到的,导致出错! 所以在对nor flash 进行解锁操作编程时,应当关闭所有中断。


#include "norflash.h"
#include "my_printf.h"
#include "string_utils.h"


#define NOR_FLASH_BASE  0  /* jz2440, nor-->cs0, base addr = 0 */

/* Nor 初始化 */
void norflash_init(void)
{
	BANKCON0 = 0x0500;	//Tacc	0b101  80ns>70ns	访问时钟
}

/* 
 * 操作 nor flash 内部寄存器地址对应的是 nor flash 芯片上对应的地址线 bit0 - bit15 而不是 CPU的 地址线 
 * 而我们只能通过CPU的地址线来访问,由于物理接线是 CPU -> bit1  == nor flash -> bit0 详看原理图
 * 所以我们需要  (offset << 1) 这是访问的才是 nor flash 内部 寄存器
 *
 * 当我们要读写擦除数据时,对应的是CPU的地址线,不用左移,由于我们调用的是一个字函数,所以CPU地址需要右移一位,抵消子函数中的左移
 */
 
 
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;
}

/* offset是基于NOR的角度看到 */
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);
}


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


/* 进入NOR FLASH的CFI模式
 * 读取各类信息
 */
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);
	device = nor_dat(1);
	nor_cmd(0, 0xf0);        /* reset */
	
	nor_cmd(0x55, 0x98);  /* 进入cfi模式 */

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

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

	printf("erasing ...\n\r");
	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);
}

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

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

最近编辑记录 xinxiaoci (2018-06-03 16:52:33)

离线

#2 2018-06-03 17:14:39

晕哥
管理员
所在地: 微信 whycan_cn
注册时间: 2017-09-06
已发帖子: 9,223
积分: 9197

Re: NOR Flash 原理及读写擦除操作

这种并行NOR FLASH已经是过去式了,新的SOC基本已经绝迹了,现在都是 spi/qspi flash(nor, nand)
比如w25q128/256, mx25l128/256等.





离线

楼主 #3 2018-06-04 05:57:19

xinxiaoci
会员
注册时间: 2018-04-18
已发帖子: 71
积分: 71

Re: NOR Flash 原理及读写擦除操作

晕哥 说:

这种并行NOR FLASH已经是过去式了,新的SOC基本已经绝迹了,现在都是 spi/qspi flash(nor, nand)
比如w25q128/256, mx25l128/256等.

嗯嗯,谢谢晕哥提醒,以后知道该侧重哪些地方了

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn