一、概述
1、NandFlash
NAND结构能提供极高的单元密度,可以达到高存储密度,比如能达到256M,并且写入和擦除的速度也很快。应用NAND的困难在于flash的管理需要特殊的系统接口。
2、NandFlash和NorFlash对比
Nor | Nand | |
容量 | 1MB~32MB | 16M~512MB |
XIP | Yes | No |
擦除 | 非常慢(5S) | 快(3ms) |
写 | 慢 | 快 |
读 | 快 | 快 |
可靠性 | 比较高,位反转的比例小于NandFlash的10% | 比较低,位反转比较常见,必须有校验措施,比如TNR必须有坏块管理措施 |
可擦除次数 | 10000~100000 | 100000~1000000 |
生命周期 | 低于NandFlash的10% | 是NorFlash的10倍以上 |
接口 | 与RAM接口相同 | I/O接口 |
访问方法 | 随机访问 | 顺序访问 |
易用性 | 容易 | 复杂 |
主要用途 | 常用于保存代码和关键数据 | 用于保存数据 |
价格 | 贵 | 便宜 |
二、NandFlash常见引脚
1、输入输出(I/O0~I/O7)
数据输入/输出
2、芯片使能(Chip Enable,CE#)
如果没有检测到CE信号,那么NAND器件就保持待机模式,不对任何控制信号作出响应
3、写使能(Write Enable, WE#)
WE#负责将数据、地址或指令写入NAND之中
4.读使能(Read Enable, RE#)
允许输出数据缓冲器。
5.指令锁存使能(Command Latch Enable, CLE)
当CLE为高时,在WE#信号的上升沿,指令被锁存到NAND指令寄存器中
6.地址锁存使能(Address Latch Enable, ALE)
当ALE为高时,在W6.地址锁存使能(Address Latch Enable, ALE)E#信号的上升沿,地址被锁存到NAND地址寄存器中
7.就绪/忙(Ready/Busy, R/B#)
如果NAND器件忙,R/B#信号将变低。该信号是漏极开路,需要采用上拉电阻
三、NandFlash的物理结构
以下内容皆以K9F2G08U0B为例进行说明。
1、结构框图
2、存储结构
1 Page = (2K+64)Bytes
1 Block = 64 Pages
= (128K + 4K) Byte
1 Device= 2048 Blocks
= 128K Pages
= (256M + 8M)Byte
= 2112 Mbits
ECC
Linux系统中,一般叫做OOB(Out Of Band)。NandFlash在操作过程中容易出错,为了保证数据的正确性,需要有检测和纠错机制,此机制被叫做EDC(Error Detection Code)/ECC(Error Code Correction, 或者 Error Checking and Correcting),因此设计了多余的区域,用于放置数据的校验值。
3、地址序列
A0~A11 页内寻址 不仅可以寻址到有效数据,还能寻址到校验区
A12~A28 页寻址
其中:
A12~A17 块内页寻址
A18~A28 块寻址
四、NandFlash相对于RAM使用的特殊性
|
RAM |
NandFlash |
读取/写入的叫法 |
读取/写入 |
读取/编程(Program)① |
读取/写入的最小单位 |
字节 |
Page/页 |
擦除(Erase)操作的最小单位 |
字节 |
Block/块② |
擦除操作的含义 |
将数据删除/全部写入0 |
将整个块都擦除成全是1,也就是里面的数据都是0xFF ③ |
对于写操作 |
直接写即可 |
在写数据之前,要先擦除,然后再写 |
注:
①之所以将写操作叫做编程,是因为,flash 和之前的EPROM,EEPROM继承发展而来,而之前的EEPROM(Electrically Erasable Programmable Read-Only Memory),往里面写入数据,就叫做编程Program,之所以这么称呼,是因为其对数据的写入,是需要用电去擦除/写入的,就叫做编程。
②对于目前常见的页大小是2K/4K的Nand Flash,其块的大小有128KB/256KB/512KB等。而对于Nor Flash,常见的块大小有64K/32K等。
③在写数据之前,要先擦除,内部就都变成0xFF了,然后才能写入数据,也就是将对应位由1变成0。
五、实验代码
1、擦除操作
/* *********************擦除函数******************************* 参数说明: block 要擦除的块 返回参数: 0 擦除失败 1 擦除成功 */ unsigned char EraseBlock(unsigned int block) { unsigned int blockpage = 0; blockpage = block << 6; /* 选中芯片 */ nand_select_chip(); /* 发出WRITE0命令 */ write_cmd(0x60); /* Write Address */ NFADDR = blockpage & 0xff; /* Row Address A12~A19 */ NFADDR = (blockpage >> 8) & 0xff; /* Row Address A20~A27 */ NFADDR = (blockpage >> 16) & 0x01; /* Row Address A28 */ write_cmd(0xD0); wait_idle(); write_cmd(0x70); wait_idle(); if(read_data()&0x1) // Page write error { /* 取消片选信号 */ nand_deselect_chip(); return 0; } else { /* 取消片选信号 */ nand_deselect_chip(); return 1; } }
2、页读操作
/* *********************页读函数******************************* 参数说明: buf 存放读取数据的目的地址 addr 从nandflash中读取数据的源地址 */ static unsigned char ReadPage(unsigned char * buf,unsigned int addr) { int j; if (addr & NAND_BLOCK_MASK) { return 0; /* 地址或长度不对齐 */ } /* 选中芯片 */ nand_select_chip(); /* 发出READ0命令 */ write_cmd(0); /* Write Address */ write_addr(addr); write_cmd(0x30); wait_idle(); for(j=0;j < NAND_SECTOR_SIZE; j++) { *buf = read_data(); buf++; /*i每经过一个大循环就增加NAND_SECTOR_SIZE*/ } /* 取消片选信号 */ nand_deselect_chip(); return 1; }
3、页写操作
/* *********************页写函数******************************* 参数说明: buf 源地址 addr 写入nandflash的目的地址 */ static unsigned char WritePage(unsigned char * buf,unsigned int addr) { unsigned int i = 0; if (addr & NAND_BLOCK_MASK) { return 0; /* 地址或长度不对齐 */ } /* 选中芯片 */ nand_select_chip(); /* 发出WRITE0命令 */ write_cmd(0x80); /* Write Address */ write_addr(addr); /* Write Data */ for(i=0;i<2048;i++) { write_data(*buf); buf++; } write_cmd(0x10); wait_idle(); write_cmd(0x70); wait_idle(); if(read_data()&0x1) // Page write error { /* 取消片选信号 */ nand_deselect_chip(); return 0; } else { /* 取消片选信号 */ nand_deselect_chip(); return 1; } }
4、打印页内容(调试用)
/* *********************打印出指定地址的一个页的内容******************** */ void nand_printf() { unsigned int i; unsigned char block=0, page=0; unsigned char * downPt; downPt=(unsigned char *)_NONCACHE_STARTADDRESS; printf("\n\rK9F2G08 NAND Page Read.\n"); printf("\nBlock # to read: "); block = getc()-48; printf("%d\n\r", block); printf("\nPage # to read: "); page = getc()-48; printf("%d\n\r", page); if(ReadPage(downPt,((block<<6)+page)<<11)==0) { printf("\nRead error.\n"); } else { printf("\nRead OK.\n"); } // Print data. printf("Read data(%d-block,%d-page)\n", block, page); for(i=0; i<2048; i++) { if((i%16)==0) printf("\n\r%4x: ", i); printf("%02x ", *downPt++); } printf("\n"); }
5、发送地址函数
static void write_addr(unsigned int addr) { int i; unsigned int col, page; col = addr & NAND_BLOCK_MASK; page = addr / NAND_SECTOR_SIZE; NFADDR = col & 0xff; /* Column Address A0~A7 */ for(i=0; i<10; i++); NFADDR = (col >> 8) & 0x0f; /* Column Address A8~A11 */ for(i=0; i<10; i++); NFADDR = page & 0xff; /* Row Address A12~A19 */ for(i=0; i<10; i++); NFADDR = (page >> 8) & 0xff; /* Row Address A20~A27 */ for(i=0; i<10; i++); NFADDR = (page >> 16) & 0x01; /* Row Address A28 */ for(i=0; i<10; i++); }
着重强调一下,“for(i=0; i<10; i++); ”延时代码绝不能省略。笔者发现,当S3C2440的主频在12MHz时,如果没有延时代码,NandFlash发送地址还能正常工作;一旦主频在400MHz,没有延时代码,地址就不能正常发送。
附:实现NandFlash程序下载的代码(nand.zip)