×
单片机 > 单片机程序设计 > 详情

基于stm32 的fatfs0.07e移植过程分享

发布时间:2020-08-26 发布时间:
|
         前段时间移植了fats文件系统,并基于这个文件系统,做了个简单的wav文件播放器,可以播放sd卡根目录下music文件夹里所有pcm编码的wav文件,并且支持下一曲、上一曲、暂停、开始操作。在这个过程中遇到了很多问题,在这里把我的移植经历和大家分享。

         硬件平台:英蓓特 EM-STM3210E开发板

         软件平台:mdk3.40;stm32V2.01库

         FATfs版本:R0.07e。可在http://elm-chan.org/fsw/ff/00index_e.html下载

         EM-STM3210E开发板上的sd卡例程只是简单的写和读sd卡的某个扇区,当然这样写过后卡上的文件系统格式就被破坏了,正因为这样,运行过板上的示例程序后我就不爽——要把卡重新格一遍。

         在网上找了找,看到fatfs文件系统的移植也挺简单的,于是就搜集了fat文件系统的一些资料开始看,准备看完后做移植。

        移植工作需要做的就是修改diskio.c和diskio.h这两个文件,给sd卡的底层和文件系统做个接口。

        具体的工作就是完成如下几个函数:

         disk_initialize ();//卡初始化
        disk_status ();   //返回卡的状态
        disk_read ();     //读扇区
        disk_write ();    //写扇区
        disk_ioctl ();     //支持几个命令
        get_fattime ();  //给fatfs提供时间

    其中:

       disk_initialize ();  disk_read ();  disk_read ();三个函数比较重要。

       在进行上面函数的编写前,我们一定要保证我们已经写好了sd卡的底层,如卡的初始化,读一个扇区、读多个扇区、写一个扇区、写多个扇区等。我的开发板上这些功能函数比较全,sd卡的访问使用sd模式,可选择 查询、中断、DMA三种方式进行操作,当然DMA方式是最快的。

      /**************************************************************************
功能 :磁盘初始化

***************************************************************************/
DSTATUS disk_initialize (BYTE drv  )// Physical drive nmuber (0..) 
{
    SD_Error Status;  
    if(drv)
    {
        return STA_NOINIT;  //仅支持磁盘0的操作
    }
    Status = SD_Init();
    if(Status != SD_OK)
    {
        return STA_NOINIT; //其他错误:初始化失败
    }
    else
    {
  Status = SD_GetCardInfo(&SDCardInfo); //读sd卡信息
   if (Status != SD_OK)
    {
     return  STA_NOINIT;//RES_NOTRDY;  //报NOT READY错误
        }
              // Select Card 
     Status = SD_SelectDeselect((u32) (SDCardInfo.RCA << 16));
     if (Status != SD_OK)
     {
        return  STA_NOINIT;//RES_NOTRDY;  //报NOT READY错误
     }

   switch(mda_or_interrupt)
   {
    case 1:  //dma方式

    Status = SD_EnableWideBusOperation(SDIO_BusWide_4b);
       if (Status != SD_OK)
        {  
         return RES_NOTRDY;  //报NOT READY错误
        }
       Status = SD_SetDeviceMode(SD_DMA_MODE);
      if (Status != SD_OK)
      {
         return RES_NOTRDY;  //报NOT READY错误
      }

      break;

    case 0:  //中断方式
       Status = SD_EnableWideBusOperation(SDIO_BusWide_1b);
       if (Status != SD_OK)
        {  
         return RES_NOTRDY;  //报NOT READY错误
        }
       Status = SD_SetDeviceMode(SD_INTERRUPT_MODE);  
      if (Status != SD_OK)
      {
         return RES_NOTRDY;  //报NOT READY错误
      }
       break;
     default :
      break;
   }

 return 0;           //初始化成功
    }
}

       下面的函数一般可以直接返回0,也可用 sd_ncd 脚检测有无sd卡

/**************************************************************************
功能:Return Disk Status   可用 sd_ncd 脚检测有无sd卡   有: 拉低 ; 无: 浮空

***************************************************************************/
DSTATUS disk_status (BYTE drv)  // Physical drive nmuber (0..) 
{
 if(drv)
    {
        return STA_NOINIT;  //仅支持磁盘0的操作
    }
 return 0;               //初始化成功
}    

/**************************************************************************
功能: Read Sector(s)

***************************************************************************/
DRESULT disk_read (
 BYTE drv,  /* Physical drive nmuber (0..) */
 BYTE *buff,  /* Data buffer to store read data */
 DWORD sector, /* Sector address (LBA) */
 BYTE count  /* Number of sectors to read (1..255) */
)
{
    vu8 res1 = 0, res2 = 0;
   SD_Error Status; 

    if (drv || !count)
    {    
        return RES_PARERR;  //仅支持单磁盘操作,count不能等于0,否则返回参数错误
    }

 switch(mda_or_interrupt)
  {
   case 1:  //dma方式
      if(count==1)            //1个sector的读操作      
      {      
    Status = SD_ReadBlock(sector << 9,buff2,BlockSize);//sector<<9 扇区地址转为字节地址 一个扇区512字节
          memcpy(buff,buff2,BlockSize); 
    res1=buff[510]; res2=buff[511];  //调试用                                          
      }                                                
      else                    //多个sector的读操作     
      {    
       Status = SD_ReadMultiBlocks(sector << 9,buff2,BlockSize,count);
          memcpy(buff,buff2,BlockSize * count);                                          
      } 

     break;
  case 0:  //中断方式
      if(count==1)            //1个sector的读操作      
      {      
    Status = SD_ReadBlock(sector<<9,(u32 *)(&buff[0]),BlockSize);
    res1=buff[510]; res2=buff[511]; //调试用                                              
      }                                                
      else                    //多个sector的读操作     
      {    
       Status = SD_ReadMultiBlocks(sector<<9 ,(u32 *)(&buff[0]),BlockSize,count);                                     
      }  
  
      break;
    default :
     break;
 }
    //处理返回值,将sdcard.c的返回值转成ff.c的返回值
    if(Status == SD_OK)
        return RES_OK;
    else
        return RES_ERROR;
}

/**************************************************************************
功能: Write Sector(s)

***************************************************************************/                                                     
#if _READONLY == 0
DRESULT disk_write (
 BYTE drv,   /* Physical drive nmuber (0..) */
 const  BYTE *buff, /* Data to be written */
 DWORD sector,  /* Sector address (LBA) */
 BYTE count   /* Number of sectors to write (1..255) */
)
{
   SD_Error Status;
  
    if (drv || !count)
    {    
        return RES_PARERR;  //仅支持单磁盘操作,count不能等于0,否则返回参数错误
    }

 switch(mda_or_interrupt)
  {
   case 1:  //dma方式

      if(count==1)            //1个sector的写操作      
      {      
    memcpy(buff2,buff,BlockSize);
          Status = SD_WriteBlock(sector << 9,buff2,BlockSize);//sector<<9 扇区地址转为字节地址 一个扇区512字节                                             
      }                                                
      else                    //多个sector的写操作     
      {    
     memcpy(buff2,buff,BlockSize * count);
          Status =SD_WriteMultiBlocks(sector << 9,buff2,BlockSize,count);                                          
      }  
  
     break;

   case 0:  //中断方式

      if(count==1)            //1个sector的写操作      
      {      
    Status = SD_WriteBlock(sector << 9 ,(u32 *)(&buff[0]),BlockSize);                                            
      }                                                
      else                    //多个sector的写操作     
      {    
       Status = SD_WriteMultiBlocks(sector << 9 ,(u32 *)(&buff[0]),BlockSize,count);                                     
      } 

      break;
    default :
     break;
 }
                                        
    //处理返回值,将sdcard.c的返回值转成ff.c的返回值
    if(Status == SD_OK)
        return RES_OK;
    else
        return RES_ERROR;
}

#endif /* _READONLY */

/**************************************************************************
功能: Miscellaneous s

***************************************************************************/ 
DRESULT disk_ioctl (
 BYTE drv,  /* Physical drive nmuber (0..) */
 BYTE ctrl,  /* Control code */
 void *buff  /* Buffer to send/receive control data */
)
{
 u32 x, y, z;
    DRESULT res;

    if (drv)
    {    
        return RES_PARERR;  //仅支持单磁盘操作,否则返回参数错误
    }
    //FATFS目前版本仅需处理CTRL_SYNC,GET_SECTOR_COUNT,GET_BLOCK_SIZ三个命令
    switch(ctrl)
    {
     case CTRL_SYNC:
        
         if(SD_GetTransferState()==SD_NO_TRANSFER)
         {
             res = RES_OK;
         }
         else
         {
             res = RES_ERROR;
         }
       
         break;
        
     case GET_BLOCK_SIZE:

         *(WORD*)buff = 512;
         res = RES_OK;

         break;

     case GET_SECTOR_COUNT:     //读卡容量
        ////formula of the capacity///////////////
        //
        //  memory capacity = BLOCKNR * BLOCK_LEN
        // 
        // BLOCKNR = (C_SIZE + 1)* MULT
        //
        //           C_SIZE_MULT+2
        // MULT = 2
        //
        //               READ_BL_LEN
        // BLOCK_LEN = 2
     //////////////////////////////////////////
     if (SD_GetCardInfo(&SDCardInfo)==SD_OK)//读sd卡信息
         {
          x=SDCardInfo.SD_csd.DeviceSize+1; //C_SIZE + 1
    y=SDCardInfo.SD_csd.DeviceSizeMul+2; //C_SIZE_MULT+2
    z=SDCardInfo.SD_csd.RdBlockLen+y;
       *(DWORD*)buff =x<           res = RES_OK;
   }
   else
   {
    res = RES_ERROR ;
   }

         break;

     default:

         res = RES_PARERR;

         break;
    }
    return res;
}

/*-----------------------------------------------------------------------*/
/* User defined to give a current time to fatfs module          */
/* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31)     */                                                                                                                                                                                                                                          
/* 15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2)           */ 
/*-----------------------------------------------------------------------*/                                                                                                                                                                                                                                               
DWORD get_fattime (void)
{
    struct tm t;
    DWORD date;
    t = Time_GetCalendarTime();
    t.tm_year -= 1980;  //年份改为1980年起
    t.tm_mon++;          //0-11月改为1-12月
    t.tm_sec /= 2;       //将秒数改为0-29
    
    date = 0;
    date = (t.tm_year << 25) | (t.tm_mon<<21) | (t.tm_mday<<16)|\
            (t.tm_hour<<11) | (t.tm_min<<5) | (t.tm_sec);

    return date;
} //此函数参考网友 九九的 程序

       当这些函数写好后,移植工作就完成了。

      我们在使用时在main.c中加入#i nclude “ccsbcs.c” ,并在 ffconf.h里做如下修改

       #define _USE_LFN 1

       #define_LFN_UNICODE 0//不使用UNICODE

        移植完后我进行了读写速度测试:10M文件连续写入、读出。 1s中断计时
       DMA  方式   : 读 49k-50K byte/s   写 1M byte/s
       中断 方式   : 读 47k byte/s            写 0.83M byte/s

       wav文件的播放,我主要是先遍历music文件夹下所有wav格式的文件,然后把文件名写入play_list.txt,播放时,从play_list.txt读出歌曲名,根据歌曲名读出数据送DA。

      /***************************************************************
功能: 从music 文件夹里读出所有wav格式的歌曲名写入 play_list.txt
    返回 name_num
****************************************************************/
////////////////////////////////////////////////////////////////
//当一个文件名的长度小于 8+1+3时长文件名区是空的;当长度大于12时
//长文件名区才会是 这个文件的真实文件名,而这是短文件名区是经过计算后
//的文件名,并不是真实的文件名
////////////////////////////////////////////////////////////////
FRESULT scan_files (char* path) 

    FRESULT res; 
    FILINFO fno; 
    DIR dir; 
    int i,j; 
    char *fn; 
 char namebuf_1[fn_buf_len]={0};
 static u8 fs_type_copy;
#if _USE_LFN  //为 长文件名区指针赋初值
    static char lfn[_MAX_LFN * (_DF1S ? 2 : 1) + 1]; 
    fno.lfname = lfn; 
    fno.lfsize = sizeof(lfn); 
#endif 
 name_num=0;

 res =f_mount(0, &fs); //每次 open一个对象时都先要 注册一个 缓冲区
 res = f_unlink ("music/play_list.txt");//每次重建文件名时,先删掉原来的。
    res = f_opendir(&dir, path); // 打开指定的目录
    if (res == FR_OK) { 
    for (;;) 
 { 
repet:            //第一次执行读一个文件,第二次执行读第二个文件……………… 
 res = f_readdir(&dir, &fno);//读出目录里的 文件信息 存入 fno
 fs_type_copy=dir.fs->fs_type;

    if (res != FR_OK || fno.fname[0] == 0) break; 
#if _USE_LFN 
 {
 fn = *fno.lfname ? fno.lfname : fno.fname;  
 j = *fno.lfname ? (fno.lfsize-1): (8+1+3 -1); 
 }    
#else 
    {
 fn = fno.fname; j=8+1+3 -1; 
 }
#endif

   for(i=j;i>=3;i--) //文件名 从右到左判断 当文件扩展名为 wav时成立
   {
             if ((*(fn+i-2) == 'w')&&(*(fn+i-1) == 'a')&&(*(fn+i) == 'v'))
    {
    *(fn+i+1)='\0';//为文件名添加字符串结束符
     ///////////////  成功检测到相符的文件了    
//对文件名进行存储 存入"music/play_list.txt"
/////////////////////////////////////////////
//f_open 函数的型参 指的是 从根目录开始的文件名称
//故muxic文件夹下的goon.wav 文件的名字要写做 "music/goon.wav" 
/////////////////////////////////////////////
    for(j=0;j        {namebuf[j]=0;}
    namebuf[0]='m';namebuf[1]='u';namebuf[2]='s';namebuf[3]='i';namebuf[4]='c';namebuf[5]='/';
    memcpy(&namebuf[6],fn,(i+2));
    for(j=0;j   
     //文件名是不是写完一遍了
    if(!strcmp(namebuf,namebuf_1)) 
    {
    res = f_close (&F);
      res = f_mount(0, NULL);
    return FR_OK; //相等 返回0
    }
    if(updata_list==1){updata_list=0;memcpy(namebuf_1,namebuf,fn_buf_len);}
 
    // res =f_mount(0, &fs);
       res = f_open (&F,"music/play_list.txt", FA_CREATE_ALWAYS |FA_OPEN_EXISTING | FA_WRITE|FA_READ);
    res = f_lseek (&F, name_num*fn_buf_len );
    res = f_write (&F, namebuf, fn_buf_len, &rw_num);
       res = f_sync (&F);
    name_num++;

      dir.fs->fs_type=fs_type_copy;  //dir 不知怎么被改动了,导致无法正确读下一个文件
// 改动的原因是: f_mount(0,&fs)时 fs->fs_type=0;而下次f_readdir时 就用了 fs这个缓冲
// 只 f_mount(0,&fs1)而不f_mount(0,&fs)?
    goto repet;
 
    }     
     }
  goto repet; 
    }
    } 
    return res; 
}




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
8051单片机的函数发生器的设计