韦东山嵌入式Linux学习----015 Nand Flash(3)

Nand Flash编程实现读地址信息

/*
 *硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
 *软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
 *参考资料:开发版原理图,S3C2440A datasheet,K9F2G08U0C datasheet
*/


一、基础知识

1、Nand Flash 存储结构介绍

  在《韦东山嵌入式Linux学习----015 Nand Flash(1)》这一篇中简单的从电路图与芯片手册介绍了一下K9F2G08U0C这块芯片,下面讲具体分析下它的存储结构。

在这里插入图片描述

分析:
  ①、Nand Flash 的数据:以bit的方式保存在memory cell
  ②、Nand Device的位宽:一般来说,一个cell 中只能存储一个bit。这些cell 以8个或者16个为单位,连成bit line,形成所谓的byte(x8)/word(x16)图中可以看出芯片采用的时x8。
  ③、Nand Flash的Page:多条的Line会再组成Page图中可以看出1 Page = 2048 Bytes + 64 Bytes。
  ④、Nand Flash的Block:多个的page形成一个Block。图中可以看出1 Block = 64 Page。
  ⑤、可以看到,每条公式以(xK + yK)的形式表示,yK代表的是OOB块的大小

2、行列地址的提取

  NAND flash以页为单位读写数据,而以块为单位擦除数据。按照这样的组织方式可以形成所谓的三类地址:列地址 :Column Address页地址 :Page Address块地址 :Block Address
  在具体操作中,我们发送地址时是一次性输入操作的内存的地址,如0x100000。但是由于我们使用的地址和命令只能在I/O[7:0]上传递,数据宽度是8位,所在传输写读地址时,程序设计需要单独提取行列地址
  结合上图,可以得出如下提取方式:
  col = (addr & 0xfff); //取列数
  page = (addr >> 12); //取页数

1st Cycle Column Address nand_addr(col & 0xff);
2st Cycle Column Address nand_addr((col >> 8) & 0x0f);
3st Cycle Row Address nand_addr((page & 0xff));
4st Cycle Row Address nand_addr((page >> 8) & 0xff);
5st Cycle Row Address nand_addr(page>> 16);

3、Nand Flash OOB块介绍

①、为什么会有OOB区?
  由于NAND Flash的工艺不能保证NAND的Memory Array在其生命周期中保持性能的可靠,因此,在NAND的生产中及使用过程中会产生坏块。当编程/擦除这个块时,会造成Page Program和Block Erase操作时的错误,相应地反映到OOB区的相应位
②、OOB区多大,在哪里?
  如上图所示,每个Page都会有OOB区,大小为64 Btyes,位置在每个Page的2048位
③、注意
   当读写擦除Nand Flash时,需要避免破坏OOB区。

4、位反转

   CPU在寻址的时候看不到OOB区,所以当CPU读到2048个数据时,此时访问的是下一个Page的第0个数据
  这里也可以看出,OOB的存在,是为了解决Nand Flash的缺陷。对于CPU而已,只关心数据,不需要看OOB

二、实现功能

  功能:通过串口输入指定的地址,并读取该地址的信息

三、编程原理

  1 Page = (2K(数据)+ 64 (OOB区))Bytes

  查K9F2G08U0C 芯片手册,读操作的时序图如下

在这里插入图片描述

1、编写读地址函数

/* Nand Flash 读地址 
 * addr:地址 buf:存储信息 len:读取的信息长度
 */
void nand_read(unsigned int addr,unsigned char *buf ,int len)
{
	int i =0 ;
	int col = (addr & 0xfff);	//取列数
	int page = (addr >> 12);	//取页数
	
	//	1、片选
	nand_select();
	while(i<len)
	{
		//	2、发00命令
		nand_cmd(0x00);

		//	3、发五个周期的地址
		/* 横坐标 */
		nand_addr(col & 0xff);
		nand_addr((col >> 8)    & 0x0f);
		
		/* 纵坐标 */
		nand_addr((page & 0xff));
		nand_addr((page >> 8) & 0xff);
		nand_addr(page>> 16);

		//	4、发30命令
		nand_cmd(0x30);

		//	等待时间
		nand_wait_ready();
		
		//	5、读数据
		/* 保证列数不超过2047,i<len */
		for(; (col < 2048) && (i < len);col++)
		{
			buf[i++] = nand_data();
		}
		/* 完成读取 */	
		if (i == len)
			break;
		/* 未完成读取,位反转 */
		col = 0;
		page++;		
	}	
	//	6、取消片选
	nand_disselect();
}

2、进一步封装读地址函数

/* 读某个地址 
 * 1、获取地址
 * 2、打印地址信息
 */
void do_read_nand_flash(void)
{
	int				i,j;
	unsigned int	addr;
	unsigned char	c;
	unsigned char	buf[64];
	unsigned char	str[16];
	volatile unsigned char *tmpbuf;

	//	1、获取地址
	printf("Enter the address to read: ");
	addr = get_uint();

	//	2、执行读函数
	nand_read(addr,buf,64);
	tmpbuf = (volatile unsigned char *)buf;

	// 3、打印地址信息
	/* 固定长度64字节
	 * 按照市面上的标准以16个字节为一行,前为数值,后为字符
	 * 分辨字符是否可视
	 */
	 printf("Data: \n\r");
	 for(i=0;i<4;i++)
	 {
	 	for(j=0;j<16;j++)
	 	{
	 		c = *tmpbuf++;
			str[j] = c;
			printf("%02x ",c);			
		}
		printf("   ;");
		
		for(j=0;j<16;j++)
	 	{
	 		if((str[j] < 0x20) || (str[j] > 0x7E))
				printf(".");
			else
				printf("%c",str[j]);
		}
		printf("\n\r");
	 }
}

3、实现从Nand Flash启动

  由于开发版上电时,Nand Flash中的4K会自动被复制到4K的RAM中,之前我们已经实现了SDRAM重定位,将这4K代码完全复制到SDRAM中。同时,为了确保4K代码包含nand_flash.c文件,需要修改Makefile文件与sdram.c文件。

3.1 修改sdram_init.c文件

/* 判断NOR启动还是NAND启动 
 * 先保存0地址中的数据,后往0地址里面写入0x123
 * 判断0地址中是否成功被修改
 * --修改成功为NOR启动,恢复0地址信息
 * --修改失败为NAND启动
 */
int IsBootNorFlash(void)
{
	volatile unsigned int *p = (volatile unsigned int *)0;
	unsigned int val = *p;
	
	*p = 0x123;
	if(*p == 0x123)
	{
		*p = val;
		return 0;
	}
	else
	{
		return 1;
	}
}

/*功能:复制整个text,rodata,data段到SDRAM中*/
void copy_to_sdram(void)
{
	extern int start,__bss_start;    //建立外部变量,方便获取lds文件中的量
	int len;
	
	volatile unsigned int *text =(volatile unsigned int *) &start;		//text指向程序开头地址
	volatile unsigned int *end = (volatile unsigned int *)&__bss_start;	//end指向bss段开头的地址
	volatile unsigned int *src = (volatile unsigned int *)0;			//src指向0地址.即FLASH的开头地址
	len = ((int)&__bss_start) - ((int)&start);

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

3.2 修改Makefile文件

确保复制的前4K代码中包含nand_flash.c文件,把start.o sdram_init.o nand_flash.o 放前面

all: start.o sdram_init.o nand_flash.o uart.o main.o uart.o led.o  exception.o eint.o timer.o my_printf.o string_utils.o lib1funcs.o

四、编程文件

1、修改后的Makefile文件

all: start.o sdram_init.o nand_flash.o uart.o main.o uart.o led.o  exception.o eint.o timer.o my_printf.o string_utils.o lib1funcs.o
	arm-linux-ld -T sdram.lds $^ -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis
	
%.o : %.c
	arm-linux-gcc -march=armv4 -c -o $@ $<

%.o : %.S
	arm-linux-gcc -march=armv4 -c -o $@ $<	

2、修改后的nand_init.c文件

#include "s3c2440_soc.h"
#include "my_printf.h"

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

}

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

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

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

/* 写地址 */
void nand_addr(unsigned char addr)
{
	volatile int i;
	NFADDR = addr;
	for(i=0; i<10; i++);
}

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

void nand_wait_ready(void)
{
	while (!(NFSTAT & 1));
}

void nand_write_data(unsigned char val)
{
	NFDATA = val;
}

/* Nand Flash 读ID */
void do_scan_nand_flash(void)
{
	unsigned char buf[5];
	
	//	1、片选信号
	nand_select();

	//	2、写读ID命令
	nand_cmd(0x90);

	//	3、写地址
	nand_addr(0x00);

	//	4、读5次数据
	buf[0]  = nand_data();	//厂商ID
	buf[1]  = nand_data();	//设备ID
	buf[2]  = nand_data();	//3rd
	buf[3]  = nand_data();	//4th
	buf[4]  = nand_data();	//5th

	//	5、打印数据
	/* 打印5次读取的数据 */
	printf("Maker  Code :0x%x\n\r",buf[0]);
	printf("Device Code :0x%x\n\r",buf[1]);
	printf("3rd Cycle   :0x%x\n\r",buf[2]);
	printf("4rd Cycle   :0x%x\n\r",buf[3]);
	printf("5rd Cycle   :0x%x\n\r",buf[4]);

	//	6、禁止片选
	NFCONT |= (1<<1);
	
	/* 通过提取,打印页大小和块大小 */
	printf("Page Size   :%d KB\n\r",1 << (buf[3]&0x03));
	printf("Block Size  :%d KB\n\r",64 << ((buf[3]>>4)&0x03));
}

/* Nand Flash 读地址 */
void nand_read(unsigned int addr,unsigned char *buf ,int len)
{
	int i =0 ;
	int col = (addr & 0xfff);	//取列数
	int page = (addr >> 12);	//取页数
	
	//	1、片选
	nand_select();
	while(i<len)
	{
		//	2、发00命令
		nand_cmd(0x00);

		//	3、发五个周期的地址
		/* 横坐标 */
		nand_addr(col & 0xff);
		nand_addr((col >> 8)    & 0x0f);
		
		/* 纵坐标 */
		nand_addr((page & 0xff));
		nand_addr((page >> 8) & 0xff);
		nand_addr(page>> 16);

		//	4、发30命令
		nand_cmd(0x30);

		//	等待时间
		nand_wait_ready();
		
		//	5、读数据
		/* 保证列数不超过2047,i<len */
		for(; (col < 2048) && (i < len);col++)
		{
			buf[i++] = nand_data();
		}
		if (i == len)
			break;

		col = 0;
		page++;
		
	}
	
	//	6、取消片选
	nand_disselect();
}

/* 读某个地址 
 * 1、获取地址
 * 2、打印地址信息
 */
void do_read_nand_flash(void)
{
	int				i,j;	
	unsigned int	addr;	
	unsigned char	c;		
	unsigned char	buf[64];
	unsigned char	str[16];
	volatile unsigned char *tmpbuf;

	//	1、获取地址
	printf("Enter the address to read: ");
	addr = get_uint();

	//	2、执行读函数
	nand_read(addr,buf,64);
	tmpbuf = (volatile unsigned char *)buf;

	// 3、打印地址信息
	/* 固定长度64字节
	 * 按照市面上的标准以16个字节为一行,前为数值,后为字符
	 * 分辨字符是否可视
	 */
	 printf("Data: \n\r");
	 for(i=0;i<4;i++)
	 {
	 	for(j=0;j<16;j++)
	 	{
	 		c = *tmpbuf++;
			str[j] = c;
			printf("%02x ",c);	
		}
		printf("   ;");
		
		for(j=0;j<16;j++)
	 	{
	 		if((str[j] < 0x20) || (str[j] > 0x7E))
				printf(".");
			else
				printf("%c",str[j]);
		}
		printf("\n\r");
	 }

}

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':
				do_scan_nand_flash();
				break;

			case 'e':
			case 'E':
				break;

			case 'w':
			case 'W':
				break;

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

3、main.c文件

#include "s3c2440_soc.h"
#include "uart.h"
#include "sdram_init.h"
#include "nand_flash.h"

char g_Char = 'A';
char g_Char2 = 'a';
char i ='0';

int main(void)
{
	unsigned char c;
	int flag;

	//interrupt_init();
	key_eint_int();
//	timer0_init();
	uart0_init();

	//sdram_init();
	
	nand_init();
	nand_flash_test();
	
	puts("uart0 init success!\n\r''");
	putchar(i);
	
	while(1)
	{
		putchar(g_Char); 
		g_Char++;
		delay(1000000);
		putchar(g_Char2); 
		g_Char2++;
		delay(1000000);
	}

	return 0;
}

五、运行结果

在这里插入图片描述

通过两个图的对比可知,代码成功烧写到了Nand Flash,并进行了重定位,读地址功能实现成功。

发布了40 篇原创文章 · 获赞 29 · 访问量 3613

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/105147921
今日推荐