【韦东山旧1期学习笔记】09.S3C2440 Nand Flash实验(二)


基于第一篇文章的介绍,我们接下来进行有关Nand Flash数据操作的实验。。

Nand寻址

        由上一篇文章可知,K9F2G08U0C Nand闪存阵列分为一系列128kB的区块(block),这些区块是 Nand器件中最小的可擦除实体。擦除一个区块就是把所有的位(bit)设置为"1"(而所有字节(byte)设置为FFh)。有必要通过编程,将已擦除 的位从"1"变为"0"。最小的编程实体是字节(byte)。一些Nor闪存能同时执行读写操作(见下图1)。虽然Nand不能同时执行读写操作,它可以采用称为"映射(shadowing)"的方法,在系统级实现这一点。这种方法在个人电脑上已经沿用多年,即将BIOS从速率较低的ROM加载到速率较高 的RAM上v。
        K9F2G08U0C Nand Flash由2048个块组成,每个块有64个页,每一个页均包含一个2048字节的数据区和64字节的空闲区,总共包含2,112字节。空闲区通常被用于ECC、耗损均衡(wear leveling)和其它软件开销功能,尽管它在物理上与其它页并没有区别。Nand器件具有8或16位接口。通过8或16位宽的双向数据总线,主数据被 连接到Nand存储器。在16位模式,指令和地址仅仅利用低8位,而高8位仅仅在数据传输周期使用。
        Nand Flash的数据是以bit的方式保存在memory cell,一般来说,一个cell中只能存储一个bit。这些cell以8个或者16个为单位,连成bit line,形成所谓的byte(x8)/word(x16),这就是Nand Device的位宽。
这些Line会再组成Page。按照这样的组织方式可以形成所谓的三类地址:

  • Block Address
  • Page Address
  • Column Address
    首先,必须清楚一点,对于Nand Flash来讲,地址和命令只能在I/O[7:0]上传递,数据宽度可以是8位或者16位,但是,对于x16的NAND Device,I/O[15:8]只用于传递数据。

NAND Flash的地址表示为:

Block Address | Page Address | Column Address

Nand Flash数据读取操作(不考虑坏块的情况)

K9F2G08U0C 读操作时序图

在这里插入图片描述
在这里插入图片描述
我们需要先发出0x00命令,之后发出5个地址周期,之后发出0x30确认命令,等待Nand设备就绪后,就可以开始按页读取数据了。这5个地址周期都是先发送字节。

实验代码

如下源码中nand_Read函数即为我们的读取nand flash数据驱动。同时,为验证nand_Read函数是否正确,我们编写了nand_Dump函数将Nand数据输出到串口显示,并使用hexdump工具进行对比校验。

nand.c

#include "s3c2440_soc.h"
#include "myprintf.h"
#include "Ctype.h"

void K9F2G08U0C_nand_init(void){
	NFCONF = (0x0 << 12) | (0x1 << 8) | (0x0 << 4) | (0x0 << 0);
	NFCONT = (0x1 << 1) | (0x1 << 0);
}

void nand_init(void){
	K9F2G08U0C_nand_init();
	printf("K9F2G08U0C init is done.\n\r");
}

static void nand_selectChip(void){
	NFCONT &= ~(1 << 1);
}

static void nand_deSelectChip(void){
	NFCONT |= (1 << 1);
}

static void nand_cmd(unsigned char cmd){
	NFCMMD = cmd;
	volatile unsigned int idx = 0;
	for(idx = 0; idx < 10; idx++){
		//do nothing,delay
	}

}

static unsigned char nand_ReadData(void){
	return NFDATA;
}

static void nand_WriteData(unsigned char data){
	NFDATA = data;
}

static void nand_addr(unsigned char addr){
	NFADDR = addr;
}

static void nand_waitReady(void) {
	//0: NAND Flash memory busy
	//1: NAND Flash memory ready to operate
	while(!(NFSTAT & 0x1));
}

void nand_ReadID(void) {
	nand_selectChip();
	nand_waitReady();
	nand_cmd(0x90);
	nand_addr(0x00);
	nand_waitReady();
	unsigned char data[5];
	unsigned int idx = 0;
	for(idx = 0; idx < 5; idx++){
		data[idx] = nand_ReadData();
	}
	//reset nand chip
	nand_cmd(0xFF);
	nand_waitReady();
	nand_deSelectChip();
	printf("Nand Chip ID: 0x%x-0x%x-0x%x-0x%x-0x%x\r\n", data[0],
			data[1], data[2], data[3], data[4]);
	unsigned int blockSize = (64 << ((data[3] >> 4) & 0x3));
	unsigned int pageSize = (1 << (data[3] & 0x3));
	printf("Nand Flash Block Size: %dKB, Page Size: %dKB\n\r", blockSize, pageSize);

}

void nand_Read(unsigned char* nandSrc, unsigned char* ramDest, unsigned int len) {
	//从指定的Nand Flash源地址src处开始读取数据,
	//存储到SDRAM的地址dest处,字节长度由len指定
#define K9F2G08U0C_PAGE_SIZE 2048
	unsigned int pageAddr = (int)nandSrc / K9F2G08U0C_PAGE_SIZE;
	//对src取模,得到列地址
	unsigned int colAddr = (int)nandSrc & (K9F2G08U0C_PAGE_SIZE - 1);
	unsigned idx = 0;
	//Nand是按页读取,按块擦除
	nand_selectChip();
	nand_waitReady();
	while(idx < len) {
		nand_cmd(0x00);
		//发送12位列地址
		nand_addr(colAddr & 0xFF);
		nand_addr((colAddr >> 8) & 0x1F);
		//发送17位行地址
		nand_addr(pageAddr & 0xFF);
		nand_addr((pageAddr >> 8) & 0xFF);
		nand_addr((pageAddr >> 16) & 0x1);
		nand_cmd(0x30);
		nand_waitReady();

		for( ;(colAddr < 2048) & (idx < len); idx++){
			ramDest[idx] = nand_ReadData();	
			colAddr++;
		}
		//当前页的数据读取完毕,开始下一页,从头列开始读取
		colAddr = 0;
		pageAddr++;
	}
	
	//reset nand chip
	nand_cmd(0xFF);
	nand_waitReady();
	nand_deSelectChip();
	return;
}

//显示从nand芯片中addrInNand地址开始的len字节的数据
#define DATA_BUFFER 8192
char nandDataBuffer[DATA_BUFFER];
void nand_Dump(unsigned char* addrInNand, unsigned int len) {
	if(len > DATA_BUFFER) {
		printf("length is too big.Nothing is done.\n\r");
		return;
	}
	nand_Read(addrInNand, nandDataBuffer, len);
	unsigned int idx = 0;
	unsigned int lineIdx = 0;
	for(idx = 0; idx < len;) {
//每一行显示的字节个数
#define BYTES_PER_LINE 16
		//打印本行对应的nand flash数据地址
		printf("%08x\t", idx);
		for(lineIdx = 0; (lineIdx < BYTES_PER_LINE) & (idx + lineIdx < len) ; lineIdx++){
			printf("%02x ", nandDataBuffer[idx + lineIdx]);
		}
		//该行的字符数不足BYTES_PER_LINE个,用空格补齐
		while(lineIdx < BYTES_PER_LINE) {
			printf("%2c ", ' ');
			lineIdx++;
		}
		printf("\t|");
		char curCh = 0;
		for(lineIdx = 0; (lineIdx < BYTES_PER_LINE) & (idx + lineIdx < len) ; lineIdx++){
			curCh = nandDataBuffer[idx + lineIdx];
			//如果是可打印字符,则打印出来
			//如果不可打印,则使用.代替
			//Ascii码的取值范围是[0,127],十进制,闭区间
			//其中,[32,126]是可打印字符,其余是不可打印字符
			if(curCh >= 32 && curCh <= 126) {
				printf("%c", nandDataBuffer[idx + lineIdx]);
			} else {
				printf(".");
			}
		}
		
		printf("|\n\r");
		idx += BYTES_PER_LINE;
	}
}

我们修改main.c文件,添加打印Nand数据功能,如下所示:

#include "myprintf.h"
#include "nand.h"
#include "uart.h"

void test_nand();

int main(void) {
	uart0_init();
	printf("%s\n\r", "Nand Flash Test.");

	test_nand();
	return 0;
}

void nand_Dump(unsigned char* addrInNand, unsigned int len);
void test_nand(){
	unsigned char op;
	unsigned int nandAddr = 0, dataLen = 0;
	nand_init();

	while(1) {
		printf("Now testing nand flash...\n\r");
		printf("----> s/S: Scan Nand Flash Chip ID.\n\r");
		printf("----> r/R: Read Data From Nand Flash.\n\r");
		printf("----> e/E: Exit the test.\n\r");
		printf("Please select one operation: ");
		op = uart0_getc();
		uart0_putc(op);
		printf("\n\r");
		switch(op){
			case 's':
			case 'S':
				nand_ReadID();
				break;
			case 'r':
			case 'R':
				printf("Please input Nand Address: ");
				scanf("%d\r", &nandAddr);
				printf("%d\n\r", nandAddr);
				printf("Please input the bytes to read: ");
				scanf("%d\r", &dataLen);
				printf("%d\n\r", dataLen);
				nand_Dump((unsigned char*)nandAddr, dataLen);
				break;
			case 'e':
			case 'E':
				printf("Test is done. Bye.\n\r");
				return;
			default:
				//do nothing.
				printf("Invalid input.Please try again.\n\r");
				break;
		}
		printf("\n\r");
	}
}

编译并烧写程序

编译程序,并将程序分别烧写至nand flash和nor flash中,如下所示:
在这里插入图片描述
在这里插入图片描述

实验结果验证

我们还是设置从Nor Flash启动,并测试nand读数据程序,观察串口,如下所示:
在这里插入图片描述

我们将串口输出的内容复制到Beyond Compare中,同时使用linxu下的hexdump工具读取nand.bin文件的内容,进行比较看是否一致,如下图所示:
在这里插入图片描述
在这里插入图片描述
经过比较发现,nand读取程序完全正确。

Nand Flash代码重定位实验

有了Nand数据读取函数,我们可以完善之前的代码重定位实验了。之前的重定位实验,由于我们还没有涉及到Nand Flash的操作,而Nand Flash不能像访问内存一样地读取数据,所以当时是先将BIN文件烧写至Nor Flash,并以Nor Flash方式启动,进行整个程序的重定位。
现在我们修改重定位代码relocate.c文件如下所示:
通过isBootFromNandFlash函数来判断是否是从Nand启动,如果是从Nand启动的,则我们先初始化nand控制器,然后使用nand_Read函数将Nand Flash上的数据拷贝至SDRAM中。


extern unsigned int _text_start;
extern unsigned int _bss_start;
extern unsigned int _bss_end;

int isBootFromNandFlash(void) {
	volatile unsigned int* ptr = (volatile unsigned int*)0; 
	//读取原始地址0处的数据
	unsigned int val = *ptr;

	*ptr = 0x0;
	if(*ptr == 0x0){
		//修改成功,表明0地址对应的是SRAM
		//则目前是Nand方式启动
		//还原之前的值,并返回真
		*ptr = val;
		return 1;
	}

	//若修改0地址处的值失败,则表明此时对应的是Nor方式启动
	return 0;
}

void copyBIN2SDRAM(void){
	volatile unsigned int* srcStart = 0x0;
	volatile unsigned int* destStart = &_text_start;
	volatile unsigned int* destEnd = &_bss_start;

	if(isBootFromNandFlash()) {
		//Nand方式启动,则需要通过驱动读取Nand Flash上的数据
		unsigned int len = (destEnd - destStart) * 4;
		//初始化nand控制器
		nand_init();
		nand_Read(0, (volatile unsigned char*)destStart, len);

	}else {
		//若是Nor方式启动,则可以直接拷贝数据
		while(destStart < destEnd){
			*destStart = *srcStart;
			destStart++;
			srcStart++;
		}
	}
}

void clearBSS(void){
	volatile unsigned int* start = &_bss_start;
	volatile unsigned int* end = &_bss_end;
	while(start < end){
		*start = 0;
		start++;
	}
}

将代码编译并烧写至Nand Flash中,并以Nand方式启动,发现串口没有任何输出。
查看反汇编文件,我们发现:

30000000 <_start>:
30000000:	e3a00453 	mov	r0, #1392508928	; 0x53000000
30000004:	e3a01000 	mov	r1, #0	; 0x0
30000008:	e5801000 	str	r1, [r0]
3000000c:	e3a00313 	mov	r0, #1275068416	; 0x4c000000
30000010:	e3e01000 	mvn	r1, #0	; 0x0
30000014:	e5801000 	str	r1, [r0]
30000018:	e59f0054 	ldr	r0, [pc, #84]	; 30000074 <halt+0x4>
3000001c:	e3a01005 	mov	r1, #5	; 0x5
30000020:	e5801000 	str	r1, [r0]
30000024:	ee110f10 	mrc	15, 0, r0, cr1, cr0, {0}
30000028:	e3800103 	orr	r0, r0, #-1073741824	; 0xc0000000
3000002c:	ee010f10 	mcr	15, 0, r0, cr1, cr0, {0}
30000030:	e59f0040 	ldr	r0, [pc, #64]	; 30000078 <halt+0x8>
30000034:	e59f1040 	ldr	r1, [pc, #64]	; 3000007c <halt+0xc>
30000038:	e5801000 	str	r1, [r0]
3000003c:	e3a00000 	mov	r0, #0	; 0x0
30000040:	e5901000 	ldr	r1, [r0]
30000044:	e5800000 	str	r0, [r0]
30000048:	e5902000 	ldr	r2, [r0]
3000004c:	e1510002 	cmp	r1, r2
30000050:	e59fd028 	ldr	sp, [pc, #40]	; 30000080 <halt+0x10>
30000054:	13a0da01 	movne	sp, #4096	; 0x1000
30000058:	15801000 	strne	r1, [r0]
3000005c:	eb000770 	bl	30001e24 <sdram_init>
30000060:	eb0007ed 	bl	3000201c <copyBIN2SDRAM>
30000064:	eb000818 	bl	300020cc <clearBSS>
30000068:	e59fe014 	ldr	lr, [pc, #20]	; 30000084 <halt+0x14>
3000006c:	e59ff014 	ldr	pc, [pc, #20]	; 30000088 <halt+0x18>

虽然是位置无关的相对跳转,但是由于SRAM只有4KB,偏移量对应0x1000,故需要将初始化代码全部置于0x30001000之前的位置才可以。将sdram.o relocate.o nand.o三个文件放置在前面,修改Makefile如下所示:

CROSS_COMPILE_PREFIX=arm-linux-
CFLAGS=-g
TARGET=nand

$(TARGET).bin : CRT0.o uart.o sdram.o relocate.o $(TARGET).o myprintf.o Ctype.o  util.o  main.o
	@#$(CROSS_COMPILE_PREFIX)ld -Ttext 0x0000000 -Tdata 0x30000000 -o $(TARGET).elf $^
	$(CROSS_COMPILE_PREFIX)ld -T $(TARGET).lds -o $(TARGET).elf $^
	$(CROSS_COMPILE_PREFIX)objcopy -O binary -S $(TARGET).elf $@
	$(CROSS_COMPILE_PREFIX)objdump -D $(TARGET).elf > $(TARGET).dis

CRT0.o : CRT0.S
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -c -o $@ $^
myprintf.o : myprintf.c stdarg.h
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< 
uart.o : uart.c stdarg.h s3c2440_soc.h
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $< 
$(TARGET).o : $(TARGET).c 
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
relocate.o : relocate.c 
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
main.o : main.c
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
Ctype.o : Ctype.c Ctype.h
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
util.o : util.c util.h
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<
sdram.o : sdram.c sdram.h
	$(CROSS_COMPILE_PREFIX)gcc $(CFLAGS) -o $@ -c $<

clean:
	rm -f *.elf *.dis *.bin *.o

编译烧写至Nand Flash中,并将开发板选为Nand方式启动,上电观察,发现程序正常启动,并可以正常操作。
至此,我们的Nand读数据代码初步完成。下一篇我们将研究Nand Flash的擦除、写入和坏块检测等内容。

发布了26 篇原创文章 · 获赞 2 · 访问量 1070

猜你喜欢

转载自blog.csdn.net/BakerTheGreat/article/details/104339310