打印

在21IC DIY U盘实验板上实现的128MB U盘(含坏块管理)

[复制链接]
7338|37
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
computer00|  楼主 | 2009-3-27 13:49 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
单击此处下载该实验的程序包:https://bbs.21ic.com/upfiles/img/20093/2009327134626795.rar

    本程序在21IC DIY U盘的实验板上实现“真”U盘的功能。
学习板上有一个128MB的NAND FLASH,只要实现扇区读、写以及
坏块管理,就可以在原来的“假”U盘的基础上做成真正的U盘了。

    由于NAND FLASH擦除时,只能按按块擦除,因此在写扇区时,
首先要擦除一个块。在擦除块前,必须将块内其他数据复制出来,
由于一个块比较大(128KB),无法在MCU内开辟如此大的缓冲区。
只好借助该NAND FLASH内的页复制命令,将原来的块暂时复制到
一个交换用的交换块中。但是如果仅用一个块作为交换的话,它
就会被频繁擦写,因而寿命会大大降低。所以在该系统中,保留了
10个块用来作为交换区,轮流使用。另外对扇区的连续写进行了
优化,当连续写扇区时,就不必每次重复上面的操作,只有当地址
跨块时,才需要重新擦除。连续写扇区的实现原理如下:当检测到
扇区地址跨块时,就把原来的整块数据复制到交换块中,然后将
该块内当前所写地址的前面部分页面复制到原来的块中,接着就从
交换块中取出当前扇区地址所在页(使用页复制-随机写入命令),
再把一个扇区的数据写入,并接着写入其他扇区,当扇区地址跨页
时,就把该页写入到原来的块中,直到扇区被写完(当然如果写
的过程中跨块了,还需要重复前面的块擦除以及复制过程)。接着
把交换块中剩余的页再复制回原来的块中,这样一个连续写过程就
完成了。

    因为NAND FLASH在生产和使用过程中,会产生坏块,所以必须
增加坏块管理。在该系统中,保留了50个块用做坏块管理。当上述
的擦、写过程中,如果发现坏块,那么就把该块的地址重新影射到
一个保留的块中。以后每次对该坏块地址操作时,都被重新定位到
新的块地址。使用一个二维数组来保存该影射关系。二维数组的前
半部分为坏块地址,后半部分为重新影射过后的块地址。每当发现
坏块时,就需要重建这张表(主要是增加新的影射并排序,方便地
址重新影射的二分查表法),并将其三份一样的写入到专门为此而
保留的三个块中。之所以使用三个备份保存,是考虑到这些数据的
重要性,因为一旦这个影射关系被破坏,后果将会是灾难性的。在
保存这个表格时,做了特殊处理,首先标志他们准备擦除,然后才
依次擦除并写入数据,这样即使在操作过程中突然断电,也至少有
两个块的备份数据是可以用的,在系统开机初始化时,可以将这些
数据恢复。数据使用了特殊标志(0x0055AAFF)以及累加和校验来
判断是否有效。对于新出厂的FALSH,在加载坏块表时,就会校验
失败,程序就会假设没有坏块并初始化该数组后保存。每个备用块
还由另外一个数组用来标志其状态(未用、已损坏、已用等)。
当需要使用新的备用块时,就从该数组中查找未用的,并标志为
已用。如果备用块本身是坏的,那么就标志它已损坏。该数组与
坏块重影射表一并保存。此外还保存了当前坏块的数量。

    地址的重新影射过程:当对一个地址进行读写操作时,首先要
对其进行重影射。首先判断是否有坏块,当坏块数量为0时,就直接
返回原来的地址即可。当坏块数量不为0时,先判断最后一次访问和
本次访问的地址是否属于同一页,如果属于,那么就直接影射到上
一次影射过的块地址。如果不属于,那么就需要去查坏块影射表了。
如果只有一个坏块,只要直接比较即可,不用查表。如果坏块数量
大于2,那么就需要查表。由于表中地址是按从小到大的顺序排列的,
所以可以先和第一个和最后一个判断,如果不在该范围内,那么也
不用重新影射,返回原来的地址即可。如果在该范围内,就使用二
分查表法查表,搜索它是否在坏块表中。如果是的话,就重新影射
地址,并将这个地址保存,以备下一次重影射时地址未跨块直接使用。
最大支持50个坏块,在最坏的情况下,该二分查表法需要判断6次。


                                    圈圈  2009-03-27  13:25

坏块表及FLASH存储空间划分如下图:




实验图片~~~~



相关帖子

沙发
computer00|  楼主 | 2009-3-27 13:54 | 只看该作者

FLASH.c文件中读、写扇区以及管理坏块的主要部分代码

/********************************************************************
函数功能:获取下一个可用的备用块。
入口参数:无。
返    回:找到的块地址。
备    注:在该函数中会先擦除,只有成功擦除的才被返回。
********************************************************************/
uint32 FlashGetNewRemapBlock(void)
{
 uint32 i,Addr;
 for(i=0;i<FLASH_BAD_BLOCKS_REMAP;i++)
 {
  if(FLASH_BLOCK_OK==FlashRemapBlockStatus) //如果该块还未用
  {
   Addr=FLASH_BAD_BLOCK_REMAP_ADDR+i*FLASH_BLOCK_SIZE; //计算地址
   if(0x01==(FlashEraseBlock(Addr)&0x01))  //如果擦除失败
   {
    FlashRemapBlockStatus=FLASH_BLOCK_BAD;  //标志该块为已经损坏
   }
   else //否则,擦除成功
   {
    FlashRemapBlockStatus=FLASH_BLOCK_USED; //标志为该块已被使用    
    return Addr; //返回地址
   }
  }
 }
 return -1; //如果找不到,则返回-1。
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:标志当前重影射的块为坏块。
入口参数:Addr:要标志的块的地址。
返    回:无。
备    注:无。
********************************************************************/
void FlashMarkRemapBlockBad(uint32 Addr)
{
 uint32 i;
 i=(Addr-FLASH_BAD_BLOCK_REMAP_ADDR)/FLASH_BLOCK_SIZE;  //计算偏移量
 if(i>=FLASH_BAD_BLOCKS_REMAP)return; //出错
 FlashRemapBlockStatus=FLASH_BLOCK_BAD;  //标志为已经损坏
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:地址重新影射,坏块管理用。
入口参数:Addr:需要影射的字节地址。
返    回:影射后的字节地址。
备    注:无。
********************************************************************/
uint32 FlashAddrRemap(uint32 Addr)
{
 static uint32 CurrentRemapBlockAddr;
 uint32 i,j;
 
 if(0==FlashBadBlocksCount)  //如果坏块数量为0,则不需要处理,直接返回地址
 {
  return Addr;
 }
 
 //如果最后一次访问的地址和本次访问的地址属于同一个块地址,那么不需要重新影射
 if(0==((Addr-FlashLastAccessAddr)&(FLASH_BLOCK_SIZE-1)))
 {
  return CurrentRemapBlockAddr+(Addr&(FLASH_BLOCK_SIZE-1)); //由当前块地址加上块内偏移得到完整地址
 }
 
 FlashLastAccessAddr=Addr; //保存最后一次访问过的地址
 
 if(1==FlashBadBlocksCount) //如果坏块数量为1,则直接影射
 {
  if((Addr&(~(FLASH_BLOCK_SIZE-1)))==FlashBadBlockTable[0][0]) //如果块地址相等,则直接影射
  {
   CurrentRemapBlockAddr=FlashBadBlockTable[1][0];
   return CurrentRemapBlockAddr+(Addr&(FLASH_BLOCK_SIZE-1)); //由当前块地址加上块内偏移得到完整地址
  }
  else //不用影射
  {
   CurrentRemapBlockAddr=Addr&(~(FLASH_BLOCK_SIZE-1));  //获取当前块地址
   return Addr;  //直接返回原来的地址
  }
 }
 else //坏块数量大于1
 {
  //如果地址比第一个坏块的地址还小或者比最后一个坏块的地址还大,
  //那么肯定不会是坏快,不需要重新影射
  if((Addr<FlashBadBlockTable[0][0])
   ||((Addr&(FLASH_BLOCK_SIZE-1))>FlashBadBlockTable[0][FlashBadBlocksCount-1]))
  {
   CurrentRemapBlockAddr=Addr&(~(FLASH_BLOCK_SIZE-1));  //获取当前块地址
   return Addr;  //直接返回原来的地址
  }
  else //属于坏块区间,使用二分查表法决定是否需要影射
  {
   i=0;
   j=FlashBadBlocksCount-1;
   while(1)
   {
    if((Addr&(~(FLASH_BLOCK_SIZE-1)))==FlashBadBlockTable[0][(i+j)/2]) //如果相等,则影射
    {
     CurrentRemapBlockAddr=FlashBadBlockTable[1][(i+j)/2];
     return CurrentRemapBlockAddr+(Addr&(FLASH_BLOCK_SIZE-1)); //由当前块地址加上块内偏移得到完整地址
    }
    if(i==j)break; //如果i和j相等,则退出查找
    if((Addr&(~(FLASH_BLOCK_SIZE-1)))<FlashBadBlockTable[0][(i+j)/2])  //如果小于
    {
     j=(i+j)/2-1; //搜索前半段
    }
    else //如果大于
    {
     i=(i+j)/2+1; //搜索后半段
    }
   }
  }
 }
 //没有在坏块表中找到,则说明不是坏块
 CurrentRemapBlockAddr=Addr&(~(FLASH_BLOCK_SIZE-1));  //获取当前块地址
 return Addr;  //直接返回原来的地址
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:从FLASH的特定位置加载坏块表。
入口参数:无。
返    回:无。
备    注:无。
********************************************************************/
void FlashLoadBadBlockTable(void)
{
 uint32 i,j,k,Sum,Ok;
 uint8 Data;

 Ok=0; //设置为不成功
 for(i=0;i<FLASH_BLOCKS_TABLE;i++) //查找没有准备擦除的块
 {
  //从该块中最后一页读回第一字节,看是否为0xFF,如果为0xFF,表示该块没有准备擦除
  FlashWriteCommand(0x00);
  FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*(i+1) - FLASH_PAGE_SIZE);
  FlashWriteCommand(0x30);
  FlashWait(); //等待数据读回
  FlashReadByte(Data);
  if(Data==0xFF)  //表示该块数据还未准备擦除
  {
   //从该块中倒数第二页读回第一字节,看是否为0,如果为0,表示该块已经写入了数据
   FlashWriteCommand(0x00);
   FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*(i+1) - 2*FLASH_PAGE_SIZE);
   FlashWriteCommand(0x30);
   FlashWait(); //等待数据读回
   FlashReadByte(Data);
   if(Data==0) //表示数据有效
   {
    FlashReadByte(Data); //读出校验和
    Sum=Data;
    FlashReadByte(Data); //读出校验和
    Sum=(Sum<<8)+Data;
    FlashReadByte(Data); //读出校验和
    Sum=(Sum<<8)+Data;
    FlashReadByte(Data); //读出校验和
    Sum=(Sum<<8)+Data;
    //从该块开始位置读
    FlashWriteCommand(0x00);
    FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i);
    FlashWriteCommand(0x30);
    FlashWait(); //等待数据读回
    //检查第1字节是否为0
    FlashReadByte(Data);
    if(Data!=0)continue;
    //检查第2字节是否为0x55
    FlashReadByte(Data);
    if(Data!=0x55)continue;
    //检查第3字节是否为0xAA
    FlashReadByte(Data);
    if(Data!=0xAA)continue;
    //检查第4字节是否为0xFF
    FlashReadByte(Data);
    if(Data!=0xFF)continue;
    Sum+=0x1FE;
    
    //读坏块数量
    FlashReadByte(Data);
    FlashBadBlocksCount=Data;
    Sum+=Data;
    FlashReadByte(Data);
    FlashBadBlocksCount=(FlashBadBlocksCount<<8)+Data;
    Sum+=Data;
    FlashReadByte(Data);
    FlashBadBlocksCount=(FlashBadBlocksCount<<8)+Data;
    Sum+=Data;
    FlashReadByte(Data);
    FlashBadBlocksCount=(FlashBadBlocksCount<<8)+Data;
    Sum+=Data;
    j=8;
    //读回坏块表
    for(k=0;k<sizeof(FlashBadBlockTable[0][0])*FLASH_BAD_BLOCKS_REMAP*2;k++)
    {
     if(0==(j&(FLASH_PAGE_SIZE-1))) //如果超过了页,则需要重新读新页
     {
      FlashWriteCommand(0x00);
      FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i+j);
      FlashWriteCommand(0x30);
      FlashWait(); //等待数据读回
     }
     FlashReadByte(Data);
     Sum+=Data; //求校验和
     ((uint8 *)FlashBadBlockTable)[k]=Data;  //读回一字节到坏块表中
     j++;
    }
    //读回重影射区的状态表
    for(k=0;k<sizeof(FlashRemapBlockStatus[0])*FLASH_BAD_BLOCKS_REMAP;k++)
    {
     if(0==(j&(FLASH_PAGE_SIZE-1))) //如果超过了页,则需要重新读新页
     {
      FlashWriteCommand(0x00);
      FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i+j);
      FlashWriteCommand(0x30);
      FlashWait(); //等待数据读回
     }
     FlashReadByte(Data);
     Sum+=Data; //求校验和
     ((uint8 *)FlashRemapBlockStatus)[k]=Data;   //读回一字节到重影射区状态表中
     j++;
    }
    if(Sum==0) //如果校验成功,则说明数据正确
    {
     Ok=0xFF; //设置为成功
     break;   //并退出循环
    }
   }
  }
 }
 
 if(Ok==0) //如果在已写入的表中找不到好的坏块表,再去准备擦除的中去找
 {
  for(i=0;i<FLASH_BLOCKS_TABLE;i++) //查找准备擦除的块
  {
   //从该块中最后一页读回第一字节,看是否为0,如果为0,表示该块已经准备擦除了
   FlashWriteCommand(0x00);
   FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*(i+1) - FLASH_PAGE_SIZE);
   FlashWriteCommand(0x30);
   FlashWait(); //等待数据读回
   FlashReadByte(Data);
   if(Data==0x00)  //表示该块数据准备擦除
   {
    //从该块中倒数第二页读回第一字节,看是否为0,如果为0,表示该块已经写入了数据
    FlashWriteCommand(0x00);
    FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*(i+1) - 2*FLASH_PAGE_SIZE);
    FlashWriteCommand(0x30);
    FlashWait(); //等待数据读回
    FlashReadByte(Data);
    if(Data==0) //表示数据有效
    {
     FlashReadByte(Data); //读出校验和
     Sum=Data;
     FlashReadByte(Data); //读出校验和
     Sum=(Sum<<8)+Data;
     FlashReadByte(Data); //读出校验和
     Sum=(Sum<<8)+Data;
     FlashReadByte(Data); //读出校验和
     Sum=(Sum<<8)+Data;
     //从该块开始位置读
     FlashWriteCommand(0x00);
     FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i);
     FlashWriteCommand(0x30);
     FlashWait(); //等待数据读回
     //检查第1字节是否为0
     FlashReadByte(Data);
     if(Data!=0)continue;
     //检查第2字节是否为0x55
     FlashReadByte(Data);
     if(Data!=0x55)continue;
     //检查第3字节是否为0xAA
     FlashReadByte(Data);
     if(Data!=0xAA)continue;
     //检查第4字节是否为0xFF
     FlashReadByte(Data);
     if(Data!=0xFF)continue;
     Sum+=0x1FE;
     
     //读坏块数量
     FlashReadByte(Data);
     FlashBadBlocksCount=Data;
     Sum+=Data;
     FlashReadByte(Data);
     FlashBadBlocksCount=(FlashBadBlocksCount<<8)+Data;
     Sum+=Data;
     FlashReadByte(Data);
     FlashBadBlocksCount=(FlashBadBlocksCount<<8)+Data;
     Sum+=Data;
     FlashReadByte(Data);
     FlashBadBlocksCount=(FlashBadBlocksCount<<8)+Data;
     Sum+=Data;
     j=8;
     //读回坏块表
     for(k=0;k<sizeof(FlashBadBlockTable[0][0])*FLASH_BAD_BLOCKS_REMAP*2;k++)
     {
      if(0==(j&(FLASH_PAGE_SIZE-1))) //如果超过了页,则需要重新读新页
      {
       FlashWriteCommand(0x00);
       FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i+j);
       FlashWriteCommand(0x30);
       FlashWait(); //等待数据读回
      }
      FlashReadByte(Data);
      Sum+=Data; //求校验和
      ((uint8 *)FlashBadBlockTable)[k]=Data;  //读回一字节到坏块表中
      j++;
     }
     //读回重影射区的状态表
     for(k=0;k<sizeof(FlashRemapBlockStatus[0])*FLASH_BAD_BLOCKS_REMAP;k++)
     {
      if(0==(j&(FLASH_PAGE_SIZE-1))) //如果超过了页,则需要重新读新页
      {
       FlashWriteCommand(0x00);
       FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i+j);
       FlashWriteCommand(0x30);
       FlashWait(); //等待数据读回
      }
      FlashReadByte(Data);
      Sum+=Data; //求校验和
      ((uint8 *)FlashRemapBlockStatus)[k]=Data;   //读回一字节到重影射区状态表中
      j++;
     }
     if(Sum==0) //如果校验成功,则说明数据正确
     {
      FlashSaveBadBlockTable(); //将其保存到FLASH中      
      Ok=0xFF; //设置为成功
      break;   //并退出循环
     }
    }
   }
  }
 }
 
 if(Ok==0) //如果还是没找到,那么只好重新初始化了
 {
  FlashBadBlocksCount=0; //坏块数设置为0
  for(i=0;i<FLASH_BAD_BLOCKS_REMAP;i++)
  {
   //所有影射块都设置为好块
   FlashRemapBlockStatus=FLASH_BLOCK_OK;
   //所有影射关系设置为-1
   FlashBadBlockTable[0]=-1;
   FlashBadBlockTable[1]=-1;
  }
  //设置好之后保存起来
  FlashSaveBadBlockTable();
 }
 //设置当前访问过的地址为无效值
 FlashLastAccessAddr=-1;
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:保存坏块表到FLASH的特定位置。
入口参数:无。
返    回:无。
备    注:无。
********************************************************************/
void FlashSaveBadBlockTable(void)
{
 uint32 i,j,k,Sum;
 
 for(i=0;i<FLASH_BLOCKS_TABLE;i++) //标志为准备擦除
 {
  FlashWriteCommand(0x80);
  FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*(i+1) - FLASH_PAGE_SIZE);
  FlashSetPortAsOut();  //总线设置为输出口
  FlashWriteByte(0x00);  //将第一字节设置为0,表示准备擦除
  //剩余字节写0xFF
  for(j=1;j<FLASH_PAGE_SIZE;j++)
  {
   FlashWriteByte(0xFF);
  }
  FlashWritePage(); //写页
 }
 
 for(i=0;i<FLASH_BLOCKS_TABLE;i++) //将坏块表写入这三块
 {
  FlashEraseBlock(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i); //擦除一块
  FlashWriteCommand(0x80);
  FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i); //写入第一块的开始位置
  FlashSetPortAsOut();  //总线设置为输出口
  FlashWriteByte(0x00);  //将第1字节设置为0
  FlashWriteByte(0x55);  //将第2字节设置为0x55
  FlashWriteByte(0xAA);  //将第3字节设置为0xAA
  FlashWriteByte(0xFF);  //将第4字节设置为0xFF
  Sum=0x1FE;
  //接着写坏块数量,并统计校验和
  FlashWriteByte((FlashBadBlocksCount>>24)&0xFF);
  Sum+=(FlashBadBlocksCount>>24)&0xFF;
  FlashWriteByte((FlashBadBlocksCount>>16)&0xFF);
  Sum+=(FlashBadBlocksCount>>16)&0xFF;
  FlashWriteByte((FlashBadBlocksCount>>8)&0xFF);
  Sum+=(FlashBadBlocksCount>>8)&0xFF;
  FlashWriteByte((FlashBadBlocksCount)&0xFF);
  Sum+=(FlashBadBlocksCount)&0xFF;
  j=8; //写了8字节
  //保存坏块表
  for(k=0;k<sizeof(FlashBadBlockTable[0][0])*FLASH_BAD_BLOCKS_REMAP*2;k++)
  {
   if(0==(j&(FLASH_PAGE_SIZE-1))) //如果超过了页,则需要重新写新页
   {
    FlashWritePage(); //写页
    FlashWriteCommand(0x80);
    FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i + j);
    FlashSetPortAsOut();  //总线设置为输出口
   }
   Sum+=((uint8 *)FlashBadBlockTable)[k]; //求校验和
   FlashWriteByte(((uint8 *)FlashBadBlockTable)[k]);  //写一字节
   j++;
  }
  //保存重影射区的状态表
  for(k=0;k<sizeof(FlashRemapBlockStatus[0])*FLASH_BAD_BLOCKS_REMAP;k++)
  {
   if(0==(j&(FLASH_PAGE_SIZE-1))) //如果超过了页,则需要重新写新页
   {
    FlashWritePage(); //写页
    FlashWriteCommand(0x80);
    FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*i + j);
    FlashSetPortAsOut();  //总线设置为输出口
   }
   Sum+=((uint8 *)FlashRemapBlockStatus)[k]; //求校验和
   FlashWriteByte(((uint8 *)FlashRemapBlockStatus)[k]);  //写一字节
   j++;
  }
  for(;0!=(j&(FLASH_PAGE_SIZE-1));j++) //将剩余部分写入0xFF
  {
   FlashWriteByte(0xFF);
  }
  FlashWritePage();   //写页
  
  //已完成写状态及校验和写入到该块的倒数第二页
  FlashWriteCommand(0x80);
  FlashWriteAddr4Byte(FLASH_BLOCK_TABLE_ADDR + FLASH_BLOCK_SIZE*(i+1) - 2*FLASH_PAGE_SIZE);
  FlashSetPortAsOut();  //总线设置为输出口
  FlashWriteByte(0x00);  //将第一字节设置为0,表示已经写入
  //将校验和取反加1,这样累加结果就为0
  Sum=(~Sum)+1;
  //写校验和
  FlashWriteByte((Sum>>24)&0xFF);
  FlashWriteByte((Sum>>16)&0xFF);
  FlashWriteByte((Sum>>8)&0xFF);
  FlashWriteByte((Sum)&0xFF);
  //剩余字节写0xFF
  for(j=5;j<FLASH_PAGE_SIZE;j++)
  {
   FlashWriteByte(0xFF);
  }
  FlashWritePage(); //写页
 }
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:更新坏块表。
入口参数:OldAddr:旧地址;NewAddr:影射之后的地址。
返    回:无。
备    注:无。
********************************************************************/
void FlashUpdateBadBlockTable(uint32 OldAddr,uint32 NewAddr)
{
 uint32 i,j;
 OldAddr&=~(FLASH_BLOCK_SIZE-1); //求得一块内起始地址
 NewAddr&=~(FLASH_BLOCK_SIZE-1);
 if(OldAddr>FLASH_MAX_SECTOR_ADDR) //如果比能够达到的最大地址还大,说明坏块地址本来就是被重新影射过的
 {
  //先要找到它原来影射的位置
  for(i=0;i<FlashBadBlocksCount;i++)
  {
   if(OldAddr==FlashBadBlockTable[1]) //如果与某个地址吻合,则说明就是该地址了
   {
    FlashBadBlockTable[1]=NewAddr; //重新影射到新的地址
    //并将原来的交换块设置为已损坏
    FlashMarkRemapBlockBad(OldAddr);
    break;
   }
  }
 }
 else //说明坏块地址是没有被影射过的
 {
  //查找比它大的块地址,将它插入到前面,排好序,方便二分查表
  for(i=0;i<FlashBadBlocksCount;i++) 
  {
   if(OldAddr<FlashBadBlockTable[0]) //找到比它大的地址
   {
    break;
   }
  }
  for(j=FlashBadBlocksCount;j>i;j--) //将上面的部分往上移动,腾出一个空位
  {
   FlashBadBlockTable[0][j]=FlashBadBlockTable[0][j-1];
   FlashBadBlockTable[1][j]=FlashBadBlockTable[1][j-1];
  }
  //将当前块的影射写入
  FlashBadBlockTable[0][j]=OldAddr;
  FlashBadBlockTable[1][j]=NewAddr;
  FlashBadBlocksCount++; //增加一个坏块计数
 }
 FlashSaveBadBlockTable(); //存储坏块表
 //修改当前访问过的地址为无效地址,这样下次操作时就会重新影射
 FlashLastAccessAddr=-1;
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:FLASH坏块处理。
入口参数:Addr: 字节地址。
返    回:无。
备    注:处理后的地址。
********************************************************************/
uint32 FlashDealBadBlock(uint32 Addr, uint32 Type)
{
 uint32 i;
 uint32 RemapBlockAddr;
 uint32 SwapBlockAddr;
 while(1)
 {
  RemapBlockAddr=FlashGetNewRemapBlock();
  if(RemapBlockAddr==-1)  //如果已经找不到新的可用的替换品,那只好直接返回了。
  {
   return Addr;
  }
  switch(Type)
  {
   //擦除时遇到的坏块,不需要将当前页缓冲区数据写回
   //只需要返回新的地址即可。地址统一在最后返回,这里不用处理
   case 1:
    goto Exit;
   break;
   
   //复制页时遇到的坏块,需要将该块中前面的页及当前页从交换区中重新复制
   case 2:
   //从交换区去复制前面页以及当前页的数据
   SwapBlockAddr=FlashGetCurrentSwapBlock();  //获取当前所使用的交换块
   //复制前面以及当前页
   for(i=0;i<(Addr&(FLASH_BLOCK_SIZE-1))/FLASH_PAGE_SIZE+1;i++)
   {
    if(0x01==(FlashCopyPage(SwapBlockAddr+i*FLASH_PAGE_SIZE,RemapBlockAddr+i*FLASH_PAGE_SIZE)&0x01))
    {
     //如果复制失败,则说明该块有问题,需要找新的块
     goto BadRemapBlock;
    }
   }
   //复制完毕,则退出循环
   goto Exit;
   break;
   
   //写数据时遇到的坏块,需要将该块中前面的页从交换区中重新复制,
   //还需要将当前页从交换区中复制并将缓冲区的输入写入到当前页中
   //这里无法再获取到缓冲区的数据了,只好直接从原来的页复制数据
   case 3:  
   //从交换区去复制前面页数据
   SwapBlockAddr=FlashGetCurrentSwapBlock();  //获取当前所使用的交换块
   //复制前面的页
   for(i=0;i<(Addr&(FLASH_BLOCK_SIZE-1))/FLASH_PAGE_SIZE;i++)
   {
    if(0x01==(FlashCopyPage(SwapBlockAddr+i*FLASH_PAGE_SIZE,RemapBlockAddr+i*FLASH_PAGE_SIZE)&0x01))
    {
     //如果复制失败,则说明该块有问题,需要找新的块
     goto BadRemapBlock;
    }
   }
   //对于当前页,只好从刚刚写入的错误地址去复制
   if(0x01==(FlashCopyPage(Addr,RemapBlockAddr+i*FLASH_PAGE_SIZE)&0x01))
   {
    //如果复制失败,则说明该块有问题,需要找新的块
    goto BadRemapBlock;
   }   
   //复制完毕,则退出循环
   goto Exit;
   break;
   
   default:
   break;
  }
  BadRemapBlock:
  //如果操作过程中失败,则要标志该块已经损坏
  FlashMarkRemapBlockBad(RemapBlockAddr);
 }
 Exit:
 //更新坏块表
 FlashUpdateBadBlockTable(Addr,RemapBlockAddr);
 return RemapBlockAddr+(Addr&(FLASH_BLOCK_SIZE-1));
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:管理可用的交换块地址。
入口参数:Op:对应的操作。
返    回:下一个可用的交换块的地址。
备    注:无。
********************************************************************/
uint32 FlashManageSwapBlock(uint32 Op)
{
 static uint32 Current;
 static uint8 FlashSwapBlockStatus[FLASH_SWAP_BLOCKS];
 uint32 i;
 
 switch(Op)
 {
  case 0:  //如果操作为1,表示初始化
   Current=0;
   for(i=0;i<FLASH_SWAP_BLOCKS;i++)
   {
    FlashSwapBlockStatus=0; //初始化所有交换块为好的
   }
  break;
  
  case 1: //如果操作为1,表示获取下一个可用的交换区
   while(1)//一直尝试,如果交换区都用完(坏)了,那么就死循环了,
   {
    Current++;
    if(Current>=FLASH_SWAP_BLOCKS)
    {
     Current=0;
    }
    if(FlashSwapBlockStatus[Current]==0)break; //如果该块标志为0,则说明未损坏
   }
  break;
  
  case 2: //如果操作为2,说明获取当前交换区地址
  break;
  
  case 3: //如果操作为3,设置当前交换块为坏块
   FlashSwapBlockStatus[Current]=FLASH_BLOCK_BAD;
  break;
  
  default:
  break;
 }
 return FLASH_SWAP_BLOCK_ADDR+Current*FLASH_BLOCK_SIZE; //返回可用的交换块地址
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:将一块数据复制到交换区。同时将原来的块删除,
          并将该块内Addr所在页前面的页面复制回原来的块。
入口参数:Addr:要复制出来的块地址。
返    回:原来块的地址。
备    注:如果在复制回去的过程中,出现错误,
          那么说明原来的块已经损坏,需要重新影射到一个好的块。
          这时返回的地址就是重新影射过后的地址。
********************************************************************/
uint32 FlashCopyBlockToSwap(uint32 Addr)
{
 uint32 SwapAddr;
 uint32 i;
 uint32 BlockStartAddr;
 
 BlockStartAddr=(Addr)&(~(FLASH_BLOCK_SIZE-1));  //计算块起始地址
 
 while(1)
 {
  SwapAddr=FlashGetNextSwapBlock(); //获取下一个交换区
  if(0x00==(FlashEraseBlock(SwapAddr)&0x01)) //如果擦除成功
  {
   for(i=0;i<FLASH_BLOCK_SIZE/FLASH_PAGE_SIZE;i++)  //将对应块中所有页复制到交换区中
   {
    //复制一页
    if(0x01&FlashCopyPage(BlockStartAddr+i*FLASH_PAGE_SIZE,SwapAddr+i*FLASH_PAGE_SIZE))
    {
     //如果复制失败,则说明该交换块已经损坏,查找下一个可用的交换块
     goto BadSwapBlock;
    }
   }
   //全部复制完毕,则擦除掉原来的块
   if(0x01==(FlashEraseBlock(BlockStartAddr)&0x01)) //如果擦除失败
   {
    Addr=FlashDealBadBlock(Addr,1); //处理擦除时遇到的坏块
    BlockStartAddr=(Addr)&(~(FLASH_BLOCK_SIZE-1));  //计算块起始地址
   }
   //将前面部分不会写到的页复制回去
   for(i=0;i<(Addr-BlockStartAddr)/FLASH_PAGE_SIZE;i++)
   {
    //复制一页
    if(0x01&FlashCopyPage(SwapAddr+i*FLASH_PAGE_SIZE,BlockStartAddr+i*FLASH_PAGE_SIZE))
    {
     //如果复制失败,则处理该坏块
     //注意FlashDealBadBlock返回的是当前正在操作的扇区地址,
     //需要取出其块地址加上Addr原来的扇区地址合成新的扇区地址
     Addr=(FlashDealBadBlock(BlockStartAddr+i*FLASH_PAGE_SIZE,2)&(~(FLASH_BLOCK_SIZE-1)))
         +(Addr&(FLASH_BLOCK_SIZE-1));
     BlockStartAddr=(Addr)&(~(FLASH_BLOCK_SIZE-1));  //计算块起始地址
    }
   }
   return Addr; //复制完毕,返回
  }
  else //否则,擦除失败
  {
   BadSwapBlock:
   //标志该块擦除时被损坏
   FlashMarkBadCurrentSwapBlock();
  }
 }
 return Addr;
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:往FLASH中写一个扇区(FLASH_SECTOR_SIZE字节)。
入口参数:Addr: 字节地址;pBuf:保存数据的缓冲区;Remain:预计接下来还需要写多少扇区
返    回:写入的状态。0:成功。非0:失败。
备    注:当Remain不为0时,当前页以及该块内剩余部分将不会回写!
          如果数据传输结束,应该将Remain置0,将数据写回。
********************************************************************/
uint32 FlashWriteOneSector(uint32 Addr, uint8 * pBuf, uint32 Remain)
{
 uint32 i;
 uint32 SwapPageAddr;
 
 FlashClrCe(); //选中芯片
 if(Addr>FLASH_MAX_SECTOR_ADDR)return 1; //如果地址超出范围,则返回失败代码1,越界
 Addr=FlashAddrRemap(Addr); //重新影射地址
 if((Addr&(~(FLASH_PAGE_SIZE-1)))!=(FlashCurrentWriteSectorAddr&(~(FLASH_PAGE_SIZE-1)))) //如果跨page
 {
  if(FlashNeedWriteBack) //如果前面写了数据,则需要将当前读出的page写回
  {
   if(FlashWritePage()&0x01) //写入失败
   {
    Addr=FlashDealBadBlock(Addr-FLASH_PAGE_SIZE,3)+FLASH_PAGE_SIZE;  //坏块处理
   }
  }
  if((Addr&(~(FLASH_BLOCK_SIZE-1)))!=(FlashCurrentWriteSectorAddr&(~(FLASH_BLOCK_SIZE-1))))  //如果跨block,则需要擦除新的块,
  {
   //在擦除之前,要先将原来的块复制到交换区,并且将该块前面部分数据写回
   //该函数除了将整块数据复制到交换区以外,并且还将擦除掉原来的块,然后将前面部分复制回原来的块
   Addr=FlashCopyBlockToSwap(Addr);
  }
  //从交换区中读出对应的一页
  FlashWriteCommand(0x00);
  FlashWriteAddr4Byte(FlashGetCurrentSwapBlock()+(Addr&(FLASH_BLOCK_SIZE-1)));
  FlashWriteCommand(0x35);
  FlashWait();
  //随机写
  FlashWriteCommand(0x85);
  FlashWriteAddr4Byte(Addr); //写4字节地址
  FlashSetPortAsOut();  //总线设置为输出口
  for(i=0;i<FLASH_SECTOR_SIZE;i++)
  {
   FlashWriteByte(pBuf);
  }
  FlashSetPortAsIn(); //总线设置为输入口
  FlashNeedWriteBack=1; //需要写回
 }
 else  //没有超过一页地址,则直接写数据
 {
  //随机写
  FlashWriteCommand(0x85);
  FlashWriteAddr2Byte(Addr);
  FlashSetPortAsOut();  //总线设置为输出口
  for(i=0;i<FLASH_SECTOR_SIZE;i++)
  {
   FlashWriteByte(pBuf);
  }
  FlashSetPortAsIn(); //总线设置为输入口
  FlashNeedWriteBack=1; //需要写回
 }
 FlashCurrentWriteSectorAddr=Addr; //保存本次地址 
 if(Remain==0) //剩余扇区数为0,不会再写了,需要写回
 {
  if(FlashNeedWriteBack) //如果前面写了数据,则需要将当前读出的page写回
  {
   if(FlashWritePage()&0x01) //写入失败
   {
    Addr=FlashDealBadBlock(Addr,3);  //坏块处理
   }
  }
  //计算剩余页数
  Remain=(((Addr+FLASH_BLOCK_SIZE)&(~(FLASH_BLOCK_SIZE-1)))-(Addr&(~(FLASH_PAGE_SIZE-1))))/FLASH_PAGE_SIZE-1;
  //计算在交换块中的起始页地址
  SwapPageAddr=FlashGetCurrentSwapBlock()+(Addr&(FLASH_BLOCK_SIZE-1));
  
  for(i=0;i<Remain;i++)  //将该块内保存在交换块中剩余部分页的数据复制回该块
  {
   Addr+=FLASH_PAGE_SIZE;   //从下一页开始写
   SwapPageAddr+=FLASH_PAGE_SIZE;   
   if(0x01==(FlashCopyPage(SwapPageAddr,Addr)&0x01)) //如果复制失败
   {
    Addr=FlashDealBadBlock(Addr,2);  //处理坏块
   }
  }
  FlashNeedWriteBack=0; //清除需要写回标志
  FlashCurrentWriteSectorAddr=-1;
 }
 FlashSetCe(); //释放FLASH芯片
 return 0;
}
/////////////////////////End of function/////////////////////////////

/********************************************************************
函数功能:从FLASH中读出一扇区数据保存到缓冲区中。
入口参数:Addr: 字节地址;pBuf:保存数据的缓冲区;Remain:预计接下来还需要读多少扇区
返    回:读取的状态。0:成功。非0:失败。
备    注:当Remain不为0时,将保存当前地址以备后面的继续读当前页,当不为0时,
          设置当前读地址为无效,从而下次读时必须重新使用读命令将数据从flash中读入到页缓存。
********************************************************************/
uint32 FlashReadOneSector(uint32 Addr, uint8 * pBuf, uint32 Remain)
{
 uint32 i;
 FlashClrCe(); //选中芯片
 if(Addr>FLASH_MAX_SECTOR_ADDR)return 1; //如果地址超出范围,则返回失败代码1,越界
 Addr=FlashAddrRemap(Addr); //重新影射地址
 if((Addr&(~(FLASH_PAGE_SIZE-1)))
    !=(FlashCurrentReadSectorAddr&(~(FLASH_PAGE_SIZE-1)))) //如果跨page
 {
  //如果跨页的,则写读数据命令
  FlashWriteCommand(0x00);
  FlashWriteAddr4Byte(Addr);
  FlashWriteCommand(0x30);
  FlashWait(); //等待数据读回
 }
 else
 {
  //如果没有跨页,则可以直接读
  FlashWriteCommand(0x05);
  FlashWriteAddr2Byte(Addr);
  FlashWriteCommand(0xE0);
  FlashWait(); //等待数据读回
 }
 for(i=0;i<FLASH_SECTOR_SIZE;i++)
 {
  FlashReadByte(pBuf);  //读一字节数据
 }
 FlashCurrentReadSectorAddr=Addr; //保存当前操作的地址
 if(Remain==0) //如果不会接着读,那么就设置当前读过的地址为无效值
 {
  FlashCurrentReadSectorAddr=-1;
 }
 FlashSetCe(); //释放总线
 return 0;
}
/////////////////////////End of function/////////////////////////////

使用特权

评论回复
板凳
lhj200304| | 2009-3-27 14:45 | 只看该作者

留个记号

慢慢看,沙发

使用特权

评论回复
地板
古道热肠| | 2009-3-27 16:16 | 只看该作者

呵呵,下载试试效果.

使用特权

评论回复
5
古道热肠| | 2009-3-27 17:05 | 只看该作者

哈哈,试过了,蛮好的,稳定性不错,截图与大家共赏

图中用红线框圈中的移动盘系开发板生成的优盘.软件由Computer00研制成功.

使用特权

评论回复
6
computer00|  楼主 | 2009-3-27 23:25 | 只看该作者

哈哈~~~格式化了没?烧几个大文件进去测试下吧...

顺便帮俺检查下bug……

现在还有一个隐患,就是将数据随机写入后,如果这时发生写错误,那么将无法恢复
到原来的数据,只能使用错误的数据了。不单独在内存中开一个页缓冲,就没办法解决这个问题,
因为数据已经写入到FLASH的缓冲区去了。可以考虑再对代码进行改进,开一个2K的页缓冲,
这样出错时,就可以把页缓冲区中的数据写出去。但是牵涉到页数据的读出,速度又会降低一些。

使用特权

评论回复
7
wswh2o| | 2009-3-28 08:47 | 只看该作者

oo真是高产

使用特权

评论回复
8
古道热肠| | 2009-3-28 10:19 | 只看该作者

俺测试是格式化了,而且拷贝了一大推文件进去

目前没有出现错误.

使用特权

评论回复
9
computer00|  楼主 | 2009-3-28 10:31 | 只看该作者

还没出现坏块时应该不会有什么问题。就是坏块处理时m可能

我只是简单地模拟了一些坏块调试了下,可能没有考虑到全部的情况。

先不管了,用段时间发现问题再找原因吧....

接下来就可以考虑放汉字字库进去拉,这样就可以显示汉字了~~~~~

以前没研究过windows下的字库文件,啥时来研究下这个,最好能够直接用windows的字库文件就好了……
实在不行只好自己写个程序或者去网上找个汉字字库提取的软件了。

使用特权

评论回复
10
ch2003_23| | 2009-3-28 23:50 | 只看该作者

oo牛

使用特权

评论回复
11
古道热肠| | 2009-3-29 10:13 | 只看该作者

用点阵字库,12点阵和16点阵的最实用.

字库文件UcDos下面有现成的.

使用特权

评论回复
12
computer00|  楼主 | 2009-3-29 11:07 | 只看该作者

俺准备趁这个机会研究下windows的字库文件,嘿嘿。

使用特权

评论回复
13
alex74| | 2009-3-30 17:55 | 只看该作者

不错

这个原理很合理

使用特权

评论回复
14
alex74| | 2009-3-30 18:00 | 只看该作者

12点阵和16点阵的代码我给你

如题
把pdf后缀改成C就可以
相关链接:https://bbs.21ic.com/upfiles/img/20093/2009330175816170.pdf

使用特权

评论回复
15
alex74| | 2009-3-30 18:03 | 只看该作者

接上面

上面那个是16点阵
这个是12点阵.


相关链接:https://bbs.21ic.com/upfiles/img/20093/200933018135876.pdf

使用特权

评论回复
16
computer00|  楼主 | 2009-3-30 18:58 | 只看该作者

谢谢alex74~~~俺在想能不能直接用windows自带的字库文件

这样以后想换个字体时,直接从windows下的字库文件下复制一个对应的字库文件到U盘中就可以拉~~~
而不想放到源文件中去编译……


使用特权

评论回复
17
computer00|  楼主 | 2009-3-30 19:06 | 只看该作者

不小心发现程序中的一个bug,哈哈~~~~

 //如果最后一次访问的地址和本次访问的地址属于同一个块地址,那么不需要重新影射
 if(0==((Addr-FlashLastAccessAddr)&(FLASH_BLOCK_SIZE-1)))
 {
  return CurrentRemapBlockAddr+(Addr&(FLASH_BLOCK_SIZE-1)); //由当前块地址加上块内偏移得到完整地址
 }

这里搞错了,判断是否跨块,应该保留高半部分……将代码修改成如下:

 //如果最后一次访问的地址和本次访问的地址属于同一个块地址,那么不需要重新影射
 if((Addr&(~(FLASH_BLOCK_SIZE-1)))==(FlashLastAccessAddr&(~(FLASH_BLOCK_SIZE-1))))
 {
  return CurrentRemapBlockAddr+(Addr&(FLASH_BLOCK_SIZE-1)); //由当前块地址加上块内偏移得到完整地址
 }


另外,说明中的“当坏块数量不为0时,先判断最后一次访问和
本次访问的地址是否属于同一页,如果属于,那么就直接影射到上
一次影射过的块地址。”,应该是“是否属于同一块”,这里不小心
写成“同一页”了。

使用特权

评论回复
18
alex74| | 2009-3-30 21:44 | 只看该作者

...

我用的是ucdos 7.0的字库.

使用特权

评论回复
19
alex74| | 2009-3-30 21:47 | 只看该作者

....

windows字库完全不同的.windows是用unicode的,unicode这个东西没有大内存(至少上百K字节)是玩不了的.
你别想了

使用特权

评论回复
20
alex74| | 2009-3-30 21:51 | 只看该作者

oo其实你这个代码和yaffs比有重大缺陷

就是写均衡没有做

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

246

主题

14693

帖子

210

粉丝