GD32学习笔记(3)NAND Flash管理
目录
- 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行中存在错误。校验值进行异或得到的结果有以下几种:
- 全为0表示数据无错误;
- 一半为1时,表示出现了1bit的错误,新得到的校验值与读取到的校验值中的CP5、CP3、CP1进行异或得到的3bit数据为错误位的列地址,RP2log2n-1、…、RP5、RP3、RP1进行异或得到的数据为错误位的行地址;
- 只有1位为1表示空闲区域出现错误;
- 其它情况则说明至少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的功能如下:
- 标记坏块
当通过读写数据或ECC校验检测出坏块时,需要将其标记以不再对该区域进行读写操作。坏块的标记一般是将空闲区域的第一个字节写入非0xFF的值来表示。 - 地址映射管理
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管理相关推荐
- NAND FLASH学习笔记之nand flash基础(一)
我入职以来接触的第一个实践内容就是MTD下的NAND FLASH的驱动,下面我将从nand flash的基础和驱动程序两个方面来探讨该知识点,同时最后我会把自己的 动手实验也展示出来,我学习是基于jz ...
- GD32学习笔记1(高难度工程,点亮一个LED灯)
系列文章目录 第一章 GD32学习笔记1(高难度工程,点亮一个LED灯) 文章目录 系列文章目录 前言 一.工作流程 二.新建工程的准备工作 三.新建工程 四.工程目录管理 五.代码实现 1.初始化 ...
- 『SQL Server 2000 Reporting Services学习笔记』(1)报表管理器的使用 与 通过角色分配配置安全性...
『SQL Server 2000 Reporting Services学习笔记』(1)报表管理器的使用 与 通过角色分配配置安全性 __________________________________ ...
- 学习笔记:MOOC 文献管理与信息分析
学习笔记:MOOC 文献管理与信息分析 文章目录 学习笔记:MOOC 文献管理与信息分析 前言 本科硕士博士的差异 科研的特性 读研的意义 学习策略 学习与搜索 两种类型的知识 什么是需求? 搜商 基 ...
- blfs(systemd版本)学习笔记-配置远程访问和管理lfs系统
要实现远程管理和配置lfs系统需要配置以下软件包: 前几页章节脚本的配置:https://www.cnblogs.com/renren-study-notes/p/10390598.html wget ...
- MySQL学习笔记8:权限管理、数据库备份与设计
1.前言 学习视频源自:[狂神说Java]MySQL最新教程通俗易懂 B站视频链接:https://www.bilibili.com/video/BV1NJ411J79W [狂神说Java]MySQL ...
- Nand Flash管理算法介绍之FTL简介
和传统磁盘相比,Nand Flash存储设备存储延迟低.功耗低.更高的存储密度.抗震型号更好和噪声低.但是,由于Nand Flash的特性影响(读写擦的单位不一致,每个块有P/E次数限制),Nand ...
- STM32F103学习笔记——SPI读写Flash(二)
此系列文章是小白学习STM32的一些学习笔记.小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流! 目录 1.软件设计流程 2.SPI初始化 3.SPI发送接收一字节函数编写 4.FLASH ...
- 【学习笔记】C++内存管理-侯捷
文章目录 C++内存管理:从平地到万丈高楼 一.primitives-基础工具 1.内存分配的四个层面 2.new/delete表达式 2.1 new表达式 2.2 delete表达式 学习笔记源自博 ...
最新文章
- 当Docker遇到Intellij IDEA,再次解放了生产力~
- Lambda表达式入门,看这篇就够了!
- 卷积神经网络(CNN)新手指南
- python学习第一章要点
- 计算机无法用telnet,为何我的电脑cmd没法使用telnet命令?
- IPC$连接常见问答
- HDU2049(错列排序)
- Laravel5.5执行 npm run dev时报错,提示cross-env找不到(not found)的解决办法
- Web报表工具iReport 1.2.2 详解
- 云计算决策指南:解析医疗的7大解决方案
- 使用bus hound发送包的方法
- STM32串口通信以寄存器地址和HAL两种方式实验Hello Windows!
- 布局中颜色搭配怎么看最舒服之白色的最佳10种颜色搭配
- java 数组怎么求和,感动,我终于学会了Java对数组求和
- tensorflow 机器学习资料及其工具库
- redis恢复阿里云rdb文件
- excel导出文件加密
- 华硕触摸板关闭后AsusTPCenter.exe占用太多cpu资源
- maven项目中报错 could not find class that it depends on找不到依赖的问题
- python小海龟turtle绘图作业代码