【转载】基于SD卡的FatFs文件系统(FatFs移植到STM32)

https://blog.csdn.net/zyxhangiian123456789/article/details/79098483

平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线

工程介绍:主要文件在USER组中,bsp_sdio_sdcard.c,bsp_sdio_sdcard.h和main.c,另外FatFs是用来后面移植文件系统使用的,对于本节内容暂时不需要。bsp_sdio_sdcard.c和bsp_sdio_sdcard.h文件主要参考教材《STM32库开发实战指南——基于STM32F03》。另外就是本教材用到的FatFs系统源代码,这里温馨提示一下,关于FatFs目前网上笔者找到的最新版的参考资料不是太多,所以笔者在用最新版做的时候,一些函数原型发生了变化,虽然变化不大,也给我造成了一定的阻碍,所以建议大家下载稍微老一点的版本,这样资料较多,出问题好解决,我最终用的版本是R0.09,因为在使用函数f_mkfs()的过程中遇到问题,无法格式化SD卡,最终选择较老的版本。本文有些内容来自于其他网友总结,在此表示感谢。


整体的项目和上一节中的SDIO读写SD卡类似,细节上在添加了FatFs组,专门存放FatFs移植的内容。对于移植过程,就是对diskio.c文件的修改的过程。这个文件完成了与底层有多的操作,我们只需要实现一下函数即可,不同的平台函数实现略有不同,但是提供给用户最终的接口是相同的,也正是因为这一点,使得FatFs文件系统有了很好的可移植性。


再次申明一下,FatFs文件系统与存储设备的接口函数再diskio.c文件中,我们只需要完善该文件即可。这也就是所谓的软件移植裁剪过程吧。我们需要完善五个函数disk_status,disk_initialize,disk_read,disk_write,disk_ioctl等,还有一个获取时间戳的函数,可以直接return 0;

1.宏定义


   
   
  1. #define SD_CARD 0 //SD卡,卷标为0
  2. #define EX_FLASH 1 //外部flash,卷标为1,为以后外部Flash扩展预留的
  3. #define SD_BLOCKSIZE 512
  4. //声明外部变量
  5. extern SD_CardInfo SDCardInfo;
2.dis_status 获取磁盘的状态


   
   
  1. //获得磁盘状态
  2. DSTATUS disk_status (
  3. BYTE pdrv /* Physical drive nmuber to identify the drive */
  4. )
  5. {
  6. DSTATUS status = STA_NOINIT;
  7. switch(pdrv)
  8. {
  9. case SD_CARD: //SD卡
  10. status &= ~STA_NOINIT;
  11. break;
  12. case EX_FLASH: //外部flash
  13. break;
  14. default:
  15. status = STA_NOINIT;
  16. break;
  17. }
  18. return status;
  19. }
3. disk_initialize 初始化磁盘,当遇到无法挂载SD卡,或者其他问题时,优先检查该函数,该函数会调用SD_Init函数,需要检查

SD_Init函数的返回结果是否为SD_OK。


   
   
  1. //初始化磁盘
  2. DSTATUS disk_initialize (
  3. BYTE pdrv /* Physical drive nmuber to identify the drive */
  4. )
  5. {
  6. DSTATUS status = STA_NOINIT;
  7. switch(pdrv)
  8. {
  9. case SD_CARD: //SD卡
  10. if(SD_OK == SD_Init()) //SD卡初始化
  11. {
  12. status &= ~STA_NOINIT;
  13. }
  14. else
  15. {
  16. status = STA_NOINIT;
  17. }
  18. break;
  19. case EX_FLASH: //外部flash
  20. break;
  21. default:
  22. status = STA_NOINIT;
  23. break;
  24. }
  25. return status;
  26. }
4. disk_read 读取SD卡


   
   
  1. //读扇区
  2. //pdrv:磁盘编号0~9
  3. //*buff:数据接收缓冲首地址
  4. //sector:扇区地址
  5. //count:需要读取的扇区数
  6. DRESULT disk_read (
  7. BYTE pdrv, /* Physical drive nmuber to identify the drive */
  8. BYTE *buff, /* Data buffer to store read data */
  9. DWORD sector, /* Sector address in LBA */
  10. UINT count /* Number of sectors to read */
  11. )
  12. {
  13. DRESULT status = RES_PARERR;
  14. SD_Error SD_Status = SD_OK;
  15. switch(pdrv)
  16. {
  17. case SD_CARD: //SD卡
  18. if((DWORD)buff & 3)
  19. {
  20. DRESULT res = RES_OK;
  21. DWORD scratch[SD_BLOCKSIZE / 4];
  22. while(count--)
  23. {
  24. res = disk_read(SD_CARD, ( void*)scratch, sector++, 1);
  25. if(res != RES_OK)
  26. {
  27. break;
  28. }
  29. memcpy(buff, scratch, SD_BLOCKSIZE);
  30. buff += SD_BLOCKSIZE;
  31. }
  32. return res;
  33. }
  34. SD_Status = SD_ReadMultiBlocks(buff, sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
  35. if(SD_Status == SD_OK)
  36. {
  37. //检查传输是否完成
  38. SD_Status = SD_WaitReadOperation();
  39. while(SD_GetStatus() != SD_TRANSFER_OK);
  40. }
  41. if(SD_Status != SD_OK)
  42. {
  43. status = RES_PARERR;
  44. }
  45. else
  46. {
  47. status = RES_OK;
  48. }
  49. break;
  50. case EX_FLASH: //外部flash
  51. break;
  52. default:
  53. status = RES_PARERR;
  54. break;
  55. }
  56. return status;
  57. }
5.disk_write 写数据到SD卡


   
   
  1. //写扇区
  2. //pdrv:磁盘编号0~9
  3. //*buff:发送数据首地址
  4. //sector:扇区地址
  5. //count:需要写入的扇区数
  6. #if _USE_WRITE
  7. DRESULT disk_write (
  8. BYTE pdrv, /* Physical drive nmuber to identify the drive */
  9. const BYTE *buff, /* Data to be written */
  10. DWORD sector, /* Sector address in LBA */
  11. UINT count /* Number of sectors to write */
  12. )
  13. {
  14. DRESULT status = RES_PARERR;
  15. SD_Error SD_Status = SD_OK;
  16. //检查参数
  17. if(!count)
  18. {
  19. return RES_PARERR;
  20. }
  21. switch(pdrv)
  22. {
  23. case SD_CARD: //SD卡
  24. if((DWORD)buff & 3)
  25. {
  26. DRESULT res = RES_OK;
  27. DWORD scratch[SD_BLOCKSIZE / 4];
  28. while(count--)
  29. {
  30. memcpy(scratch, buff, SD_BLOCKSIZE);
  31. res = disk_write(SD_CARD, ( void*)scratch, sector++, 1);
  32. if(res != RES_OK)
  33. {
  34. break;
  35. }
  36. buff += SD_BLOCKSIZE;
  37. }
  38. return res;
  39. }
  40. SD_Status = SD_WriteMultiBlocks(( uint8_t *)buff, sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
  41. if(SD_Status == SD_OK)
  42. {
  43. //检查传输是否完成
  44. SD_Status = SD_WaitReadOperation();
  45. while(SD_GetStatus() != SD_TRANSFER_OK);
  46. }
  47. if(SD_Status != SD_OK)
  48. {
  49. status = RES_PARERR;
  50. }
  51. else
  52. {
  53. status = RES_OK;
  54. }
  55. break;
  56. case EX_FLASH: //外部flash
  57. break;
  58. default:
  59. status = RES_PARERR;
  60. break;
  61. }
  62. return status;
  63. }
6.disk_ioctl 函数,获取SD卡的某些参数,如块大小等。


   
   
  1. //其他表参数的获得
  2. //pdrv:磁盘编号0~9
  3. //ctrl:控制代码
  4. //*buff:发送/接收缓冲区指针
  5. #if _USE_IOCTL
  6. DRESULT disk_ioctl (
  7. BYTE pdrv, /* Physical drive nmuber (0..) */
  8. BYTE cmd, /* Control code */
  9. void *buff /* Buffer to send/receive control data */
  10. )
  11. {
  12. DRESULT res = RES_ERROR;
  13. switch(pdrv) //SD卡
  14. {
  15. case SD_CARD:
  16. switch(cmd)
  17. {
  18. case GET_SECTOR_SIZE:
  19. *(WORD*)buff = SD_BLOCKSIZE;
  20. break;
  21. case GET_BLOCK_SIZE:
  22. *(DWORD*)buff = SDCardInfo.CardBlockSize;
  23. break;
  24. case GET_SECTOR_COUNT:
  25. *(DWORD*)buff = SDCardInfo.CardCapacity / SD_BLOCKSIZE;
  26. break;
  27. case CTRL_SYNC:
  28. break;
  29. }
  30. res = RES_OK;
  31. break;
  32. case EX_FLASH: //外部flash
  33. break;
  34. default:
  35. res = RES_PARERR;
  36. break;
  37. }
  38. return res;
  39. }
7.get_fattime函数,获取时间戳。没有实现。


   
   
  1. //获得时间
  2. DWORD get_fattime (void)
  3. {
  4. //返回当前时间戳
  5. return 0;
  6. }
8.另外需要修改ffconf.h文件,另外注意,新版本的该文件都会在宏的前面加上FF前缀,例如对于_USE_LEN,新版本的可能是

FF_USE_LLEN,本节的内容都是按照旧版本来说的,因此宏没有以FF为前缀。


   
   
  1. #define _USE_LEN 2 //长文件名支持,默认不支持长文件名
  2. #define _USE_MKFS 1 //格式化功能选择,使能FatFs的格式化功能
  3. #define _CODE_PAGE 936 //语言功能选择,需要同时把语言文件添加到工程中,为支持简体中文,我们需要添加cc963.c文件
  4. #define _VOLUMES 2 //物理设备数量,这里设置为2,包括SD卡和预留的外部Flash芯片
  5. #define _MIN_SS 512 //指定扇区大小的最小值
  6. #define _MAX_SS 4096 //指定扇区大小的最大值
以上 就完成了FatFs的移植过程,接下来是对功能的测试,直接上代码了。

测试:


   
   
  1. //FAT功能测试:格式化测试,文件写入测试,文件读取测试(基本功能)
  2. FATFS fs; //FatFs文件系统对象
  3. FIL fnew; //文件对象
  4. FRESULT res_sd; //文件操作结果
  5. UINT fnum; //文件成功读写数量
  6. BYTE ReadBuffer[ 1024] = { 0};
  7. BYTE WriteBuffer[] = "成功移植了FatFs文件系统!\r\n"; //写缓存区
main 主函数,包含格式化测试和文件读写测试。

   
   
  1. int main()
  2. {
  3. //串口配置
  4. USART_Config();
  5. //初始化LED
  6. LED_GPIO_Config();
  7. //初始化SD卡
  8. if(SD_Init() == SD_OK)
  9. {
  10. printf( "SD卡初始化成功,即将挂载SD卡。\r\n");
  11. }
  12. //挂载SD卡
  13. res_sd = f_mount(&fs, "0:", 1);
  14. //***********************格式化测试****************************
  15. if(res_sd == FR_NO_FILESYSTEM)
  16. {
  17. printf( "SD卡没有文件系统,即将进行格式化...\r\n");
  18. //格式化
  19. res_sd = f_mkfs( "0:", 0, 0);
  20. if(res_sd == FR_OK)
  21. {
  22. printf( "SD卡成功格式化!\r\n");
  23. //格式化后先取消挂载
  24. res_sd = f_mount( NULL, "0:", 1);
  25. //再重新挂载
  26. res_sd = f_mount(&fs, "0:", 1);
  27. }
  28. else
  29. {
  30. printf( "文件格式化失败!错误代码:%d\r\n",res_sd);
  31. while( 1);
  32. }
  33. }
  34. else if(res_sd != FR_OK)
  35. {
  36. printf( "挂载文件系统失败!可能是因为文件初始化失败!错误代码:%d\r\n", res_sd);
  37. }
  38. else
  39. {
  40. printf( "文件系统挂载成功, 可进行读写测试!\r\n");
  41. }
  42. //***********************写测试****************************
  43. //打开文件,如果文件不存在则创建它
  44. printf( "即将进行文件写入测试....\r\n");
  45. //打开文件,若不存在就创建
  46. res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE);
  47. //文件打开成功
  48. if(res_sd == FR_OK)
  49. {
  50. printf( "打开文件成功!开始写入数据!\r\n");
  51. res_sd= f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum);
  52. if(res_sd == FR_OK)
  53. {
  54. printf( "数据写入成功!\r\n");
  55. printf( "数据:%s。共写入%d个字符\r\n", WriteBuffer, fnum);
  56. }
  57. else
  58. {
  59. printf( "数据写入失败!\r\n");
  60. }
  61. //关闭文件
  62. f_close(&fnew);
  63. }
  64. //***********************读测试****************************
  65. //打开文件,如果文件不存在则创建它
  66. printf( "即将进行文件读取测试....\r\n");
  67. //打开文件,若不存在就创建
  68. res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_OPEN_EXISTING | FA_READ);
  69. //文件打开成功
  70. if(res_sd == FR_OK)
  71. {
  72. printf( "打开文件成功!开始读取数据!\r\n");
  73. res_sd= f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
  74. if(res_sd == FR_OK)
  75. {
  76. printf( "数据读取成功!\r\n");
  77. printf( "数据:%s\r\n", ReadBuffer);
  78. }
  79. else
  80. {
  81. printf( "数据读取失败!\r\n");
  82. }
  83. //关闭文件
  84. f_close(&fnew);
  85. }
  86. scan_files( "FatFs读写测试文件.txt");
  87. //其他功能测试
  88. file_check();
  89. //多项功能测试
  90. miscellaneous();
  91. //取消挂载文件系统
  92. f_mount( NULL, "0:", 1);
  93. while( 1);
  94. }
补充,如有需要可以借鉴,其他功能测试。

   
   
  1. //多项功能测试
  2. static FRESULT miscellaneous()
  3. {
  4. DIR dir;
  5. FATFS *pfs;
  6. DWORD fre_clust, fre_sect, tot_sect;
  7. printf( "\r\n*************************设备信息获取***************************\r\n");
  8. //获取设备信息和空簇大小
  9. res_sd = f_getfree( "0:", &fre_clust, &pfs);
  10. //计算得到总的扇区个数和空扇区个数
  11. tot_sect = (pfs->n_fatent - 2) * pfs->csize;
  12. fre_sect = fre_clust * pfs->csize;
  13. printf( "设备总空间:%10lu KB\r\n可用空间:%10lu KB。\r\n", tot_sect* 4, fre_sect* 4);
  14. printf( "\r\n*************************文件定位和格式化写入功能测试***************************\r\n");
  15. //打开文件,若不存在就创建
  16. res_sd = f_open(&fnew, "0:FatFs多项功能测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
  17. //文件打开成功
  18. if(res_sd == FR_OK)
  19. {
  20. printf( "打开文件成功!开始读取数据!\r\n");
  21. res_sd= f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum);
  22. if(res_sd == FR_OK)
  23. {
  24. printf( "数据写入成功!\r\n");
  25. printf( "数据:%s\r\n", WriteBuffer);
  26. //文件定位,定位到文件末尾,f_size获取文件大小
  27. res_sd = f_lseek(&fnew, f_size(&fnew) - 1);
  28. if(res_sd == FR_OK)
  29. {
  30. //在原文件中追加一行内容
  31. f_printf(&fnew, "在原文件中追加一行内容。\n");
  32. f_printf(&fnew, "设备总空间:%10lu KB\r\n可用空间:%10lu KB。\r\n", tot_sect* 4, fre_sect* 4);
  33. //文件定位到起始位置
  34. res_sd = f_lseek(&fnew, 0);
  35. if(res_sd == FR_OK)
  36. {
  37. //打开文件,若不存在就创建
  38. res_sd = f_open(&fnew, "0:FatFs多项功能测试文件.txt", FA_OPEN_EXISTING | FA_READ);
  39. //文件打开成功
  40. if(res_sd == FR_OK)
  41. {
  42. printf( "打开文件成功!开始读取数据!\r\n");
  43. res_sd= f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
  44. if(res_sd == FR_OK)
  45. {
  46. printf( "数据读取成功!\r\n");
  47. printf( "数据:%s\r\n", ReadBuffer);
  48. }
  49. else
  50. {
  51. printf( "数据读取失败!\r\n");
  52. }
  53. //关闭文件
  54. f_close(&fnew);
  55. }
  56. }
  57. }
  58. }
  59. else
  60. {
  61. printf( "数据读取失败!\r\n");
  62. }
  63. //关闭文件
  64. f_close(&fnew);
  65. }
  66. printf( "\r\n*************************目录创建和重命名功能测试***************************\r\n");
  67. //尝试打开目录
  68. res_sd = f_opendir(&dir, "0:TestDir");
  69. if(res_sd != FR_OK)
  70. {
  71. //打开目录失败,开始创建目录
  72. res_sd = f_mkdir( "0:TestDir");
  73. }
  74. else
  75. {
  76. //如果目录已经存在,关闭它
  77. res_sd = f_closedir(&dir);
  78. //删除文件
  79. f_unlink( "0:FatFs读写测试文件.txt");
  80. }
  81. if(res_sd == FR_OK)
  82. {
  83. //重命名并移动文件
  84. res_sd = f_rename( "0:FatFs多项功能测试文件.txt", "0:/TestDir/FatFs多项功能测试文件.txt");
  85. if(res_sd == FR_OK)
  86. {
  87. printf( "重命名并移动文件成功!\r\n");
  88. }
  89. else
  90. {
  91. printf( "重命名并移动文件失败!\r\n");
  92. }
  93. }
  94. }
  95. //文件信息获取
  96. static FRESULT file_check()
  97. {
  98. //文件信息
  99. static FILINFO fno;
  100. //获取文件信息,必须确保文件存在
  101. res_sd = f_stat( "0:FatFs读写测试文件.txt", &fno);
  102. if(res_sd == FR_OK)
  103. {
  104. printf( "0:FatFs读写测试文件.txt的信息如下:\r\n");
  105. printf( "文件大小:%ld\r\n", fno.fsize);
  106. printf( "时间戳:%u/%02u/%02u, %02u:%02u\r\n", (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31, fno.ftime >> 11, fno.ftime >> 5 & 63);
  107. printf( "属性:%c%c%c%c%c\r\n",
  108. (fno.fattrib & AM_DIR) ? 'D' : '-', //目录
  109. (fno.fattrib & AM_RDO) ? 'R' : '-', //只读
  110. (fno.fattrib & AM_HID) ? 'H' : '-', //隐藏
  111. (fno.fattrib & AM_SYS) ? 'S' : '-', //系统文件
  112. (fno.fattrib & AM_ARC) ? 'A' : '-'); //档案文件
  113. }
  114. }
  115. //路径扫描
  116. static FRESULT scan_files(char* path)
  117. {
  118. FRESULT res; //在递归过程中被修改,不用全局变量
  119. FILINFO fno;
  120. DIR dir;
  121. int i;
  122. char* fn;
  123. #if _USE_LEN//长文件名支持
  124. //简体中文需要两个字节保存一个字
  125. static char lfn[_MAX_LFN* 2+ 1];
  126. fno.lfname = lfn;
  127. fno.lfsize = sizeof(lfn);
  128. #endif
  129. //打开目录
  130. res = f_opendir(&dir, path);
  131. if(res == FR_OK)
  132. {
  133. i = strlen(path);
  134. while( 1)
  135. {
  136. //读取目录下的内容
  137. res = f_readdir(&dir, &fno);
  138. if(res != FR_OK || fno.fname[ 0] == 0)
  139. {
  140. break;
  141. }
  142. #if _USE_LEN
  143. fn = *fno.lfname ? fno.lfname : fno.fname;
  144. #else
  145. fn = fno.fname;
  146. #endif
  147. //点表示当前目录,跳过
  148. if(*fn == '.')
  149. continue;
  150. //目录,递归读取
  151. if(fno.fattrib & AM_DIR)
  152. {
  153. //合成完整目录名
  154. sprintf(&path[i], "/%s", fn);
  155. //递归遍历
  156. res = scan_files(path);
  157. path[i] = 0;
  158. if(res != FR_OK)
  159. {
  160. break;
  161. }
  162. else
  163. {
  164. printf( "%s/%s\r\n", path, fn); //输出文件名,可以在这里提取特定格式的文件路径
  165. }
  166. }
  167. }
  168. }
  169. return res;
  170. }
感谢大家的耐心阅读,如果有问题,欢迎一起讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值