STM32H750XB_RT-THREAD/25-FMC—扩展外部NAND/User/nand/bsp_nand.c
2025-07-21 14:34:29 +08:00

727 lines
28 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "./nand/bsp_nand.h"
NAND_HandleTypeDef NAND_Handler; //NAND FLASH句柄
nand_attriute nand_dev; //nand重要参数结构体
/**
* @brief 延迟一段时间
* @param 延迟的时间长度
* @retval None
*/
static void NAND_Delay(__IO uint32_t nCount)
{
__IO uint32_t index = 0;
for(index = ( nCount); index != 0; index--)
{
}
}
//初始化NAND FLASH
uint8_t NAND_Init(void)
{
FMC_NAND_PCC_TimingTypeDef ComSpaceTiming,AttSpaceTiming;
NAND_MPU_Config();
NAND_Handler.Instance=FMC_Bank3;
NAND_Handler.Init.NandBank=FMC_NAND_BANK3; //NAND挂在BANK3上
NAND_Handler.Init.Waitfeature=FMC_NAND_PCC_WAIT_FEATURE_DISABLE; //关闭等待特性
NAND_Handler.Init.MemoryDataWidth=FMC_NAND_PCC_MEM_BUS_WIDTH_8; //8位数据宽度
NAND_Handler.Init.EccComputation=FMC_NAND_ECC_DISABLE; //禁止ECC
NAND_Handler.Init.ECCPageSize=FMC_NAND_ECC_PAGE_SIZE_512BYTE; //ECC页大小为512字节
NAND_Handler.Init.TCLRSetupTime=10; //设置TCLR(tCLR=CLE到RE的延时)=(TCLR+TSET+2)*THCLK,THCLK=1/200M=5ns
NAND_Handler.Init.TARSetupTime=10; //设置TAR(tAR=ALE到RE的延时)=(TAR+TSET+1)*THCLK,THCLK=1/200M=5n。
ComSpaceTiming.SetupTime=10; //建立时间
ComSpaceTiming.WaitSetupTime=10; //等待时间
ComSpaceTiming.HoldSetupTime=10; //保持时间
ComSpaceTiming.HiZSetupTime=10; //高阻态时间
AttSpaceTiming.SetupTime=10; //建立时间
AttSpaceTiming.WaitSetupTime=10; //等待时间
AttSpaceTiming.HoldSetupTime=10; //保持时间
AttSpaceTiming.HiZSetupTime=10; //高阻态时间
HAL_NAND_Init(&NAND_Handler,&ComSpaceTiming,&AttSpaceTiming);
NAND_Reset(); //复位NAND
NAND_Delay(100);
nand_dev.id=NAND_ReadID(); //读取ID
printf("NAND ID:%#x\r\n",nand_dev.id);
NAND_ModeSet(4); //设置为MODE4,高速模式
if(nand_dev.id==MT29F16G08ABABA) //NAND为MT29F16G08ABABA
{
nand_dev.page_totalsize=4320;
nand_dev.page_mainsize=4096;
nand_dev.page_sparesize=224;
nand_dev.block_pagenum=128;
nand_dev.plane_blocknum=2048;
nand_dev.block_totalnum=4096;
}
else if(nand_dev.id==W29N01GVSIAA)//NAND为W29N01GVSIAA
{
nand_dev.page_totalsize=2112;
nand_dev.page_mainsize=2048;
nand_dev.page_sparesize=64;
nand_dev.block_pagenum=64;
nand_dev.plane_blocknum=1024;
nand_dev.block_totalnum=2048;
}else if (nand_dev.id==W29N01HVSINA)
{
nand_dev.page_totalsize=2112;
nand_dev.page_mainsize=2048;
nand_dev.page_sparesize=64;
nand_dev.block_pagenum=64;
nand_dev.plane_blocknum=1024;
nand_dev.block_totalnum=2048;
}
else return 1; //错误,返回
return 0;
}
/**
* @brief NAND FALSH底层驱动,引脚配置,时钟使能
* @note 此函数会被HAL_NAND_Init()调用
* @param 无
* @retval 无
*/
void HAL_NAND_MspInit(NAND_HandleTypeDef *hnand)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_FMC_CLK_ENABLE(); //使能FMC时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIOD时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //使能GPIOE时钟
__HAL_RCC_GPIOG_CLK_ENABLE(); //使能GPIOG时钟
//初始化PD6 R/B引脚
GPIO_Initure.Pin=GPIO_PIN_6;
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //高速
HAL_GPIO_Init(GPIOD,&GPIO_Initure);
//初始化PG9 NCE3引脚
GPIO_Initure.Pin=GPIO_PIN_9;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //输入
GPIO_Initure.Pull=GPIO_NOPULL; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF12_FMC; //复用为FMC
HAL_GPIO_Init(GPIOG,&GPIO_Initure);
//初始化PD0,1,4,5,11,12,14,15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5|\
GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Pull=GPIO_NOPULL;
HAL_GPIO_Init(GPIOD,&GPIO_Initure);
//初始化PE7,8,9,10
GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
HAL_GPIO_Init(GPIOE,&GPIO_Initure);
}
//配置MPU的region
void NAND_MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_Initure;
HAL_MPU_Disable(); //配置MPU之前先关闭MPU,配置完成以后在使能MPU
//配置RAM为region1大小为256MB此区域可读写
MPU_Initure.Enable=MPU_REGION_ENABLE; //使能region
MPU_Initure.Number=NAND_REGION_NUMBER; //设置regionNAND使用的region0
MPU_Initure.BaseAddress=NAND_ADDRESS_START; //region基地址
MPU_Initure.Size=NAND_REGION_SIZE; //region大小
MPU_Initure.SubRegionDisable=0X00;
MPU_Initure.TypeExtField=MPU_TEX_LEVEL0;
MPU_Initure.AccessPermission=MPU_REGION_FULL_ACCESS; //此region可读写
MPU_Initure.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE; //允许读取此区域中的指令
MPU_Initure.IsShareable=MPU_ACCESS_NOT_SHAREABLE;
MPU_Initure.IsCacheable=MPU_ACCESS_NOT_CACHEABLE;
MPU_Initure.IsBufferable=MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_Initure);
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); //开启MPU
}
/**
* @brief 设置NAND速度模式
* @param mode : 0~5, 表示速度模式
* @retval 0,成功; 其他,失败
*/
uint8_t NAND_ModeSet(uint8_t mode)
{
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_FEATURE;//发送设置特性命令
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=0X01; //地址为0X01,设置mode
*(__IO uint8_t*)NAND_ADDRESS=mode; //P1参数,设置mode
*(__IO uint8_t*)NAND_ADDRESS=0;
*(__IO uint8_t*)NAND_ADDRESS=0;
*(__IO uint8_t*)NAND_ADDRESS=0;
if(NAND_WaitForReady()==NSTA_READY)return 0;//成功
else return 1; //失败
}
/**
* @brief 读取NAND FLASH的ID
* @note 不同的NAND略有不同请根据自己所使用的NAND FALSH数据手册来编写函数
* @param 无
* @retval NAND FLASH的ID值
*/
uint32_t NAND_ReadID(void)
{
uint8_t deviceid[5];
uint32_t id;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_READID; //发送读取ID命令
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=0X00;
//ID一共有5个字节
deviceid[0]=*(__IO uint8_t*)NAND_ADDRESS;
deviceid[1]=*(__IO uint8_t*)NAND_ADDRESS;
deviceid[2]=*(__IO uint8_t*)NAND_ADDRESS;
deviceid[3]=*(__IO uint8_t*)NAND_ADDRESS;
deviceid[4]=*(__IO uint8_t*)NAND_ADDRESS;
//镁光的NAND FLASH的ID一共5个字节但是为了方便我们只取4个字节组成一个32位的ID值
//根据NAND FLASH的数据手册只要是镁光的NAND FLASH那么一个字节ID的第一个字节都是0X2C
//所以我们就可以抛弃这个0X2C只取后面四字节的ID值。
id=((uint32_t)deviceid[1])<<24|((uint32_t)deviceid[2])<<16|((uint32_t)deviceid[3])<<8|deviceid[4];
return id;
}
/**
* @brief 读NAND状态
* @param 无
* @retval NAND状态值
* @arg bit0:0,成功;1,错误(编程/擦除/READ)
* @arg bit6:0,Busy;1,Ready
*/
uint8_t NAND_ReadStatus(void)
{
__IO uint8_t data=0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_READSTA;//发送读状态命令
data++;data++;data++;data++;data++; //加延时,防止-O2优化,导致的错误.
data=*(__IO uint8_t*)NAND_ADDRESS; //读取状态值
return data;
}
/**
* @brief 等待NAND准备好
* @param 无
* @retval NSTA_TIMEOUT 等待超时了
* NSTA_READY 已经准备好
*/
uint8_t NAND_WaitForReady(void)
{
uint8_t status=0;
__IO uint32_t time=0;
while(1) //等待ready
{
status=NAND_ReadStatus(); //获取状态值
if(status&NSTA_READY)break;
time++;
if(time>=0X1FFFF)return NSTA_TIMEOUT;//超时
}
return NSTA_READY;//准备好
}
/**
* @brief 复位NAND
* @param 无
* @retval 0,成功; 其他,失败
* NSTA_READY 已经准备好
*/
uint8_t NAND_Reset(void)
{
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_RESET; //复位NAND
if(NAND_WaitForReady()==NSTA_READY)return 0;//复位成功
else return 1; //复位失败
}
/**
* @brief 等待RB信号为某个电平
* @param rb:0,等待RB==0; 1,等待RB==1
* @retval 0,成功; 1,超时
*/
uint8_t NAND_WaitRB(__IO uint8_t rb)
{
__IO uint16_t time=0;
while(time<10000)
{
time++;
if(NAND_RB==rb)return 0;
}
return 1;
}
/**
* @brief 读取NAND Flash的指定页指定列的数据(main区和spare区都可以使用此函数)
* @param PageNum : 要读取的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNum : 要读取的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
* @param *pBuffer : 指向数据存储区
* @param NumByteToRead : 读取字节数(不能跨页读)
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_ReadPage(uint32_t PageNum,uint16_t ColNum,uint8_t *pBuffer,uint16_t NumByteToRead)
{
__IO uint16_t i=0;
uint8_t res=0;
uint8_t eccnum=0; //需要计算的ECC个数每NAND_ECC_SECTOR_SIZE字节计算一个ecc
uint8_t eccstart=0; //第一个ECC值所属的地址范围
uint8_t errsta=0;
uint8_t *p;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_AREA_A;
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)ColNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(ColNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>16);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_AREA_TRUE1;
//下面两行代码是等待R/B引脚变为低电平其实主要起延时作用的等待NAND操作R/B引脚。因为我们是通过
//将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO代码中通过读取NWAIT引脚的电平来判断NAND是否准备
//就绪的。这个也就是模拟的方法所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
//闲状态结果我们就读取了R/B引脚,这个时候肯定会出错的,事实上确实是会出错!大家也可以将下面两行
//代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
res=NAND_WaitRB(0); //等待RB=0
if(res)return NSTA_TIMEOUT; //超时退出
//下面2行代码是真正判断NAND是否准备好的
res=NAND_WaitRB(1); //等待RB=1
if(res)return NSTA_TIMEOUT; //超时退出
if(NumByteToRead%NAND_ECC_SECTOR_SIZE)//不是NAND_ECC_SECTOR_SIZE的整数倍不进行ECC校验
{
//读取NAND FLASH中的值
for(i=0;i<NumByteToRead;i++)
{
*(__IO uint8_t*)pBuffer++ = *(__IO uint8_t*)NAND_ADDRESS;
}
}else
{
eccnum=NumByteToRead/NAND_ECC_SECTOR_SIZE; //得到ecc计算次数
eccstart=ColNum/NAND_ECC_SECTOR_SIZE;
p=pBuffer;
for(res=0;res<eccnum;res++)
{
FMC_Bank3->PCR|=1<<6; //使能ECC校验
for(i=0;i<NAND_ECC_SECTOR_SIZE;i++) //读取NAND_ECC_SECTOR_SIZE个数据
{
*(__IO uint8_t*)pBuffer++ = *(__IO uint8_t*)NAND_ADDRESS;
}
while(!(FMC_Bank3->SR&(1<<6))); //等待FIFO空
nand_dev.ecc_hdbuf[res+eccstart]=FMC_Bank3->ECCR;//读取硬件计算后的ECC值
FMC_Bank3->PCR&=~(1<<6); //禁止ECC校验
}
i=nand_dev.page_mainsize+0X10+eccstart*4; //从spare区的0X10位置开始读取之前存储的ecc值
NAND_Delay(30);//等待tADL
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=0X05; //随机读指令
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)i;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(i>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=0XE0; //开始读数据
NAND_Delay(30);//等待tADL
pBuffer=(uint8_t*)&nand_dev.ecc_rdbuf[eccstart];
for(i=0;i<4*eccnum;i++) //读取保存的ECC值
{
*(__IO uint8_t*)pBuffer++= *(__IO uint8_t*)NAND_ADDRESS;
}
for(i=0;i<eccnum;i++) //检验ECC
{
if(nand_dev.ecc_rdbuf[i+eccstart]!=nand_dev.ecc_hdbuf[i+eccstart])//不相等,需要校正
{
printf("err hd,rd:0x%x,0x%x\r\n",nand_dev.ecc_hdbuf[i+eccstart],nand_dev.ecc_rdbuf[i+eccstart]);
printf("eccnum,eccstart:%d,%d\r\n",eccnum,eccstart);
printf("PageNum,ColNum:%d,%d\r\n",PageNum,ColNum);
res=NAND_ECC_Correction(p+NAND_ECC_SECTOR_SIZE*i,nand_dev.ecc_rdbuf[i+eccstart],nand_dev.ecc_hdbuf[i+eccstart]);//ECC校验
if(res)errsta=NSTA_ECC2BITERR; //标记2BIT及以上ECC错误
else errsta=NSTA_ECC1BITERR; //标记1BIT ECC错误
}
}
}
if(NAND_WaitForReady()!=NSTA_READY)errsta=NSTA_ERROR; //失败
return errsta; //成功
}
/**
* @brief 读取NAND Flash的指定页指定列的数据(main区和spare区都可以使用此函数),并对比(FTL管理时需要)
* @param PageNum : 要读取的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNum : 要读取的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
* @param CmpVal : 要对比的值,以uint32_t为单位
* @param NumByteToRead : 读取字数(以4字节为单位,不能跨页读)
* @param NumByteEqual : 从初始位置持续与CmpVal值相同的数据个数
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_ReadPageComp(uint32_t PageNum,uint16_t ColNum,uint32_t CmpVal,uint16_t NumByteToRead,uint16_t *NumByteEqual)
{
uint16_t i=0;
uint8_t res=0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_AREA_A;
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)ColNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(ColNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>16);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_AREA_TRUE1;
//下面两行代码是等待R/B引脚变为低电平其实主要起延时作用的等待NAND操作R/B引脚。因为我们是通过
//将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO代码中通过读取NWAIT引脚的电平来判断NAND是否准备
//就绪的。这个也就是模拟的方法所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
//闲状态结果我们就读取了R/B引脚,这个时候肯定会出错的,事实上确实是会出错!大家也可以将下面两行
//代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
res=NAND_WaitRB(0); //等待RB=0
if(res)return NSTA_TIMEOUT; //超时退出
//下面2行代码是真正判断NAND是否准备好的
res=NAND_WaitRB(1); //等待RB=1
if(res)return NSTA_TIMEOUT; //超时退出
for(i=0;i<NumByteToRead;i++)//读取数据,每次读4字节
{
if(*(__IO uint32_t*)NAND_ADDRESS!=CmpVal)break; //如果有任何一个值,与CmpVal不相等,则退出.
}
*NumByteEqual=i; //与CmpVal值相同的个数
if(NAND_WaitForReady()!=NSTA_READY)return NSTA_ERROR;//失败
return 0; //成功
}
/**
* @brief 在NAND一页中写入指定个字节的数据(main区和spare区都可以使用此函数)
* @param PageNum : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNum : 要写入的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
* @param pBbuffer : 指向数据存储区
* @param NumByteToWrite: 要写入的字节数,该值不能超过该页剩余字节数!!!
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_WritePage(uint32_t PageNum,uint16_t ColNum,uint8_t *pBuffer,uint16_t NumByteToWrite)
{
__IO uint16_t i=0;
uint8_t res=0;
uint8_t eccnum=0; //需要计算的ECC个数每NAND_ECC_SECTOR_SIZE字节计算一个ecc
uint8_t eccstart=0; //第一个ECC值所属的地址范围
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_WRITE0;
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)ColNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(ColNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>16);
NAND_Delay(30);//等待tADL
if(NumByteToWrite%NAND_ECC_SECTOR_SIZE)//不是NAND_ECC_SECTOR_SIZE的整数倍不进行ECC校验
{
for(i=0;i<NumByteToWrite;i++) //写入数据
{
*(__IO uint8_t*)NAND_ADDRESS=*(__IO uint8_t*)pBuffer++;
}
}else
{
eccnum=NumByteToWrite/NAND_ECC_SECTOR_SIZE; //得到ecc计算次数
eccstart=ColNum/NAND_ECC_SECTOR_SIZE;
for(res=0;res<eccnum;res++)
{
FMC_Bank3->PCR|=1<<6; //使能ECC校验
for(i=0;i<NAND_ECC_SECTOR_SIZE;i++) //写入NAND_ECC_SECTOR_SIZE个数据
{
*(__IO uint8_t*)NAND_ADDRESS=*(__IO uint8_t*)pBuffer++;
}
while(!(FMC_Bank3->SR&(1<<6))); //等待FIFO空
nand_dev.ecc_hdbuf[res+eccstart]=FMC_Bank3->ECCR; //读取硬件计算后的ECC值
FMC_Bank3->PCR&=~(1<<6); //禁止ECC校验
}
i=nand_dev.page_mainsize+0X10+eccstart*4; //计算写入ECC的spare区地址
NAND_Delay(30);//等待
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=0X85; //随机写指令
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)i;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(i>>8);
NAND_Delay(30);//等待tADL
pBuffer=(uint8_t*)&nand_dev.ecc_hdbuf[eccstart];
for(i=0;i<eccnum;i++) //写入ECC
{
for(res=0;res<4;res++)
{
*(__IO uint8_t*)NAND_ADDRESS=*(__IO uint8_t*)pBuffer++;
}
}
}
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_WRITE_TURE1;
if(NAND_WaitForReady()!=NSTA_READY)return NSTA_ERROR;//失败
return 0;//成功
}
/**
* @brief 在NAND一页中的指定地址开始,写入指定长度的恒定数字
* @param PageNum : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNum : 要写入的列开始地址(也就是页内地址),范围:0~(page_totalsize-1)
* @param cval : 要写入的指定常数
* @param NumByteToWrite : 要写入的字节数(以4字节为单位)
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_WritePageConst(uint32_t PageNum,uint16_t ColNum,uint32_t cval,uint16_t NumByteToWrite)
{
uint16_t i=0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_WRITE0;
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)ColNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(ColNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(PageNum>>16);
NAND_Delay(30);//等待tADL
for(i=0;i<NumByteToWrite;i++) //写入数据,每次写4字节
{
*(__IO uint32_t*)NAND_ADDRESS=cval;
}
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_WRITE_TURE1;
if(NAND_WaitForReady()!=NSTA_READY)return NSTA_ERROR;//失败
return 0;//成功
}
/**
* @brief 将一页数据拷贝到另一页,不写入新数据
* @note 源页和目的页要在同一个Plane内
* @param Source_PageNo : 源页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param Dest_PageNo : 目的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_CopyPageWithoutWrite(uint32_t Source_PageNum,uint32_t Dest_PageNum)
{
uint8_t res=0;
uint16_t source_block=0,dest_block=0;
//判断源页和目的页是否在同一个plane中
source_block=Source_PageNum/nand_dev.block_pagenum;
dest_block=Dest_PageNum/nand_dev.block_pagenum;
if((source_block%2)!=(dest_block%2))return NSTA_ERROR; //不在同一个plane内
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD0; //发送命令0X00
//发送源页地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)Source_PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Source_PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Source_PageNum>>16);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD1;//发送命令0X35
//下面两行代码是等待R/B引脚变为低电平其实主要起延时作用的等待NAND操作R/B引脚。因为我们是通过
//将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO代码中通过读取NWAIT引脚的电平来判断NAND是否准备
//就绪的。这个也就是模拟的方法所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
//闲状态结果我们就读取了R/B引脚,这个时候肯定会出错的,事实上确实是会出错!大家也可以将下面两行
//代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
res=NAND_WaitRB(0); //等待RB=0
if(res)return NSTA_TIMEOUT; //超时退出
//下面2行代码是真正判断NAND是否准备好的
res=NAND_WaitRB(1); //等待RB=1
if(res)return NSTA_TIMEOUT; //超时退出
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD2; //发送命令0X85
//发送目的页地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)Dest_PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Dest_PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Dest_PageNum>>16);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD3; //发送命令0X10
if(NAND_WaitForReady()!=NSTA_READY)return NSTA_ERROR; //NAND未准备好
return 0;//成功
}
/**
* @brief 将一页数据拷贝到另一页,并且可以写入数据
* @note 源页和目的页要在同一个Plane内
* @param Source_PageNo : 源页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param Dest_PageNo : 目的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNo : 页内列地址,范围:0~(page_totalsize-1)
* @param pBuffer : 要写入的数据
* @param NumByteToWrite : 要写入的数据个数
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_CopyPageWithWrite(uint32_t Source_PageNum,uint32_t Dest_PageNum,uint16_t ColNum,uint8_t *pBuffer,uint16_t NumByteToWrite)
{
uint8_t res=0;
__IO uint16_t i=0;
uint16_t source_block=0,dest_block=0;
uint8_t eccnum=0; //需要计算的ECC个数每NAND_ECC_SECTOR_SIZE字节计算一个ecc
uint8_t eccstart=0; //第一个ECC值所属的地址范围
//判断源页和目的页是否在同一个plane中
source_block=Source_PageNum/nand_dev.block_pagenum;
dest_block=Dest_PageNum/nand_dev.block_pagenum;
if((source_block%2)!=(dest_block%2))return NSTA_ERROR;//不在同一个plane内
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD0; //发送命令0X00
//发送源页地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)0;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)Source_PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Source_PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Source_PageNum>>16);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD1; //发送命令0X35
//下面两行代码是等待R/B引脚变为低电平其实主要起延时作用的等待NAND操作R/B引脚。因为我们是通过
//将STM32的NWAIT引脚(NAND的R/B引脚)配置为普通IO代码中通过读取NWAIT引脚的电平来判断NAND是否准备
//就绪的。这个也就是模拟的方法所以在速度很快的时候有可能NAND还没来得及操作R/B引脚来表示NAND的忙
//闲状态结果我们就读取了R/B引脚,这个时候肯定会出错的,事实上确实是会出错!大家也可以将下面两行
//代码换成延时函数,只不过这里我们为了效率所以没有用延时函数。
res=NAND_WaitRB(0); //等待RB=0
if(res)return NSTA_TIMEOUT; //超时退出
//下面2行代码是真正判断NAND是否准备好的
res=NAND_WaitRB(1); //等待RB=1
if(res)return NSTA_TIMEOUT; //超时退出
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD2; //发送命令0X85
//发送目的页地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)ColNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(ColNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)Dest_PageNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Dest_PageNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(Dest_PageNum>>16);
//发送页内列地址
NAND_Delay(30);//等待tADL
if(NumByteToWrite%NAND_ECC_SECTOR_SIZE)//不是NAND_ECC_SECTOR_SIZE的整数倍不进行ECC校验
{
for(i=0;i<NumByteToWrite;i++) //写入数据
{
*(__IO uint8_t*)NAND_ADDRESS=*(__IO uint8_t*)pBuffer++;
}
}else
{
eccnum=NumByteToWrite/NAND_ECC_SECTOR_SIZE; //得到ecc计算次数
eccstart=ColNum/NAND_ECC_SECTOR_SIZE;
for(res=0;res<eccnum;res++)
{
FMC_Bank3->PCR|=1<<6; //使能ECC校验
for(i=0;i<NAND_ECC_SECTOR_SIZE;i++) //写入NAND_ECC_SECTOR_SIZE个数据
{
*(__IO uint8_t*)NAND_ADDRESS=*(__IO uint8_t*)pBuffer++;
}
while(!(FMC_Bank3->SR&(1<<6))); //等待FIFO空
nand_dev.ecc_hdbuf[res+eccstart]=FMC_Bank3->ECCR; //读取硬件计算后的ECC值
FMC_Bank3->PCR&=~(1<<6); //禁止ECC校验
}
i=nand_dev.page_mainsize+0X10+eccstart*4; //计算写入ECC的spare区地址
NAND_Delay(30);//等待
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=0X85; //随机写指令
//发送地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)i;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(i>>8);
NAND_Delay(30);//等待tADL
pBuffer=(uint8_t*)&nand_dev.ecc_hdbuf[eccstart];
for(i=0;i<eccnum;i++) //写入ECC
{
for(res=0;res<4;res++)
{
*(__IO uint8_t*)NAND_ADDRESS=*(__IO uint8_t*)pBuffer++;
}
}
}
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_MOVEDATA_CMD3; //发送命令0X10
if(NAND_WaitForReady()!=NSTA_READY)return NSTA_ERROR; //失败
return 0; //成功
}
/**
* @brief 读取spare区中的数据
* @param PageNum : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNum : 要写入的spare区地址(spare区中哪个地址),范围:0~(page_sparesize-1)
* @param pBuffer : 接收数据缓冲区
* @param NumByteToRead : 要读取的字节数(不大于page_sparesize)
* @retval 0,成功; 其他,错误代码
*/
uint8_t NAND_ReadSpare(uint32_t PageNum,uint16_t ColNum,uint8_t *pBuffer,uint16_t NumByteToRead)
{
uint8_t temp=0;
uint8_t remainbyte=0;
remainbyte=nand_dev.page_sparesize-ColNum;
if(NumByteToRead>remainbyte) NumByteToRead=remainbyte; //确保要写入的字节数不大于spare剩余的大小
temp=NAND_ReadPage(PageNum,ColNum+nand_dev.page_mainsize,pBuffer,NumByteToRead);//读取数据
return temp;
}
/**
* @brief 向spare区中写数据
* @param PageNum : 要写入的页地址,范围:0~(block_pagenum*block_totalnum-1)
* @param ColNum : 要写入的spare区地址(spare区中哪个地址),范围:0~(page_sparesize-1)
* @param pBuffer : 要写入的数据首地址
* @param NumByteToWrite: 要写入的字节数(不大于page_sparesize)
* @retval 0,成功; 其他,失败
*/
uint8_t NAND_WriteSpare(uint32_t PageNum,uint16_t ColNum,uint8_t *pBuffer,uint16_t NumByteToWrite)
{
uint8_t temp=0;
uint8_t remainbyte=0;
remainbyte=nand_dev.page_sparesize-ColNum;
if(NumByteToWrite>remainbyte) NumByteToWrite=remainbyte; //确保要读取的字节数不大于spare剩余的大小
temp=NAND_WritePage(PageNum,ColNum+nand_dev.page_mainsize,pBuffer,NumByteToWrite);//读取
return temp;
}
/**
* @brief 擦除一个块
* @param BlockNum : 要擦除的BLOCK编号,范围:0-(block_totalnum-1)
* @retval 0,擦除成功; 其他,擦除失败
*/
uint8_t NAND_EraseBlock(uint32_t BlockNum)
{
if(nand_dev.id==MT29F16G08ABABA)BlockNum<<=7; //将块地址转换为页地址
else if(nand_dev.id==MT29F4G08ABADA)BlockNum<<=6;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_ERASE0;
//发送块地址
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)BlockNum;
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(BlockNum>>8);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_ADDR)=(uint8_t)(BlockNum>>16);
*(__IO uint8_t*)(NAND_ADDRESS|NAND_CMD)=NAND_ERASE1;
if(NAND_WaitForReady()!=NSTA_READY)return NSTA_ERROR;//失败
return 0; //成功
}
//全片擦除NAND FLASH
void NAND_EraseChip(void)
{
uint8_t status;
uint16_t i=0;
for(i=0;i<nand_dev.block_totalnum;i++) //循环擦除所有的块
{
status=NAND_EraseBlock(i);
if(status)printf("Erase %d block fail!!,错误码为%d\r\n",i,status);//擦除失败
}
}
/**
* @brief 获取ECC的奇数位/偶数位
* @param oe : 0,偶数位; 1,奇数位
* @param eccval : 输入的ecc值
* @retval 计算后的ecc值(最多16位)
*/
uint16_t NAND_ECC_Get_OE(uint8_t oe,uint32_t eccval)
{
uint8_t i;
uint16_t ecctemp=0;
for(i=0;i<24;i++)
{
if((i%2)==oe)
{
if((eccval>>i)&0X01)ecctemp+=1<<(i>>1);
}
}
return ecctemp;
}
/**
* @brief ECC校正函数
* @param data_buf : 数据缓存区
* @param eccrd : 读取出来, 原来保存的ECC值
* @param ecccl : 读取数据时, 硬件计算的ECC值
* @retval 0,错误已修正; 其他,ECC错误(有大于2个bit的错误,无法恢复)
*/
uint8_t NAND_ECC_Correction(uint8_t* data_buf,uint32_t eccrd,uint32_t ecccl)
{
uint16_t eccrdo,eccrde,eccclo,ecccle;
uint16_t eccchk=0;
uint16_t errorpos=0;
uint32_t bytepos=0;
eccrdo=NAND_ECC_Get_OE(1,eccrd); //获取eccrd的奇数位
eccrde=NAND_ECC_Get_OE(0,eccrd); //获取eccrd的偶数位
eccclo=NAND_ECC_Get_OE(1,ecccl); //获取ecccl的奇数位
ecccle=NAND_ECC_Get_OE(0,ecccl); //获取ecccl的偶数位
eccchk=eccrdo^eccrde^eccclo^ecccle;
if(eccchk==0XFFF) //全1,说明只有1bit ECC错误
{
errorpos=eccrdo^eccclo;
printf("errorpos:%d\r\n",errorpos);
bytepos=errorpos/8;
data_buf[bytepos]^=1<<(errorpos%8);
}else //不是全1,说明至少有2bit ECC错误,无法修复
{
printf("2bit ecc error or more\r\n");
return 1;
}
return 0;
}