背景

在本系列的前两篇文章(
使用vscode + gcc进行 STM32 单片机开发(一)编译及调试
使用vscode + gcc进行 STM32 单片机开发(二)gcc环境 移植rtthread)

的基础上,我想再多写一点,一方面是想试试vscode环境开发STM32到底怎么样;另一方面是在和同事的私下讨论的时候,讨论到STM32的SD卡不怎么好用。

综上所示,本文主要描述的是在vscode + gcc的环境下,配合rt-thread RTOS系统,实现STM32的SD卡读写及文件系统的移植,并给出最终的SD卡读写速率测试结果。

相关知识

在阅读本文之前,为了确保有一个良好的阅读体验,需要提前了解一些知识。

  1. 本系列的前两篇文章,具体链接请见本文开头
  2. SD卡的基础知识,包括但不限于:
    · SD卡、MicroSD卡 、MMC卡、TF卡
    · 主机SD卡的接口定义
    · 主机和SD卡的通讯传输流程及协议(了解即可)
  3. DMA(Direct Memory Access)传输的概念
  4. 文件系统 的基本概念,常见的文件系统:FAT、NTFS、Ext等

开始

废话少说,言归正传。 下面开始我们的移植工作。

准备工作

先准备一下:

  • 硬件环境:

    • 一块STM32开发板,带SD卡接口。
    • 一张SD卡
  • 软件环境:

    • 本系列的前两篇文章所搭建的vscode + gcc + rtthread开发环境

第一步:在STM32CubeMX中开启SD卡接口

直接点点点就行了,如下图所示:

选择 Connectivity - SDMMC1, 选择 SD 4 bits Wide bus,下面开启NVIC中断。

这里有个比较关键的参数 SDMMC clock divide factor,指的是SD卡硬件接口的时钟分频参数,此参数决定最终的时钟频率。
根据SD卡接口的官方文档,可以查阅到其频率一般为1MHz~50MHz。 所以这里我们后面再根据实际情况动态的调整。


设置完成,生成代码 OK

这一步完成后,我们既可以简单的实现往SD卡里面读写二进制的数据了。 但是绝大多数情况下,我们往SD卡读写的都是以文件的形式,文件是有格式的二进制数据。 所以接下来我们开始第二步。

第二步:移植文件系统

文件系统的概念这里不再累述,请自行查阅。 这里我们选择移植FATFS文件系统,官方网站为:http://elm-chan.org/fsw/ff/00index_e.html。

这是好奇的同学就要问了,为什么你直接就选了FATFS呢? 为什么不选其他类似于 NTFS、Ext这些文件系统呢?
我的回答是: 因为我查了一圈资料,就只查到了FATFS的移植文档,其他文件系统我连源码都没找到 /汗

首先我们去上述的官网上下载FATFS的源码,一共就6、7个C文件和H文件。 下载后随意在项目里面找个顺眼的位置放着,等下把这个位置加到makefile里面进行编译。

这里我放到了 Core/Src目录下,如下图所示:
接下来把源文件和头文件加到Makefile里面去,这里需要你稍微去学习一下makefile,有很多种方法都可以添加,我这里随便选择一种,代码如下所示:

OBJECTS += Core/Src/ff14b/source/diskio.o Core/Src/ff14b/source/ff.o Core/Src/ff14b/source/ffsystem.o Core/Src/ff14b/source/ffunicode.o \Core/Src/sd_card_speed.o
C_INCLUDES += -ICore/Src/ff14b/source \

ok,接下来进行编译。

不出意外的话,你会编译失败,报错部分函数未定义。 这是当然的咯,上述我们只是下载了FATFS源码而已,还没有和STM32的SD卡读写函数进行对接(移植)。

因此接下来我们进行移植。

根据报错提示,我们打开 diskio.c这个文件,参考官方的移植指南http://elm-chan.org/fsw/ff/doc/appnote.html#port。 做填空题,把里面的函数填上STM32的SD卡相关读写函数。

根据实际情况不同,这里的函数实现都不同,下面我给一个我的写法:

disk_status函数:如下图所示,根据GPIO9的状态(我的开发板上是这个引脚)判断SD卡是否插入

STATUS disk_status (BYTE pdrv        /* Physical drive nmuber to identify the drive */
)
{DSTATUS stat;int result;GPIO_PinState card_det_pin;switch (pdrv) {case DEV_RAM :card_det_pin = HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_9);result = (card_det_pin == GPIO_PIN_RESET);// translate the reslut code herestat = (result ? 0 : STA_NODISK);return stat;case DEV_MMC ://result = MMC_disk_status();// translate the reslut code herereturn STA_NODISK;case DEV_USB ://result = USB_disk_status();// translate the reslut code herereturn STA_NODISK;}return STA_NOINIT;
}

disk_initialize函数:无需改动
disk_read函数:如下图所示,先声明一段DMA的buffer,buffer制定一个“段名”.dma,这样的话后续在编译时,可以根据.dma这个段名,把buffer强制固定到特定的内存段上。 这是因为STM32H7系列,有多个RAM内存地址,分别为:

DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K

而SD卡要求的DMA必须在DTCMRAM上,否则无法工作。具体请查询STM32H7的官方手册,有详细说明。

然后再声明一个DMA读取完毕的回调函数,其中把RxCplt置1。
disk_read函数中发起一次DMA读取,然后就一直等待变量RxCplt置1。

volatile uint8_t RxCplt=0;;
static uint8_t __attribute__((section(".dma"))) dma_rx_buffer[10240]; //10kb
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{SCB_InvalidateDCache_by_Addr((uint32_t*)dma_rx_buffer, sizeof(dma_rx_buffer)/4);RxCplt=1;
}DRESULT disk_read (BYTE pdrv,      /* Physical drive nmuber to identify the drive */BYTE *buff,        /* Data buffer to store read data */LBA_t sector,   /* Start sector in LBA */UINT count     /* Number of sectors to read */
)
{DRESULT res;HAL_StatusTypeDef result;switch (pdrv) {case DEV_RAM :// translate the arguments herewhile(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);// result = HAL_SD_ReadBlocks(&hsd1, (uint8_t*)buff, (uint32_t)sector, (uint32_t)count, 500);result = HAL_SD_ReadBlocks_DMA(&hsd1, (uint8_t*)dma_rx_buffer, (uint32_t)sector, (uint32_t)count);if(result!=HAL_OK){return result;}while(RxCplt==0);RxCplt=0;SCB_CleanDCache_by_Addr(dma_rx_buffer, sizeof(dma_rx_buffer)/4);memcpy(buff, dma_rx_buffer, count*BLOCKSIZE);// translate the reslut code hereswitch (result){case HAL_OK:return RES_OK;case HAL_ERROR:return RES_ERROR;case HAL_BUSY:return RES_ERROR;case HAL_TIMEOUT:return RES_ERROR;default:return RES_ERROR;}case DEV_MMC :// translate the arguments here//result = MMC_disk_read(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;case DEV_USB :// translate the arguments here//result = USB_disk_read(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;}return RES_PARERR;
}

disk_write函数和disk_read函数类似,如下图所示,不再累述:

volatile uint8_t TxCplt=0;;
static uint8_t __attribute__((section(".dma"))) dma_tx_buffer[10240]; //10kbvoid HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{TxCplt=1;
}
DRESULT disk_write (BYTE pdrv,          /* Physical drive nmuber to identify the drive */const BYTE *buff,  /* Data to be written */LBA_t sector,       /* Start sector in LBA */UINT count         /* Number of sectors to write */
)
{DRESULT res;HAL_StatusTypeDef result;int retry = 5;switch (pdrv) {case DEV_RAM :// translate the arguments herewhile(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);memcpy(dma_tx_buffer, buff, count*BLOCKSIZE);SCB_CleanDCache_by_Addr(dma_tx_buffer, sizeof(dma_tx_buffer)/4);result = HAL_SD_WriteBlocks_DMA(&hsd1, (uint8_t*)dma_tx_buffer, (uint32_t)sector, (uint32_t)count);while(TxCplt==0);TxCplt=0;// for(;retry>0;retry--){//  while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);//     result = HAL_SD_WriteBlocks(&hsd1, (uint8_t*)buff, (uint32_t)sector, (uint32_t)count, 5000);//     if(result == HAL_OK) break;// }// translate the reslut code hereswitch (result){case HAL_OK:return RES_OK;case HAL_ERROR:return RES_ERROR;case HAL_BUSY:return RES_ERROR;case HAL_TIMEOUT:return RES_ERROR;default:return RES_ERROR;}// translate the reslut code herereturn res;case DEV_MMC :// translate the arguments here// result = MMC_disk_write(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;case DEV_USB :// translate the arguments here// result = USB_disk_write(buff, sector, count);// translate the reslut code herereturn RES_NOTRDY;}return RES_PARERR;
}

disk_ioctl函数:whille循环等待SD读写完毕,如下图所示:

DRESULT disk_ioctl (BYTE pdrv,       /* Physical drive nmuber (0..) */BYTE cmd,      /* Control code */void *buff        /* Buffer to send/receive control data */
)
{DRESULT res;int result;switch (pdrv) {case DEV_RAM :// Process of the command for the RAM driveswitch (cmd){case CTRL_SYNC:/* code */while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);return RES_OK;break;case GET_SECTOR_COUNT:*(DWORD*)buff = hsd1.SdCard.BlockNbr;return RES_OK;break;case GET_BLOCK_SIZE:*(DWORD*)buff = hsd1.SdCard.BlockSize;return RES_OK;break;default:break;}return RES_ERROR;case DEV_MMC :// Process of the command for the MMC/SD cardreturn RES_NOTRDY;case DEV_USB :// Process of the command the USB drivereturn RES_NOTRDY;}return RES_PARERR;
}

上述函数写完后,再进行编译,应该就没问题了。

第三步:测试SD卡读写速度

这一步其实就相对比较简单了,我们写入一个100MB的文件,来测试一下写入的速度,测试代码如下:

#include <stdio.h>
#include <rtthread.h>
#include <stdint.h>
#include "sd_card_speed.h"
static const int SIZE_MB=100;
static const long long SIZE_BYTE = SIZE_MB*1024*1024;/*
@brief: 此函数测试sd卡的读写速度,
@note:  测试方法,写入200MB的文件,
@return: 返回速度 单位 b/s
*/
static int write_count=0;
int sd_card_write_speed(FATFS* fs){char filename[64]={0};int ret;ret = snprintf(&filename, sizeof(filename), "/SD_CARD_SPEED_TEST_%dMB", SIZE_MB);if(ret < 0){rt_kprintf("snprintf error\r\n");return -1;}// write 200MB in// cpu have 1MB RAM, so we write 200KB each timeint data_size_each = 200*1024;uint8_t *data = rt_malloc(data_size_each);if(data == NULL){rt_kprintf("rt_malloc failed!\r\n");return -1;}FRESULT f_ret;FIL fil;f_ret = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE);if(f_ret != FR_OK){rt_kprintf("f_open failed, error code is %d\r\n", f_ret);return -1;}for(int i=0;i< SIZE_BYTE/(long long)data_size_each;i++){int bw = 0;f_ret = f_write(&fil, data, data_size_each, &bw);if(f_ret != FR_OK){rt_kprintf("f_write failed, error code is %d\r\n", f_ret);return -1;}if(bw != data_size_each){rt_kprintf("bw != data_size_each, bw is %d\r\n", bw);}write_count++;}f_close(&fil);return 0;
}int sd_card_read_speed(){}

在main函数里面调用这个函数,并打印一下执行前后的时间t1和t2,即可算出SD卡的读写速度。

TODO:补图,SD卡的测试结果

结语

最后,代码写的不怎么样,我就没有上传了。 如果有任何疑问,欢迎在下面留言,我看到了就会回复。

使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统相关推荐

  1. 【FatFs】基于STM32 SD卡移植FatFs文件系统

    相关文章 <[SDIO]SDIO.SD卡.FatFs文件系统相关文章索引> 1.前言 FatFs是一个通用的FAT/exFAT文件系统模块,用于小型嵌入式系统.它完全是由 ANSI C 语 ...

  2. 使用STM32在SPI模式下读写SD卡

    使用STM32在SPI模式下读写SD卡 代码分享 一.实验原理图 二.SD卡 三.实验操作 1.在stm32cubemx建立工程 2.根据上面的原理图连接硬件 3.keil编译和烧录 四.实验结果 总 ...

  3. STM32挂载SD卡基于Fatfs文件系统读取文件信息

    STM32挂载SD卡基于Fatfs文件系统读取文件信息

  4. STM32+雷龙SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

    一.前言 在STM32项目开发中,经常会用到存储芯片存储数据. 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复:在存储芯片里也会存放很多资源文件.比如,开机音乐,界面上的菜单 ...

  5. 基于SD卡的FatFs文件系统(FatFs移植到STM32)

    平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线 工程介绍:主要文件在USER组中,bsp_sdio_sdcard.c,bsp_sdio_sdcard.h和main.c, ...

  6. STM32 通过USB接口读写挂载的SD卡(支持fatfs文件系统)

    通过USB接口读写挂载的SD卡 HAL库 标准库 本文以STM32F407为例分别用HAL库(STM32CubeMX)和标准库实现SD卡对fatfs支持,以及通过USB接口读写SD卡.首先看电路连接: ...

  7. 基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

    一.前言 在STM32项目开发中,经常会用到存储芯片存储数据. 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复:在存储芯片里也会存放很多资源文件.比如,开机音乐,界面上的菜单 ...

  8. 基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试(中篇)

    3.2 SPI硬件时序方式 上面的3.1小节是采用SPI模拟时序驱动SD NAND,STM32本身集成有SPI硬件模块,可以直接利用STM32硬件SPI接口读写. 下面贴出底层的适配代码. 上面贴出的 ...

  9. VSCode搭建STM32单片机开发环境

    前言 由于之前的学习过程中使用过了VSCode.Source Insight这类强大的代码编辑器,感觉实在是太好用了.但是最近自己要用到Keil进行STM32单片机的开发,因为使用过了VSCode这类 ...

最新文章

  1. C++第10周项目4参考——完数
  2. 2011-02 Emacs相关闪存
  3. mybatis 解析Integer为0的属性,解析成空字符串
  4. BZOJ-1082-[SCOI2005]栅栏(二分+dfs判定)
  5. 跟随光标下划线导航插件
  6. ai与虚拟现实_将AI推向现实世界
  7. MySQL启用SSL连接
  8. Oracle字符拆分函数,Oracle拆分字符串函数有哪些呢?
  9. 2017计算机c语言大纲,2017年计算机考研大纲
  10. python︱Python进程、线程、协程详解、运行性能、效率(tqdm)
  11. 对软件未来走向的看法
  12. CentOS下启动mysql出现“MySQL Daemon failed to start”解决办法
  13. paip.web数据绑定 下拉框的api设计 选择框 uapi python .net java swing jsf总结
  14. 【实用工具】如何录制电脑屏幕gif动图?
  15. java围棋对弈实例
  16. 思维导图使用技巧:手把手教你怎么画思维导图
  17. Cisco Packet Tracer 命令大全 【思科交换机模拟器命令大全】
  18. 基于浏览器的Linux
  19. WOL远程开机,实际落地成功。
  20. 淘宝客推广(一):利用淘礼金做拉新

热门文章

  1. 燕山大学高数AⅡ资料
  2. 洗煤厂数字孪生三维可视化平台_洗煤厂webgl大屏可视化管理系统
  3. 如何选择适合你的兴趣爱好(三),瑜伽
  4. 教你敲代码实现在线电影票选座功能
  5. 吴军博士给工程师的分级
  6. 计算机区块链的杂志,CCF区块链技术大会收录Wanchain共识论文并推荐SCI期刊检索...
  7. 男人必看之领带的10种打法(附图)
  8. [喵咪BELK实战(3)] logstash+filebeat搭建
  9. 二叉树的三种排序方法
  10. Sublime与Evernote的结合