您尚未登录。

楼主 #1 2018-06-04 15:07:42

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

NAND Flash 读写擦除操作

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


NAND Flash : K9F2G08U0C

- Memory Cell Array : (256M + 8M) x 8bit
- Data Register : (2K + 64) x 8bit
• Automatic Program and Erase
- Page Program : (2K + 64)Byte
- Block Erase : (128K + 4K)Byte
• Page Read Operation
- Page Size : (2K + 64)Byte

--------------------
NAND Flash 引脚说明   K9F2G08U0C.pdf P8 P22

CE 片选        低电平选中芯片        空闲为高电平-> 未选中
I/O 0~7        数据输入输出引脚    接CPU的数据线 LDATA0~7
R/B 就绪/忙碌状态 READY/BUSY OUTPUT        空闲为高电平->就绪
CLE 命令使能    高电平 I/O 0~7 上放置的是命令 空闲为低电平
ALE 地址使能    高电平高电平 I/O 0~7 上放置的是地址 空闲为低电平
WE  写使能    低电平有效,低到高跳边沿将I/O 0~7 上的数据锁存
RE  读使能    低电平有效,高到低跳变通知NAND放置数据,低到高跳变,通知NAND数据读取完毕


CLE 为高 数据线上放置的是命令
ALE 为高 数据线上放置的是地址
CLE、ALE 都为低 数据线上放置的是数据。
具体请看时序图
-------------------

根据NAND FLASH的芯片手册时序图,NAND Flash一般的操作过程:
     发出命令
     发出地址
     发出数据/读数据

          NAND FLASH                      S3C2440
发命令    选中芯片                   
          CLE设为高电平                   NFCMMD=命令值     
          在DATA0~DATA7上输出命令值
          发出一个写脉冲
           
发地址    选中芯片                        NFADDR=地址值
          ALE设为高电平
          在DATA0~DATA7上输出地址值
          发出一个写脉冲

发数据    选中芯片                        NFDATA=数据值
          ALE,CLE设为低电平
          在DATA0~DATA7上输出数据值
          发出一个写脉冲

读数据    选中芯片                        val=NFDATA
          发出读脉冲
          读DATA0~DATA7的数据
---------------------------------------------------------
s3c2440 NAND 控制器 部分寄存器

NFCONF 0x4E000000 :配置寄存器 访问时序周期
NFCONT 0x4E000004 :bit0 = 1 使能NAND 控制器 bit1 = 0  片选引脚
NFCMMD 0x4E000008 :命令寄存器    发送指令
NFADDR 0x4E00000C :地址寄存器    发送地址
NFDATA 0x4E000010 :数据寄存器     读/写数据
---------------------------------------------------------
通过u-boot体验 NAND Flash 操作

1. 读ID
                               S3C2440                 u-boot
选中                           NFCONT的bit1设为0   md.l 0x4E000004 1; mw.l 0x4E000004  1
发出命令0x90                   NFCMMD=0x90         mw.b 0x4E000008 0x90
发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00
读数据得到0xEC                 val=NFDATA          md.b 0x4E000010 1
读数据得到device code          val=NFDATA          md.b 0x4E000010 1
          0xda
退出读ID的状态                 NFCMMD=0xff         mw.b 0x4E000008 0xff
     
2. 读内容: 读0地址的数据
使用UBOOT命令:

nand dump [addr]  最少读取1页 page

使用寄存器读取
                               S3C2440                 u-boot
选中                           NFCONT的bit1设为0   md.l 0x4E000004 1; mw.l 0x4E000004  1
发出命令0x00                   NFCMMD=0x00         mw.b 0x4E000008 0x00
发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00        // 列地址 低8位
发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00        // 列地址 高8位
发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00        // 行地址 低8位
发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00        // 行地址 中间8位
发出地址0x00                   NFADDR=0x00         mw.b 0x4E00000C 0x00        // 行地址 高8位
发出命令0x30                   NFCMMD=0x30         mw.b 0x4E000008 0x30
读数据得到0x17                 val=NFDATA          md.b 0x4E000010 1
读数据得到0x00                 val=NFDATA          md.b 0x4E000010 1
读数据得到0x00                 val=NFDATA          md.b 0x4E000010 1
读数据得到0xea                 val=NFDATA          md.b 0x4E000010 1
退出读状态                     NFCMMD=0xff         mw.b 0x4E000008 0xff

3. 代码



#include "s3c2440.h"
#include "my_printf.h"

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

/* 读状态 */
void read_state(void)
{
	while (!(NFSTAT & 1));
}


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

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

/* 发命令 */
void nand_cmd(unsigned char cmd)
{
	volatile int i;
	NFCMMD = cmd;
	for(i=0; i<10; i++);
	//read_state();
}

/* 发送地址 */
void nand_addr_byte(unsigned char addr)
{
	volatile int i;
	NFADDR = addr;
	for(i=0; i<10; i++);
	//read_state();
}

/* 读数据 */
unsigned char nand_read_data(void)
{
	return	NFDATA;
}

/* 写数据 */
void nand_write_data(unsigned char val)
{
	NFDATA = val;
}

void nand_chip_id(void)
{ 
	unsigned char buf[5]={0};
	
	nand_select(); 
	nand_cmd(0x90);
	nand_addr_byte(0x00);
	
	buf[0] = nand_read_data();
	buf[1] = nand_read_data();	
	buf[2] = nand_read_data();
	buf[3] = nand_read_data();
	buf[4] = nand_read_data();	
	nand_deselect(); 	

	printf("maker   id  = 0x%x\n\r",buf[0]);
	printf("device  id  = 0x%x\n\r",buf[1]);	
	printf("3rd byte    = 0x%x\n\r",buf[2]);		
	printf("4th byte    = 0x%x\n\r",buf[3]);			
	printf("page  size  = %d kb\n\r",1  <<  (buf[3] & 0x03));	
	printf("block size  = %d kb\n\r",64 << ((buf[3] >> 4) & 0x03));	
	printf("5th byte    = 0x%x\n\r",buf[4]);

	
}


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

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

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

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


int nand_erase(unsigned int addr, unsigned int len)
{
	int page = addr / 2048;

	if (addr & (0x1FFFF))
	{
		printf("nand_erase err, addr is not block align\n\r");
		return -1;
	}
	
	if (len & (0x1FFFF))
	{
		printf("nand_erase err, len is not block align\n\r");
		return -1;
	}
	
	nand_select(); 

	while (1)
	{
		page = addr / 2048;
		
		nand_cmd(0x60);
		
		/* row/page addr */
		nand_addr_byte(page & 0xff);
		nand_addr_byte((page>>8) & 0xff);
		nand_addr_byte((page>>16) & 0xff);

		nand_cmd(0xD0);

		read_state();

		len -= (128*1024);
		if (len == 0)
			break;
		addr += (128*1024);
	}
	
	nand_deselect(); 	
	return 0;
}

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

	nand_select(); 

	while (1)
	{
		nand_cmd(0x80);

		/* 发出地址 */
		/* 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);

		/* 发出数据 */
		for (; (col < 2048) && (i < len); )
		{
			nand_write_data(buf[i++]);
		}
		nand_cmd(0x10);
		read_state();

		if (i == len)
			break;
		else
		{
			/* 开始下一个循环page */
			col = 0;
			page++;
		}
		
	}
	
	nand_deselect(); 	
}

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

	nand_read(addr, buf, 64);
	p = (volatile unsigned char *)buf;

	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 do_erase_nand_flash(void)
{
	unsigned int addr;
	
	/* 获得地址 */
	printf("Enter the address of sector to erase: ");
	addr = get_uint();

	printf("erasing ...\n\r");
	nand_erase(addr, 128*1024);
}


void do_write_nand_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");
	nand_write(addr, str, strlen(str)+1);

}


void nand_flash_test(void)
{
	char c;

	while (1)
	{
		/* 打印菜单, 供我们选择测试内容 */
		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 selection: ");

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

		/* 测试内容:
		 * 1. 识别nand flash
		 * 2. 擦除nand flash某个扇区
		 * 3. 编写某个地址
		 * 4. 读某个地址
		 */
		switch (c)		 
		{
			case 'q':
			case 'Q':
				return;
				break;
				
			case 's':
			case 'S':
				nand_chip_id();
				break;

			case 'e':
			case 'E':
				do_erase_nand_flash();
				break;

			case 'w':
			case 'W':
				do_write_nand_flash();
				break;

			case 'r':
			case 'R':
				do_read_nand_flash();
				break;
			default:
				break;
		}
	}
}

4. 设置NAND启动注意事项

NAND 启动时会把代码的前4k拷贝至内部4k RAM 运行,如果编译的代码大于4k 我们需要把NAND代码搬运至 外部SDRAM 运行。
如果重定位前一个调用函数的代码段不在4k以内,调用时不要用bl 调用。bl是 相对跳转指令,能调用正负32M的范围,但如果函数
所在的代码段地址超过4k 范围,此时代码将不在内部 4k RAM 中,bl 跳转将指向一个不存在或不正确的地址。所用我们的正确做法是移
动pc至SDRAM中去执行从NAND拷贝过去的代码。

将NAND代码搬运至 SDRAM执行,支持nor flash 和 nand flash 启动

#include "nand_flash.h"

/* 判断启动方式 */

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

/* 根据不同的启动方式,从不同的位置拷贝代码到SDRAM */
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);
	}
}


/* 清除.bss段 */
void clean_bss(void)
{
	/* 要从lds文件中获得 __bss_start, _end
	 */
	extern int _end, __bss_start;

	volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *end = (volatile unsigned int *)&_end;


	while (start <= end)
	{
		*start++ = 0;
	}
}

----------------------------------------------------------
扩展知识

ECC: Error Checking and Correction
OOB: out of band
BBT: bad block table
https://blog.csdn.net/seasonyrq/article/details/51510965
----------------------------------------------------------

离线

页脚

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

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