STM32H7 SD卡使用以及其DMA读写

STM32H7 SD卡使用以及其DMA读写

我这里使用的STM32H743XIH6

我使用的是arm-none-eabi-gcc工具链,就是免费的那个,也是CubeIDE用的那个

各种不足还请大佬在评论区指点

我绑定的资源时最终实现DMA访问,并且使用了Cache的SD卡Fatfs文件系统

基本的使用

先不加文件系统,说明一下直接读写SD卡的块

为了简单Cache MPU等先不使用

直接读写

配置说明

没啥可说的,直接上CubeMX配置
在这里插入图片描述

在这里插入图片描述

SD卡时钟频率和分频系数,根据自己需要修改

手册要求不超过25MHz

注意一下,如果你的板子上SD卡相关引脚没有上拉电阻,就配置一下GPIO的上拉

我板子上有上拉电阻,就不配置了

在这里插入图片描述

生成工程

在这里插入图片描述

测试代码

注意一下SD卡读写后,应当检查一下状态,确认是不是读写完了

This API should be followed by a check on the card state through HAL_SD_GetCardState().

/**
  * @brief  Reads block(s) from a specified address in a card. The Data transfer
  *         is managed by polling mode.
  * @note   This API should be followed by a check on the card state through
  *         HAL_SD_GetCardState().
  * @param  hsd: Pointer to SD handle
  * @param  pData: pointer to the buffer that will contain the received data
  * @param  BlockAdd: Block Address from where data is to be read
  * @param  NumberOfBlocks: Number of SD blocks to read
  * @param  Timeout: Specify timeout value
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks,
                                    uint32_t Timeout);
/**
  * @brief  Allows to write block(s) to a specified address in a card. The Data
  *         transfer is managed by polling mode.
  * @note   This API should be followed by a check on the card state through
  *         HAL_SD_GetCardState().
  * @param  hsd: Pointer to SD handle
  * @param  pData: pointer to the buffer that will contain the data to transmit
  * @param  BlockAdd: Block Address where data will be written
  * @param  NumberOfBlocks: Number of SD blocks to write
  * @param  Timeout: Specify timeout value
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, const uint8_t *pData, uint32_t BlockAdd,
                                     uint32_t NumberOfBlocks, uint32_t Timeout);

我这里为了测试代码简单,读写后直接延时1S

void SD_BasicTest1()
{
  puts("测试1 轮询方式读写SD卡");

  uint8_t saveBuffer[512] = {0x00};//保存原本数据,测试完后写回去,防止破坏文件系统
  uint32_t blockTest = 0xEF;//测试的块地址

  uint8_t writeBuffer[512] = {0x00};//保存写入的数据
  for(int i=0; i<sizeof(writeBuffer); i++)
  {
    writeBuffer[i] = i % 256;
  }

  uint8_t readBuffer[512] = {0x00};//保存读取的数据

  puts("读取原本数据并保存");
  if(HAL_SD_ReadBlocks(&hsd1, saveBuffer, blockTest, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    puts("读取失败!");
  }
  HAL_Delay(1000);//延时1S等待传输完毕

  puts("开始读写测试");
  if(HAL_SD_WriteBlocks(&hsd1, writeBuffer, blockTest, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    puts("写入失败");
  }
  else
  {
    printf("write success 前4个字节 %X %X %X %X\n", writeBuffer[0], writeBuffer[1], writeBuffer[2], writeBuffer[3]);
  }
  HAL_Delay(1000);//延时1S等待传输完毕

  if(HAL_SD_ReadBlocks(&hsd1, readBuffer, blockTest, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    puts("读取失败!");
    return;
  }
  else
  {
    printf("read success 前4个字节 %X %X %X %X\n", readBuffer[0], readBuffer[1], readBuffer[2], readBuffer[3]);
  }

  if(memcmp(writeBuffer, readBuffer, sizeof(writeBuffer)) == 0)
  {
    puts("读写的数据一样,测试通过");
  }
  else
  {
    puts("读写的数据不同,测试失败");
  }

  puts("写回原本数据,防止破坏文件系统");
  if(HAL_SD_WriteBlocks(&hsd1, saveBuffer, blockTest, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    puts("写入失败");
  }
  HAL_Delay(1000);//延时1S等待传输完毕
}

现象,串口调试工具应当看到如下内容

测试1 轮询方式读写SD卡
读取原本数据并保存
开始读写测试
write success 前4个字节 0 1 2 3
read success 前4个字节 0 1 2 3
读写的数据一样,测试通过
写回原本数据,防止破坏文件系统

DMA读写

首先说一下:

STM32H7中,SD卡(通过SDMMC接口)的读写内置了专用的DMA控制器,不需要额外配置

至于MDMA中SD卡的相关配置是干什么用的,见后面

注意:

STM32H7 的 SDMMC(SD/SDIO/MMC 接口)内置DMA 仅支持访问 AXI SRAM(0x2400 0000)。

请看一下你缓冲的数组在哪里!

在这里插入图片描述

生成代码时,如果选择了CMake 或 Makefile

生成的STM32H743XX_FLASH.ld以128KB的DTCRAM为默认使用的内存

/* Highest address of the user mode stack */
_estack = ORIGIN(DTCMRAM) + LENGTH(DTCMRAM);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x400;      /* required amount of heap  */
_Min_Stack_Size = 0x800; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
DTCMRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
RAM (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
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 2048K
}

而这个内存区域不支持DMA!!!!
在这里插入图片描述

所以,请仔细确认!!!!

STM32CubeIDE生成的STM32H743XIHX_FLASH.ld以AXI_SRAM作为主内存,才是符合本实验需要的!!

/* Highest address of the user mode stack */
_estack = ORIGIN(AXI_SRAM) + LENGTH(AXI_SRAM);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x400;      /* required amount of heap  */
_Min_Stack_Size = 0x800; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
  FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 2048K
  DTCMRAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K
  AXI_SRAM (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
}
配置说明

打开对应中断即可

在这里插入图片描述

测试代码
void SD_BasicTest2()
{
  puts("测试2 DMA方式读写SD卡");

  uint8_t saveBuffer[512] = {0x00};//保存原本数据,测试完后写回去,防止破坏文件系统
  uint32_t blockTest = 0xEF;//测试的块地址

  uint8_t writeBuffer[512] = {0x00};//保存要写入的数据
  for(int i=0; i<sizeof(writeBuffer); i++)
  {
    writeBuffer[i] = i % 256;
  }

  uint8_t readBuffer[512] = {0x00};//保存读取到的数据

  puts("读取原本数据并保存");
  if(HAL_SD_ReadBlocks(&hsd1, saveBuffer, blockTest, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    puts("读取失败!");
  }
  HAL_Delay(1000);//给个延时,等它传输完成(因为是测试故意给的很长)
  
  puts("开始DMA读写测试");
  printf("尝试写入的前4个数据 %X %X %X %X\n", writeBuffer[0], writeBuffer[1], writeBuffer[2], writeBuffer[3]);
  puts("设置DMA写入");
  if(HAL_SD_WriteBlocks_DMA(&hsd1, writeBuffer, blockTest, 1) != HAL_OK)
  {
    puts("DMA写入设置失败");
  }
  else
  {
    puts("DMA写入设置成功");
  }
  HAL_Delay(1000);//给个延时,等它DMA传输完成(因为是测试故意给的很长)

  puts("尝试DMA读取");
  if(HAL_SD_ReadBlocks_DMA(&hsd1, readBuffer, blockTest, 1) != HAL_OK)
  {
    puts("DMA读取设置失败!");
  }
  else
  {
    puts("DMA读取设置成功");
    
  }
  HAL_Delay(1000);//给个延时,等它DMA传输完成(因为是测试故意给的很长)
  printf("读缓冲区中的前4个数据 %X %X %X %X\n", readBuffer[0], readBuffer[1], readBuffer[2], readBuffer[3]);

  if(memcmp(writeBuffer, readBuffer, sizeof(writeBuffer)) == 0)
  {
    puts("PASS 读写的数据一样,测试通过");
  }
  else
  {
    puts("FAIL 读写的数据不同,测试失败");
  }


  puts("写回原本数据,防止破坏文件系统");
  if(HAL_SD_WriteBlocks(&hsd1, saveBuffer, blockTest, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    puts("写入失败");
  }
  HAL_Delay(1000);//给个延时,等它传输完成(因为是测试故意给的很长)
}

现象

串口助手应当输出

测试2 DMA方式读写SD卡
读取原本数据并保存
开始DMA读写测试
尝试写入的前4个数据 0 1 2 3
设置DMA写入
DMA写入设置成功
尝试DMA读取
DMA读取设置成功
读缓冲区中的前4个数据 0 1 2 3
PASS 读写的数据一样,测试通过
写回原本数据,防止破坏文件系统

加入文件系统

文件系统中如果不使用DMA没啥可说的,一切正常

这里只讲文件系统FATFS使用DMA读写SD卡

不使用Cache

先不使用缓冲

配置说明

在这里插入图片描述

测试代码
void Fatfs_test()
{
  // 文件系统对象
  FATFS fs;                         // 文件系统对象
  FIL fil;                          // 文件对象
  FRESULT res;                      // FatFS 返回值

  // 2. 挂载文件系统
  res = f_mount(&fs, "", 0);    // 挂载 SD 卡("" 表示根目录,1 表示强制重新挂载)
  if (res != FR_OK) {
      puts("挂载文件系统失败");
      return;
  }
  puts("文件系统挂载成功");

  // 3. 打开并读取 test1.txt
  UINT readBW;                          // 实际读取字节数
  char read_buffer[256];            // 读取缓冲区
  res = f_open(&fil, "test1.txt", FA_READ);  // 打开 test1.txt
  if (res == FR_OK) {
      // 如果文件存在且打开成功
      memset(read_buffer, 0, sizeof(read_buffer));
      res = f_read(&fil, read_buffer, sizeof(read_buffer) - 1, &readBW);  // 读取最多 255 字节
      if (res != FR_OK) {
          puts("读取文件失败");
          return;
      } else {
          puts("从 test1.txt 读取内容如下:");
          puts(read_buffer);
      }
      f_close(&fil); // 关闭文件
  } else {
      puts("未找到 test1.txt 或打开失败");
  }

  // 4. 创建并写入 test_A.txt
  const char write_content[] = "abcdefg1234567"; // 要写入的内容
  UINT writeBW;                          // 实际写入字节数
  res = f_open(&fil, "test_A.txt", FA_CREATE_ALWAYS | FA_WRITE);  // 创建或覆盖 test_A.txt
  if (res != FR_OK) {
      puts("创建/打开文件失败");
      return;
  } else {
      // 写入内容
      res = f_write(&fil, write_content, strlen(write_content), &writeBW);  // 写入内容
      if (res != FR_OK) {
          puts("写入文件失败");
          return;
      } else {
          puts("已成功将内容写入 test_A.txt");
      }
      f_close(&fil); // 关闭文件
  }

  puts("操作完成");
}

使用Cache

工程见我绑定的资源

当使用了缓冲
注意配置MPU如下
注意MPU Sharability Permission 应当打开
经本人测试如下配置没有问题
在这里插入图片描述

注意:使用Cache需要打开sd_diskio.c中的宏定义

FATFS\Target\sd_diskio.c

/*
 * when using cacheable memory region, it may be needed to maintain the cache
 * validity. Enable the define below to activate a cache maintenance at each
 * read and write operation.
 * Notice: This is applicable only for cortex M7 based platform.
 */
/* USER CODE BEGIN enableSDDmaCacheMaintenance */
#define ENABLE_SD_DMA_CACHE_MAINTENANCE  1
/* USER CODE END enableSDDmaCacheMaintenance */

/*
* Some DMA requires 4-Byte aligned address buffer to correctly read/write data,
* in FatFs some accesses aren't thus we need a 4-byte aligned scratch buffer to correctly
* transfer data
*/
/* USER CODE BEGIN enableScratchBuffer */
#define ENABLE_SCRATCH_BUFFER
/* USER CODE END enableScratchBuffer */

其它

经过本人测试,SDMMC的DMA功能可以访问外部SDRAM

以下问题还没测试明白……

经过测试,使用DMA功能时,不光是读写的缓冲,文件系统本身也应该运行在DMA可以访问的内存中

MDMA中SD卡相关功能

以下是我和deepseek的一些对话

本人目前不清楚MDMA如何使用,还请大佬指点

MDMA中SD卡相关功能说明

在STM32H7中,SD卡(通过SDMMC接口)的读写内置了专用的DMA控制器,通常称为SDMMC-DMA,而不是直接使用通用的DMA1/DMA2或MDMA。

**MDMA(Master DMA)**仍然可以在某些高级场景下与SD卡存储结合使用,例如数据后处理或缓存管理。以下是SD卡读写与MDMA的关联及配置要点:


1. SDMMC 内置DMA vs. MDMA
  • SDMMC-DMA

    • 专用于SDMMC接口,自动处理块数据传输(如512字节扇区读写)。
    • 无需手动配置DMA通道,HAL库函数(如HAL_SD_ReadBlocks_DMA())已集成其控制逻辑。
    • 直接访问AXI SRAMSDMMC1仅支持AXI SRAM的缓冲区)。
  • MDMA 的角色

    • 不直接用于SD卡原始数据传输(由SDMMC-DMA完成)。
    • 可用于:
      • 数据搬移:将SD卡数据从AXI SRAM搬运至其他内存(如TCM或外部SDRAM)。
      • 缓存一致性维护:在启用Cache时,使用MDMA或手动SCB_CleanInvalidateDCache确保数据一致性。
      • 复杂数据流:如SD卡数据解密/压缩后,用MDMA传输至其他外设(如LCD或网络接口)。

2. MDMA 配置与SD卡联用示例

若需用MDMA处理SD卡数据,典型配置如下(以数据搬移为例):

MDMA_HandleTypeDef hmdma;
hmdma.Init.Request = MDMA_REQUEST_SW;           // 软件触发
hmdma.Init.TransferTriggerMode = MDMA_BLOCK_TRANSFER;
hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;  // 32位宽
hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
hmdma.Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS;   // 4拍突发
hmdma.Init.DestBurst = MDMA_DEST_BURST_4BEATS;
hmdma.Init.BufferTransferLength = 128;          // 传输128字(512字节,匹配SD卡扇区)
HAL_MDMA_Init(&hmdma);

// 触发MDMA搬运(SD卡数据已通过SDMMC-DMA读到AXI SRAM的BufferA)
HAL_MDMA_Start_IT(&hmdma, (uint32_t)&BufferA, (uint32_t)&BufferB, 128);

关键点

  • 突发传输:MDMA支持突发(如4拍、8拍),但需满足Burst * DataSize ≤ 128字节
  • 数据对齐:SD卡缓冲区需32字节对齐(Cache维护要求)。
  • 缓存处理:若目标内存启用Cache,需在MDMA传输前后调用SCB_CleanInvalidateDCache

3. 性能优化与注意事项
  1. SDMMC-DMA 效率

    • 优先使用SDMMC内置DMA,其硬件优化更适合SD卡协议。
    • 使能AHB突发传输(通过FMC配置)以提升吞吐量。
  2. MDMA 适用场景

    • 当SD卡数据需二次处理(如FFT、图像解码)时,用MDMA搬移至TCM(零延迟)或SDRAM(大缓冲区)。
    • 避免MDMA与SDMMC-DMA同时操作同一AXI SRAM区域(总线竞争可能降速)。
  3. Cache 配置

    • AXI SRAM的Cache策略推荐Write-Through(写直达)或手动维护一致性。

    • 示例MPU配置(AXI SRAM区域):

      MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
      MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;  // WT模式
      

4. 完整数据流示例(SD卡 → AXI SRAM → TCM)
  1. SDMMC-DMA 读取数据到AXI SRAM的BufferAHAL_SD_ReadBlocks_DMA)。

  2. MDMABufferA数据搬移至DTCM(实时处理用):

    HAL_MDMA_Start(&hmdma, (uint32_t)&BufferA, (uint32_t)&ProcessBuffer, length);
    
  3. Cache维护:在MDMA传输前清理AXI SRAM Cache,传输后无效化DTCM Cache(若启用)。


总结
  • 默认SD卡读写:直接使用SDMMC内置DMA,无需MDMA。
  • MDMA 的用途:数据后处理、跨内存域搬运或复杂缓存管理。
  • 关键配置:突发传输、Cache一致性、内存对齐。

如需进一步优化(如双缓冲策略或RTOS集成),可参考硬汉论坛的SD卡性能测试案例。

参考资料

硬汉嵌入式论坛

安富莱_STM32-V7开发板_用户手册,含BSP驱动包设计(V3.5)

https://www.armbbs.cn/forum.php?mod=viewthread&tid=86980&extra=page%3D1

9.2CubeMx配置SD卡FATFS系统_stm32H7系列 SD卡 FR_NO_FILESYSTEM 找不到FatFs系统的问题

https://blog.csdn.net/qq_36561846/article/details/133808890?spm=1001.2014.3001.5506

ST它们社区的一个问答

https://community.st.com/t5/stm32cubemx-mcus/stm32h7-how-to-use-sdmmc-with-mdma/td-p/220163

STM32H743+CubeMX-梳理MPU的设置

https://blog.csdn.net/wallace89/article/details/117233443

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜哥万岁万岁万万岁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值