分类: LINUX
2014-08-12 15:33:06
原文地址:NAND Flash--嵌入式NAND Flash读写技术 作者:wpneu
NAND Flash控制器
S
为了支持NAND Flash的启动装载,S
一般情况下,这4KB的启动代码需要将NAND Flash中的内容复制到SDRAM中。使用S
NAND Flash控制其具有以下特性:
* NAND Flash模式:支持读/擦除/编程NAND Flash存储器。
* 自动启动模式:复位后,启动代码被传送到Steppingstone中。传送完毕后,启动代码在Steppingstone中执行。
* 具备硬件ECC(校验码:Error Correction Code)生成模块(硬件生成校验码,通过软件校验)
* NAND Flash启动以后,4KB的内部SRAM缓冲器Steppingstone可以作为其他用途使用。
* NAND Flash控制器不能通过DMA访问,可以使用LDM/STM指令来代替DMA操作。
自启动模式的执行步骤如下:
(1)完成复位
(2)如果自动启动模式使能,NAND Flash存储器的前4KB自动复制到Steppingstone内部缓冲器;
(3)Steppingstone映射到nGCS0;
(4)CPU在Steppingstone的4KB内部缓冲器中开始执行启动代码。
注意:在自动启动模式下,不进行ECC检测。因此,应确保NAND
Flash的前4KB不能有位错误(一般NAND Flash厂家都能确保)。
NAND Flash模式需要进行以下配置:
(1)通过NFCONF寄存器设置NAND Flash配置;
(2)将NAND Flash命令写入NFCONF寄存器;
(3)将NAND Flash地址写入NFADDR寄存器;
(4)通过NFSTAT寄存器检查NAND Flash状态,并读/写数据。在读操作之前或者编程操作之后应该检查R/nB信号。
引脚配置
D[7:0] 数据/命令/地址的输入/输出口(与数据总线共享)
CLE 命令锁存使能(输出)
ALE 地址锁存使能(输出)
nFCE NAND Flash片选使能(输出)
nFRE NAND Flash读使能(输出)
nFWE NAND Flash写使能(输出)
R/nB NAND Flash就绪/忙(输入)
系统启动和NAND Flash所需的配置如下:
(1)OM[1:0]=00b:使能NAND Flash控制器为自动启动模式;
(2)NAND Flash存储器的页面大小应该为512字节;
(3)NCON:NAND Flash存储器寻址步数选择。0为3步;1为4步寻址。
相关寄存器
NAND Flash配置寄存器
NFCONF 地址0x4E000000
NAND Flash命令设置寄存器
NFCMD 地址0x4E000004
NAND Flash地址设置寄存器
NFADDR 地址0x4E000008
NAND Flash数据寄存器
NFDATA 地址0x4E
NAND Flash操作状态寄存器
NFSTAT 地址0x4E000010
NAND Flash ECC寄存器
NFECC 地址0x4E000014
下面针对三星的K
NAND Flash物理组成
正如硬盘的盘片被分为磁道,每个磁道又分为若干扇区,一块nand
flash也分为若干block,每个block分为如干page。一般而言,block、page之间的关系随着芯片的不同而不同,典型的分配是这样的:
1block = 32page
1page = 512bytes(datafield) +
16bytes(oob)
需要注意的是,对于flash的读写都是以一个page开始的,但是在读写之前必须进行flash的擦写,而擦写则是以一个block为单位的。按照这种组织方式形成三类地址
Column Address:列地址,地址的低8位
Page Address:页地址
Block Address:块地址
8个I/O引脚充当地址、数据、命令的复用端口,所以每次传地址只能传8位,而nand
falsh的地址位位26位,因此读写一次nand flash需要传送4次(A[7:0] A[16:9] A[24:17]
A[25]
一页有528B,在每一页中,最后16个字节(OOB)用于nand
flash执行完命令后设置状态用的,剩余512B又分为前半部(1st half Page Register)和后半部(2nd
half Page Register)。可以通过nand flash命令对1st half和2nd half
以及OOB进行定位通过nand flash内置的指针指向各自的首地址
存储操作特点:
1.擦除操作的最小单位是块
2.Nand Flash芯片每一位只能从1变为0,而不能从0变为1,所以在对其进行写入操作之前一定要将相应块擦除(擦除就是将相应块的位全部变为1
3 OOB部分的第六字节(即517字节)标志是否坏块,如果不是坏块该值为FF,否则为坏块
4 除OOB第六字节外,通常至少把OOB前3字节存放Nand
Flash硬件ECC码
NAND Flash寻址方式
512byte需要9bit来表示,对于528byte系列的NAND,这512byte被分成1st
half Page Register和2nd half Page Register,各自的访问由地址指针命令来选择,A[7:0]就是所谓的column
address(列地址),在进行擦除操作时不需要列地址,为什么?因为以块为单位擦除。32个page需要5bit来表示,占用A[13:9],即该page在块内的相对地址。A8这一位地址被用来设置512byte的1st
half page还是2nd half page,0表示1st,1表示2nd。Block的地址是由A14以上的bit来表示。
例如64MB(512Mb)的NAND flash(实际中由于存在spare
area,故都大于这个值),共4096block,因此,需要12个bit来表示,即A[25:14],如果是128MB(1Gbit) 的528byte/page的NAND
Flash,则block address用A[26:14]表示。由于地址只能在I/O[7:0]上传递,因此,必须采用移位的方式进行。以NAND_ADDR 为例:
第1 步是传递column address,就是NAND_ADDR[7:0],不需移位即可传递到I/O[7:0]上,而halfpage pointer即A8 是由操作指令决定的,即指令决定在哪个halfpage 上进行读写,而真正的A8
的值是不需程序员关心的。
第2 步就是将NAND_ADDR
右移9位,将NAND_ADDR[16:9]传到I/O[7:0]上;
第3 步将NAND_ADDR[24:17]放到I/O上;
第4步需要将NAND_ADDR[25]放到I/O上;
因此,整个地址传递过程需要4 步才能完成,即4-step
addressing。 如果NAND Flash 的容量是32MB(256Mbit)以下,那么,block adress最高位只到bit24,因此寻址只需要3步。
Nand flash主要的内设命令
Nand flash命令执行是通过将命令字送到Nand flash控制寄存器的命令寄存器中来执行的,其命令是分周期执行的,每条命令都有一个或多个执行周期,每个执行周期都有相应的代码表示将要执行的动作。
功能 |
第一时钟周期 |
第二时钟周期 |
读取数据寄存器 Read1 |
00h/01h |
|
读取数据寄存器下半区(OOB) Read2 |
50h |
|
读取芯片ID |
90h |
|
RESET |
FFh |
|
写页面(page program) (首先写入00h(A区)/01h(B区)/05h(C区)表示写入区;再写入80h开始编程模式(写入模式),接下来写入地址和数据,最后写入10h表示编程结束。 |
80h |
10h |
块擦除(block erase) |
60h |
D0h |
读取状态(read status) |
70h |
|
Nand Flash地址的计算
Column Address: 列地址。Column Address其实就是指定Page上的某个Byte,指定这个Byte其实也就是指定此页的读写起始地址。
Paage Address:页地址。由于页地址总是以512Bytes对齐的,所以它的低9位总是0。确定读写操作是在Flash上的哪个页进行的。
当我们得到一个Nand Flash地址srcaddr时候,我们可以这样分解出Column
Address和Page Address
columnaddr=srcaddr%512 //column address
pageaddr=srcaddr>>9 //page address
也可以这么认为,一个Nand Flash地址的A0~A7是它的column_addr,A9~A25是它的Page
Address。(注意地址位A8并没有出现,也就是A8被忽略,在下面你将了解到这是什么原因)
以read1命令为例:
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的命令。现在我可以结合上图给你说明为什么K
如上文所提及的,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; ß
从2nd half开始读取
NF_ADDR=column_addr&0xff; 1st Cycle
NF_ADDR=page_address&0xff; 2nd.Cycle
NF_ADDR=(page_address>>8)&0xff; 3rd.Cycle
NF_ADDR=(page_address>>16)&0xff; 4th.Cycle
其中NF_CMD和NF_ADDR分别是NandFlash的命令寄存器和地址寄存器的地址解引用,我一般这样定义它们,
#define rNFCMD (*(volatile unsigned char
*)0x4e000004) //NADD Flash command
#define rNFADDR (*(volatile unsigned char
*)0x4e000008) //NAND Flash address
事实上,当NF_CMD=0x01时,地址寄存器中的第8位(A8)将被设置为1(如上文分析,A8位不在我们传递的地址中,这个位其实就是硬件电路根据
01h或是00h这两个命令来置高位或是置低位),这样我们传递column_addr的值256随然由于数据溢出变为1,但A8位已经由于NF_CMD =0x01的关系被置为1了,所以我们传到地址寄存器里的值变成了
A
0 0
0 0 0
0 0 0
1 & 0xff = 0000 0000
这8个位所表示的正好是256,这样读操作将从此页的第256号byte(2nd
half的第0号byte)开始读取数据。
现在举一个例子,假设我要从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; 从2nd
half开始读取
NF_ADDR= column_addr
&0xff; 1st Cycle
A[7:0]
NF_ADDR=page_address&0xff; 2nd.Cycle A[16:9]
NF_ADDR=(page_address>>8)&0xff; 3rd.Cycle A[24:17]
NF_ADDR=(page_address>>16)&0xff; 4th.Cycle A[25]
向NandFlash的命令寄存器和地址寄存器发送完以上命令和参数之后,我们就可以从rNFDATA寄存器(NandFlash数据寄存器)读取数据了.
我用下面的代码进行数据的读取.
for(i=column_addr;i<512;i++)
{
*buf++=NF_RDDATA();
}
每当读取完一个Page之后,数据指针会落在下一个Page的0号Column(0号Byte).
例如实现一个从某字节处开始读取size大小的数据
static int 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)
{
NF_nFCE_L(); //enable chip
if(column_addr
> 255)
NF_CMD(0x01);
else
NF_CMD(0x00);
NF_ADDR(cloumn_addr
& 0xff); //column address A[7:0];
NF_ADDR(page_address
& 0xff); //page address A[16:9]
NF_ADDR((page_address
>> 8) & 0xff); //A[24:17]
NF_ADDR((page_address
>>16) & 0xff); //A[25];
for(i=0;i<10;i++);
NF_WAITRB();
for(i=column_addr;i<512;i++)
{
*buf++=NF_RDDATA();
}
NF_nFCE_H();
column_addr
= 0;
page_address
++;
}
return
;
}
打开s
#define rNFCONF (*(volatile unsigned *)0x4e000000) //nand flash configuration
#define rNFCMD (*(volatile char *)0x4e000004 //nand flash command
#define rNFADDR (*(volatile char *)0x4e000008 //nand flash address
#define rNFDATA (*(volatile
char *)0x4e
#define rNFSTAT (*(volatile
unsigned *)0x4e000010 //nand flash
opreation status
#define rNFECC (*(volatile int *)0x4e000014 //nand flash ecc
#define rNFECC0 (*(volatile
char *)0x4e000014
#define rNFECC1 (*(volatile char *)0x4e000015
#define rNFECC2 (*volatile char *)0x4e000016
#define NF_CMD(cmd) {rNFCMD=cmd;}
#define NF_ADDR(addr) {rNFADDR=addr;}
#define NF_nFCEL_L() {rNFCONF &= ~(1<<11);}
#define NF_nFCLE_H() {rNFCONF |= (1<<11);}
#define NF_RSTECC() {rNFCONF |= (1<<12);} //Initialize
ECC
#define NF_RDDATA() (rNFDATA)
#define NF_WRDATA(data) {rNFDATA=data;}
#define NF_WATRB() {while(!(rNFSTAT&(1<<0)));}
//读一页数据的程序。
static int NF_RreadPage(int
block,int page,char *buffer)
{
unsigned
int blockpage;
char
*pbuf=buffer;
char
*oob[16];
unsigned
char ecc[3];
page=page&0x
blockpage=(block
<< 5)+page;
NF_RSTECC(); //Initialize ECC;
NF_nFCE_L();
NF_CMD(0x00); //read command;
NF_ADDR(0); //A[7:0]
column=0 从第0字节开始读一直读完512B
NF_ADDR(blockpage&0xff); //A[16:9];
NF_ADDR((blockpage>>8)&0xff); //A[24:17]
NF_ADDR((blockpage>>16)&0xff);
//A[25];
for(i=0;i<10;i++); //wait tWB(100ns)
NF_WAITRB(); //wait tR(max 12us)
for(i=0;i<512;i++)
{
*pbuf++=NF_RDDATA();
}
ecc[0]=rNFECC0;
ecc[1]=rNFECC1;
ecc[2]=rNFECC2;
for(i=0;i<16;i++)
{
oob[i]=NF_RDDATA(); //read oob;
}
NF_nFCE_H();
if(ecc[0]==oob[0]
&& ecc[1] == oob[1] && ecc[2] == oob[2]) //Ecc校验;
{
print("ECC
OK:%x,%x,%x\n",oob[0],oob[1],oob[2]);
return
1;
}else{
printf("ECC
ERROR: read:%x,%x,%x, ECC
reg:%x,%x,%x\n",oob[0],oob[1],oob[2],ecc[0],ecc[1],ecc[2]);
return
0;
}
}
static int NF_WritePage(unsigned
int block,unsigned int page,char *buffer)
{
int
i;
unsigned
int blockpage=(block<<5)+page;
char
*pbuf=buffer;
oobbuf[16]={0xff};
NF_RSRECC();
NF_nFCE_L();
NF_CMD(0x00);
NF_ADDR(blockpage&0xff);
NF_ADDR((blockpage>>8)&0xff);
NF_ADDR((blockpage>>16)&0xff);
for(i=0;i<512;i++)
{
NF_WRDATA(*pbuf++);
}
oobbuf[0]=rNFECC0;
oobbuf[1]=rNFECC1;
oobbuf[2]=rNFECC2;
oobbuf[5]=0xff;
for(i=0;i<16;i++)
{
NF_WRDATA(oobbuf[i]);
}
NF_CMD(0x10); //Write 2nd command;
for(i=0;i<10;i++); //tWB=100ns;
NF_WAITRB();
NF_CMD(0x70); //read status command;
for(i=0;i<3;i++);
if(NF_RDDATA()&0x1) //write error
{
NF_nFCE_H();
NF_MarkBadBlock(block);
return
0;
}else{
NF_nFCE_H();
return
1;
}
}
static int NF_EraseBlock(U32
block)
{
U32 blockPage=(block<<5);
int i;
#if BAD_CHECK//坏块校验
if(NF_IsBadBlock(block))
return
0;
#endif
NF_nFCE_L();//NF的CE(片选)拉低
NF_CMD(0x60); // Erase one block 1st command
NF_ADDR(blockPage&0xff); // 块擦除只针对页
NF_ADDR((blockPage>>8)&0xff);
NF_ADDR((blockPage>>16)&0xff);
NF_CMD(0xd0); // Erase one blcok 2nd command
for(i=0;i<10;i++); //wait
tWB(100ns)//??????
NF_WAITRB(); // Wait tBERS max 3ms.
NF_CMD(0x70); // Read status command
if (NF_RDDATA()&0x1) // Erase error
{
NF_nFCE_H();
Uart_Printf("[ERASE_ERROR:block#=%d]\n",block);
NF_MarkBadBlock(block);
return
0;
}
else
{
NF_nFCE_H();////NF的CE(片选)拉高
return 1;
}
}
NAND设备存在坏块,为和上层文件系统接口,NAND设备的驱动程序必须给文件系统提供一个可靠的存储空间,这就需要ECC(Error
Corection Code)校验,坏块标注、地址映射等一系列的技术手段来达到可靠存储目的。
SSFDC软件规范中,详细定义了如何利用NAND设备每个页中的冗余信息来实现上述功能。这个软件规范中,很重要的一个概念就是块的逻辑地址,它将在物理上可能不连续、不可靠的空间分配编号,为他们在逻辑空间上给系统文件提供一个连续可靠的存储空间。
表3给出了SSFDC规范中逻辑地址的标注方法。在系统初始化的时候,驱动程序先将所有的块扫描一遍,读出他们所对应的逻辑地址,并把逻辑地址和虚拟地址的映射表建好。系统运行时,驱动程序通过查询映射表,找到需要访问的逻辑地址所对应的物理地址然后进行数据读写。
表3 冗余字节定义
字节序号 |
内容 |
字节序号 |
内容 |
512 |
用户定义数据 |
520 |
后256BECC校验和 |
513 |
521 |
||
514 |
522 |
||
515 |
523 |
块逻辑地址 |
|
516 |
数据状态 |
524 |
|
517 |
块状态 |
525 |
前256BECC校验和 |
518 |
块逻辑地址1 |
526 |
|
519 |
527 |
表4给出了块逻辑地址的存放格式,LA表示逻辑地址,P代表偶校验位。逻辑地址只有10bit,代表只有1024bit的寻址空间。而SSFDC规范将NAND设备分成了多个zone,每个zone
内有1024块,但这物理上的1024块映射到逻辑空间只有1000块,其他的24块就作为备份使用,当有坏块存在时,就可以以备份块将其替换。
表4 逻辑地址格式
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
|
|
|||||||||
0 |
0 |
0 |
1 |
0 |
LA9 |
LA8 |
LA7 |
第518 523字节 |
|
|||||||||
|
LA6 |
LA5 |
LA4 |
LA3 |
LA2 |
LA1 |
LA0 |
P |
第519 524字节 |
|||||||||
有了以上的软件规范,就可以对NAND设备写出较标准的ECC校验,并可以编写检测坏块、标记坏块、建立物理地址和逻辑地址的映射表的程序了。
static int NF_IsBadBlock(unsigned
int block)
{
int i;
unsigned int blockPage;
unsigned char data;
blockPage=(block<<5); // For 2'nd cycle I/O[7:5]
NF_nFCE_L();
NF_CMD(0x50); // Spare array read command
NF_ADDR(517&0xf); // Read the mark of bad block in spare
array(M addr=5)
NF_ADDR(blockPage&0xff); // The mark of bad block is in 0 page
NF_ADDR((blockPage>>8)&0xff);
// For block number A[24:17]
NF_ADDR((blockPage>>16)&0xff);
// For block number A[25]
for(i=0;i<10;i++); // wait
tWB(100ns) //?????
NF_WAITRB(); // Wait tR(max 12us)
data=NF_RDDATA();
NF_nFCE_H();
if(data!=0xff)
{
printf("[block %d has been marked
as a bad block(%x)]\n",block,data);
return 1;
}
else
{
return 0;
}
}
static int NF_MarkBadBlock(U32
block)
{
int i;
unsigned int blockPage=(block<<5);
seBuf[0]=0xff;
seBuf[1]=0xff;
seBuf[2]=0xff;
seBuf[5]=0x44; // Bad blcok mark=0
NF_nFCE_L();
NF_CMD(0x50); //read OOB
NF_CMD(0x80); // Write 1st command
NF_ADDR(0x0); // The mark of bad block is
NF_ADDR(blockPage&0xff); // marked 5th spare array
NF_ADDR((blockPage>>8)&0xff);
// in the 1st page.
NF_ADDR((blockPage>>16)&0xff);
//
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]); // Write spare array
}
NF_CMD(0x10); // Write 2nd command
for(i=0;i<10;i++); //tWB = 100ns.
NF_WAITRB(); // Wait tPROG(200~500us)
NF_CMD(0x70);
for(i=0;i<3;i++); //twhr=60ns
if (NF_RDDATA()&0x1) // Spare arrray
write error
{
NF_nFCE_H();
printf("[Program error is occurred
but ignored]\n");
}
else
{
NF_nFCE_H();
}
printf("[block #%d is marked as a bad
block]\n",block);
return 1;
}
int search_logic_block(void) //建立物理地址到逻辑地址的映射表
{
unsigned int
block,i,blockPage,logic_no,zone,zone_i;
unsigned char oob[16];
for(i=0;i
lg2ph[i]=space_block[i]=0xffff;
logic_number=0;
space_nr=0;
NF_nFCE_L();
zone=BLOCK_NR/1024; //确定NAND设备中zone的个数
for(zone_i=0;zone_i
{
//搜索每个zone 内逻辑地址和物理地址的映射关系
for(block=0;block<1024;block++)
{
blockPage=((block+zone_i*1024)<
NF_WATIRB(); //等待R/B#信号有效
NF_CMD(0x50); // 读取每个block内部第0个Page内冗余的16个字节
NF_ADDR(0); // Column 0
NF_ADDR(blockPage&0xff);
NF_ADDR((blockPage>>8)&0xff); // Block & page num.
NF_ADDR((blockPage>>16)&0xff);
NF_WATIRB(); //等待R/B#信号有效
for(i=0;i<16;i++) se[i]=NF_RDDATA(); // Write spare array
NF_WATIRB();
if(oob[5]!=0xff)[q8] //检测是否存在坏块
printk("\n\rphysic block %d is
bad block\n\r",block);
else if(oob[7]!=se[12][q9] )
printk("block
address1:%d!=block address2 %d\n\r",oob[7],oob[12]);
else if((oob[6][q10] &0xf8)==0x10)
{
//计算该block对应的逻辑地址
logic_no=((0x7&oob[6])<<7)+(se[7]>>1)+zone_i*1000;
if(lg2ph[logic_no]!=0xffff) //说明有2个block拥有相同的逻辑地址
printk("physical block %d
and block %d have the same logic number
%d\n",lg2ph[logic_no],block,logic_no);
else lg2ph[logic_no]=block; //将该block的逻辑地址关系记入lg2ph表
logic_number++;
}
else if(oob[7]==0xff) //说明该block尚未编号
{space_block[space_nr]=block;
space_nr++;
}
}
}
printk("there are totally %d logic
blocks\n\r",logic_number);
NF_nFCE_H();
return logic_number;
}
这段代码的主要作用就是产生数组lg2ph[],这个数组的含义就是“块物理地址=lg2ph[逻辑地址]”。
static 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++); //wait
tWB(100ns)////?????
id=NF_RDDATA()<<8; // Maker code(K9S1208V:0xec)
id|=NF_RDDATA(); // Devide code(K9S1208V:0x76)
NF_nFCE_H();
return id;
}
static void NF_Reset(void)
{
int i;
unsigned short id;
NF_nFCE_L();
NF_CMD(0xFF); //reset command
for(i=0;i<10;i++); //tWB = 100ns.
NF_WAITRB(); //wait 200~500us;
NF_nFCE_H();
}
static void NF_Init(void)
{
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
// 1
1 1 1,
1 xxx, r xxx,
r xxx
// En 512B 4step ECCR nFCE=H tACLS tWRPH0
tWRPH1
NF_Reset();
}