在本节内容中总结之前的nand flash操作,在另一篇文章中介绍了关于nor flash的一些测试,在那里简单的介绍了一下nor flash和nand flash的一些区别,这篇文章就来详细讲解一些nand flash的操作过程 使用开发板:jz2440 nand flash芯片:K9F2G08U0C 1、nand flash引脚介绍 在最开始的时候先来看一下jz2440上面的nand flash原理图,这一步好像是固定的,在每开始学习一款芯片的时候,都先要去看看它的原理图来了解最基本的操作信息,话不多少,上图 从原理图上可以看出nand flash的数据线是8位的,之前操作SDRAM的时候都是有地址线和数据线,那么这里好像没有区分地址线和数据线,这是因为在nand flash中地址线和数据线是复用的(8位)
引脚介绍 RnB:状态引脚,在读写的时候数据不可能立即达到,需要根据该引脚来判断芯片是否处于“忙状态” CLE:命令锁存,当CLE为高电平的时候表示数据线上传输的是命令,并且在CLE为下降沿的时候锁存命令 CE:片选引脚,低电平有效 ALE:地址锁存,当ALE为高电平的时候表示数据线上传输的是地址,并且在ALE为下降沿的时候锁存地址 当ALE与CLE都为低电平的时候表示数据线上传输的是一般数据 WE:写信号,低电平有效 RE:读信号,低电平有效2、操作时序 了解了nand flash的芯片引脚作用之后就可以去操作芯片了,那么在操作芯片之前还需要知道该芯片的操作时序才行,查看开发板上的nand flash数据手册,寻找关于操作时序的内容 以读ID的过程来分析一下nand flash的操作时序,其他的擦除、写操作也就是一个道理了
1、片选信号CE拉低,选中新芯片,并在整个过程中都处于低电平 2、CLE拉高,WE拉低,表明接下来的是写命令 3、数据线发出读ID命令90h 4、在WE信号上升沿锁存数据 5、CLE拉低,写命令过程结束 6、ALE拉高,WE拉低,表明接下来的是写地址 7、数据线发出地址数据,并在WE信号为上升沿的时候锁存数据 8、RE信号拉低,开始读取数据,在RE信号上升沿的时候锁存数据从读取芯片ID的过程可以看出操作nand flash的整个过程的时序还是有一点复杂的,不过不用担心,在jz2440开发板中接有nand flash控制器,那么复杂的时序就由nand flash控制器来替我们发出去,我们只需要控制nand flash控制器即可,那么,我们该如何来使用nand flash控制器呢,其实我们的需求就只有发命令、发地址、读数据,每一个动作都对应nand flash控制器的一个寄存器,所以只需要去操作对应的寄存器就行了,接下来,先总结一下后面需要使用的命令 3、操作指令
读发出0x00命令发出行列地址发出0x30命令等待就绪,并读取数据写发出0x80命令发出行列地址写数据发出0x10命令,等待就绪擦除发出0x60命令发出行地址发出0xd0命令等待就绪从上面的表格中可以看出,在操作的时候经常要发出行和列地址,这是为什么呢? 因为nand flash的结构可以看做是一张表格,既然是表格的话就有行和列的区分 我们所使用的这款nand flash芯片的结构如上图,它的每一个page的大小是2K,也就是从0到2047,后面的区域是OOB区域,用于nand flash的纠错,大小是64个字节 发送数据的时候根据上面的表来发送,先发出列地址的低8位,然后发出列地址的高8位 然后发出行地址的第八位,然后是中间八位以及最后的高八位 4、nand flash控制器初始化
nand flash最基本的时序信号 TACLS :发出ALE/CLE之后多久可以发出写信号 TWRPH0 :写信号维持的时间 TWRPH1 :释放写信号之后多久可以释放ALE/CLETACLS = tcls - twp tcls(最小12ns) - twp(最小12ns),TACLS可以为0 TWRPH0 = twp(最小12ns) TWRPH1 = tclh(最小5ns)
在开发板上配置寄存器满足上面的时序
TACLS可以配置为0,所以[13:12] = 0 HCLK(10ns) x ( TWRPH0 + 1 ) >= 12 TWRPH0 >= 1 [10:8] = 1 HCLK x ( TWRPH1 + 1 ) >= 5 TWRPH1 >= 0 [6:4] = 0 [0] : 1 使能nand flash控制器 [1] : 1 开始的时候先禁止片选 [4] : 1 使能ECC #################################################################### void nand_init(void) { #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 /*设置NAND FLASH的时序*/ NFCONF = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); /*使能NAND FLASH控制器,初始化ECC,禁止片选*/ NFCONT = (1<<4) | (1<<1) | (1<<0); }5、实现函数
使能片选、禁止片选
void nand_select(void) { /*使能片选*/ NFCONT &=~(1<<1); } void nand_deselect(void) { /*禁止片选*/ NFCONT |= (1<<1); }发送命令、地址
/* 写命令,往NFCCMD寄存器写值 */ bashvoid nand_cmd(unsigned char cmd) { volatile int i; /* 直接写入命令就行 */ NFCCMD = cmd; for(i=0; i<10; i++); } /* 写地址,往NFDATA寄存器写值 */ void nand_addr_byte(unsigned char addr) { volatile int i; /* 直接写入地址 */ NFADDR = addr; for(i=0; i<10; i++); } /* 读数据,直接返回NFDATA寄存器的值就行 */ unsigned char nand_data(void) { return NFDATA; } /*写数据,往寄存器NFDATA寄存器写值*/ void nand_w_data(unsigned char val) { NFDATA = val; } /* 等待就绪,读取状态寄存器的引脚 */ void wait_ready(void) { while (!(NFSTAT & 1)); }测试菜单
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); switch (c) { case 'q': case 'Q': return; break; case 's': case 'S': nand_chip_id(); break; case 'e': case 'E': do_erase_nand_flash(); break; case 'w': case 'W': do_write_nand_flash(); break; case 'r': case 'R': do_read_nand_flash(); break; default: break; } } }读
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len) { int i = 0; int page = addr / 2048; /* 计算行地址 */ int col = addr % 2048; /* 计算列地址 */ nand_select(); /* 使能片选 */ while (i < len) { /* 发出00h命令 */ nand_cmd(00); /* 发出地址 col addr */ nand_addr_byte(col & 0xff); /* 低8位 */ nand_addr_byte((col>>8) & 0xff); /* 高8位 */ /* row/page addr */ nand_addr_byte(page & 0xff); nand_addr_byte((page>>8) & 0xff); nand_addr_byte((page>>16) & 0xff); /* 发出30h命令 */ nand_cmd(0x30); /* 等待就绪 */ wait_ready(); /* 读数据 */ for (; (col < 2048) && (i < len); col++) { buf[i++] = nand_data(); } if (i == len) /* 根据上面的循环条件,如果i先等于len,就说明是读取的数据长度已经满足,跳出 */ break; col = 0; /* 否则,调到下一个page的第0列开始读取,直到i==len */ page++; } nand_deselect(); //禁止片选 }写
void nand_write(unsigned int addr, unsigned char *buf, unsigned int len) { int page = addr / 2048; int col = addr % 2048; int i = 0; nand_select(); while (1) { /* 发出写命令 */ nand_cmd(0x80); /* 发出地址 col addr */ nand_addr_byte(col & 0xff); nand_addr_byte((col>>8) & 0xff); /* row/page addr */ nand_addr_byte(page & 0xff); nand_addr_byte((page>>8) & 0xff); nand_addr_byte((page>>16) & 0xff); /* 发出数据 */ for (; (col < 2048) && (i < len); ) { nand_w_data(buf[i++]); } /* 发出确定命令,并等待就绪 */ nand_cmd(0x10); wait_ready(); if (i == len) break; else { /* 开始下一个循环page */ col = 0; page++; } } nand_deselect(); }擦除
int nand_erase(unsigned int addr, unsigned int len) { int page = addr / 2048; /* 擦除一块,只需要发送行地址就行 */ /* 地址对齐 */ if (addr & (0x1FFFF)) { printf("nand_erase err, addr is not block align\n\r"); return -1; } /* 长度对齐 */ if (len & (0x1FFFF)) { printf("nand_erase err, len is not block align\n\r"); return -1; } nand_select(); while (1) { page = addr / 2048; nand_cmd(0x60); /* row/page addr */ nand_addr_byte(page & 0xff); nand_addr_byte((page>>8) & 0xff); nand_addr_byte((page>>16) & 0xff); nand_cmd(0xD0); wait_ready(); len -= (128*1024); if (len == 0) break; addr += (128*1024); /* 调到下一个page */ } nand_deselect(); return 0; }6、扩展 nand flash的特性引起nand flash中有坏块的存在,那么在读的时候进行坏块的检测,如果是坏块就跳到下一个block 检测坏块
int nand_bad(unsigned int addr) { /* 读取block的第2048个字节,每一个block的检测数据存放在OOB区(2048字节处),如果这个block为坏块,则2048字节处读出的数据是0xff */ unsigned int col = 2048; unsigned int page = addr / 2048; unsigned char val; /* 1. 选中 */ nand_select(); /* 2. 发出读命令00h */ nand_cmd(0x00); /* 3. 发出地址(分5步发出) */ nand_col(col); nand_page(page); /* 4. 发出读命令30h */ nand_cmd(0x30); /* 5. 判断状态 */ wait_ready(); /* 6. 读数据 */ val = nand_data(); /* 7. 取消选中 */ nand_deselect(); if (val != 0xff) return 1; /* bad blcok */ else return 0; }读函数改进
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len) { int col = addr % 2048; /* 计算列地址 */ int i = 0; while (i < len) { if (!(addr & 0x1FFFF) && nand_bad(addr)) /* 一个block只判断一次 */ { addr += (128*1024); /* 跳过当前block */ continue; } /* 1. 选中 */ nand_select(); /* 2. 发出读命令00h */ nand_cmd(0x00); /* 3. 发出地址(分5步发出) */ nand_addr(addr); /* 4. 发出读命令30h */ nand_cmd(0x30); /* 5. 判断状态 */ wait_ready(); /* 6. 读数据 */ for (; (col < 2048) && (i < len); col++) { buf[i] = nand_data(); i++; addr++; } if (i == len) break; col = 0; } /* 7. 取消选中 */ nand_deselect(); }