nand Flash
Posted 今天天气眞好
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nand Flash相关的知识,希望对你有一定的参考价值。
1.NAND_FLASH操作原理
首先看到nand flash的电路图:
可以看到nand flash只有地址线,同时注意到nand flash是一个存储芯片,那么就可以执行"读地址A的数据,把数据B写到地址A"这样的操作。
下面有几个问题:
问1. 原理图上NAND FLASH和S3C2440之间只有数据线,怎么传输地址?
答:在DATA0~DATA7上既传输数据,又传输地址,当ALE
为高电平
时传输的是地址
问2. 从NAND FLASH芯片手册可知,要操作NAND FLASH需要先发出命令,怎么传入命令?
答:在DATA0~DATA7上既传输数据
,又传输地址
,也传输命令
当ALE为高电平
时传输的是地址
,
当CLE为高电平
时传输的是命令
当ALE和CLE都为低电平
时传输的是数据
问3. 数据线既接到NAND FLASH,也接到NOR FLASH
,还接到SDRAM
、DM9000
等等,那么怎么避免干扰?
答:这些设备,要访问之必须"选中
",没有选中的芯片不会工作,相当于没接一样
问4. 假设烧写NAND FLASH,把命令、地址、数据发给它之后,NAND FLASH肯定不可能瞬间完成烧写的,怎么判断烧写完成?
答: 通过状态引脚RnB
来判断:它为高电平表示就绪
,它为低电平表示正忙
问5. 怎么操作NAND FLASH呢?
答: 根据NAND FLASH的芯片手册,一般的过程是:
发出命令
发出地址
发出数据
/读数据
对于我们s3c2440来说,内部集成了一个NAND FLASH控制器
,NAND FLASH控制器,帮我们简化了对NAND FLASH的操作,下面来分析一下不使用NAND FLASH控制器和使用NAND FLASH控制器对外设NAND FLASH的操作
发命令:
发地址:
发数据:
读数据:
用UBOOT来体验NAND FLASH的操作:
1.读ID
看到下面读ID的时序图:
对应的操作如下:
S3C2440 u-boot
选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1
发出命令0x90 NFCMMD=0x90 mw.b 0x4E000008 0x90
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
读数据得到0xEC val=NFDATA md.b 0x4E000010 1
读数据得到device code val=NFDATA md.b 0x4E000010 1
0xda
退出读ID的状态 NFCMMD=0xff mw.b 0x4E000008 0xff
2. 读内容: 读0地址的数据
下图是读操作时序图
对于存储为256M的NAND FLASH,需要28条地址线,来表示这个地址值,根据原理图可以,只用8根地址线,所以需要4个周期的地址,为了兼容更大容量的NAND FLASH,要发出5个周期的地址
我们可以先使用uboot的命令:nand dump 0
来查看0地址的内容
具体操作如下:
nand dump 0
Page 00000000 dump:
17 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5
S3C2440 u-boot
选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 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 val=NFDATA md.b 0x4E000010 1
读数据得到0x00 val=NFDATA md.b 0x4E000010 1
读数据得到0x00 val=NFDATA md.b 0x4E000010 1
读数据得到0xea val=NFDATA md.b 0x4E000010 1
退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff
2.NandFlash时序及初始化
NAND FLASH控制器的时序,是为了让NAND FLASH外设工作起来,假如外接不同的NAND FLASH外设,那么它的操作时序可能就会不同,所以NAND FLASH控制器发出的时序图,就是不一样的,所以我们根据NAND FLASH外设来设置NAND FLASH控制器
NAND FLASH时序图,如下所示:
注意这里的HCLK在之前我们就设置为100M了,即1000/100 = 10ns。
来看到TACLS
,我们可以将它理解为当我们发出CLE/ALE信号之后,再过多长时间可以发出nWE信号(nWE和WE非都是表示低电平有效)
来看到命令锁存周期:
可以发现涉及到的时间有tCLS,tALS,tWP
三者时间最小都为12ns,说明三者可以同时发出,因此TACLS可以设置为0
再来看TWRPH0
,表明写使能信号WE的持续时间,涉及到的时间为tWP = 12ns,再通过以下计算公式:
即:10*(TWRPH0 + 1)>= 12ns—>TWRPH0 >= 1
即可以取TWRPH0为1
同理来看TWRPH1
,表示写使能信号结束之后过多长时间,CLE/ALE才会释放,涉及到的时间为tCH,tALH,最小都为5ns,再通过以下计算公式:
即:10*(TWRPH1 + 1)>= 5ns—>TWRPH1 >= 0
即可以取TWRPH0为0
综上,将TACLS设置为0,TWRPH0设置为1,TWRPH1设置为0
MODE [0]: 设置为1,使能NAND FLASH。
Reg_nCE [1]: 设置为1,禁止片选。因为我们现在还没有使用。为例错误的操作。
InitECC [4]: 初始化ECC的编码器,后边要使用,我们设置为1,来初始化。
下面进行编程设置NAND FLASH的时序:
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);
}
3.NandFlash的芯片id读取
参考NAND FLASHh的芯片手册,如下图所示:(NAND FLASH读操作时序图)
我们一般先操作片选使能,只有片选使能之后才能进行后边的操作,片选是能代码如下:
void nand_select(void)
{
/*使能片选*/
NFCONT &=~(1<<1);
}
有使能片选,一定有禁止片选,禁止片选的代码如下:
void nand_deselect(void)
{
/*禁止片选*/
NFCONT |= (1<<1);
}
我们按照从左往右的时间点,来分析,片选信号像一个总开关,只有使能了片选信号,后续的操作才会有意义
,我们使能片选信号之后,片选引脚nCE后续一直为低电平,在前面的命令时序图中知道tCLS和tWP最小的时间参数都是12us,就表明CLE和nWE这两个信号可以同时发出,就表示要命令了,对于写什么命令,就要看数据总线上要发送的命令了,当CLE从高电平变为低电平后,表示上次的写操作已经结束了。
对于上面复杂的时序,我们可以使用2440上的NAND FLASH控制器简化操作,只需要往NFCMMD寄存器写入要传输的命令就可以了,NAND FLASH控制器默认把上面复杂的时序发出来。
发命令后,后面就需要发送地址了,当nWE和ALE有效的时候,表示写地址,上图中,要写入的地址是0x00,当ALE从高电平变为低电平的时候,表示写地址结束,我们可以简化为:往NFADDR寄存器中写值就可以了,比如:NFADDR=0x00。
下面我们写代码:发命令的函数,和发地址的函数代码如下:
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCCMD = cmd;
for(i=0; i<10; i++);
}
void nand_addr_byte(unsigned char addr)
{
volatile int i;
NFADDR = addr;
for(i=0; i<10; i++);
}
接下来就可以读取数据了,数据可以直接通过读取NFDATA寄存器里面数据来获得数据,根据时序图,是读5个字节的数据,代码如下:
unsigned char nand_data(void)
{
return NFDATA;
}
读芯片ID之前先打开片选, 读取芯片ID函数,代码如下:
void nand_chip_id(void)
{
unsigned char buf[5]={0};
nand_select();
nand_cmd(0x90);
nand_addr_byte(0x00);
buf[0] = nand_data();
buf[1] = nand_data();
buf[2] = nand_data();
buf[3] = nand_data();
buf[4] = nand_data();
nand_deselect();
printf("maker id = 0x%x\\n\\r",buf[0]);
printf("device id = 0x%x\\n\\r",buf[1]);
printf("3rd byte = 0x%x\\n\\r",buf[2]);
printf("4th byte = 0x%x\\n\\r",buf[3]);
printf("page size = %d kb\\n\\r",1 << (buf[3] & 0x03));
printf("block size = %d kb\\n\\r",64 << ((buf[3] >> 4) & 0x03));
printf("5th byte = 0x%x\\n\\r",buf[4]);
}
下面我们再写一个打印菜单的函数,在菜单中调用读取芯片ID的函数,代码如下
oid 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);
/* 测试内容:
* 1. 识别nand flash
* 2. 擦除nand flash某个扇区
* 3. 编写某个地址
* 4. 读某个地址
*/
switch (c)
{
case 'q':
case 'Q':
return;
break;
case 's':
case 'S':
nand_chip_id();
break;
case 'e':
case 'E':
break;
case 'w':
case 'W':
break;
case 'r':
case 'R':
break;
default:
break;
}
}
}
在主函数中调用nand flash的初始化函数,和nand flash的测试函数。
int main(void)
{
led_init();
//interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
//timer_init();
puts("\\n\\rg_A = ");
printHex(g_A);
puts("\\n\\r");
//nor_flash_test();
nand_init();
nand_flash_test();
return 0;
}
4.NandFlash的数据读取
下图为NAND FLASH内部结构图,从图中可以可以知道,一个page含有2k 字节的页数据,和64字节的oob区
nand flash相比于nor flash,他有一个缺点:就是读一页数据或者写一页数据的时候会出现位反转,就比如在读取page0这一页数据的时候有可能某一位是错误的,那么怎么解决这个问题?
因此引入了OOB区,写数据的时候就把数据写进去,同时生成一个检验码,把校验码写在OOB区中,读取数据的时候,读出一页数据,去过某一位有错误,就读出原来的检验码,使用检验码来修正里面的数据。
因此OOB(out of bank)的作用就在于解决了nand flash的缺陷
下面我们开始写程序,想去读NAND FLASH应该怎样操作,下面是nand flash的地址周期。
下图为读NAND FLASH的时序操作:
读NAND FLASH步骤:(从程序的角度来说),我们需要先发出00命令再发出5个周期的地址,再发出30命令,然后就可以读数据了。比如:我想访问某个地址的数据,需要确定在哪一行page(row),在哪一列col(0~2047)。从NAND FLASH的地址周期中可以看出来,先发出2个col(列地址),再发出3个(Row)行地址。
下面是程序的编写:
wait_ready函数等待NAND FLASHh空闲,从上图可以看出当NFSTAT寄存器[0]的值为1时NAND FLASH是空闲的,我们可以通过该位来判断NAND FLASH是否繁忙。代码如下:
void wait_ready(void)
{
while (!(NFSTAT & 1));
}
nand_read函数为NAND FLASH的读函数,代码如下:
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
int i = 0;
int page = addr / 2048;
int col = addr & (2048 - 1);
nand_select();
while (i < len)
{
/* 发出00h命令 */
nand_cmd(00);
/* 发出地址 */
/* 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);
/* 发出30h命令 */
nand_cmd(0x30);
/* 等待就绪 */
wait_ready();
/* 读数据 */
for (; (col < 2048) && (i < len); col++)
{
buf[i++] = nand_data();
}
if (i == len)
break;
col = 0;
page++;
}
nand_deselect();
}
在init.c文件中,加上如下代码,用来判断所使用的FLASH是NOR FLASH还是NAND FLASH。代码如下:
int isBootFromNorFlash(void)
{
volatile unsigned int *p = (volatile unsigned int *)0;
unsigned int val = *p;
*p = 0x12345678;
if (*p == 0x12345678)
{
/* 写成功, 对应nand启动 */
*p = val;
return 0;
}
else
{
return 1;
}
}
在init.c文件中的copy2sdram函数里面加上如下代码,用来支持NAND FLASH启动,当isBootFromNorFlash函数的返回值为1时,是从NOR FLASH启动,当isBootFromNorFlash函数的返回值为0是,是从NAND FLASH启动。
if (isBootFromNorFlash())
{
while (dest < end)
{
*dest++ = *src++;
}
}
else
{
nand_init();
nand_read(src, dest, len);
}
}
5.NandFlash的擦除与烧写
需要做的事情:
1,实现nand_erase
2, 实现nand_write
3, 实现测试菜单
下面我们逐个来实现他们:
这里的NAND FLASH的烧写和擦除还是比较简单的,它只涉及到页数据区,不涉及到oob区,擦出的时候是以块为单位。下图为擦除的时序图:
我们就根据擦除的时序图发出对应的命令和地址,NAND FLASH是以块为单位进行擦除的,假如我们传入len的值为1,但是它仍然会擦出一个块(128k字节),我们根据芯片手册,来操作NAND FLASH的擦出操作,函数功能:从addr地址开始,擦除len长度的数据。代码如下:
nt 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);
}
nand_deselect();
return 0;
}
操作NAND FLASH之前要,选中芯片,然后就可以根据芯片手册来操作NAND FLASH的擦除操作了,操作完之后,要取消片选。
往NAND FLASH写数据时,只需要把要写的数据复制给NFDATA寄存器即可。代码如下:
void nand_w_data(unsigned char val)
{
NFDATA = val;
}
下图为烧写的时序图:
从上图中的NAND FLASH烧写时序图可以知道对于NAND FLASH的烧写,先发出0x80命令,再发出地址周期,然后发出要烧写的数据,最后发出0x10,就开始内部烧写,然后等待烧写成功。(我们写数据的时候是逐页写的,开始要烧写的数据地址可能不是该页的起始地址)。操作之前需要选中片选,操作完之后取消片选,代码如下:
void nand_write(unsigned int addr, unsigned char *buf, unsigned int len)
{
int page = addr / 2048;
int col = addr & (2048 - 1);
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();
}
我们封装擦除操作NAND FLASH函数的时候,每一次擦除的大小是一个块(128*1024)代码如下:
void do_erase_nand_flash(void)
{
unsigned int addr;
/* 获得地址 */
printf("Enter the address of sector to erase: ");
addr = get_uint();
printf("erasing ...\\n\\r");
nand_erase(addr, 128*1024);
}
我们封装读取操作NAND FLASH函数,我们实现NAND FLASH每次的读取,每次读取64字节数据。把从地址addr读取得到的64字节数据存放到buf缓冲区中,然后通过串口显示出来,代码如下图所示:
void do_read_nand_flash(void)
{
unsigned int addr;
volatile unsigned char *p;
int i, j;
unsigned char c;
unsigned char str[16];
unsigned char buf[64];
/* 获得地址 */
printf("Enter the address to read: ");
addr = get_uint();
nand_read(addr, buf, 64);
p = (volatile unsigned char *)buf;
printf("Data : \\n\\r");
/* 长度固定为64 */
for (i = 0; i < 4; i++)
{
/* 每行打印16个数据 */
for (j = 0; j < 16; j++)
{
/* 先打印数值 */
c = *p++;
str[j] = c;
printf("%02x ", c);
}
printf(" ; ");
for (j = 0; j < 16; j++)
{
/* 后打印字符 */
if (str[j] < 0x20 || str[j] > 0x7e) /* 不可视字符 */
putchar('.');
else
putchar(str[j]);
}
printf("\\n\\r");
}
}
NAND FLASH的烧写封装函数代码如下:
void do_write_nand_flash(void)
{
unsigned int addr;
unsigned char str[100];
int i, j;
unsigned int val;
/* 获得地址 */
printf("Enter the address of sector to write: ");
addr = get_uint();
printf("Enter the string to write: ");
gets(str);
printf("writing ...\\n\\r");
nand_write(addr, str, strlen(str)+1);
}
NAND FLASH的测试菜单函数代码如下:
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);
/* 测试内容:
* 1. 识别nand flash
* 2. 擦除nand flash某个扇区
* 3. 编写某个地址
* 4. 读某个地址
*/
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;
}
}
}
以上是关于nand Flash的主要内容,如果未能解决你的问题,请参考以下文章