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 SRAM(SDMMC1仅支持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. 性能优化与注意事项
-
SDMMC-DMA 效率:
- 优先使用SDMMC内置DMA,其硬件优化更适合SD卡协议。
- 使能AHB突发传输(通过FMC配置)以提升吞吐量。
-
MDMA 适用场景:
- 当SD卡数据需二次处理(如FFT、图像解码)时,用MDMA搬移至TCM(零延迟)或SDRAM(大缓冲区)。
- 避免MDMA与SDMMC-DMA同时操作同一AXI SRAM区域(总线竞争可能降速)。
-
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)
-
SDMMC-DMA 读取数据到AXI SRAM的
BufferA
(HAL_SD_ReadBlocks_DMA
)。 -
MDMA 将
BufferA
数据搬移至DTCM(实时处理用):HAL_MDMA_Start(&hmdma, (uint32_t)&BufferA, (uint32_t)&ProcessBuffer, length);
-
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