Nand Flash 原理及硬件操作(1)
NAND Flash是一个存储芯片。
那么这样的操作很合理:“读地址A的数据,把数据B写到地址A”。
jz2440 NAND Flash 原理图:
问1:原理图上Nand Flash 和 s3c2440之间只有数据线,怎么传输地址?
答1:在DATA7~DATA0上既传输数据,又传输地址。
当ALE为高电平时传输的是地址。
当ALE为低电平时传输的是数据。
问2:从Nand Flash芯片数据手册可知,要操作Nand Flash需要先发出命令,如何分辨是命令还是数据呢?
答2:在DATA7~DATA0上既传输数据,又传输地址,又传输命令。
当ALE为高电平时传输的是地址。
当CLE为高电平时传输的是命令。
当ALE和CLE都为低电平时传输的是数据。
问3:数据线既接到Nand Flash,也接到Nor Flash,还接到SDRAM,DM9000等等,那么怎么避免干扰呢?
答3:这些设备要访问之前,必须先“选中”,没有“选中”的芯片不会工作,相当于没接一样。
问4:假设烧写Nand Flash,把命令、地址、数据发送给它之后,Nand Flash如何判断烧写完成?
答4:通过判断引脚RnB: 高电平表示就绪,低电平表示正忙状态。
问5:怎么操作Nand Flash呢?
答5:根据Nand Flash的芯片手册,一般的过程是:
发出命令,发出地址,发出数据/读数据。
操作 | Nand Flash | jz2440 |
发出命令 |
1.CE 低电平 2.CLE 设为高电平 3.DATA7~DATA0上写入命令值 4.发出一个写脉冲WE |
把命令值写到寄存器:NFCMMD 例如:NFCMMD = 命令值 |
发出地址 |
1.CE 低电平 2.ALE 设为高电平 3.DATA7~DATA0上写入地址 4.发出一个写脉冲WE |
把地址的值写到寄存器:NFADDR 例子: NFADDR = 地址 |
发出数据 |
1.CE 低电平 2.ALE和CLE 设为高电平 3.DATA7~DATA0上写入数据 4.发出一个写脉冲WE |
把数据的值写到寄存器:NFDATA 例子:NFDATA = 数据值 |
读数据 |
1.CE 低电平 2.发出一个读脉冲RE 3.读取DATA7~DATA0上的数据
|
读取寄存器:NFDATA 例子: 数据 = NFDATA |
用u-boot来体验Nand Flash的操作:
1.实验一:读ID
操作 | jz2400 | u-boot |
选中 | NFCONT=0 | md.l 0x4E000004 1;mw.w 0x4E000004 1 |
发出命令0x90 | NFCMMD=0x90 | mw.b 0x4E000008 0x90 |
发出地址0x00 | NFADDR=0x00 | mw.b 0x4E00000c 0x00 |
读数据得到0xec | value=NFDATA | md.b 0x4E000010 1 |
读数据得到Device Code | value=NFDATA | md.b 0x4E000010 1 |
退出读ID状态 | NFCMMD = 0xff | mw.b 0x4E000008 0xff |
mw.w 0x4E000004 1是将bit[1]=0吗???明显不是吧!
那是为什么呢?仔细查看一下手册才知道:
ec即为Maker Code。
2.实验二:读内容:读0地址的数据
使用u-boot命令:
nand dump 0
操作 | jz2440 | u-boot |
选中 | NFCONT=0 | md.l 0x4E000004 1;mw.w 0x4E000004 1 |
发出命令0x00 | NFCMMD=0x00 | mw.b 0x4E000008 0x00 |
发出地址0x00 | NFADDR=0x00 | mw.b 0x4E00000c 0x00 |
发出地址0x00 | NFADDR=0x00 | mw.b 0x4E00000c 0x00 |
发出地址0x00 | NFADDR=0x00 | mw.b 0x4E00000c 0x00 |
发出地址0x00 | NFADDR=0x00 | mw.b 0x4E00000c 0x00 |
发出地址0x00 | NFADDR=0x00 | mw.b 0x4E00000c 0x00 |
发出命令0x30 | NFCMMD=0x30 | mw.b 0x4E000008 0x30 |
读数据得到0x17 | value=NFDATA | md.b 0x4E000010 1 |
读数据得到0x00 | value=NFDATA | md.b 0x4E000010 1 |
读数据得到0x00 | value=NFDATA | md.b 0x4E000010 1 |
读数据得到0xea | value=NFDATA | md.b 0x4E000010 1 |
... | ||
退出读数据状态 | NFCMMD = 0xff | mw.b 0x4E000008 0xff |
Nand_Flash的编程:
程序框架:
1 void nand_flash_test(void) 2 { 3 char c; 4 5 /*打印菜单,供我们选择测试内容*/ 6 myprintf("\n\r"); 7 myprintf("[s] Scan Nand Flash\n\r"); 8 myprintf("[e] Erase Nand Flash\n\r"); 9 myprintf("[w] Write Nand Flash\n\r"); 10 myprintf("[r] Read Nand Flash\n\r"); 11 myprintf("[q] Quit\n\r"); 12 myprintf("Enter selection:"); 13 14 c = getchar(); 15 myprintf("%c\n\r",c); 16 17 /*测试内容: 18 *1.识别nor_falsh 19 *2.擦出nor_flash某扇区 20 *3.编写某个地址 21 *4.读某个地址 22 */ 23 switch(c) 24 { 25 case 's': 26 case 'S': 27 nand_flash_read_id(); 28 break; 29 30 case 'e': 31 case 'E': 32 do_erase_nand_flash(); 33 break; 34 35 case 'w': 36 case 'W': 37 do_write_nand_flash(); 38 break; 39 40 case 'r': 41 case 'R': 42 do_read_nand_flash(); 43 break; 44 45 case 'q': 46 case 'Q': 47 return; 48 break; 49 50 default: 51 break; 52 53 } 54 }
1.Nand_Flash的初始化函数:
1 void nand_flash_init(void) 2 { 3 /*设置NNAD Flash 的时序,HCKL=100MHz*/ 4 NFCONF = ((0<<12)|(1<<8)|(0<<4)); 5 6 /*使能NAND Flash 控制器、禁止片选使能、初始化ECC*/ 7 NFCONT = ((1<<0)|(1<<1)|(1<<4)); 8 } 9 10 void nand_flash_selected(void) 11 { 12 /*使能片选*/ 13 NFCONT &= ~(1<<1); 14 } 15 16 void nand_flash_deselected(void) 17 { 18 /*禁止片选使能*/ 19 NFCONT |= (1<<1); 20 }
2.由于Nand_Flash需要发出命令和发出地址才能进行读/写/擦除操作,所以我们需要单独地实现发出命令函数、发出地址函数。
1 void nand_flash_cmd(unsigned char cmd ) 2 { 3 volatile int i; 4 NFCMD = cmd; 5 for(i=0; i<10; i++); 6 } 7 8 void nand_flash_addr_byte(unsigned char addr) 9 { 10 volatile int i; 11 NFADDR = addr; 12 for(i=0; i<10; i++); 13 }
3.由于对Nand_Flash读/写需要通过寄存器实现,所以我们还需要单独地实现读/写函数。
1 unsigned char nand_flash_r_data(void) 2 { 3 return NFDATA; 4 } 5 6 void nand_flash_w_data(unsigned char val) 7 { 8 NFDATA = val; 9 }
4.nand_flash_read_id函数:
Page Size = 1<<(buf[3] & 0x03) //等价于1x2^n
Block Size = 64<<(((buf[3]>>4) & 0x03)) //等价于64x2^n
1 void nand_flash_read_id(void) 2 { 3 int i; 4 unsigned char buf[5]={0}; 5 6 nand_flash_selected(); 7 nand_flash_cmd(0x90); 8 nand_flash_addr_byte(0x00); 9 10 for(i=0;i<5;i++) 11 { 12 buf[i]=nand_flash_r_data(); //读取数据 13 } 14 15 myprintf("Maker id= 0x%x\n\r",buf[0]); 16 myprintf("Device id= 0x%x\n\r",buf[1]); 17 myprintf("3rd byte= 0x%x\n\r",buf[2]); 18 myprintf("4th byte= 0x%x\n\r",buf[3]); 19 myprintf("page size = %d KB\n\r",1<<(buf[3]&0x03)); 20 myprintf("block size = %d KB\n\r",64<<((buf[3]>>4)&0x03)); 21 myprintf("5th byte= 0x%x\n\r",buf[4]); 22 23 nand_flash_deselected(); 24 }
5.do_erase_nand_flash函数:
(1)实现do_erase_nand_flash函数之前,我们需要先实现擦除函数:
注意:Nand_Flash的是以Block为单位进行擦除的!!!
由于Nand_Flash是8位寻址方式,所以64M的Nand_Flash寻址范围为:64*2048=(131072-1)Byte = 0x1ffff
1 int nand_flash_erase(unsigned int addr,unsigned int len) 2 { 3 int row = addr/2048; //以Block为单位进行擦除 4 5 if(addr & (0x1ffff)) //擦除的地址不能超过Nand_Flash的寻址范围 6 { 7 myprintf("Nand Flash Erase Err, Addr Is Not Block Align:\n\r"); 8 return -1; 9 } 10 if(len & (0x1ffff)) //擦出的长度也不能超过Nand_Flash的最大长度 11 { 12 myprintf("Nand Flash Erase Err, Len Is Not Block Align:\n\r"); 13 return -1; 14 } 15 16 nand_flash_selected( ); 17 18 while(1) 19 { 20 row = addr/2048; //擦除的Block 21 22 nand_flash_cmd(0x60); 23 24 /*发出row地址*/ 25 nand_flash_addr_byte(row & 0xff); 26 nand_flash_addr_byte((row >>8) & 0xff); 27 nand_flash_addr_byte((row >>16) & 0xff); 28 29 nand_flash_cmd(0xd0); 30 31 wait_ready(); 32 33 len -= (128*1024); //1Block = 128*1024 Byte,不考虑OOB 34 if(len == 0) 35 break; 36 addr += 128*1024; //擦除下一个Block 37 } 38 39 nand_flash_deselected(); 40 41 return 0; 42 }
(2)do_erase_nand_flash函数:
1 void do_erase_nand_flash(void ) 2 { 3 unsigned int addr; 4 5 /*获得地址*/ 6 myprintf("Enter the address of sector to erase: "); 7 addr = get_uint(); 8 9 myprintf("Erase ...\n\r"); 10 nand_flash_erase(addr,128*1024); 11 }
6.do_read_nand_flash函数:
(1)实现do_read_nand_flash函数之前,我们还需要实现nand_flash_read函数:
首先要明白Nand_Flash是以页(Page)为最小单位进行读/写,以块(Block)为最小单位进行擦除,也就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本Page的最后一个 Byte为止(可以包括OOB)。
1Block = 64 Pages
1Page = (2K+64)Bytes
Row Address - 行地址可以简单的认为是页号(Page)。
Column Address - 列地址相当于在一个页内的偏移地址,有了页号和偏移地址便能正确的访问到每个存储单元。
Address=2048*Row_Address + Column_Address
1 void nand_flash_read(unsigned int addr,unsigned char *buf,unsigned int len) 2 { 3 int i = 0; 4 int row = addr/2048; 5 int column = addr & (2048-1); //等价于addr%2048,很重要的结论哦! 6 7 nand_flash_selected( ); //片选选中 8 9 while(i<len) 10 { 11 /*发出00h命令*/ 12 nand_flash_cmd(0x00); 13 14 /*发出column地址*/ 15 nand_flash_addr_byte(column & 0xff); 16 nand_flash_addr_byte((column >>8) & 0xff); 17 18 /*发出row地址*/ 19 nand_flash_addr_byte(row & 0xff); 20 nand_flash_addr_byte((row >>8) & 0xff); 21 nand_flash_addr_byte((row >>16) & 0xff); 22 23 /*发出30h命令*/ 24 nand_flash_cmd(0x30); 25 26 /*等待就绪*/ 27 wait_ready(); 28 29 /*读数据*/ 30 for(;(column<2048) && (i < len);column++) 31 { 32 buf[i++] = nand_flash_r_data(); 33 } 34 if(i==len) 35 { 36 break; 37 } 38 column = 0; 39 row++; //读取下一页 40 41 } 42 43 nand_flash_deselected(); //取消选中 44 }
(2)do_read_nand_flash函数:
1 void do_read_nand_flash(void ) 2 { 3 unsigned int addr; 4 volatile unsigned char *p; 5 int i,j; 6 unsigned char c; 7 unsigned char str[16]; 8 unsigned char buf[64]; 9 10 11 /*获得地址*/ 12 myprintf("Enter the address to read: "); 13 addr = get_uint(); 14 15 nand_flash_read(addr,buf,64); //读取长度为64 Byte 16 17 p = (volatile unsigned char *)buf; 18 19 myprintf("Data :\n\r"); 20 21 /*长度固定为64Bytes*/ 22 for(i=0; i<4; i++) 23 { 24 /*每行打印16个字符数据*/ 25 for(j=0; j<16;j++) 26 { 27 /*先打印数值*/ 28 c = *p++; 29 str[j] = c; 30 myprintf("%02x",c); 31 myprintf(" "); 32 } 33 34 myprintf(" ;"); 35 for(j=0; j<16;j++) 36 { 37 /*后打印不可显示字符*/ 38 if(str[j] < 0x20 || str[j] > 0x7e) 39 { 40 putchar('.'); 41 } 42 else 43 { 44 putchar(str[j]); 45 } 46 } 47 myprintf("\n\r"); 48 } 49 }
7.do_write_nand_flash函数:
(1)实现do_write_nand_flash函数之前,我们还需要实现nand_flash_write函数:
1 void nand_flash_write(unsigned int addr,unsigned char *buf,unsigned int len) 2 { 3 int i = 0; 4 int row = addr/2048; 5 int column = addr & (2048-1); 6 7 nand_flash_selected( ); 8 while(1) 9 { 10 nand_flash_cmd(0x80); 11 12 /*发出column地址*/ 13 nand_flash_addr_byte(column & 0xff); 14 nand_flash_addr_byte((column >>8) & 0xff); 15 16 /*发出row地址*/ 17 nand_flash_addr_byte(row & 0xff); 18 nand_flash_addr_byte((row >>8) & 0xff); 19 nand_flash_addr_byte((row >>16) & 0xff); 20 21 /*发出数据*/ 22 for(;column<2048 && (i<len);) 23 { 24 nand_flash_w_data(buf[i++]); 25 26 } 27 28 nand_flash_cmd(0x10); 29 30 wait_ready(); 31 32 if(i == len) 33 { 34 break; 35 } 36 column = 0; 37 row++; 38 } 39 40 nand_flash_deselected(); 41 }
(2)do_write_nand_flash函数:
void do_write_nand_flash(void ) { unsigned int addr; unsigned char str[100]; int i,j; unsigned int val; /*获得地址*/ myprintf("Enter the address of sector to write: "); addr = get_uint(); myprintf("Enter the string to write: "); gets(str); myprintf("Writing ...\n\r"); nand_flash_write(addr,str,strlen(str)+1); myprintf("Writing finished!"); }
8.读/写状态函数:
1 static void wait_ready(void) 2 { 3 while(!(NFSTAT & 1)); 4 }
在jz2440开发板中,如果程序的大小超过4K的话,是无法从Nand_Flash启动的!原因就不赘述了
解决方案:
1.代码重定位
利用连接脚本将程序重定位到SDRAM中运行,前提是执行重定位功能的代码大小不能超过4K!
Ps:小生不才,自己编写的执行重定位功能的代码超过了4K,所以一直。。。
Nand_Flash启动的条件:
1.代码大小小于4K;
2.具有重定位功能的代码大小小于4K;
3.编译时将具有重定位功能的源文件放在前面先编译;
之前的代码重定位代码:
1 /* 2 *reload all codes to SDRAM by C codes 3 *we must know from where copy to where 4 */ 5 void copy_to_sdram(void) 6 { 7 8 extern int code_start,bss_start; 9 10 volatile unsigned int *des = (volatile unsigned int *)&code_start; 11 volatile unsigned int *src = (volatile unsigned int *)0; 12 volatile unsigned int *end = (volatile unsigned int *)&bss_start; 13 14 while(des < end) 15 { 16 *des++ = *src++; 17 } 18 } 19 20 21 /* 22 *clean .bss segment 23 *we must know from where clean where 24 */ 25 void clean_bss_seg(void) 26 { 27 extern int bss_start,bss_end; 28 29 volatile unsigned int *start = (volatile unsigned int *)&bss_start; 30 volatile unsigned int *end = (volatile unsigned int *)&bss_end; 31 32 while(start <= end) 33 { 34 35 *start++=0; 36 } 37 }
改进的重定位代码:
1 int isBootFromNorFlash(void) 2 { 3 volatile unsigned int *p = (volatile unsigned int *)0; 4 unsigned int value = *p; 5 6 *p = 0x12345678; 7 if(*p == 0x12345678) 8 { 9 /*写成功,对应nand启动*/ 10 *p = value; 11 return 0; 12 } 13 else 14 { 15 return 1; 16 } 17 } 18 19 /* 20 *reload all codes to SDRAM by C codes 21 *we must know from where copy to where 22 */ 23 void copy_to_sdram(void) 24 { 25 extern int code_start,bss_start; 26 27 volatile unsigned int *des =(volatile unsigned int *)&code_start; 28 volatile unsigned int *end =(volatile unsigned int *)&bss_start; 29 volatile unsigned int *src =(volatile unsigned int *)0; 30 int len; 31 len = ((int)&bss_start) - ((int)&code_start); 32 33 if(isBootFromNorFlash()) 34 { 35 while(des<end) 36 { 37 *des++ = *src++; 38 } 39 40 } 41 else 42 { 43 /*需要使用到Nand Flash,先初始化Nand Flash*/ 44 nand_flash_init(); 45 /*调用nand flash读取数据函数 46 *从 src 复制到 des ,总共复制len字节,也就是重定位的代码 47 */ 48 nand_flash_read(src,des,len); 49 } 50 } 51 52 /* 53 *clean .bss segment 54 *we must know from where clean where 55 */ 56 void clean_bss_seg(void) 57 { 58 extern int bss_start,bss_end; 59 60 volatile unsigned int *start = (volatile unsigned int *)&bss_start; 61 volatile unsigned int *end = (volatile unsigned int *)&bss_end; 62 63 while(start <= end) 64 { 65 66 *start++=0; 67 } 68 }