常用的FLASH主要有NOR FLASH和NAND FLASH两种:
NAND FLASH不能执行代码,主要用于存储大量数据;具有极高的单元密度,可以达到高存储密度,并且写入和檫除速度也很快;应用困难在于FLASH的管理需要特殊的系统接口。
NOR FLASH中的数据掉电不会丢失,而且可以执行程序,常用于存储系统的启动代码;特点是芯片内执行(XIP),应用程序可以直接在NOR FLASH内运行,不需要再把代码读到系统RAM中。很低的写入和檫除速度大大影响了它的性能。
为什么NAND FLASH无法执行程序呢?这主要是由于NAND FLASH的接口主要包括几个I/O口,对其中数据的访问是穿行的访问,无法实现随机访问,因此NAND FLASH无法执行程序。
因为NAND FLASH接口电路是通过NAND FLASH控制器与S3C2440处理器相接的,因为读者不需要关心具体的访问时序,之所以提供NAND FLASH控制器就是为了便于使用NAND FLASH。如果没有NAND FLASH控制器,则需要产生相应的访问时序。
NAND FLASH的存储器组成主要有两部分:页(Page)、块(BIock)。以K9F2G08U0B为例:
每页大小为:2K 64字节,2K字节用来存储数据,64字节主要用来存储控制信息,主要是为了便于管理每一页。
每块大小为:64个页;整个NAND FLASH由2K(2048)个块组成(128KPages)。
从数据输出可以看到K9F2G08U0B的容量是256MBytes*8Bit,怎么算出来的呢?
NAND FLASH容量=块的数目*每块的容量
=块的数目*(每块包含页的数目*每页的容量)
=2K块*(64页*(2KBytes 64Bytes))
=2K块*64页*2KBytes 2K块*64页*64Bytes
=256MBytes 8MBytes(256MBytes表示该NAND FLASH可以存储256M个字节数据,后面的8M字节的数据主要用于保存每一页的控制信息)
1K(Bytes)=1024字节,1M(Bytes)=1K(Bytes)*1K(Bytes);
NAND FLASH接口电路:
S3C2440处理器内部已经集成了NAND FLASH控制器,因此,接口电路将变得很简单,只需要将S3C2440处理器对应引脚和NAND FLASH的对应引脚接上即可。
但是不同的NAND FLASH的容量不一样,接口线宽不一样,S3C2440处理器如何获得这些信息呢?先对NAND FLASH控制器的作用进行简单的总结:
1.提供外接NAND FLASH的接口信息,包括接口线宽、容量等信息。
2.提供访问NAND FLASH所需要的时序信息。
S3C2440处理器提供了如下方法来识别NAND FLASH接口线宽和容量,OM[1:0]、NCON、GPG13、GPG14和GPG15共5个信号来选择NAND FLASH启动。
OM[1:0]:当两个引脚均为低电平是,从NAND FLASH启动。
NCON:NAND FLASH的类型选择信号。0、正常型NAND FLASH;1、高级型NAND FLASH;
GPG13:NAND FLASH页容量选择信号。0、256Word(NCON=0)或1KWords(NCON=1);
1、512Bytes(NCON=0)或2KBytes(NCON=1);
GPG14:NAND FLASH地址周期选择。0、3地址周期(NCON=0)或4地址周期(NCON=1)。
1、4地址周期(NCON=0)或5地址周期(NCON=1)。
GPG15:NAND FLASH接口线宽选择。0、8位总线宽度。1、16位总线宽度。
如何访问NAND FLASH?
发送命令,即对NAND FLASH采取哪种操作,读,写还是擦除;
发送地址,即对NAND FLASH的哪一页进行上述操作;
发送数据,在此期间要检测NAND FLASH的内部状态。
NAND FLASH内部的地址如何确定呢?从对应数据手册上可以找到有关地址序列的信息:
所谓的列地址即在一页中的地址,因为每页大小是2K 64Bytes,所以需要12根地址线来寻址,也即A0~A11,整个NAND FLASH包含128K个页面,则需要17跟地址线来寻址一个页面,即A12~A28,这也就是所谓的行地址。
S3C2440 NAND FLASH控制器,初始化只需要根据K9F2G08U0B数据手册给出的时序参数,正确初始化S3C2440处理器相关的寄存器即可。1.配置GPACON寄存器,将GPA17~GPA22设置为NAND FLASH控制器信号模式。2.配置NAND FLASH,主要是初始化寄存器NFCONF.
A.寄存器NFCONT,用于开启NAND FLASH控制器。
B.向寄存器NFCMD写入命令。向NAND FLASH发送命令,只需要将命令写入该寄存器即可,NAND FLASH控制器会根据上述参数自动产生出访问NAND FLASH所需要的命令信号。
C.向NAND FLASH发送命令:#define NF_CMD(cmd) {rNFCMD=(cmd);}
D.向寄存器NFADDR写入地址:#define NF_ADDR(addr) {rNFADDR=(addr);}
E.使用寄存器NFDATA进行数据读写,在此期间需要检查NFSTAT来检测NAND FLASH的状态。
F.寄存器NFSTAT,用于指示NAND FLASH是否处于忙状态。
#define NF_DETECT_RB() {while(!(rNFSTAT&(1<<2)));}
使用宏代替简单的函数:
在程序开发过程中,经常将一个很大的工程分解为几个小的模块,每个模块使用单独的函数来实现,最后在工程中通过对各个模块函数的调用来实现整个工程所完成的功能,这也是典型的模块化开发技巧。但是,项目中调用关系复杂,尤其是存在多级函数调用时,需要将每一级的返回地址保存在栈中,容易导致溢出,此外函数调用开销也会增大。
为了更好地解决上述问题,一般使用宏的形式来实现规模较小的函数。因为宏调用是预处理阶段,由预处理器对源程序中的宏进行展开,所以宏展开不占用运行时间。因为每一次宏调用都需要进行宏展开,所以会加大程序的代码量,因此规模较大的函数不宜使用宏的形式来实现。
#define rNFCONT (*(volatile unsigned *)0x4E000004) 等等
具体操作函数:
#define NF_Enable() {rNFCONT &=~(1<<1);}
#define NF_Disable() {rNFCONT |=~(1<<1);}
#define NF_Send_Cmd(cmd) {rNFCONT =(cmd);}
#define NF_Send_Addr(addr) {rNFADDR =(addr);}
#define NF_Send_Data(data) {rNFDATA8 =(data);}
#define NF_Enable_RB() {rNFSTAT|=(1<<2);}开启忙检测功能
#define NF_Check_Busy() {while(!(rNFSTAT&(1<<2)));}检测NAND FLASH是否处于忙状态。
#define NF_Read_Byte() {rNFDATA8}
NAND FLASH基本操作函数分析:其中包括复位,初始化,页写入,页读入,快擦除等操作函数。(一般使用命令,是将所有的命令以宏的形式定义好,以后使用的时候直接使用相应的宏即可)
NAND FLASH复位函数:
- static void NF_Reset()
- {
- NF_Enable();//打开片选信号
- NF_Enable_RB();//开启忙信号检测
- NF_Send_Cmd(CMD_RESET);//发送复位命令
- NF_Check_Busy();//检测忙信号
- NF_Disable();//关闭片选信号
- }
- {
NAND FLASH初始化函数:将相应的时序参数写入NAND FLASH控制器即可。分析TACLS、TWRPH0、TWRPH1的值。需要分别与芯片时间对应关系。tCLS-tWP、tWP、tCLH对应。
查对应芯片手册:tCLS=12ns、tWP=12ns、tCLH=5ns(参考值,实际可长),则TACLS=1、TWRPH1=4、TWRPH1=0.
- #define TACLS 1
- #define TWRPH0 4
- #define TWRPH1 0
- void NF_Init(void)
- {
- rGPACON &=~(0X3f<<17);
- rGPACON |=(0X3F<<17); //GPA17~GPA22设置为NAND FLASH控制器信号输出模式;
- rNFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);//三参数写入NFCON的相应位
- rNFCONT = (0<<12)|(1<<0);
- rNFSTAT = 0;
- NF_Reset();
- }
NAND FLASH页写入函数:NAND FLASH主要用于大量数据段的存取,因此向NAND FLASH写入数据是以页为单位的。当然,有些类型的NAND FLASH也支持一个字节一个字节的写入(这就是所谓的随即读写,K9F2G08U0B就支持单字节写入)。NAND FLASH页写入函数的功能是向某一页写入数据。由不同得快组成(2048块),每块含64页,因此,要想写入某一页,首先需要确定该页属于哪一块。即向某一页写入数据,就像给某一层楼的所有用户送快递,首先要找到哪栋楼,然后在确定那层楼。
NAND FLASH页写入命令有两个,分别是0x80和0x10。页写入操作的流程为:
(1)发送页写入命令0x80.
(2)发送页地址。
(3)发送要写入的数据。
(4)发送页写入确认命令0x10。
(5)检测忙信号。
当发送页写入确认命令0x10之前,并没有将数据写入NAND FLASH的存储单元,仅仅是将数据写入了NAND FLASH是数据寄存器(Data Register &S/A)里,只有收到页写入确认命令0x10后,NAND FLASH才会自动将数据寄存器中的数据写入对应的存储单元中(这样做的主要目的是为了降低页写入的时间),同时这也需要一段时间,因此在发送完页写入确认命令后0x10之后,需要不断地检测忙信号,只有NAND FLASH真正写完数据后,才能对其进行后续的操作。
当发送页写入确认命令0x10之前,并没有将数据写入NAND FLASH的存储单元,仅仅是将数据写入了NAND FLASH是数据寄存器(Data Register &S/A)里,只有收到页写入确认命令0x10后,NAND FLASH才会自动将数据寄存器中的数据写入对应的存储单元中(这样做的主要目的是为了降低页写入的时间),同时这也需要一段时间,因此在发送完页写入确认命令后0x10之后,需要不断地检测忙信号,只有NAND FLASH真正写完数据后,才能对其进行后续的操作。
函数NF_WritePage(),该函数有三个参数,第1个是块号,第2个是在快内的页好=号,第3个是一个指针,指向将要写入的数据的基地址。可以使用如下方式调用:
unsigned char buf[2048];
NF_WritePage(17,4,buf);即将buf中的数据写入到NAND flash的第17块中的第4页。需要注意一个问题:向地址寄存器中写入的地址指的是NAND FLASH中的页的绝对地址,即该页距离第0页的绝对地址。由于NAND FLASH中含有的页数目太多,为了便于管理,才使用的块的概念,把整个NAND FLASH分成了2K个块,每块有64页。因此,上例中第17块中的第4页的绝地地址为:17*64+4=1092页
点击(此处)折叠或打开
- void NF_WritePage(unsigned int block,unsigned int page,unsigned char *buffer)
- {
- unsigned int i;
- unsigned int blockPage=(block<<6)+page;//左移6位就相当于乘以了64
- unsigned char *bufPt=buffer;
- NF_Reset(); //复位NAND FLASH
- NF_Enable(); //然后打开NAND FLASH
- NF_Enable_RB();//开启RnB监视模式,开启忙信号检测功能。
-
- NF_Send_Cmd(CMD_WRITE1); /* 写第一条命令,发送页写入命令 */
- NF_Send_Addr(0x00);//整页写入的话列地址设为0即可;发送页的绝对地址,发了5次
- NF_Send_Addr(0x00);//原因在于NAND FLASH的接口电路,地址线和数据线复用的,接口位宽8位,因此每次只能发送一个字节,又因为NAND FLASH的地址是28位的(NAND FLASH地址周期表),需要5个地址周期才能将地址发送完毕,发送完毕后,NAND FLASH内部地址译码电路会自动将收到的地址进行组合,不需要读者关心,但是需要注意发送的顺序,按照先发送低地址,在发送高地址的顺序发送。
- NF_Send_Addr((blockPage) & 0xff);
- NF_Send_Addr((blockPage >> 8) & 0xff);
- NF_Send_Addr((blockPage >> 16) & 0x1);//以上都是根据NAND FLASH的地址周期进行操作的。地址的低12位用于页内寻址,这是对整页进行写入,因此,低12为设为0即可(每页2K,12位),剩下的才是绝对地址。
- for(i=0;i<2048;i++)
- {
- NF_Send_Data(*bufPt++); /* 写一个页512字节到Nand Flash芯片 */
- }//for将2K的数据发送给NAND FLASH,发送的数据是一个字节一个字节地发送,只需要将数据写入数据寄存器,NAND FLASH控制器会自动发送。
- NF_Send_Cmd(CMD_WRITE2);//发送完毕后,发送页写入确认命令。
- NF_Check_Busy(); //忙检测信号,等待NAND FLASH将所有数据写入完毕,在此期间无法对NAND FLASH进行其他操作。
- NF_Disable();//关闭片选信号
- }
- }
NAND FLASH页读取函数:页读取操作也需要两个命令:页读取发送命令0x00和页读取确认命令0x30。基本原理:当发送页读取发送命令0x00后,需要紧接着发送需要读取的页的绝地地址,然后发送页读取确认命令0x30,NAND FLASH收到第2个命令0x30后,自动将数据从内部存储单元复制到NAND FLASH的数据寄存器(Data Register &S/A)里,在此期间需要忙检测信号(如果忙信号有效,说明NAND FLASH存储单元中的数据还没有全部复制到数据寄存器),数据复制完毕后,可以通过读取S3C2440内部的特殊功能寄存器NFDATA来得到所需要的数据。
NAND FLASH页读取命令有两个,分别是0x00和0x30,也读取操作流程为:
(1)发送也读取发起命令0x00。
(2)发送页地址,
(3)发送也读取确认命令0x30
(4)检测忙信号。
(5)从S3C2440处理器寄存器NFDATA中读取数据。
点击(此处)折叠或打开
- void NF_ReadPage(unsigned int block,unsigned int page, unsigned char * dstaddr)//这段程序用于,Nand Flash每页大小是2048个字节
- {
- unsigned int i; //这里没有用到ECC校验;只是简单的读取数据
- unsigned int blockPage = (block<<6)+page; //这里的addr实际上就是页号
- NF_Reset();
- NF_Enable();
- NF_Enable_RB();//5~7复位NAND FLASH,然后打开NAND FLASH(在复位结束后关闭了NAND FLASH,因此需要重新打开),同时开启忙信号检测功能,以后就可以对NAND FLASH进行操作,然后通过检测忙信号来获取NAND FLASH内部的工作状态。
- NF_Send_Cmd(CMD_READ1); //CMD_READ1= 0x00
-
- NF_Send_Addr(0x00);
- NF_Send_Addr(0x00);
- NF_Send_Addr((blockPage) & 0xff);
- NF_Send_Addr((blockPage >> 8) & 0xff);
- NF_Send_Addr((blockPage >> 16) & 0x1);//以上11~15发送绝对地址
- NF_Send_Cmd(CMD_READ2); //CMD_READ12= 0x30 //发送页读取确认命令0x30
- NF_Check_Busy(); //检测忙信号等待NAND FLASH将内部存储单元中的数据复制到数据寄存器中。
- for (i = 0; i < 2048; i++)
- {
- dstaddr[i] = NF_Read_Byte();//读取,从NFDATA寄存器读取数据即可。
- }
- NF_Disable(); //读完后关闭片选信号
- }
NAND FLASH块擦除函数:对NAND FLASH写之前,需要先进行擦除,这是由NAND FLASH自身的存储器结构决定的。对于某一存储单元来说,只能向该单元写0,无法向其写1.所谓擦除是将所有的存储单元全部写为1,然后在对其进行写操作,0可以写入,1虽然无法写入,但是由于擦除时该存储单元已经为1,所以该存储单元保留1.因此,利用擦除操作达到间接地向存储单元写1的目的。
擦除操作是以块为单位进行的,无法对一页进行擦除操作。基本原理:发出块擦除发起命令0x60后,需要发送三个地址周期的块地址(注意,因为快地址只使用了A12~A28,所以需要三个地址周期),最后发送块擦除确认命令(D0H),NAND FLASH接收到块擦除确认命令后会启动内部的擦除过程。可以通过检测忙信号来确定擦除是否完成。
每个页面大小是2K+64Bytes,因此,页内地址使用A0~A11来寻址;每一块包含64页,因此,使用地址A12~A17来寻址,整个NAND FLASH包含2K个块,所以使用A18~A28来寻址。
快擦除操作需要两个命令:块擦除发起命令0x60和块擦除确认命令0xD0。快擦除操作的流程为:
(1)快擦除发起命令0x60
(2)发送块地址。
(3)块擦除确认命令0xD0
(4) 检测忙信号。
点击(此处)折叠或打开
- int NF_EraseBlock(unsigned int block)
- {
- unsigned int blocknum=(block<<6);//将块号左移6位
- NF_Reset();
- NF_Enable();
- NF_Enable_RB();
-
- NF_Send_Cmd(CMD_ERASE1);
- NF_Send_Addr( blocknum & 0xff);
- NF_Send_Addr((blocknum>>8) & 0xff);
- NF_Send_Addr((blocknum>>16) & 0xff);
- NF_Send_Cmd(CMD_ERASE2) ;
- NF_Check_Busy() ;
- NF_Disable() ;
- return 1 ;
- }