- | NOR FLASH | NAND FLASH |
---|---|---|
接口 | 引脚多(像RAM一样) | 引脚少(复用) |
容量 | 小(1M/2M/32M等) | 大(64M/128M/256M等) |
读 | 简单(像RAM一样) | 复杂 |
写 | 慢 | 快 |
擦除 | 慢 | 快 |
性能 | 好(无位反转与坏块) | 不是很好(存在位反转与坏块) |
价格 | 贵 | 便宜 |
XIP | 可以 | 不可以 |
关于NOR FLASH的操作可以翻看我上一篇博文JZ2440之NOR FLASH简单操作篇
NAND FLASH不像NOR FLASH一样可以直接在芯片内执行程序,NAND FLASH只有通过与NAND FLASH控制器相连后才能与CPU进行数据交换,因此当我们要对NAND FLASH进行操作时必须先使能 NAND FLASH控制器。
在使用S3C2440中的NAND FLASH控制器时我们需要根据NAND FLASH芯片手册来设置NAND FLASH的时序。下图中的图1和图2是NAND FLASH控制器中需要我们配置的时序图与寄存器,其中TACLS表示发出CLE/ALE信号后过多久发送nWE信号,TWRPH0表示nWE信号的脉冲宽度,TWRPH1表示释放nWE后过多久释放CLE/ALE
。其具体的值可以参考图3和图4;(其中HCLK=100MHz
,参考我之前的博文S3C2440中时钟配置的那些事儿)
|
|
|
|
在设置好时序后我们还得使能NAND FLASH控制器,这样我们才可以顺利地操作我们的NAND FLASH。在使能NAND FLASH控制器时我们先禁止片选(等要操作NAND FLASH时再使能片选),我们只需配置如图5所示的3位。
|
到此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<<0) | (1<<1) | (1<<4);
}
void nand_select(void)
{
NFCONT &= ~(1<<1); /* 使能片选 */
}
void nand_deselect(void)
{
NFCONT |= (1<<1); /* 禁止片选 */
}
对NAND FLASH的操作主要分为三步 ①发命令 ②发地址 ③进行操作
,S3C2440中的NAND FLASH控制器中与上面三步相关的寄存器有NFCMMD、NFADDR、NFDATA和NFSTAT
,在对NAND FLASH进行操作时,我们只需操作上面那行寄存器就可以了。
NAND FLASH没有像NOR FLASH一样的CFI接口, 因此NAND FLASH的一些重要参数要通过发送特殊的命令来进入特殊的模式读取参数信息,对应的操作如图6和图7
|
|
具体实现代码如下
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = 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++);
}
unsigned char nand_data(void)
{
return NFDATA;
}
void do_scan_nand_flash(void)
{
unsigned char buf[5] = {0};
nand_select(); /* 使能片选 */
nand_cmd(0x90);
nand_addr_byte(0x00);
buf[0] = nand_data(); /* 厂家ID */
buf[1] = nand_data(); /* 设备ID */
buf[2] = nand_data(); /* 3rd cyc */
buf[3] = nand_data(); /* 4th cyc */
buf[4] = nand_data(); /* 5th cyc */
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]);
}
读操作可以根据图8所示进行操作,注意在发出命令地址后需要读取NFSTA寄存器第0位的值来判断读取是否就绪
。
|
具体代码如下
void nand_wait_ready(void)
{
while (!(NFSTAT & 1));
}
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
unsigned int i = 0;
unsigned int page = addr / 2048; /* 一页可以存放2048个字节的数据 */
unsigned int col = addr & (2048 - 1); /* 对2048取余 */
nand_select(); /* 使能片选 */
while (1)
{
nand_cmd(0x00); /* 发出00命令 */
nand_addr_byte(col & 0xff); /* 发出 col addr */
nand_addr_byte((col>>8) & 0xff);
nand_addr_byte(page & 0xff); /* 发出 rol/page addr */
nand_addr_byte((page>>8) & 0xff);
nand_addr_byte((page>>16) & 0xff);
nand_cmd(0x30); /* 发出30命令 */
nand_wait_ready(); /* 等待就绪 */
/* 读数据 */
for (; (col < 2048) && (i < len); col++)
{
buf[i++] = nand_data();
}
if (i == len)
{
break;
}
else
{
col = 0;
page++;
}
}
nand_deselect(); /* 禁止片选 */
}
对NAND FLASH执行擦除操作时一次是擦除一个block的(我们所用的NAND FLASH是K9F2G08U0C
,其中一个page为2K个byte,一个block为128K个byte
),块擦除的操作如图9。
|
具体操作如下
int nand_erase(unsigned int addr, unsigned int len)
{
unsigned int page;
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); /* 发出60命令 */
nand_addr_byte(page & 0xff); /* 发出 rol/page addr */
nand_addr_byte((page>>8) & 0xff);
nand_addr_byte((page>>16) & 0xff);
nand_cmd(0xD0); /* 发出D0命令 */
nand_wait_ready(); /* 等待就绪 */
len -= (128*1024);
if (len == 0)
{
break;
}
addr += (128*1024);
}
nand_deselect(); /* 禁止片选 */
return 0;
}
这里我们要求addr必须是一个block的起始地址,擦除长度len也必须是整数个block。
写操作相较于前几种操作略有差别,它是在发出80命令和地址后就要发送要写的数据,最后再发送10命令,如图10。
|
具体实现如下
unsigned char nand_w_data(unsigned char val)
{
NFDATA = val;
}
void nand_write(unsigned int addr,unsigned char *buf, unsigned int len)
{
unsigned int i = 0;
unsigned int page = addr / 2048;
unsigned int col = addr & (2048 - 1);
nand_select(); /* 使能片选 */
while (1)
{
nand_cmd(0x80); /* 发出80命令 */
nand_addr_byte(col & 0xff); /* 发出 col addr */
nand_addr_byte((col>>8) & 0xff);
nand_addr_byte(page & 0xff); /* 发出 rol/page addr */
nand_addr_byte((page>>8) & 0xff);
nand_addr_byte((page>>16) & 0xff);
/* 写数据 */
for (; (col < 2048) && (i < len); col++)
{
nand_w_data(buf[i++]);
}
nand_cmd(0x10); /* 发出10命令 */
nand_wait_ready(); /* 等待就绪 */
if (i == len)
{
break;
}
else
{
/* 开始下一个page */
col = 0;
page++;
}
}
nand_deselect(); /* 禁止片选 */
}
因为还没有搞懂如何检查坏块以及检查到坏块后怎么进行跳转,所以这里就没有列出了,等以后搞懂了如何检查坏块和使用ECC后再来补吧。
注意:
因为此时我们的代码已经超过4K了,如果依然要进行NAND启动的话我们就得用前4K的代码将整个程序复制到SDRAM中执行,具体操作如下
/* 判断是nor启动还是nand启动 */
int isBootFromNorFlash(void)
{
volatile unsigned int *p = (volatile unsigned int *)0;
unsigned int val = *p;
*p = 0x12345678;
if (*p == 0x12345678) /* nor flash不能直接写,nand flash可以 */
{
/* 写成功,nand启动 */
*p = val;
return 0;
}
else
{
return 1;
}
}
void copy2sdram(void)
{
/* 要从lds文件中获得__code_start, __bss_start
* 然后从0地址把数据复制到__code_start
*/
extern int __code_start, __bss_start;
volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
volatile unsigned int *src = (volatile unsigned int *)0;
int len;
len = ((int)&__bss_start - (int)&__code_start);
if (isBootFromNorFlash())
{
while (dest < end)
{
*dest++ = *src++;
}
}
else
{
nand_init();
nand_read(src, dest, len);
}
}
可翻看我之前的博文如何对代码重定位。