基于第一篇文章的介绍,我们接下来进行有关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的擦除、写入和坏块检测等内容。