Nand Flash基本操作

Nand Flash操作原理

在这里插入图片描述

					NAND FLASH原理图

由于NAND FLASH是一个存储芯片,那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A"。问1. 原理图上NAND FLASH和S3C2440之间只有数据线,怎么传输地址?
答1.在DATA0~DATA7上既传输数据,又传输地址当ALE为高电平时传输的是地址,
那么在数据线上是不是只传输数据和只传输地址呢?
我们参考NAND FLASH的芯片手册可以知道,对NAND FLASH的操作还需要发出命令,下面有个NAND FLASH的命令表格
在这里插入图片描述
问2. 从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令怎么传入命令?
答2.在DATA0~DATA7上既传输数据,又传输地址,也传输命令:
当ALE为高电平时传输的是地址。
当CLE为高电平时传输的是命令。
当ALE和CLE都为低电平时传输的是数据。
问3. 数据线既接到NAND FLASH,也接到NOR FLASH,还接到SDRAM、DM9000等等
那么怎么避免干扰?
答3. 这些设备,要访问之必须"选中",没有选中的芯片不会工作,相当于没接一样。

问4. 假设烧写NAND FLASH,把命令、地址、数据发给它之后,NAND FLASH肯定不可能瞬间完成烧写的,怎么判断烧写完成?
答4. 通过状态引脚RnB来判断:它为高电平表示就绪,它为低电平表示正忙

问5. 怎么操作NAND FLASH呢?
答5. 根据NAND FLASH的芯片手册,一般的过程是:
1、发出命令
2、发出地址
3、发出数据/读数据
综上所述,对于存储芯片的编程可以总结如下:
1、初始化(主要为时序和控制寄存器的设置);
2、识别(产生在生成芯片时,都有一个独一无二的编号,即ID,识别过程可以判断初始化是否成功,芯片是否正常等);
3、读、写操作(需要查看对应芯片手册如何读、写);
4、擦除操作。

Nand Flash的内部结构与缺陷

在这里插入图片描述

NAND FLASH内部结构图,从图中可以可以知道,一个page含有2k 字节的页数据,和64字节的oob区。

为什么有OOB区(out of block)?
答:对于Nand Flash,每一个页,对应一个空闲区域(OOB),这个区域是基于Nand Flash的硬件特性,数据在读写的时候容易出错,为了保证数据的正确性,就产生了这样一个检测和纠错的区域,用来放置数据的校验值。OOB的读写操作,一般都是随着页的操作一起完成,也就是在读写页的时候,对应的OOB就产生了,那么OOB有什么用途呢?
1). 标记是否存在坏块
2). 存储ECC校验
3). 存储一些与文件系统相关的信息,对于ramfs/jffs2文件系统映像文件中没有OOB的内容,需要根据OOB的标记略过坏块,然后将一页的数据写入,然后计算这一页数据的ECC校验码,然后将它写入到OOB区;而对于yaffs文件系统,因为本身包含有OOB区的数据(里面纪录有坏块标记,ECC校验码,其他信息),所以首先需要检查坏块,如果是,则跳过,然后写入数据,最后写入OOB数据。
比如:在写页数据时,会生成EEC,写入OOB;在读页数据时,会重新算出ECC,然后和以前的ECC进行比较。若相等 ,则没有出现位反转;若不相等,则出现了位反转,然后会根据特殊的算法找出出错的位,然后进行修正。

我们该如何表述OOB的内容,举个例子,下图的2048个数据表示的是哪一个字节?
在这里插入图片描述
答:是Page1的第0个字节。CPU使用某个地址访问数据的时候,是在页数据空间来寻址的,根本就看不到oob区。
我们知道NAND FLASH 和 NOR FLASH相比有个缺点,NAND FLASH读或写一页数据的时候,可能会发生位反转,里面可能有一位是错误的,为了解决这个问题,引入oob区,
它写页数据的时候,把数据写进页数据的同时会生成一个校验码,把这个校验码写进oob区里面,当读数据的时候,读出1页数据,读取1数据里面有可能有某一位发生错误,它继续读出原来的校验码,使用oob区里面的校验码,来修正页数据里面的数据。从这里我们可以得出一个结论,oob区的存在是为了解决NAND FLASH的缺陷而存在的。
CPU: 只关心数据,不需要看到oob区的校验码(把数据读出来,然后进行校验再把正确的数据返回,就可以了)。CPU想使用某个addr来访问数据的时候,addr是在页数据区间来寻址的,addr根本不会在oob区里面寻址。

Nand Flash的数据读取

对于JZ2440,Nand Flash的大小只有4K。如果,烧写到Nand Flash的代码大于4k,则会导致烧写失败。如何解决这个问题,方法之一是我们可以把Nand Flash的代码重定位到SDRAM中。即需要先读出Nand Flash的数据,然后把数据逐个写入到SDRAM中去。

首先,如果我们要使用SDRAM,首先要完成SDRAM的初始化,代码如下

.text
.global _start
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
	/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]

	/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]

	/* 设置CPU工作于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0
	
    /* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */
	/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
	 *  m = MDIV+8 = 92+8=100
	 *  p = PDIV+2 = 1+2 = 3
	 *  s = SDIV = 1
	 *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]

	/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
	 * 然后CPU工作于新的频率FCLK
	 */
	 //初始化sdram
	bl sdram_init
	/* 重定位text, rodata, data段整个程序 */
	bl copy2sdram
	/* 清除BSS段 */
	bl clean_bss
void sdram_init(void)
{
	BWSCON = 0x22000000;

	BANKCON6 = 0x18001;
	BANKCON7 = 0x18001;

	REFRESH  = 0x8404f5;

	BANKSIZE = 0xb1;

	MRSRB6   = 0x20;
	MRSRB7   = 0x20;
}

代码重定位:

扫描二维码关注公众号,回复: 9737219 查看本文章
int isBootFromNorFlash(void)
{
	volatile unsigned int *p = (volatile unsigned int *)0;
	unsigned int val = *p;

	*p = 0x12345678;
	if (*p == 0x12345678)
	{
		/* 写成功, 对应nand启动 */
		*p = val;
		return 0;
	}
	else
	{
		return 1;
	}
}

void copy2sdram(void)
{
	/* 要从lds文件中获得 __code_start, __bss_start
	 * 然后从0地址把数据复制到__code_start
	 */

	extern int __code_start, __bss_start;

	volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
	volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *src = (volatile unsigned int *)0;
	int len;

	len = ((int)&__bss_start) - ((int)&__code_start);

	if (isBootFromNorFlash())
	{
		while (dest < end)
		{
			*dest++ = *src++;
		}
	}
	else
	{
		nand_init();
		nand_read(src, dest, len);
	}
}

可以根据性质判断此时为Nor启是Nand启动。即Nor可以像内存一样读但是不能像内存一样写;Nand可以像内存一样对,也可以像内存一样写。因此,可以根据能否写数据,判断此时的启动方式。

接下来,是把Nand中的数据重定位到Sdram中

void nand_init(void)
{
#define  TACLS   0
#define  TWRPH0  1
#define  TWRPH1  0
	/*设置NAND FLASH的时序*/
	NFCONF = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);
	/*使能NAND FLASH控制器,初始化ECC,禁止片选*/
	NFCONT = (1<<4) | (1<<1) | (1<<0);
}

void nand_select(void)
{
	/*使能片选*/
	NFCONT &=~(1<<1);
}

void nand_deselect(void)
{
	/*禁止片选*/
	NFCONT |= (1<<1);
}

void nand_cmd(unsigned char cmd)
{
	volatile int i;
	NFCCMD = cmd;
	for(i=0; i<10; i++);
}

void nand_addr_byte(unsigned char addr)
{
	volatile int i;
	NFADDR = addr;
	for(i=0; i<10; i++);
}

unsigned char nand_data(void)
{
	return	NFDATA;
}

void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
	int i = 0;
	int page = addr / 2048;
	int col  = addr & (2048 - 1);
	
	nand_select(); 

	while (i < len)
	{
		/* 发出00h命令 */
		nand_cmd(00);

		/* 发出地址 */
		/* col addr */
		nand_addr_byte(col & 0xff);
		nand_addr_byte((col>>8) & 0xff);

		/* row/page addr */
		nand_addr_byte(page & 0xff);
		nand_addr_byte((page>>8) & 0xff);
		nand_addr_byte((page>>16) & 0xff);

		/* 发出30h命令 */
		nand_cmd(0x30);

		/* 等待就绪 */
		wait_ready();

		/* 读数据 */
		for (; (col < 2048) && (i < len); col++)
		{
			buf[i++] = nand_data();			
		}
		if (i == len)
			break;

		col = 0;
		page++;
	}
	
	nand_deselect(); 	
}

相应的时序图
在这里插入图片描述
在这里插入图片描述

发布了29 篇原创文章 · 获赞 1 · 访问量 549

猜你喜欢

转载自blog.csdn.net/qq_45173769/article/details/103744649