【OK210试用体验】Nand驱动实现 - 在线问答 - 电子威廉希尔官方网站 论坛 - 最好最受欢迎电子论坛!

【OK210试用体验】Nand驱动实现

刘天 ( 楼主 ) 2015-7-26 11:33:25  只看该作者 倒序浏览
Nand驱动实现
象棋小子    1048272975
Nand flash具有大容量、改写速度快、接口简单等优点,适用于大量数据的存储,为固态大容量存储提供了廉价有效的解决方案。各种电子产品中如手机存储器、sd卡、u盘等均采用Nand flash存储,笔者此处就Nand驱动实现作一个简单的介绍。
1.  Nand flash概述
东芝公司在1989年最先发表Nand flash结构,强调降低每比特的成本,更高的性能,并且像磁盘一样可以通过接口轻松升级。随着Nand威廉希尔官方网站 的发展,根据Nand颗粒存储单元,可以分为SLC(Single Level Cell)、MLC(Multi LevelCell)、TLC(Trinary Level Cell)。SLC为1bit/cell,即1个储存单元存放1bit数据,其特点是成本高、容量小、速度快、寿命长(约10万次擦写)。MLC为2bit/cell,即1个储存单元存放2bit数据,相比SLC来说,其价格一般,容量可以更大,速度一般、寿命一般(约1万次擦写)。TLC为3bit/cell,即1个储存单元存放3bit数据,因此相同容量下其成本最低,容量相比最大,但速度最慢,寿命也最短(约1000次擦写)。目前市面上基于Nand flash的存储器,如sd卡、u盘等大多为MLC型,但对于高容量Nand存储器,如64G以上sd卡等,很有可能为TLC型Nand flash。
2. Nand驱动实现
Nand flash由Block(块)构成,Block的基本单元为Page(页),每个页又分为Data area(数据存储区域)以及Spare area(备用区域)。由于Nand flash在出厂以及在使用过程中会出现坏块,以及会出现位反转的问题,为了数据的可靠性,通常需要采用ecc(Error Correcting Code)算法,一般对于SLC Nand,采用1bit ecc即可,对于MLC Nand,需采用4bit以上ecc,而Spare area即用来保存坏块标记、ecc数据等额外信息。通常Nand flash的读取和编程以页为基础,而擦除却是基于块的。Nand flash编程只能把1编程成0,而不能把0编程成1,因此在页编程时,必须已先擦除。
OK210采用的Nand flash为Micron(美国镁光)的MT29F8G08ABABAWP,一页有(4096+224)Byte,一个Block有128页,容量大小为(1G+224M)Byte,是一款8位宽的SLC Nand flash。
Nand flash驱动一般应实现Nand初始化、页读、页编程、坏块标记、坏块检查、块擦除这几个接口实现。
2.1. Nand初始化
Nand初始化主要是对Nand引脚功能初始化、根据具体的Nand flash,设置最佳的Nand访问时序。
void Nand_Init(void)
{
    // 配置nand控制引脚
    MP01CON_REG =(MP01CON_REG & ~(0xf<<8)) | (0x3<<8); // NFCS0
    MP01PUD_REG&= ~(3<<4); // pull-up/down disable
    MP03CON_REG =0x22222222;
    MP03PUD_REG =0;
    // 配置MT29F8G08ABABAWP timing(HCLK@133M)
    // TACLS=2,(tALS或tCLS-tWP=0)(ALE或CLE有效后需保持才能发出写脉冲)
    //TWRPH0=3,tWP=15ns(最小写脉冲宽度)
    //TWRPH1=1,tALH或tCLH=5ns(写脉冲后ALE或CLE需保持有效时间)
    // 4096Bytes/page, 5 address cycle
    // Disable1-bit and 4-bit ECC
    NFCONF_REG =(2<<12)|(2<<8)|(1<<4)|(0<<3)|(0<<2)|(1<<1);
    // 上升沿检查nand准备好信号线
    NFCONT_REG =(1<<23)|(1<<22)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0);
    Nand_Reset();
}
2.2. Nand页读
MT29F8G08ABABAWP一页分为4k的data area与224字节的spare area,data area用来存储正常数据,spare area用来存储附加数据(如ecc数据)。对于flash存储器,是会出现位反转或坏块的问题,写入flash的数据或从flash读出的数据可能是有错的,因此必须采用ecc算法,确保写入的数据与读出的数据是一致的。对于SLC Nand Flash,只需1位ecc即可,每512 byte产生4 byte的ecc数据,最多可纠错1位,spare area 0~1字节存放坏块标记,2~33字节存放该页ecc数据,34~37字节存放spare area产生的ecc数据,剩余spare area存放接口需要写入的其它spare area数据。
int32_t Nand_ReadWithOob(uint32_t page, uint8_t *data,uint32_t data_len, uint8_t *oob, uint32_t oob_len)
{
uint8_t ecc[32]; // 4 byte ecc/512 byte, total 32byte/4096
uint32_t i;
uint8_t *pecc;
uint8_t *pBuffer;
uint32_t MECC;
uint16_t Col;
uint8_t Status;
   
if (!data && (data_len!=0)) {
    return -1;
}
if (!oob && (oob_len!=0)) {
    return -1;
}
// 保留oob前面38字节作为坏块标记以及数据ecc
if ((data_len>4096) || (oob_len>224-38)) {      
    return -2;
}
// ecc data 32字节开始于spare区的第2个字节偏移处
Col = 4096 + 2;
NF_INIT_MECC();
NF_MECC_UNLOCK(); // 解锁main ECC
   
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_READ0); // page read cycle 1
NF_ADDR(Col & 0xff); // column address
NF_ADDR(Col >> 8); // columu address
NF_ADDR(page & 0xff); // 传输3字节的页地址
NF_ADDR((page>>8) & 0xff);
NF_ADDR((page>>16) & 0xff);
NF_CMD(NAND_CMD_READSTART); // page read cycle 2   
   
NF_WAIT_READY(); // 等待页读完成   
for (i=0; i<32; i++) {
    ecc =NF_READ_BYTE();
}   
        
NF_MECC_LOCK();
MECC = NF_READ_BYTE();
MECC |= NF_READ_BYTE() << 8;
MECC |= NF_READ_BYTE() << 16;
MECC |= NF_READ_BYTE() << 24;   
   
for (i=0; i
    oob =NF_READ_BYTE();   
}
NFMECCDATA0_REG =((MECC<<8)&(0xff<<16)) | (MECC&0xff);
NFMECCDATA1_REG =((MECC>>8)&(0xff<<16)) | ((MECC>>16)&0xff);
   
NF_CE_DISABLE();
   
// Read ecc status
Status = NFESTAT0_REG;
if ((Status & 0x3) == 1) {
    // 1biterror, Correctable
    ecc[(Status>>7)&0x7ff]^= (1 << ((Status>>4) & 0x7));
} else if ((Status & 0x3) > 1) {
    Debug("ECCuncorrectable error detectedrn");
    NF_CE_DISABLE();            
    return -3;
}   
if (data_len == 0) {
    return 0;
}
   
NF_CE_ENABLE(); // 使能片选
NF_CLEAR_RB(); // 清数据传输标志   
   
NF_CMD(NAND_CMD_READ0); // page read cycle 1
NF_ADDR(0); // column address
NF_ADDR(0); // columu address
NF_ADDR(page & 0xff); // 写入3字节的页地址
NF_ADDR((page>>8) & 0xff);
NF_ADDR((page>>16) & 0xff);
NF_CMD(NAND_CMD_READSTART); // page read cycle 2
NF_WAIT_READY(); // 等待命令完成        
   
// read main area
pecc = ecc;
while (data_len) {
    pBuffer =data;
    NF_INIT_MECC();// main区ECC清空
    NF_MECC_UNLOCK();// main区ECC解锁,开始ECC计算
    for (i=0;i<512; i++) {
        *data++ =NF_READ_BYTE();
        data_len--;
        if (data_len == 0) {
            break;
        }
    }
    NF_MECC_LOCK(); // 锁定main ECC
    // SLC: Write ecc to compare
    MECC = (pecc[1] << 16) | (pecc[0] << 0);
    NFMECCDATA0_REG = MECC;
    MECC = (pecc[3] << 16) | (pecc[2] << 0);
    NFMECCDATA1_REG =MECC;
    pecc += 4;
    // Read eccstatus
    Status =NFESTAT0_REG;
    if ((Status& 0x3) == 1) {
    // 1biterror, Correctable
    pBuffer[(Status>>7)&0x7ff]^= (1 << ((Status>>4) & 0x7));
    } else if((Status & 0x3) > 1) {
        Debug("ECCuncorrectable error detectedrn");
        NF_CE_DISABLE();            
        return-4;
    }   
}
NF_CE_DISABLE();   
return 0;
}
2.3. Nand页编程
当要写数据到一个页时,要先确保这个块已经被擦除。数据写完后,为确保写入与读取的数据一致,应同时写入数据的ecc值到spare area约定好的位置。
int32_t Nand_WriteWithOob(uint32_t page, const uint8_t*data, uint32_t data_len, const uint8_t *oob, uint32_t oob_len)
{
uint8_t ecc[32]; // 4 byte ecc/512 byte, total 32byte/4096
uint32_t i;
uint8_t *pecc;
uint32_t MECC;  
uint32_t data_len_temp;
uint16_t Col;
uint8_t State;
   
if (!data && (data_len!=0)) {
    return -1;
}
if (!oob && (oob_len!=0)) {
    return -1;
}
// 保留oob前面38字节作为坏块标记以及数据ecc
if ((data_len>4096) || (oob_len>224-38)) {
    return -2;
}
NF_CE_ENABLE(); // 使能片选
NF_CLEAR_RB(); // 清数据传输标志
   
NF_CMD(NAND_CMD_SEQIN); // page program cycle 1
NF_ADDR(0); // column address
NF_ADDR(0); // columu address
NF_ADDR(page & 0xff); // 写入3字节页地址
NF_ADDR((page>>8) & 0xff);
NF_ADDR((page>>16) & 0xff);     
pecc = ecc;
data_len_temp = data_len;
// write main area
while (data_len_temp) {
    NF_INIT_MECC();// main区ECC清空
    NF_MECC_UNLOCK();// main区ECC解锁,开始ECC计算
    // Writebuffer to main area
    for (i=0;i<512; i++) {
        NF_WRITE_BYTE(*data++);
        data_len_temp--;
        if(data_len_temp == 0) {
            break;
        }
    }      
    NF_MECC_LOCK();// 锁定main ECC     
    MECC =NFMECC0_REG; // 4字节写main区数据的ECC
    *pecc++ =MECC & 0xff;
    *pecc++ =(MECC>>8) & 0xff;
    *pecc++ =(MECC>>16) & 0xff;
    *pecc++ =(MECC>>24) & 0xff;
}
if (data_len < 4096) {
    // 调整到oob区
    Col = 4096;
    NF_CMD(NAND_CMD_RNDIN);// 页内随机写命令
    NF_ADDR(Col& 0xff); // 2字节页内地址
    NF_ADDR((Col>>8)& 0xff);
}
// Reserved for bad block (2 byte)  
NF_WRITE_BYTE(0xff);
NF_WRITE_BYTE(0xff);
   
NF_INIT_MECC();
NF_MECC_UNLOCK(); // 解锁main ECC
// main ecc 32 byte, start spare area offset 2
for (i=0; i<32; i++) {
    NF_WRITE_BYTE(ecc);
}
NF_MECC_LOCK(); // 锁定main ECC
MECC = NFMECC0_REG; // 4字节写main区数据的ECC
NF_WRITE_BYTE(MECC & 0xff); // 继续写入MECC
NF_WRITE_BYTE((MECC>>8) & 0xff);
NF_WRITE_BYTE((MECC>>16) & 0xff);
NF_WRITE_BYTE((MECC>>24) & 0xff);
        
for (i=0; i
    NF_WRITE_BYTE(oob);
}
   
NF_CMD(NAND_CMD_PAGEPROG); // page program cycle 2
   
NF_WAIT_READY(); // 等待写完
NF_CMD(NAND_CMD_STATUS); // 读取nand状态
do {
    State = NF_READ_BYTE();
} while(!(State &(1<<6))); // 等待状态变成Ready
   
NF_CE_DISABLE();
   
// 是否写成功,第0位为0则pass,不然fail
if (State & (1<<0)){
    return -3; // 写不成功
}
return 0;   
}
2.4. Nand坏块检查
坏块标记约定用spare area第0~1字节来作标志,坏块这两字节标志为非0xff,好块为0xff。我们读取block中页0,spare  area第0~1字节的值即可判断这个block是否坏块。
int32_t Nand_IsBadBlock(uint32_t Block)
{
    uint8_toob[2];
    // 每个block第一页spare区0, 1字节非0xff标记为好坏
    Nand_Read(Block,0, 4096, 2, oob);
    if((oob[0]==0xff) && (oob[1]==0xff)) {
        return 0; // 好块
    }
    return 1; // 坏块
}
2.5. 坏块标记
如果页编程不成功或者擦除失败,检查出坏块,需要对相应的块在约定位置进行坏块标记,以免再对这个坏块进行读写。其代码实现如下:
int32_t Nand_MarkBadBlock(uint32_t Block)
{
    // 每个block第一页spare区第0, 1字节标记非0xff坏块
    uint8_t oob[2];
    oob[0] = 0;
    oob[1] = 0;
    returnNand_Write(Block, 0, 4096, 2, oob);
}
2.6. 块区擦除
数据写入块区前,对应的块应已擦除好。
int32_t Nand_EraseBlock(uint32_t Block)
{
uint8_t State;
NF_CE_ENABLE();
NF_CLEAR_RB();
NF_CMD(NAND_CMD_ERASE1); // erase block command cycle1
// write 3 cycle block address[BA7:BA17]
NF_ADDR((Block<<7) & 0xff); // [BA7]
NF_ADDR((Block>>1) & 0xff); // [BA15:BA8]
NF_ADDR((Block>>9) & 0xff); // [BA17:BA16]
NF_CMD(NAND_CMD_ERASE2); // erase block command cycle2
NF_WAIT_READY();
NF_CMD(NAND_CMD_STATUS);
do {
    State =NF_READ_BYTE();
} while(!(State & (1<<6))); // 等待状态变成Ready
   
NF_CE_DISABLE();
// 是否擦写成功,第0位为0则pass,不然fail
if (State & (1<<0)) {
    return -1; //擦除不成功
}   
return 0; // 成功擦除
}
3. Nand启动
对于OneNand/Nand启动设备,BL0除了检验BL1的检验和之外,还会采用8bit ecc检验BL1代码的正确性,BL1在烧录进Nand设备时,还应生成相应的8/16位ECC数据,写入到spare area的指定位置,不然ecc失败也将无法从Nand设备启动。Nand启动需要8bit ecc处理,8bit ecc参考Bootloader中相应的NandBoot.c这个Nand驱动。

                              
图3-1  Nand ECC检验
4. 附录
Nand.rar,1GB 1bit ecc Nand驱动实现。
Nand.rar (3.96 KB, 下载次数: 13)

0个回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则


关闭

站长推荐上一条 /7 下一条

小黑屋|手机版|Archiver|电子发烧友 ( 湘ICP备2023018690号 )

GMT+8, 2024-11-10 18:57 , Processed in 0.301100 second(s), Total 33, Slave 24 queries .

Powered by 电子发烧友网

© 2015 bbs.elecfans.com

微信扫描
快速回复 返回顶部 返回列表