目录

  • NAND Flash介绍
  • ECC算法
  • FTL
  • 参考代码
    • ECC
      • 写入数据后获取ECC并写入相应区域
      • 读出数据、获取ECC并重新计算ECC,校验
      • ECC校正
    • FTL
      • 标记某一个块为坏块
      • 标记某一个块已经使用
      • 逻辑块号转换为物理块号
      • 创建LUT表

NAND Flash介绍

Flash根据存储单元电路的不同,可以分为NOR Flash和NAND Flash。NOR Flash的数据线和地址线分开,可以实现和RAM一样的随机寻址功能。NAND Flash数据线和地址线复用,不能利用地址线随机寻址,读取时只能按页读取。

由于NAND Flash引脚上可复用,因此读取速度比NOR Flash慢,但是擦除和写入速度更快,并且由于NAND Flash内部电路简单,数据密度大,体积小,成本低,因此大容量的Flash都是NAND型的,而小容量(2~12MB)的Flash大多为NOR型。
       在使用寿命上,NAND Flash的可擦除次数是NOR Flash的数倍。另外,NAND Flash可以标记坏块,从而使软件跳过坏块,而NOR Flash一旦损坏则无法再使用。

以NAND Flash芯片HY27UF081G2A为例,该芯片通过EXMC接口,与微控制器连接,如下图所示。本文并不介绍该芯片的具体用法,而是介绍管理NAND Flash所需要的ECC算法、FTL等。

ECC算法

由于Nand Flash串行组织的存储结构,数据读取时,读出放大器所检测的信号强度会被削弱,降低信号的准确性,导致读数出错,通常采用ECC算法进行数据检测及校准。

ECC(Error Checking and Correction)是一种错误检测和校准的算法。NAND Flash数据产生错误时一般只有1bit出错,而ECC能纠正1bit错误和检测2bit的错误,并且计算速度快,但缺点是无法纠正1bit以上的错误,且不确保能检测2bit以上的错误(至于为什么,可以看下面的原理介绍再思考一下)。

ECC算法的基本原理如下:假设对512个字节的数据进行校验,那么将这些数据视为512行、8列的矩阵,即每行表示1字节数据,矩阵的每个元素表示1位(bit),如下图所示。校验过程分为行校验和列校验(下面将bitn中的n称为索引值)。

列校验:首先将矩阵每个列进行异或,得到如上图所示1号阴影区域的8位数据。其次每次取出4位并进行异或,重复进行6次,将得到的6位数据称为CP0~CP5:
CP0=bit0 ^bit2 ^bit4 ^bit8(每取1位隔1位,索引值对应二进制的bit0为0的位)
CP1=bit1 ^bit3 ^bit5 ^bit7(每隔1位取1位,索引值对应二进制的bit0为1的位)
CP2=bit0 ^bit1 ^bit4 ^bit5(每取2位隔2位,索引值对应二进制的bit1为0的位)
CP3=bit2 ^bit3 ^bit6 ^bit7(每隔2位取2位,索引值对应二进制的bit1为1的位)
CP4=bit0 ^bit1 ^bit2 ^bit3(每取4位隔4位,索引值对应二进制的bit2为0的位)
CP5=bit4 ^bit5 ^bit6 ^bit7(每隔4位取4位,索引值对应二进制的bit2为1的位)

列校验最终得到的校验值即为上述6位数据

行校验:首先将矩阵每个行进行异或,得到如上图所示2号阴影区域的512位数据。其次每次取出256位并进行异或,重复进行18次,将得到的数据称为RP0~RP17:
RP0=bit0 ^bit2 ^bit4 ^… ^bit510(每取1位隔1位,索引值对应二进制的bit0为0的位)
RP1=bit1 ^bit3 ^bit5 ^… ^bit511(每隔1位取1位,索引值对应二进制的bit0为1的位)
RP2=bit0 ^bit1 ^bit4 ^bit5 ^… ^bit508 ^bit509(每取2位隔2位,索引值对应二进制的bit1为0的位)
RP3=bit2 ^bit3 ^bit6 ^bit7 ^… ^bit510 ^bit511(每隔2位取2位,索引值对应二进制的bit1为1的位)
……
RP16=bit0 ^bit1 ^… ^bit254 ^bit255(每取256位隔256位,索引值对应二进制的bit9为0的位)
RP17=bit256 ^bit257 ^… ^bit510 ^bit511(每隔256位取256位,索引值对应二进制的bit9为1的位)

行校验最终得到的校验值即为上述18位数据。

综上所述,通过汉明码编码的ECC校验,n字节的数据对应的校验值为2log2n+6位(log2n指以2为,n的对数)。校验值在对应数据写入Nand Flash时一同写入,被保存到空闲区域中(空闲区域是NAND Flash中特有的,用来存在除有效数据外的数据,比如校验值等)。微控制器读取对应数据时将对应校验值一同读取,并对数据进行再一次校验,将新得到的校验值与读取到的校验值进行异或,此时得到的结果为1表示校验码不同,可以判定产生了错误:如果新得到的CP1和读取到的CP1异或后为1,即两者不同,表示数据中的1、3、5、7列中存在错误,如果新得到的RP16和读取到的RP16异或后为1,表示数据中的0 ~ 255行中存在错误。校验值进行异或得到的结果有以下几种:

  1. 全为0表示数据无错误;
  2. 一半为1时,表示出现了1bit的错误,新得到的校验值与读取到的校验值中的CP5、CP3、CP1进行异或得到的3bit数据为错误位的列地址,RP2log2n-1、…、RP5、RP3、RP1进行异或得到的数据为错误位的行地址;
  3. 只有1位为1表示空闲区域出现错误;
  4. 其它情况则说明至少2bit数据错误。

一般器件出现2bit及以上的错误很少见,因此汉明码编码的ECC校验基本上够用。

一般EXMC模块中NAND Flash对应的Bank都包含带有ECC算法的硬件模块,因此ECC算法的使用实际上通过寄存器完成配置即可。

注意,NAND Flash芯片不会提供ECC计算的,只提供了空闲区域而已。ECC算法使用需要在存入数据时在微控制器中计算,并写入对应的空闲区域中。然后再读出时重新计算,并与写入值进行比较。

如果觉得上面的ECC算法很难理解,实际上可以看一下老鼠试毒这篇文章,原理有点像,通过有限位的变化,反推出大量数据中某个数据的变化。

FTL

NAND Flash在生产和使用过程中都有可能产生坏块,并且每个块的擦除次数有限,即超过一定次数后将无法擦除,也就产生了坏块(注意Flash只能由1变0,因此必须擦除才能正常写入)。坏块的存在使得NAND Flash物理地址不连续,而微控制器访问存储设备时要求地址连续,否则无法通过地址直接读写存储设备,因此一般添加闪存转换层FTL(Flash Translation Layer)完成对NAND Flash的操作。FTL的功能如下:

  1. 标记坏块
           当通过读写数据或ECC校验检测出坏块时,需要将其标记以不再对该区域进行读写操作。坏块的标记一般是将空闲区域的第一个字节写入非0xFF的值来表示。
  2. 地址映射管理
           FTL将逻辑地址映射到NAND Flash中可读写的物理地址,并创建相应的映射表,当处理器对相应的逻辑地址读写数据时,实际上是通过FTL读写Nand Flash中对应的物理地址,如下图所示。由于坏块导致的物理地址不连续,逻辑地址对应的物理地址不固定,逻辑地址1可能对应物理地址5,逻辑地址3可能对应物理地址2


3. 坏块管理和磨损均衡
       坏块管理:当坏块产生后,用空闲并且可读写的块替代坏块在映射表中的位置,保证每个逻辑地址都映射到可读写的物理地址。例如上图的物理地址1损坏后,可以用还没使用的物理地址3187代替,这个时候使逻辑地址1对应该物理地址即可。
       磨损均衡:向某个已写入值的块重新写入数据时,向其它块写入并使其替代原本已写入值的块在映射表中的位置,这是由于每个块的擦除及编程次数有限,为防止部分块访问次数过多而提前损坏,使得全部块尽可能地同时达到磨损阈值。例如想再次向上图的物理地址1写入,可以不再次擦除它,而是擦除还没使用的物理地址3187并将数据写入该物理地址,最后使逻辑地址1对应该物理地址即可。

参考代码

//Nand Flash操作定义
#define NAND_CMD_AREA       (*(__IO u8 *)(BANK_NAND_ADDR | EXMC_CMD_AREA))  //写命令
#define NAND_ADDR_AREA      (*(__IO u8 *)(BANK_NAND_ADDR | EXMC_ADDR_AREA)) //写地址
#define NAND_DATA_AREA      (*(__IO u8 *)(BANK_NAND_ADDR | EXMC_DATA_AREA)) //读写数据

ECC

写入数据后获取ECC并写入相应区域

u32 NandWritePage(u32 block, u32 page, u32 column, u8* buf, u32 len)
{……//设置写入地址NAND_CMD_AREA  = NAND_CMD_WRITE_1ST;           //发送写命令NAND_ADDR_AREA = (column >> 0) & 0xFF;         //列地址低位NAND_ADDR_AREA = (column >> 8) & 0xFF;         //列地址高位NAND_ADDR_AREA = (block << 6) | (page & 0x3F); //块地址和列地址NAND_ADDR_AREA = (block >> 2) & 0xFF;          //剩余块地址NandDelay(NAND_TADL_DELAY);                    //tADL等待延迟//写入数据byteCnt = 0;eccCnt = 0;for(i = 0; i < len; i++){//写入数据NAND_DATA_AREA = buf[i];//保存ECC值byteCnt++;if(byteCnt >= 512){//等待FIFO空标志位while(RESET == exmc_flag_get(EXMC_BANK1_NAND, EXMC_NAND_PCCARD_FLAG_FIFOE));//获取ECC值ecc[eccCnt] = exmc_ecc_get(EXMC_BANK1_NAND);  //用于获取对应ECC值的固件库函数eccCnt++;//清空计数byteCnt = 0;}}//计算写入ECC的spare区地址eccAddr = NAND_PAGE_SIZE + 16 + 4 * (column / 512);//设置写入Spare位置NandDelay(NAND_TADL_DELAY);             //tADL等待延迟NAND_CMD_AREA  = 0x85;                  //随机写命令NAND_ADDR_AREA = (eccAddr >> 0) & 0xFF; //随机写地址低位NAND_ADDR_AREA = (eccAddr >> 8) & 0xFF; //随机写地址高位NandDelay(NAND_TADL_DELAY);             //tADL等待延迟//将ECC写入Spare区指定位置for(i = 0; i < eccCnt; i++){NAND_DATA_AREA = (ecc[i] >> 0)  & 0xFF;NAND_DATA_AREA = (ecc[i] >> 8)  & 0xFF;NAND_DATA_AREA = (ecc[i] >> 16) & 0xFF;NAND_DATA_AREA = (ecc[i] >> 24) & 0xFF;}//发送写入结束命令NAND_CMD_AREA = NAND_CMD_WRITE_2ND;……
}

读出数据、获取ECC并重新计算ECC,校验

u32 NandReadPage(u32 block, u32 page, u32 column, u8* buf, u32 len)
{……//设置读取地址NAND_CMD_AREA  = NAND_CMD_READ1_1ST;           //发送读命令NAND_ADDR_AREA = (column >> 0) & 0xFF;         //列地址低位NAND_ADDR_AREA = (column >> 8) & 0xFF;         //列地址高位NAND_ADDR_AREA = (block << 6) | (page & 0x3F); //块地址和列地址NAND_ADDR_AREA = (block >> 2) & 0xFF;          //剩余块地址NAND_CMD_AREA  = NAND_CMD_READ1_2ND;           //读命令结束if(NANDWaitRB(0)) {return NAND_FAIL;}          //等待RB = 0if(NANDWaitRB(1)) {return NAND_FAIL;}          //等待RB = 1NandDelay(NAND_TRR_DELAY);                     //tRR延时等待//读取数据byteCnt = 0;eccCnt = 0;for(i = 0; i < len; i++){//读取数据buf[i] = NAND_DATA_AREA;//保存ECC值byteCnt++;if(byteCnt >= 512){//等待FIFO空标志位while(RESET == exmc_flag_get(EXMC_BANK1_NAND, EXMC_NAND_PCCARD_FLAG_FIFOE));//获取ECC值eccHard[eccCnt] = exmc_ecc_get(EXMC_BANK1_NAND);eccCnt++;//清空计数byteCnt = 0;}}//计算读取ECC的spare区地址eccAddr = NAND_PAGE_SIZE + 16 + 4 * (column / 512);//设置读取Spare位置NandDelay(NAND_TWHR_DELAY);             //tWHR等待延迟NAND_CMD_AREA  = 0x05;                  //随机读命令NAND_ADDR_AREA = (eccAddr >> 0) & 0xFF; //随机读地址低位NAND_ADDR_AREA = (eccAddr >> 8) & 0xFF; //随机读地址高位NAND_CMD_AREA  = 0xE0;                  //命令结束NandDelay(NAND_TWHR_DELAY);             //tWHR等待延迟NandDelay(NAND_TREA_DELAY);             //tREA等待延时//从Spare区指定位置读出之前写入的ECCfor(i = 0; i < eccCnt; i++){spare[0] = NAND_DATA_AREA;spare[1] = NAND_DATA_AREA;spare[2] = NAND_DATA_AREA;spare[3] = NAND_DATA_AREA;eccFlash[i] = ((u32)spare[3] << 24) | ((u32)spare[2] << 16) | ((u32)spare[1] << 8) | ((u32)spare[0] << 0);}//校验并尝试修复数据for(i = 0; i < eccCnt; i++){if(eccHard[i] != eccFlash[i]){if(0 != NandECCCorrection(buf + 512 * i, eccFlash[i], eccHard[i])){return NAND_FAIL;}}}//读取成功return NAND_OK;
}

ECC校正

u32 NandECCCorrection(u8* data, u32 eccrd, u32 ecccl)
{……eccrdo = NandECCGetOE(1, eccrd); //获取eccrd的奇数位eccrde = NandECCGetOE(0, eccrd); //获取eccrd的偶数位eccclo = NandECCGetOE(1, ecccl); //获取ecccl的奇数位ecccle = NandECCGetOE(0, ecccl); //获取ecccl的偶数位eccchk = eccrdo ^ eccrde ^ eccclo ^ ecccle;//全1,说明只有1bit ECC错误if(eccchk == 0xFFF) {errorpos = eccrdo ^ eccclo; printf("NandECCCorrection: errorpos:%d\r\n", errorpos); bytepos = errorpos / 8; data[bytepos] ^= 1 << (errorpos % 8);}//不是全1,说明至少有2bit ECC错误,无法修复else{printf("NandECCCorrection: 2bit ecc error or more\r\n");return 1;} return 0;
}

FTL

标记某一个块为坏块

void FTLBadBlockMark(u32 blockNum)
{//坏块标记mark,任意值都OK,只要不是0XFF//这里写前4个字节,方便FTL_FindUnusedBlock函数检查坏块(不检查备份区,以提高速度)u32 mark = 0xAAAAAAAA;//在第一个page的spare区,第一个字节做坏块标记(前4个字节都写)NandWriteSpare(blockNum, 0, 0, (u8*)&mark, 4);//在第二个page的spare区,第一个字节做坏块标记(备份用,前4个字节都写)NandWriteSpare(blockNum, 1, 0, (u8*)&mark, 4);
}

标记某一个块已经使用

u32 FTLSetBlockUseFlag(u32 blockNum)
{u8 flag = 0xCC;return NandWriteSpare(blockNum, 0, 1, (u8*)&flag, 1);
}

逻辑块号转换为物理块号

u32 FTLLogicNumToPhysicalNum(u32 logicNum)
{if(logicNum > s_structFTLDev.blockTotalNum){return INVALID_ADDR;}else{return s_structFTLDev.lut[logicNum];}
}

创建LUT表

LUT:显示查找表,在这里用于登记有效的块(即好块),并将其标记上对应的逻辑块编号(也就是使该物理块对应1个逻辑块)

u32 CreateLUT(void)
{u32 i;        //循环变量iu8  spare[6]; //Spare前6个字节数据u32 logicNum; //逻辑块编号//清空LUT表for(i = 0; i < s_structFTLDev.blockTotalNum; i++){s_structFTLDev.lut[i] = INVALID_ADDR;}s_structFTLDev.goodBlockNum = 0;s_structFTLDev.validBlockNum = 0;//读取NandFlash中的LUT表for(i = 0; i < s_structFTLDev.blockTotalNum; i++){//读取Spare区NandReadSpare(i, 0, 0, spare, 6);if(0xFF == spare[0]){NandReadSpare(i, 1, 0, spare, 1);}//是好快if(0xFF == spare[0]){//得到逻辑块编号logicNum = ((u32)spare[5] << 24) | ((u32)spare[4] << 16) | ((u32)spare[3] << 8) | ((u32)spare[2] << 0);//逻辑块号肯定小于总的块数量if(logicNum < s_structFTLDev.blockTotalNum){//更新LUT表s_structFTLDev.lut[logicNum] = i;}//好块计数s_structFTLDev.goodBlockNum++;}else{printf("CreateLUT: bad block index:%d\r\n",i);}}//LUT表建立完成以后检查有效块个数for(i = 0; i < s_structFTLDev.blockTotalNum; i++){if(s_structFTLDev.lut[i] < s_structFTLDev.blockTotalNum){s_structFTLDev.validBlockNum++;}}//有效块数小于100,有问题.需要重新格式化if(s_structFTLDev.validBlockNum < 100){return 1;}//LUT表创建完成return 0;
}

GD32学习笔记(3)NAND Flash管理相关推荐

  1. NAND FLASH学习笔记之nand flash基础(一)

    我入职以来接触的第一个实践内容就是MTD下的NAND FLASH的驱动,下面我将从nand flash的基础和驱动程序两个方面来探讨该知识点,同时最后我会把自己的 动手实验也展示出来,我学习是基于jz ...

  2. GD32学习笔记1(高难度工程,点亮一个LED灯)

    系列文章目录 第一章 GD32学习笔记1(高难度工程,点亮一个LED灯) 文章目录 系列文章目录 前言 一.工作流程 二.新建工程的准备工作 三.新建工程 四.工程目录管理 五.代码实现 1.初始化 ...

  3. 『SQL Server 2000 Reporting Services学习笔记』(1)报表管理器的使用 与 通过角色分配配置安全性...

    『SQL Server 2000 Reporting Services学习笔记』(1)报表管理器的使用 与 通过角色分配配置安全性 __________________________________ ...

  4. 学习笔记:MOOC 文献管理与信息分析

    学习笔记:MOOC 文献管理与信息分析 文章目录 学习笔记:MOOC 文献管理与信息分析 前言 本科硕士博士的差异 科研的特性 读研的意义 学习策略 学习与搜索 两种类型的知识 什么是需求? 搜商 基 ...

  5. blfs(systemd版本)学习笔记-配置远程访问和管理lfs系统

    要实现远程管理和配置lfs系统需要配置以下软件包: 前几页章节脚本的配置:https://www.cnblogs.com/renren-study-notes/p/10390598.html wget ...

  6. MySQL学习笔记8:权限管理、数据库备份与设计

    1.前言 学习视频源自:[狂神说Java]MySQL最新教程通俗易懂 B站视频链接:https://www.bilibili.com/video/BV1NJ411J79W [狂神说Java]MySQL ...

  7. Nand Flash管理算法介绍之FTL简介

    和传统磁盘相比,Nand Flash存储设备存储延迟低.功耗低.更高的存储密度.抗震型号更好和噪声低.但是,由于Nand Flash的特性影响(读写擦的单位不一致,每个块有P/E次数限制),Nand ...

  8. STM32F103学习笔记——SPI读写Flash(二)

      此系列文章是小白学习STM32的一些学习笔记.小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流! 目录 1.软件设计流程 2.SPI初始化 3.SPI发送接收一字节函数编写 4.FLASH ...

  9. 【学习笔记】C++内存管理-侯捷

    文章目录 C++内存管理:从平地到万丈高楼 一.primitives-基础工具 1.内存分配的四个层面 2.new/delete表达式 2.1 new表达式 2.2 delete表达式 学习笔记源自博 ...

最新文章

  1. 当Docker遇到Intellij IDEA,再次解放了生产力~
  2. Lambda表达式入门,看这篇就够了!
  3. 卷积神经网络(CNN)新手指南
  4. python学习第一章要点
  5. 计算机无法用telnet,为何我的电脑cmd没法使用telnet命令?
  6. IPC$连接常见问答
  7. HDU2049(错列排序)
  8. Laravel5.5执行 npm run dev时报错,提示cross-env找不到(not found)的解决办法
  9. Web报表工具iReport 1.2.2 详解
  10. 云计算决策指南:解析医疗的7大解决方案
  11. 使用bus hound发送包的方法
  12. STM32串口通信以寄存器地址和HAL两种方式实验Hello Windows!
  13. 布局中颜色搭配怎么看最舒服之白色的最佳10种颜色搭配
  14. java 数组怎么求和,感动,我终于学会了Java对数组求和
  15. tensorflow 机器学习资料及其工具库
  16. redis恢复阿里云rdb文件
  17. excel导出文件加密
  18. 华硕触摸板关闭后AsusTPCenter.exe占用太多cpu资源
  19. maven项目中报错 could not find class that it depends on找不到依赖的问题
  20. python小海龟turtle绘图作业代码

热门文章

  1. “王者荣耀”与大数据平台的产品化思想
  2. 存档属性是做什么用的?
  3. mysql中field的用法_MySQL FIELD()用法及代碼示例
  4. spring赌上未来的一击:WebFlux性能实测
  5. 【springcloud alibaba】 一条龙服务实现微服务案例
  6. Chained row
  7. 2022-2027年中国倒车雷达行业市场全景评估及发展战略规划报告
  8. android 回编译失败,Androidkiller 回编译失败
  9. 所有手机中的「北斗导航」应用,都是山寨货
  10. 移动web开发viewport记录