在本节内容中总结之前的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/CLE
TACLS = 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();
}