作者:蔡于清
' j! _2 ~9 O+ w- b1 Q0 M# \+ m" Twww.another-prj.com
9 @0 ~; n, D) v0 J1 m; N; g(约有修改)
1 L# b# b. ]. b k0 ?& f$ J% e a* d5 x3 C, m; s; Y$ ]* i+ [& B' r
正如硬盘的盘片被分为磁道,每个磁道又分为若干扇区,一块nand flash也分为若干block,每个block分为如干page。一般而言,block、page之间的关系随着芯片的不同而不同,典型的分配是这样的:
% o9 ?- r3 ~ J+ Q8 T" T1block = 32page
2 h0 `9 i* |( w/ r6 Z9 F# k1page = 512bytes(datafield) + 16bytes(oob)
' B: T0 V* R L- F" d
. Y/ z# Z0 z% X% t2 g+ X% z( M c% k; h2 Q( p5 K5 T3 u7 M
需要注意的是,对于flash的读写都是以一个page开始的,但是在读写之前必须进行flash的擦写,而擦写则是以一个block为单位的。同时必须提醒的是,512bytes理论上被分为1st half 和2sd half,每个half各占256个字节。
# l8 V3 b+ z, G* G- y8 d, D4 ^: p! } r+ f2 K/ X- K4 H' h
我们讨论的K9F1208U0B总共有4096 个Blocks,故我们可以知道这块flash的容量为4096 *(32 *528)= 69206016 Bytes = 66 MB
" ?' O/ l$ O4 Z9 n t. E; b
但事实上每个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进行容量的计算。
- m9 F8 p4 p6 F% c% E# l
' Q: h9 B- u) h8 H8 R- X读 命令有两个,分别是 Read1,Read2其中Read1用于读取Data Field的数据,而Read2则是用于读取Spare Field的数据。对于Nand Flash来说,读操作的最小操作单位为Page,也就是说当我们给定了读取的起始位置后,读操作将从该位置开始,连续读取到本Page的最后一个 Byte为止(可以包括Spare Field)
/ T" A) C- f. O8 `& B+ y! {
# y$ r1 H" \' N) { g6 q1 X+ |: fNand Flash的寻址
( \# g. g- o, W, S0 p( B, d Nand Flash的地址寄存器把一个完整的Nand Flash地址分解成Column Address与Page Address.进行寻址。: y4 w% ~- ]4 @: Y/ C' j% M
Column Address: 列地址。Column Address其实就是指定Page上的某个Byte,指定这个Byte其实也就是指定此页的读写起始地址。
, g- r( Z7 `0 Z# X7 aPaage Address:页地址。由于页地址总是以512Bytes对齐的,所以它的低9位总是0。确定读写操作是在Flash上的哪个页进行的。
?* I/ d5 q5 RRead1命令
6 o! h) h! z* T h8 {8 f当我们得到一个Nand Flash地址src_addr时我们可以这样分解出Column Address和Page Address& H' f* |% j; B# }8 p
column_addr=src_addr%512; // column address
/ n2 M. [! g9 `" C _9 P* bpage_address=(src_addr>>9); // page address
( h0 x) y/ o% O" R' e1 M L$ Z也可以这么认为,一个Nand Flash地址的A0~A7是它的column_addr,A9~A25是它的Page Address。(注意地址位A8并没有出现,也就是A8被忽略,在下面你将了解到这是什么原因)* w! w- J3 c) L' M8 F& l
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次进行发送& t! f1 `4 K4 O! R! Z+ ]& S- ^
Read1的 命令里面出现了两个命令选项,分别是00h和01h。这里出现了两个读命是否令你意识到什么呢?是的,00h是用于读写1st half的命令,而01h是用于读取2nd half的命令。现在我可以结合上图给你说明为什么K9F1208U0B的DataField被分为2个half了。
( N, I( [8 Z% Y: l( t1 p如上文我所提及的,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开 始读取此页,那么我将这样发送命令串
+ |( s) ?; \9 D( W! I; L0 I+ w5 pcolumn_addr=256;+ J& S0 ]1 b5 j% C
NF_CMD=0x01; 从2nd half开始读取
' |* |1 f( Q* m+ Q, DNF_ADDR=column_addr&0xff; 1st Cycle. S* f9 e9 ?! T
NF_ADDR=page_address&0xff; 2nd.Cycle
6 N& J- Y5 ^9 b7 n) j/ c4 wNF_ADDR=(page_address>>8)&0xff; 3rd.Cycle) s2 l/ Q8 |1 S. D \
NF_ADDR=(page_address>>16)&0xff; 4th.Cycle' C" l, q0 U; g' r1 N! y: @
其中NF_CMD和NF_ADDR分别是NandFlash的命令寄存器和地址寄存器的地址解引用,我一般这样定义它们,
( V; V: \! P. l3 @
#define rNFCMD (*(volatile unsigned char *)0x4e000004) //NADD Flash command
( h+ X" j2 ?6 K4 r$ l3 o#define rNFADDR (*(volatile unsigned char *)0x4e000008) //NAND Flash address
. ]8 d0 F3 f& q/ f% q事实上,当NF_CMD=0x01时,地址寄存器中的第8位(A8)将被设置为1(如上文分析,A8位不在我们传递的地址中,这个位其实就是硬件电路根据 01h或是00h这两个命令来置高位或是置低位),这样我们传递column_addr的值256随然由于数据溢出变为1,但A8位已经由于NF_CMD =0x01的关系被置为1了,所以我们传到地址寄存器里的值变成了
4 o- Y ^8 d5 Z( [/ x: _- d) s" y! y* D. [4 R
A0 A1 A2 A3 A4 A5 A6 A7 A8
# E1 @1 S; Q1 C: Y8 ?6 ?) E1 0 0 0 0 0 0 0 1
5 R% N% Y$ w) @2 m& T+ e, X4 G
! v7 W- t6 G# Y+ o- D! T这8个位所表示的正好是256,这样读操作将从此页的第256号byte(2nd half的第0号byte)开始读取数据。 nand_flash.c中包含3个函数
& T% P& D/ L F( a0 o
void nf_reset(void);
& o1 J2 ^ G ?! A" bvoid nf_init(void);
# D# R+ A$ l0 O! _void nf_read(unsigned int src_addr,unsigned char *desc_addr,int size);( |$ a- w! n2 ?& q
nf_reset()将被nf_init()调用。nf_init()是nand_flash的初始化函数,在对nand flash进行任何操作之前,nf_init()必须被调用。
* I! Q7 J0 r$ X/ J" k, r Knf_read(unsigned int src_addr,unsigned char *desc_addr,int size);为读函数,src_addr是nand flash上的地址,desc_addr是内存地址,size是读取文件的长度。
0 W6 a& Y) D: i% J. J \- Z在nf_reset和nf_read函数中存在两个宏
?) R6 k! }0 \" k* e, W- a& w" ONF_nFCE_L();
$ K3 e( v" _7 N3 c6 Q. F/ }% S1 INF_nFCE_H();2 h, J0 h$ n' X9 E0 Y
你可以看到当每次对Nand Flash进行操作之前NF_nFCE_L()必定被调用,操作结束之时NF_nFCE_H()必定被调用。这两个宏用于启动和关闭Flash芯片的工作(片选/取消片选)。至于nf_reset()中的
6 h3 V. Q9 }; d: V
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
6 K2 w5 X9 g- I6 P这一行代码是对NandFlash的控制寄存器进行初始化配置,rNFCONF是Nand Flash的配置寄存器,各个位的具体功能请参阅s3c2410数据手册。
% W; C: w8 a5 K$ O0 j' H现在举一个例子,假设我要从Nand Flash中的第5000字节处开始读取1024个字节到内存的0x30000000处,我们这样调用read函数
9 N, d% D8 W5 o& l8 }; `6 enf_read(5000, 0x30000000,1024);
' d9 W9 y3 `3 g! L$ {5 R w8 f我们来分析5000这个src_addr.
' j Z& c! b$ ?. _! M9 R
根据
" X8 J$ {) P& @% p: Icolumn_addr=src_addr%512; $ x' X4 P4 r* \% Q- a9 d' j
page_address=(src_addr>>9); 7 y: o, T$ b$ n
我们可得出column_addr=5000%512=392
, a0 N9 L" k9 L% [1 s- q& dpage_address=(5000>>9)=9
1 k7 j: P, y4 V0 t于是我们可以知道5000这个地址是在第9页的第392个字节处,于是我们的nf_read函数将这样发送命令和参数
8 I9 y: ~3 z. [% G; J, W7 y" N" w
column_addr=5000%512;4 t& C: k. a. _
>page_address=(5000>>9);
' _* F o+ Z& x% v8 ^1 c j. tNF_CMD=0x01; 从2nd half开始读取6 ?5 ?3 O( w$ Q2 s# L$ b
NF_ADDR= column_addr &0xff; 1st Cycle
1 l! ]" o" I) f0 E$ UNF_ADDR=page_address&0xff; 2nd.Cycle3 M( i) q; [* }& c( U. |% Z
NF_ADDR=(page_address>>8)&0xff; 3rd.Cycle5 @7 M, J- t5 y& v& ]8 M# u2 k
NF_ADDR=(page_address>>16)&0xff; 4th.Cycle" ]; M3 K- j' x F* G
向NandFlash的命令寄存器和地址寄存器发送完以上命令和参数之后,我们就可以从rNFDATA寄存器(NandFlash数据寄存器)读取数据了.
) H2 u) {3 e- V/ I) o7 Z8 G2 {
我用下面的代码进行数据的读取.
6 p+ _ }" O; {. I+ `for(i=column_addr;i<512;i++)# ? `( ?- a# X( N5 Z
{2 n1 N/ ?9 K5 I2 W7 b' m
*buf++=NF_RDDATA();/ ~2 o. }! p* o3 X* D" e4 x* Z/ ]
}
9 E8 [" I: m) W* w每当读取完一个Page之后,数据指针会落在下一个Page的0号Column(0号Byte).
( h" u# p& n9 l% V& b' h% K& ~
下面是源代码:
v" y9 x( `3 E. G- o/*
! N" Z' |7 T' U" G$ ]5 Q/ |
www.another-prj.com' h8 {; l B; W+ d
% q$ H" a; m2 c( f; z) P' f
author: caiyuqing
) F3 V$ w$ H& A) d2 j
3 l, W8 a2 G( [0 m2 F& e G; l# M 本代码只属于交流学习,不得用于商业开发
# A9 K4 Z! d& [4 l4 i6 l*/
4 g5 A; ?' j3 w( p5 s#include "s3c2410.h"
& u" g. Q/ w$ g3 Y# T#include "nand_flash.h"
6 Y/ P7 Y8 v/ ~+ k! V$ `; S
static unsigned char seBuf[16]={0xff};
9 j6 ]4 V8 ?/ Q; U; ]//--------------------------------------------------------------------------------------
) S! Y5 k) p+ y, A$ uunsigned short nf_checkId(void)
% y# j* K% Z! G: `* J ~
{
U& l7 A* }: `( g1 B* ?' ` int i;
+ }" Y7 @" |& T; n* g! S unsigned short id;
% r/ b7 q$ n; F
NF_nFCE_L(); //chip enable
2 r7 O: E4 J8 `/ Y0 ]% \: [: F
) O8 H0 @3 Z. u. L8 j: ` NF_CMD(0x90); //Read ID
( @9 b2 A6 H. M) t/ | |
NF_ADDR(0x0);
1 x3 L" u& x, u% `+ b
for(i=0;i<10;i++); //wait tWB(100ns)
s! a. D, I L: X* d 8 X; x, \' W: o
id=NF_RDDATA()<<8; // Maker code(K9S1208V:0xec)
z- A6 t5 P9 y7 e+ n id|=NF_RDDATA(); // Devide code(K9S1208V:0x76)
2 o! \2 c$ M* S# `0 ~3 Y# [& W
% Y1 x# _! {- ^ NF_nFCE_H(); //chip enable
/ W( i: Q$ r0 y
return id;
7 D: x$ N( I# y$ i}
u7 Z# E; y" m- {//--------------------------------------------------------------------------------------
* Q; A& B9 c4 k' e
static void nf_reset(void)
1 g2 j2 W; ]& @7 z1 k, I, l# Z* V{
7 I' Y+ F& X1 t5 m( }4 n8 T int i;
9 ~- ]- k! T& a/ g* V NF_nFCE_L(); //chip enable
( m; }- {" v) P$ s4 r' ]$ | f
NF_CMD(0xFF); //reset command
# {4 G, u7 N) I4 b& }4 m! X5 j3 q! ^) r
for(i=0;i<10;i++); //tWB = 100ns.
O8 x/ C' u( A8 [
NF_WAITRB(); //wait 200~500us;
. O7 F# R% \- m, \3 W NF_nFCE_H(); //chip disable
, D) b2 ]8 m6 O* ^3 a* I9 Q
}
# R/ H# k/ t" U9 J. f/ B J& T# g
//--------------------------------------------------------------------------------------
3 a! n8 j, ]* ?7 i1 a* `
void nf_init(void)
9 z! p! y) b) }6 e3 F5 Q A
{
9 o1 H# a+ v+ B
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
7 Z1 v) b/ J) V9 i // 1 1 1 1 1 xxx r xxx, r xxx
1 }% c9 S8 O# ^# M* i$ a // En r r ECCR nFCE=H tACLS tWRPH0 tWRPH1
6 f; S7 `5 [8 W
nf_reset();
0 b% q, `' D, A3 A! f" T5 ?' x
}
5 |& M& n& u% L
//--------------------------------------------------------------------------------------
) Y4 j- U% d( X1 i
/ z* k* N; I2 i" \4 e z6 N" c2 ^$ Fvoid nf_read(unsigned int src_addr,unsigned char *desc_addr,int size)
( N, G/ G5 R; i5 T3 e{
c" C, J2 k8 m+ ]* F, Q, C
int i;
6 j0 J2 ~4 t$ B) Q7 X6 J
unsigned int column_addr = src_addr % 512; // column address
& c) ^( n8 w/ \) k unsigned int page_address = (src_addr >> 9); // page addrress
3 }4 t6 ^! `9 {" ~2 O+ |
unsigned char *buf = desc_addr;
( J5 M, m: T( [% N( h W: C
while((unsigned int)buf < (unsigned int)(desc_addr) + size)
: s R/ B* o' \5 G; k: h {
8 E/ ]. G9 R+ e! e) C, P6 T
NF_nFCE_L(); // enable chip
0 T" x( ]8 I% m: ]2 o
3 v# V* u& V5 f
/*NF_ADDR和NF_CMD为nand_flash的地址和命令寄存器的解引用*/
7 A A+ y; \( ^4 @0 {5 O* j0 y
if(column_addr > 255) // 2end halft
) |. L( O7 B1 V: H! v! L. ~% a5 K
NF_CMD(0x01); // Read2 command. cmd 0x01: Read command(start from 2end half page)
1 z7 v) W3 F' E. t8 L else
! i) I9 H: Z' A% V* M0 c* X- ] NF_CMD(0x00); // 1st halft?
' {+ e, H% p+ l: A 3 m& n$ v# k+ ]
NF_ADDR(column_addr & 0xff); // Column Address
6 `- m+ ^; v y" m% ]/ }
NF_ADDR(page_address & 0xff); // Page Address
) y* p* B9 y' s6 N8 P4 v; k
NF_ADDR((page_address >> 8) & 0xff); // ...
6 I! H a. o8 l NF_ADDR((page_address >> 16) & 0xff); // ..
9 x1 F [; J3 l5 B6 g) D
for(i = 0; i < 10; i++); // wait tWB(100ns)/////??????
, [0 v G) ~. |
NF_WAITRB(); // Wait tR(max 12us)
# D. x! x6 h i+ o
) W! S! R2 N) W; ?1 ? H
// Read from main area
; Q- N ~4 _5 K
for(i = column_addr; i < 512; i++)
, ]( m& @2 @ K) r0 \+ Z
{
* Q0 E3 _# s/ o" g: [
*buf++= NF_RDDATA();
# _' w- {1 _% W
}
# _3 o/ Q" {+ |1 `
NF_nFCE_H(); // disable chip
1 q8 b7 N/ g2 }( D/ }
column_addr = 0;
$ A7 Z, b# ?* T: ^3 Z# i
page_address++;
$ q( O) q6 }1 \, r0 F: j& v }
) u7 ^4 l0 T& I1 C2 R6 g return ;
% d/ h, N. A2 |" \1 l
}