Mini2440 nand flash 操作总结
首先要感谢CSDN论坛的赵春江老师的文章。这篇文章基本上是他文章的内容。
百度上搜 “CSDN 赵春江” 就能找到。
一:硬件连接
Nand flash操作对于自己编写mini2440启动代非常关键。因为一般来说,嵌入式系统都有一片nand flash用来作为存储器。所以掌握nand flash 的操作非常的必要和关键。
在操作之前,首先应该对nand flash 的硬件上有一定的了解。我的mini2440板子自带的是K9F1G08U0A。大小是128M。其芯片上的重要引脚如下:
I/O0~I/O7:数据总线,用于nandflash的所有命令,地址的输入,和数据的双向传输.
CE :芯片使能引脚
CLE (Command Latch Enable):命令锁存允许引脚
ALE(Address Latch Enable):地址锁存允许引脚
WE/ : 写芯片允许引脚
RE/ : 读芯片允许引脚
RB : 状态读取引脚(低电平表示忙;高电平表示空闲)
其他引脚:诸如电源之类
因为S3C2440硬件自带有nand flash控制器,所以具有相对应的引脚可以和nand flash芯片连接。
系统总线引脚DATA0~DATA7 :直接与I/O0~I/O7相连接
nFCE/GPA22 :直接与CE引脚相连
CLE/GPA17 :直接与CLE引脚相连
ALE/GPA18 :直接与ALE引脚相连
nFWE/GPA19 :直接与WE/引脚相连
nFRE/GPA20 :直接与WE/引脚相连
FRnB :直接与RB引脚相连
除此之外,S3C2440还有另外几个引脚用于配置nandflash控制器。比如页大小,地址周期数等等。这些引脚主要有如下:
OM[1:0] ; NCON ; GPG13 ; GPG14 ; GPG15。 他们的配置方式在S3C2440说明书上已经很详细。
所以综上所述,S3C2440 与 nandflash(K9F1G08U0A)的硬件连接图如下所示:
二:nand flsah 工作原理简介
Nand flash 主要是用于大容量的存储。它的优点是性价比高,容量大,体积小;缺点是不能随机访问(这是为什么程序不能直接在nandflash中运行的根本原因),容易产生坏块,一般要进行ECC校验。另外nand flash智能把0写成1,不能把1写成0,所以每次往里面写东西,都必须先进行擦除操作。
Nand flash 的内存分为 “块” 和 “页”。一个页里面有两部分,一部分是Main区,另一部分是spare区。Main区主要用于存放信息数据,spare区主要用来存放ECC校验等。不同的芯片其页的大小不同,块的大小也不同。K9F1G08U0A的页大小为 (2K+64)Byte ,其中64字节就是spare区的大小。那么128M(不包含spare区)的大小,一共就有65536个页。它的一个块包含64个页,所以一共就有1024个块。
因为该芯片的页大小是2K+64 字节,所以寻址任意一页内的字节需要12根地址总线;
另外,根据芯片总大小(包括spare区)可以算出需要的地址总线一共是 28根。但是为了兼容1G(如果包括spare区,大于1G),地址总线一共是31根。前面提到的12根地址总线表示的是列地址,即寻址的是每一页里面的内容。除了列地址总线,剩余的地址总线称为行地址,用于寻址页。因为nand flash 为了缩小体积,只有8根I/O口,所以为了区分不同的操作,需要事先写入相关的命令来实现。下面是说明书上的一些命令值:
Nand flash 的常用操作一般有
初始化操作
复位操作
读取芯片ID ;读取任意页的整页内容;读取指定页的任意字节;
写任意页的整页内容;写指定页的任意地址字节
三:S3C2440 nand flash 控制器的相关寄存器介绍
主要可以分为三类。
第一类:配置、控制寄存器
NFCONF(配置寄存器):主要用来配置操作时序的长短。
NFCONT(控制寄存器):第一位用来控制nFCE/GPA22,即芯片的是能引脚。
第四位用来复位ECC。
第五位main区ECC解锁
第六位spare区ECC解锁
第二类:数据传输寄存器
NFCMMD:用于IO口传送(低8位可用)命令
NFDATA , 用于IO口传送(32位可用)数据。注意:因为是32位的,所以对该寄存器进行一次读,硬件会自动对flash进行四次读操作,并把四个字节的内容依次填入NFDAT寄存器。所以最好定义一个该地址的8位寄存器。写入数据也是一样的。
#define rNFDATA8 *((char *)0X4E000010)
NFADDR 用于IO口传送(低8位可用)地址
第三类:ECC校验相关寄存器
NFMECCD0/1 :nandflash的main区ECC寄存器(用来装载读取到得main区的ECC校
验码)
NFSECCD : nandflash的spare 区ECC寄存器(用来装载读取到的spare区的ECC
校验码)
NFMECC0/1 :nandflash用于自动保存硬件产生的main区ECC寄存器
NFSECC :nandflash用于自动保存硬件产生的spare区的ECC寄存器
第四类:状态寄存器
NFSTAT :nandflash状态寄存器 。
第0位可以用于判断nandflash是否在忙。
第2位对应RnB引脚信号。注意硬件上只能使RnB从低变高,所以每次操
作时,都应该先向这位写1,来清除RnB信号。
NFESTAT0/1:nandflash的ECC状态寄存器
四:基于S3C2440的nandflash操作函数(C语言版)的编写
首先宏定义S3C2440的相关寄存器。因为这个工作很简单,这里省略。但是千万要注意下面这个宏的定义
#define rNFDATA8 *((char *)0X4E000010)
接着对于nand flash的一些命令进行宏定义
#define READ_1 0x00 //页读命令周期1
#define READ_2 0x30 //页读命令周期2
#define READ_ID 0x90 //读ID命令
#define WRITE_1 0x80 //页写命令周期1
#define WRITE_2 0x10 //页写命令周期2
#define ERASE_1 0x60 //块擦除命令周期1
#define ERASE_2 0xd0 //块擦除命令周期2
#define READ_STATUS 0x70 //读状态命令
#define RESET 0xff //复位
#define RANDOM_READ_1 0x05 //随意读命令周期1
#define RANDOM_READ_2 0xE0 //随意读命令周期2
#define RANDOM_WRITE 0x85 //随意写命令
下面再宏定义一些简单的操作
#define ENABLE_NANDFLASH rNFCONT &= ~(1<<1) //使能芯片EN
#define DISABLE_NANDFLASH rNFCONT |= (1<<1) //不使能芯片EN
#define RESET_RNB rNFSTAT |= (1<<2) //RnB信号复位(清零)
#define WAIT_RNB_HIGH while(!(rNFSTAT&(1<<2))) //等待RnB信号变高。即
//等待芯片空闲
#define ECC_INIT rNFCONT |= (1<<4) //复位ECC
#define MAIN_ECC_UNLOCK rNFCONT &= ~(1<<5) //解锁main区的ECC
#define MAIN_ECC_LOCK rNFCONT |= (1<<5) //锁定main区的ECC
#define SPARE_ECC_UNLOCK rNFCONT &= ~(1<<6) //解锁spare区的ECC
#define SPARE_ECC_LOCK rNFCONT |= (1<<6) //锁定spare区的ECC
/*******************************
**nandflash 初始化函数
**功能:配置相应的IO引脚
**设置一些时序相关的时间
*******************************/
void NandFlash_Init ( void )
{
rGPACON = 0x3f<<17; //配置芯片控制引脚
rNFCONF = (1<<12)|(2<<8)|(0<<4)|(0<<0); //这里的时间配置。要注意和芯片的
//对应性!!!TACLS=1、TWRPH0=2、
//TWRPH1=0,8位IO
rNFCONT=(0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<
1)|(1<<0);
//非锁定,屏蔽nandflash中断,初
//始化ECC及锁定main区和spare
//区ECC,使能nandflash片选及控
//制器
}
/*****************************
**nandflash 的复位函数
**貌似nandflash使用前
**必须复位一次
*****************************/
void NandFlash_Reset()
{
ENABLE_NANDFLASH; //使能芯片EN
RESET_RNB; //RnB信号复位(清零)
rNFCMD = RESET; //写入nandflash的复位命令
WAIT_RNB_HIGH ; //等待RnB信号变高,即不忙
DISABLE_NANDFLASH; //不使能芯片EN
}
/***********************************
**写入一页函数
***********************************/
unsigend char NandFlash_Write_Page(unsigned int pages)
{
unsigned int i;
unsigned int main_ecc, spare_ecc;
unsigned char stat, temp;
temp = rNF_IsBadBlock(page_number>>6); //判断该块是否为坏块
if(temp == 0x33)
return 0x42; //是坏块,返回
ECC_INIT ; //复位ECC
MAIN_ECC_UNLOCK; //解锁main区ECC
ENABLE_NANDFLASH; //使能芯片EN
RESET_RNB; //RnB信号复位(清零)
rNFCMD=WRITE_1 //页读命令周期1
//写入5个地址周期
rNFADDR=(0x00); //列地址A0~A7
rNFADDR=(0x00); //列地址A8~A11
rNFADDR=(pages & 0xff); //行地址A12~A19
rNFADDR=((pages >> 8) & 0xff); //行地址A20~A27
rNFADDR=((pages >> 16) & 0xff); //行地址A28
//写入一页数据
for (i = 0; i < 2048; i++)
{
rNFDATA8=(INbuffer
); //INbuffer[2048]为全局数组
}
MAIN_ECC_LOCK; //锁定main区的ECC值
main_ecc=rNFMECC0; //读取main区的ECC校验码
//把ECC校验码由字型转换为字节型,并保存到全局变量数组ECCBuf中
ECCBuf[0]=(U8)(main_ecc&0xff);
ECCBuf[1]=(U8)((main_ecc>>8) & 0xff);
ECCBuf[2]=(U8)((main_ecc>>16) & 0xff);
ECCBuf[3]=(U8)((main_ecc>>24) & 0xff);
SPARE_ECC_UNLOCK; //解锁spare区的ECC
//把main区的ECC值写入到spare区的前4个字节地址内,即第2048~2051地址
for(i=0;i<4;i++)
{
rNFDATA8=ECCBuf;
}
SPARE_ECC_LOCK; //锁定spare区的ECC值
spare_ecc=rNFSECC; //读取spare区的ECC校验码
//把ECC校验码保存到全局变量数组ECCBuf中
ECCBuf[4]=(U8)(spare_ecc&0xff);
ECCBuf[5]=(U8)((spare_ecc>>8) & 0xff);
//把spare区的ECC值继续写入到spare区的第2052~2053地址内
for(i=4;i<6;i++)
{
rNFDATA8=ECCBuf;
}
rNFCMD=WRITE_2 //页写命令周期2
delay(1000); //延时一段时间,以等待写操作完成
rNFCMD=READ_STATUS; //读状态命令
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
DISABLE_NANDFLASH; //关闭nandflash片选
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
if (stat & 0x1)
{
temp = rNF_MarkBadBlock(page_number>>6); //标注该页所在的块为坏块
if (temp == 0x21)
return 0x43 //标注坏块失败
else
return 0x44; //写操作失败
}
else
return 0x66; //写操作成功
}
注释:对于写入一页内容思路上不难理解。关键在于ECC校验码部分有点难懂。当向nand flash写入或者读取数据时,s3c2440的nandflash控制器会自动产生ECC校验码,并把它保存在NFMECC0/1和NFSECC寄存器中。当然前提是要解锁main区和spare区的ECC。解锁的意思就是允许硬件产生新的ECC校验码。所以在写一页数据时,解锁main区的ECC校验码,写完之后锁定main区的ECC校验码。然后这一过程由硬件产生的ECC校验码就被保存在了NFMECC0/1寄存器中。然后读取该寄存器的值,将其写入到对应页的spare区的前面四个字节中。当然在写之前要首先解锁spare区的ECC,等把mian区的ECC校验码写完之后,锁定spare区的ECC,然后就可以读取NFSECC寄存器里面产生的spare区的ECC校验码了,再把它写入到spare区的第5,6字节处即可。当然在这一过程中,会涉及到对坏块的判断,这在后面会介绍。
/**********************************
**读取芯片ID函数
**返回一个32位数据,最低8位表示
**的是设备ID,次低8位是厂商ID
**********************************/
unsigned int NandFlash_Read_ID()
{
unsigned char FID; //厂商ID,factory_id
unsigned char DID; //设备ID,device_id
unsigned char ID_detail1,ID_detail2,ID_detail3; //详细id信息
ENABLE_NANDFLASH; //使能芯片EN
RESET_RNB; //RnB信号复位(清零)
rNFCMD = READ_ID; //读ID命令
rNFADDR = 0x00; //写0x00地址
//读五个周期的ID
FID = rNFDATA8; //厂商ID:0xEC
DID = rNFDATA8; //设备ID:0xDA
ID_detail1 = rNFDATA8; //更加详细ID信息
ID_detail1 = rNFDATA8; //更加详细ID信息
ID_detail1 = rNFDATA8; //更加详细ID信息
DISABLE_NANDFLASH; //不使能芯片EN
return ((unsined int)FID*0x10000+DIV); //返回FID和DID
}
/****************************
**读取一页内容函数
**读一页函数
****************************/
unsigned char NandFlash_Read_Page(unsigned int pages)
{
unsigned int i;
unsigned int main_ecc, spare_ecc;
ECC_INIT ; //复位ECC
MAIN_ECC_UNLOCK; //解锁main区ECC
ENABLE_NANDFLASH; //使能芯片EN
RESET_RNB; //RnB信号复位(清零)
rNFCMD=READ_1 //页读命令周期1
//写入5个地址周期
rNFADDR=(0x00); //列地址A0~A7
rNFADDR=(0x00); //列地址A8~A11
rNFADDR=(pages & 0xff); //行地址A12~A19
rNFADDR=((pages >> 8) & 0xff); //行地址A20~A27
rNFADDR=((pages >> 16) & 0xff); //行地址A28
rNFCMD=READ_2 //页读命令周期2
WAIT_RNB_HIGH ; //等待RnB信号变高,即不忙
//读取一页数据内容
for (i = 0; i < 2048; i++) //1页是2KB。所以i取到2048
{
buffer = rNFDATA8; //注意,这里的buffer【2048】数组是一个全局的数组
}
MAIN_ECC_LOCK(); //锁定main区ECC值
SPARE_ECC_UNLOCK(); //解锁spare区ECC
main_ecc=rNFDATA; //读spare区的前4个地址内容,即第2048~2051地址,这4个字节为main区的ECC
//这里的4个字节应当理解成硬件自动完成的。虽然总线只有8跟
//把读取到的main区的ECC校验码放入NFMECCD0/1的相应位置内
rNFMECCD0=((main_ecc&0xff00)<<8)|(main_ecc&0xff);
rNFMECCD1=((mecc0&0xff000000)>>8)|((mecc0&0xff0000)>>16);
SPARE_ECC_LOCK(); //锁定spare区的ECC值
spare_ecc=rNFDATA(); //继续读spare区的4个地址内容,即第2052~2055地址,其中前2个字节为spare区的ECC值
//把读取到的spare区的ECC校验码放入NFSECCD的相应位置内
rNFSECCD=((spare_ecc&0xff00)<<8)|(spare_ecc&0xff);
DISABLE_NANDFLASH; //不使能芯片EN
//判断所读取到的数据是否正确
if ((rNFESTAT0&0xf) == 0x0)
return 0x66; //正确
else
return 0x44; //错误
}
这一过程页并不难以理解。还是ECC部分有一点难懂。其思想就是,在读取main区的时候,解锁mian区的ECC,那么读取的过程就会产生mian区的ECC校验码,并自动保存到NFMECC0/1寄存器中。然后再把spare区中的前四个字节(写该页时产生的main区ECC校验码)读取出来,和现在读取的ECC校验码作对比即可。而这个作对比的过程也可以有硬件完成,只要把读取到的ECC校验码装载进NFMECCD0/1寄存器,然后再根据NFESTAT状态寄存器的相应位即可判断。
/***************************************
***在固定页内任意地址读字节函数
***************************************/
unsigned char NANDFLASH_Ramdom_Read(unsigend int pages, unsigend int add)
{
ENABLE_NANDFLASH; //使能芯片EN
RESET_RNB; //RnB信号复位(清零)
rNFCMD=READ_1 //页读命令周期1
//写入5个地址周期
NF_ADDR=(0x00); //列地址A0~A7
NF_ADDR=(0x00); //列地址A8~A11
NF_ADDR=(pages & 0xff); //行地址A12~A19
NF_ADDR=((pages >> 8) & 0xff); //行地址A20~A27
NF_ADDR=((pages >> 16) & 0xff); //行地址A28
rNFCMD=READ_2; //页读命令周期2
WAIT_RNB_HIGH ; //等待RnB信号变高,即不忙
rNFCMD=RANDOM_READ_1 //随意读命令周期1
//页内地址
rNFADDR=((char)(add&0xff)); //列地址A0~A7
rNFADDR=((char)((add>>8)&0x0f)); //列地址A8~A11
rNFCMD=RANDOM_READ_2; //随意读命令周期2
return rNFDATA8; //读取数据并返回
}
/***************************************
***在固定页内任意地址写字节函数
***************************************/
unsigned char NANDFLASH_Ramdom_Write(unsigned int pages, unsigned int add, unsigned char dat)
{
unsigned char temp,stat;
ENABLE_NANDFLASH; //使能芯片EN
RESET_RNB; //RnB信号复位(清零)
rNFCMD=WRITE_1; //页写命令周期1
//写入5个地址周期
NF_ADDR=(0x00); //列地址A0~A7
NF_ADDR=(0x00); //列地址A8~A11
NF_ADDR=(pages & 0xff); //行地址A12~A19
NF_ADDR=((pages >> 8) & 0xff); //行地址A20~A27
NF_ADDR=((pages >> 16) & 0xff); //行地址A28
rNFCMD=RANDOM_WRITE; ; //随意写命令
//页内地址
NF_ADDR=((char)(add&0xff)); //列地址A0~A7
NF_ADDR=((char)((add>>8)&0x0f)); //列地址A8~A11
rNFDATA8=dat; //写入数据
rNFCMD = WRITE_2; //页写命令周期2
delay(1000); //延时一段时间
rNFCMD=READ_STATUS; //读状态命令
//判断状态值的第6位是否为1,即是否在忙,该语句的作用与NF_DETECT_RB();相同
do{
stat = NF_RDDATA8();
}while(!(stat&0x40));
DISABLE_NANDFLASH; //关闭nandflash片选
//判断状态值的第0位是否为0,为0则写操作正确,否则错误
if (stat & 0x1)
return 0x44; //失败
else
return 0x66; //成功
}
//下面介绍上文中提到的判断坏块以及标注坏块的那两个程序:
//rNF_IsBadBlock和rNF_MarkBadBlock。在这里,我们定义在
//spare区的第6个地址(即每页的第2054地址)用来标注坏块,
//0x44表示该块为坏块。要判断坏块时,利用随意读命令来读
//取2054地址的内容是否为0x33,要标注坏块时,利用随意写
//命令来向2054地址写0x33。下面就给出这两个程序,它们的
//输入参数都为块地址,也就是即使仅仅一页出现问题,我们
//也标注整个块为坏块。
unsigned char rNF_IsBadBlock(unsigend int block)
{
return NANDFLASH_Ramdom_Read(block*64, 2054);
}
unsigend char rNF_MarkBadBlock(unsigend int block)
{
unsigend char result;
result = NANDFLASH_Ramdom_Write(block*64, 2054, 0x33);
if(result == 0x44)
return 0x21; //写坏块标注失败
else
return 0x60; //写坏块标注成功
}
中国计量学院 郑星
newstar111@163.com