一、flash介绍
常用的flash类型有nor flash 和nand flash 两种;
(1)nor flash
1、nor flash的接口和ram完全相同,可以随机访问任意地址的数据,在其上进行读操作的效率非常高,但是擦除和写操作的效率很低,另外,nor flash的容量一般比较小,通常,nor flash用于存储程序;
2、nor flash的块大小范围为64kb—128kb;
3、擦写一个nor flash块需要4s,
4、市场上nor flash 的容量通常为1mb—4mb
(2)nand flash
1、nand flash的接口仅仅包含几个i/o引脚,需要串行地访问,nand flash进行擦除和写操作的效率很高,容量较大,
通常nand flash用于存储数据;
2、nand flash的块大小范围为8kb—64kb;
3、擦写一个nand flash块需要2ms;
4、nand flash 一般以512字节为单位进行读写
5、 市场上 nand flash 的容量一般为 8m—512m
二、nand flash的物理结构
以三星公司生产的 k9f1208u0m 为例:
1、容量:64mb,
一共4个层;
每层1024个块(block);
1块包含32页
1页包含 512 + 16 = 528个字节
2、外部接口:8个i/o口,5个使能信号(ale、cle、nwe、nre、nce),1个状态引脚(rdy/b),1个写保护引脚(nwe);
3、命令、地址、数据都通过8个i/o口输入输出;
4、写入命令、地址、数据时,都需要将nwe、nce信号同时拉低;数据在we上升沿被锁存;
5、cle、ale用来区分i/o引脚上传输的是数据还是地址;
6、64mb的空间需要26位地址,因此以字节为单位访问flash时需要4个地址序列;
7、读/写页在发出命令后,需要4个地址序列,而擦除块在发出擦除命令后仅需要3个地址序列;
三、nand flash访问方法
1 特殊功能寄存器定义
#define rnfconf (*(volatile unsigned int *)0x4e000000)
#define rnfcmd (*(volatile unsigned char *)0x4e000004)
#define rnfaddr (*(volatile unsigned char *)0x4e000008)
#define rnfdata (*(volatile unsigned char *)0x4e00000c)
#define rnfstat (*(volatile unsigned int *)0x4e000010)
#define rnfecc (*(volatile unsigned int *)0x4e000014)
#define rnfecc0 (*(volatile unsigned char *)0x4e000014)
#define rnfecc 1 (*(volatile unsigned char *)0x4e000015)
#define rnfecc2 (*(volatile unsigned char *)0x4e000016)
2 操作的函数实现
1. 发送命令
#define nf_cmd(cmd) {rnfcmd=cmd; }
2. 写入地址
#define nf_addr(addr) {rnfaddr=addr;}
3. nand flash 芯片选中
#define nf_nfce_l() {rnfconf&=~(1<<11);}
4. nand flash 芯片不选中
#define nf_nfce_h() {rnfconf|=(1<<11);}
5. 初始化 ecc
#define nf_rstecc() {rnfconf|=(1<<12);}
6. 读数据
#define nf_rddata() (rnfdata)
7. 写数据
#define nf_wrdata(data) {rnfdata=data;}
8. 获取nand flash 芯片状态
#define nf_waitrb() {while(!(rnfstat&(1<<0)));}
0/假: 表示nand flash 芯片忙状态
1/真:表示nand flash 已经准备好
3.nandflash读写擦具体实现。
操作nand flash时,先传输命令,然后传输地址,最后读、写数据,期间要检查flash的状态;
k9f1208u0m 一页大小为528字节,而列地址a0——a7可以寻址的范围是256字节,所以将一页分为a、b、c三个区:
a区:0—255字节
b区:256—511字节
c区:512—527字节
(0)nand flash 初始化
void nf_init(void)
{
/* 设置 nand flash 配置寄存器, 每一位的取值见1.3 节 */
rnfconf=(1<< 15)|(1<<14)|(1<< 13)|(1<<12)|(1<< 11)|(tacls<<8)|(twrph0<<4)|(twrph1<<0);
/* 复位外部 nand flash 芯片 */
nf_reset();
}
(1)复位
命令:ffh
步骤:发出命令即可复位nand flash芯片;
static void nf_reset(void)
int i;
nf_nfce_l(); /* 片选 nand flash 芯片*/
nf_cmd(0xff); /* 复位命令 */
for(i=0;i< 10;i++); /* 等待twb = 100ns. */
nf_waitrb(); /* wait 200~500us; */
nf_nfce_h(); /* 取消nand flash 选中*/
(2)读操作
命令:
00h——读a区
01h——读b区
50h——读c区
操作步骤:
1、发出命令 00h、01h 或50h, 00h将地址位a8设为0, 01h将a8设为1 ;
2、依次发出4个地址序列;
3、检测r/nb,待其为高电平时,就可以读取数据了;
参数说明:block :块号
page :页号
buffer :指向将要读取到内存中的起始位置
返回值:1:读成功
0 :读失败
static int nf_readpage(unsigned int block, unsigned int page, unsigned char *buffer)
unsigned int blockpage;
unsigned char ecc0, ecc1, ecc2;
unsigned char *bufpt=buffer;
unsigned char se[16];
page=page&0x1f;
blockpage=(block<<5)+page;
nf_rstecc(); /* 初始化 ecc */
nf_nfce_l(); /* 片选 nand flash 芯片*/
nf_cmd(0x00); /* 从a 区开始读 */
nf_addr(0); /* a0~a7 位(column address) */
nf_addr(blockpage&0xff); /* a9-a 16, (page address) */
nf_addr((blockpage>>8)&0xff); /* a17-a24, (page address) */
nf_addr((blockpage>> 16)&0xff); /* a25, (page address) */
/* 等待nand flash 处于再准备状态 */
for(i=0;i< 10;i++);
nf_waitrb(); /*等待 tr(max 12us) */
/* 读整个页, 512 字节 */
for(i=0;i<512;i++)
{
*bufpt++=nf_rddata();
}
/* 读取 ecc 码 */
ecc0=rnfecc0;
ecc 1=rnfecc 1;
ecc2=rnfecc2;
/* 读取该页的oob 块 */
for(i=0;i< 16;i++)
se[i]=nf_rddata();
/* 校验 ecc 码, 并返回 */
if(ecc0==se[0] && ecc 1==se[1] && ecc2==se[2])
return 1;
else
return 0;
}
(3)flash编程
命令:
80h——10h :写单页;
80h——11h :对多个层进行些页操作;
操作步骤:
1、写单页步骤:
【1】发出80h命令后;
【2】发送4个地址序列;
【3】向flash发送数据;
【4】发出命令10h启动写操作,flash内部自动完成写、校验操作;
【5】通过命令70h读取状态位,查询写操作是否完成;
2、多页写
【1】发出80h、4个地址序列、最多528字节的数据;
【2】发出11h命令;
【3】接着在相邻层执行【1】、【2】两步操作;
【4】第四页的最后使用10h代替11h,启动flash内部的写操作;
【5】可以通过71h查询写操作是否完成;
以页为单位写入.
参数说明:block 块号
page 页号
buffer 指向内存中待写入nand flash 中的数据起始位置
返回值: 0 :写错误
1:写成功
static int nf_writepage(unsigned int block, unsigned int page, unsigned char *buffer)
unsigned int blockpage = (block<<5)+page;
unsigned char *bufpt = buffer;
nf_rstecc(); /* 初始化 ecc */
nf_nfce_l(); /* 片选 nand flash 芯片*/
nf_cmd(0x0); /* 从a 区开始写 */
nf_cmd(0x80); /* 写第一条命令 */
nf_addr(0); /* a0~a7 位(column address) */
nf_addr(blockpage&0xff); /* a9-a 16, (page address) */
nf_addr((blockpage>>8)&0xff); /* a17-a24, (page address) */
nf_addr((blockpage>> 16)&0xff); /* a25, (page address) */
nf_wrdata(*bufpt++); /* 写一个页512 字节到 nand flash 芯片 */
/*
* oob 一共16 bytes, 每一个字节存放什么由程序员自己定义, 通常,
* 我们在 byte0-byte2 存 ecc 检验码. byte6 存放坏块标志.
*/
sebuf[0]=rnfecc0; /* 读取 ecc 检验码 0 */
sebuf[1]=rnfecc 1; /* 读取 ecc 检验码 1 */
sebuf[2]=rnfecc2; /* 读取 ecc 检验码 2 */
sebuf[5]=0xff; /* 非坏块标志 */
nf_wrdata(sebuf [i]); /* 写该页的oob 数据块 */
nf_cmd(0x10); /* 结束写命令 */
/* 等待nand flash 处于准备状态 */
nf_waitrb();
/* 发送读状态命令 nand flash */
nf_cmd(0x70);
for(i=0;i<3;i++);
if (nf_rddata()&0x1)
{ /*如果写有错, 则标示为坏块 */
nf_nfce_h(); /* 取消nand flash 选中*/
nf_markbadblock(block);
return 0;
} else { /* 正常退出 */
nf_nfce_h(); /* 取消nand flash 选中*/
return 1;
(4)复制
命令:
00h——8ah——10h :单层页内复制
03h——8ah——11h :多层页内复制
操作步骤:
1、单层页内复制步骤:
【1】发出命令00h;
【2】4个源地址序列;
【2】接着发出8ah;
【4】发出4个目的地址序列;
【5】发出10h命令,启动写操作;
【6】通过70h命令读取状态查询操作是否完成;
2、多层页内复制步骤:
【1】发出命令00h(第一层)、4个源页地址序列;
【2】以后各层依次发出命令03h、4个源页地址序列;
【3】发出命令8ah、目的地址、命令11h;
【4】各层依次执行【3】,在最后一页的地址后,用10h代替11h,启动写操作;
【5】通过71h命令读取状态查询操作是否完成;
(5)擦除
命令:
60h——d0h :单层内块擦除
60h-60h ——d0h :多层内块擦除
操作步骤:
1、单层内块擦除:
【1】发出命令字60h;
【2】发出块(block)地址,仅需3个地址序列;
【3】发出d0h,启动擦除操作;
【4】发出70h命令查询状态,是否完成擦除;
2、多层内块擦除:
【1】发出命令字60h,3个块地址序列;
【2】对每个层执行【1】;
【3】发出命令d0h,启动擦除操作;
【4】发出71h命令查询状态,检查是否完成擦除;
(6)读取芯片id
命令:90h
操作步骤:
1、发出命令90h;
2、发出4个地址序列(均设为0);
3、连续读入5个数据,分别表示:厂商代码、设备代码、保留字节、多层操作代码;
返回值为nand flash 芯片的 id 号
unsigned short nf_checkid(void)
unsigned short id;
nf_nfce_l(); /* 片选 nand flash 芯片*/
nf_cmd(0x90); /* 发送读id 命令到 nand flash 芯片 */
nf_addr(0x0); /* 指定地址 0x0 ,芯片手册要求 */
for(i=0;i< 10;i++); /* 等待twb = 100ns. */
id=nf_rddata()<<8; /* 厂商id(k9s 1208v:0xec) */
id|=nf_rddata(); /* 设备 id(k9s 1208v:0x76) */
nf_nfce_h(); /* 取消nand flash 选中*/
return id;
(7)读状态
命令:
70h——单层状态
71h——多层状态
操作步骤:写入命令字之后,然后启动读操作即可读入此寄存器。
(8)nand flash 标记坏块
如果是坏块, 通过写 oob 块的byte6 把该块标记为坏块。
参数说明:block 块号
返回值:1:ok ,成功完成标记。
0 :表示写oob 块正确.
static int nf_markbadblock(unsigned int block)
unsigned int blockpage=(block<<5);
sebuf[0]=0xff;
sebuf[1]=0xff;
sebuf[2]=0xff;
sebuf[5]=0x44; /* 设置坏块标记 */
nf_cmd(0x50); /* 从c 区开始写 */
nf_cmd(0x80); /* 发送编程命令, 让nand flash 处理写状态 */
nf_addr(0x0); /* a0~a7 位(column address) */
nf_addr(blockpage&0xff); /* a9-a 16, (page address) */
nf_addr((blockpage>>8)&0xff); /* a17-a24, (page address) */
nf_addr((blockpage>> 16)&0xff); /* a25, (page address) */
/* 写oob 数据块 */
nf_wrdata(sebuf [i]);
nf_cmd(0x10); /* 结束写命令 */
/* 等待nandflash 准备好 */
for(i=0;i< 10;i++); /* twb = 100ns. */
nf_waitrb();
/*读nandflash 的写状态 */
for(i=0;i<3;i++); /* twhr=60ns */
if (nf_rddata()&0x1)
nf_nfce_h(); /* 取消nand flash 选中*/
return 0;
} else {
return 1;
(9)nand flash 检查坏块
检查指定块是否是坏块.
返回值:1:指定块是坏块
0 :指定块不是坏块。
static int nf_isbadblock(u32 block)
u8 data;
blockpage=(block<<5);
nf_nfce_l(); /* 片选 nand flash 芯片*/
nf_cmd(0x50); /* read oob 数据块 */
nf_addr(517&0xf); /* a0~a7 位(column address) */
nf_addr(blockpage&0xff); /* a9-a 16, (page address) */
nf_addr((blockpage>>8)&0xff); /* a17-a24, (page address) */
for(i=0;i< 10;i++); /* wait twb(100ns) */
/* 读取读出值 */
data=nf_rddata();
nf_nfce_h(); /* 取消nand flash 选中*/
/* 如果data 不为0xff 时, 表示该块是坏块 */
if(data != 0xff)
(10)擦除指定块中数据
返回值:0 :擦除错误。(若是坏块直接返回0 ;若擦除出现错误则标记为坏块然后返回0)
1 :成功擦除。
static int nf_eraseblock(unsigned int block)
{
unsigned int blockpage=(block<<5);
int i;
/* 如果该块是坏块, 则返回 */
if(nf_isbadblock(block))
return 0;
nf_nfce_l(); /* 片选 nand flash 芯片*/
nf_cmd(0x60); /* 设置擦写模式 */
nf_addr(blockpage&0xff); /* a9-a 16, (page address) , 是基于块擦*/
nf_addr((blockpage>>8)&0xff); /* a17-a24, (page address) */
nf_addr((blockpage>> 16)&0xff); /* a25, (page address) */
nf_cmd(0xd0); /* 发送擦写命令, 开始擦写 */
/* 等待nandflash 准备好 */
for(i=0;i< 10;i++); /* twb(100ns) */
nf_waitrb();
/* 读取操作状态 */
nf_cmd(0x70);
if (nf_rddata()&0x1)
{
nf_nfce_h(); /* 取消nand flash 选中*/
nf_markbadblock(block); /* 标记为坏块 */
return 0;
} else {
return 1;
}
}