NandFlash 的读写操作
作者:蔡于清
www.another-prj.com
(约有修改)
正如硬盘的盘片被分为磁道,每个磁道又分为若干扇区,一块 nand flash 也分为若干 block,
每个 block 分为如干 page。一般而言,block、page 之间的关系随着芯片的不同而不同,典
型的分配是这样的:
1block = 32page
1 page = 512bytes(datafield) + 16bytes(oob)
需要注意的是,对于 flash 的读写都是以一个 page 开始的,但是在读写之前必须进行 flash
的擦写,而擦写则是以一个 block 为单位的。同时必须提醒的是,512bytes 理论上被分为 1st
half 和 2sd half,每个 half 各占 256 个字节。
我们讨论的 K9F1208U0B 总共有 4096 个 Blocks,故我们可以知道这块 flash 的容量为
4096 *(32 *528)= 69206016 Bytes = 66 MB
但事实上每个 Page 上的最后 16Bytes 是用于存贮检验码和其他信息用的,并不能存放实
际的数据,所以实际上我们可以操作的芯片容量为 4096 *(32 *512) = 67108864 Bytes = 64
MB 由上图所示,1 个 Page 总共由 528 Bytes 组成,这 528 个字节按顺序由上而下以列为单
位进行排列(1 列代表一个 Byte。第 0 行为第 0 Byte ,第 1 行为第 1 Byte,以此类推,每
个行又由 8 个位组成,每个位表示 1 个 Byte 里面的 1bit)。这 528Bytes 按功能分为两大部分,
分别是 Data Field 和 Spare Field,其中 Spare Field 占 528Bytes 里的 16Bytes,这 16Bytes 是用
于在读写操作的时候存放校验码用的,一般不用做普通数据的存储区,除去这 16Bytes,剩下
的 512Bytes 便是我们用于存放数据用的 Data Field,所以一个 Page 上虽然有 528 个 Bytes,
但我们只按 512Bytes 进行容量的计算。
读命令有两个,分别是 Read1,Read2 其中 Read1 用于读取 Data Field 的数据,而 Read2 则
是用于读取 Spare Field 的数据。对于 Nand Flash 来说,读操作的最小操作单位为 Page,也
就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本 Page 的最
后一个 Byte 为止(可以包括 Spare Field)
Nand Flash 的寻址
Nand Flash 的地址寄存器把一个完整的 Nand Flash 地址分解成 Column Address 与 Page
Address.进行寻址。
Column Address: 列地址。Column Address 其实就是指定 Page 上的某个 Byte,指定这个
Byte 其实也就是指定此页的读写起始地址。
Page Address:页地址。由于页地址总是以 512Bytes 对齐的,所以它的低 9 位总是 0。确
定读写操作是在 Flash 上的哪个页进行的。
Read1 命令
当我们得到一个 Nand Flash 地址 src_addr 时我们可以这样分解出 Column Address 和 Page
Address
column_addr = src_addr%512;
page_address = (src_addr>>9);
也可以这么认为,一个 Nand Flash 地址的 A0~A7 是它的 column_addr,A9~A25 是它的
Page Address。(注意地址位 A8 并没有出现,也就是 A8 被忽略,在下面你将了解到这是什
么原因)
// column address
// page address
Read1 命令的操作分为 4 个 Cycle,发送完读命令 00h 或 01h(00h 与 01h 的区别请见下
文描述)之后将分4 个 Cycle 发送参数,1st.Cycle 是 发送Column Address。2nd.Cycle ,3rd.Cycle
和 4th.Cycle 则是指定 Page Address(每次向地址寄存器发送的数据只能是 8 位,所以 17 位
的 Page Address 必须分成 3 次进行发送
Read1 的命令里面出现了两个命令选项,分别是 00h 和 01h。这里出现了两个读命是否令
你意识到什么呢?是的,00h 是用于读写 1st half 的命令,而 01h 是用于读取 2nd half 的命令。
现在我可以结合上图给你说明为什么 K9F1208U0B 的 DataField 被分为 2 个 half 了。
如上文我所提及的,Read1 的 1st.Cycle 是发送 Column Address,假设我现在指定的 Column
Address 是 0,那么读操作将从此页的第 0 号 Byte 开始一直读取到此页的最后一个 Byte(包
括 Spare Field),如果我指定的 Column Address 是 127,情况也与前面一样,但不知道你发
现没有,用于传递 Column Address 的数据线有 8 条(I/O0~I/O7,对应 A0~A7,这也是 A8
为什么不出现在我们传递的地址位中),也就是说我们能够指定的 Column Address 范围为
0~255,但不要忘了,1 个 Page 的 DataField 是由 512 个 Byte 组成的,假设现在我要指定读
命令从第 256 个字节处 开始读取此页,那将会发生什么情景?我必须把 Column Address 设
置为 256,但 Column Address 最大只能是 255,这就造成数据溢出。。。正是因为这个原因我
们才把 Data Field 分为两个半区,当要读取的起始地址(Column Address)在 0~255 内时我
们用 00h 命令,当读取的起始地址是在 256~511 时,则使用 01h 命令.假设现在我要指定从
第 256 个 byte 开 始读取此页,那么我将这样发送命令串
column_addr=256;
NF_CMD=0x01;
NF_ADDR=column_addr&0xff;
NF_ADDR=page_address&0xff;
NF_ADDR=(page_address>>8)&0xff;
NF_ADDR=(page_address>>16)&0xff;
其中 NF_CMD 和 NF_ADDR 分别是 NandFlash 的命令寄存器和地址寄存器的地址解引用,
从 2nd half 开始读取
1st Cycle
2nd.Cycle
3rd.Cycle
4th.Cycle
我一般这样定义它们,
(*(volatile unsigned char *)0x4e000004)
#define rNFCMD
#define rNFADDR (*(volatile unsigned char *)0x4e000008)
事实上,当 NF_CMD=0x01 时,地址寄存器中的第 8 位(A8)将被设置为 1(如上文分
析,A8 位不在我们传递的地址中,这个位其实就是硬件电路根据 01h 或是 00h 这两个命令
来置高位或是置低位),这样我们传递 column_addr 的值 256 随然由于数据溢出变为 1,但
A8 位已经由于 NF_CMD =0x01 的关系被置为 1 了,所以我们传到地址寄存器里的值变成了
//NADD Flash command
//NAND Flash address
A0
1
A1
0
A2
0
A3 A4
0
0
A5 A6
0
0
A7
0
A8
1
这 8 个位所表示的正好是 256,这样读操作将从此页的第 256 号 byte(2nd half 的第 0 号
byte)开始读取数据。 nand_flash.c 中包含 3 个函数
void nf_reset(void);
void nf_init(void);
void nf_read(unsigned int src_addr,unsigned
nf_reset()将被 nf_init()调用。nf_init()是 nand_flash 的初始化函数,在对 nand flash 进行任
char *desc_addr,int size);
何操作之前,nf_init()必须被调用。
nf_read(unsigned int src_addr,unsigned
char *desc_addr,int size);为读函数,src_addr 是 nand
flash 上的地址,desc_addr 是内存地址,size 是读取文件的长度。
在 nf_reset 和 nf_read 函数中存在两个宏
NF_nFCE_L();
NF_nFCE_H();
你可以看到当每次对 Nand Flash 进行操作之前 NF_nFCE_L()必定被调用,操作结束之时
NF_nFCE_H()必定被调用。这两个宏用于启动和关闭 Flash 芯片的工作(片选/取消片选)。
至于 nf_reset()中的
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<
<0);
这一行代码是对 NandFlash 的控制寄存器进行初始化配置,rNFCONF 是 Nand Flash 的配置
寄存器,各个位的具体功能请参阅 s3c2410 数据手册。
现在举一个例子,假设我要从 Nand Flash 中的第 5000 字节处开始读取 1024 个字节到内
存的 0x30000000 处,我们这样调用 read 函数
nf_read(5000, 0x30000000,1024);
我们来分析 5000 这个 src_addr.
根据
column_addr=src_addr%512;
page_address=(src_addr>>9);
我们可得出 column_addr=5000%512=392
page_address=(5000>>9)=9
于是我们可以知道 5000 这个地址是在第 9 页的第 392 个字节处,于是我们的 nf_read 函数
将这样发送命令和参数
column_addr=5000%512;
>page_address=(5000>>9);
NF_CMD=0x01;
NF_ADDR= column_addr &0xff;
NF_ADDR=page_address&0xff;
NF_ADDR=(page_address>>8)&0xff;
NF_ADDR=(page_address>>16)&0xff;
向 NandFlash 的命令寄存器和地址寄存器发送完以上命令和参数之后,我们就可以从
3rd.Cycle
4th.Cycle
从 2nd half 开始读取
1st Cycle
2nd.Cycle
rNFDATA 寄存器(NandFlash 数据寄存器)读取数据了.
我用下面的代码进行数据的读取.
for(i=column_addr;i<512;i++)
{
*buf++=NF_RDDATA();
}
每当读取完一个 Page 之后,数据指针会落在下一个 Page 的 0 号 Column(0 号 Byte).
源代码:
/*
www.another-prj.com
author: caiyuqing
本代码只属于交流学习,不得用于商业开发
*/
#include "s3c2410.h"
#include "nand_flash.h"
static unsigned char seBuf[16]={0xff};
//--------------------------------------------------------------------------------------
unsigned short nf_checkId(void)
{
int i;
unsigned short id;
NF_nFCE_L();
NF_CMD(0x90);
NF_ADDR(0x0);
for(i=0;i<10;i++);
//chip enable
//Read ID
//wait tWB(100ns)
id=NF_RDDATA()<<8;
id|=NF_RDDATA();
// Maker code(K9S1208V:0xec)
// Devide code(K9S1208V:0x76)
NF_nFCE_H();
return id;
//chip enable
}
//--------------------------------------------------------------------------------------
static void nf_reset(void)
{
int i;
NF_nFCE_L();
NF_CMD(0xFF);
for(i=0;i<10;i++);
NF_WAITRB();
NF_nFCE_H();
//chip enable
//reset command
//tWB = 100ns.
//wait 200~500us;
//chip disable
}
//--------------------------------------------------------------------------------------
void nf_init(void)
{
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<
<0);
// 1
// En
}
//--------------------------------------------------------------------------------------
r xxx,
tWRPH1
nFCE=H tACLS
nf_reset();
1
ECCR
xxx
tWRPH0
1
r
1
r
1
r xxx
void nf_read(unsigned int src_addr,unsigned
{
char *desc_addr,int size)
int i;
unsigned int column_addr = src_addr % 512;
unsigned int page_address = (src_addr >> 9);
unsigned char *buf = desc_addr;
while((unsigned int)buf < (unsigned int)(desc_addr) + size)
{
// column address
// page addrress
NF_nFCE_L();
// enable chip
/*NF_ADDR 和 NF_CMD 为 nand_flash 的地址和命令寄存器的解引用*/
if(column_addr > 255)
NF_CMD(0x01);
// Read2 command.
// 2end halft
cmd 0x01: Read
command(start from 2end half page)
else
NF_CMD(0x00);
// 1st halft?
NF_ADDR(column_addr & 0xff);
NF_ADDR(page_address & 0xff);
NF_ADDR((page_address >> 8) & 0xff);
NF_ADDR((page_address >> 16) & 0xff);
for(i = 0; i < 10; i++);
NF_WAITRB();
// Column Address
// Page Address
// ...
// ..
// wait tWB(100ns)/////??????
// Wait tR(max 12us)
// Read from main area
for(i = column_addr; i < 512; i++)
{
*buf++= NF_RDDATA();
}
NF_nFCE_H();
column_addr = 0;
page_address++;
// disable chip
}
return ;
}
本文来自 CSDN 博客,转载请标明出处:
http://blog.csdn.net/abc19842008/archive/2008/01/22/2059480.aspx