恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

【CubeMX+STM32】SD卡 SDIO通信 DMA读写

  • 首页
  • 资讯中心
  • /
  • 【CubeMX+STM32】SD卡 SDIO通信 DMA读写

相关资讯

StartOS集群部署:如何构建高可用性服务器集群 2026/10/20 1:15:41
副队长JavaScript教程(4)–浏览器开发者工具 2026/10/20 1:10:40
副队长JavaScript教程(2)–在网页中使用JS 2026/10/20 1:10:40

最新资讯

2026 AI学习路线图:收藏这份系统学习框架,从基础到工程全面提升大模型能力
如何提高阅读后的知识留存率
学生成绩管理系统需求分析:表结构、技术栈与避坑指南
第67篇:空间分析-填挖方分析——网格一粗,填挖分界线就被咬成了锯齿
Edge 的 ChatGPT 侧栏突然打不开?一次 CSP frame-ancestors 故障排查与临时解决方案
Spring Boot水果管理系统:库存预警与订单事务全解析

今日推荐

高频变压器实战经验总结:磁芯选型、绕组工艺与故障排查全解析
U-Boot移植的关键:深入理解Kconfig配置系统与依赖关系
步进电机原理与调试全解析:从步距角到丢步排查

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

【CubeMX+STM32】SD卡 SDIO通信 DMA读写

发布时间:2026/10/20 1:15:41
【CubeMX+STM32】SD卡 SDIO通信 DMA读写 本篇将使用CubeMXKeil创建一个SD卡的DMA读写工程。目录一、简述二、CubeMX 配置 SDIO DMA三、Keil 编辑代码四、实验效果实现效果如下图一、简述上篇已简单介绍了SD、SDIO本篇不再啰嗦有兴趣的可翻看上篇:STM32_SD卡的SDIO通信_基础读写-CSDN博客使用SD卡存储数据常用的两种方式DMA读写(不带文件系统逻辑简单、代码体积小适合只做数据存储的应用。模拟U盘方式(FATFSUSB)方便电脑端文件存储但代码体积大、文件解释麻烦。两种方式各有优劣得按硬件限制、方案需求而进行选择。本篇示范的是不带FATFS的DMA读写。二、CubeMX 配置 SDIO DMA新建工程部分略过。1、使能SDIOMode选择SD的四线模式即SD 4 bits Wide bus.F407系列不用修改配置默认即可。F103系列需把时钟分频系数修改为6 或 8即SDIOCLK Clock divide factor这一项由默认0改为6, 不然会通信失败。2、启用DMAF407系列如下图DMA Settings /添加SDIO_RX、SDIO_TX这两项; 其它参数默认;F103系列点击ADD后添加SDIO。它只有SDIO一项因为F103的SDIO是读写共用同一DMA通道。其它参数默认。3、开启SDIO全局中断修改中断优先级F407 系列如下图三项打勾启用并修改优先级。F103 系列只有SDIO global interupt 这一项。打勾启用并修改优先级。4、时钟设置进入时钟树配置页面。如果之前没配置过SDIO、USB这时就会弹窗是否自动配置所需时钟选择NO 手动修改即可。不推荐 Yes因为它将针对已使能的SDIO、USB进行必须值的配置而已设置好的系统时钟将会被修改成其它值。F407 系列芯片如果板子用25M晶振使用下图配置即可如果是8M晶振修改晶振、分频两处为8即可。重点箭头所指的Q值它用于控制USB 、SDIO和随机数生成器的时钟这个时钟必须是 48M ! 因此这里设置为 7;F103 系列芯片使用新建工程时的默认参数即可无需额外修改。红圈处已是SDIO的默认时钟配置。好了已完成配置。重新生成工程即可三、Keil 编辑代码1、打开keil 工程先重新编译一次。正常情况编译是0 Error的。如果有Error, 应该是新建工程时路径、名称有中文了重新开建工程改为英文即可。2、重要修改SD卡的初始化使用 1-bit 模式CubeMX生成的SDIO初始化代码有一个bug需要手动修改操作如下右击 main.c 文件中函数 MX_SDIO_SD_Init(),在弹出菜单中Go To Ddfinition Of ...; 将跳转到SD卡初始化函数内部;跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后把下图位置中的 4B改为 1B 它下面还有一个4B不用修改只修改刚才那个即可。不要改错位置了重要CubeMX每次重新生成后都要手动修改一次。如果不修改初始化过程会导致程序卡死。3、DMA 读写函数 介绍我们在上篇介绍SD卡的基础读写时使用的4个函数如下这4个函数在本工程中还是可用的。1、获取SD卡信息 HAL_SD_CardInfoTypeDef pCardInfo {0}; // SD卡信息结构体 HAL_SD_GetCardInfo(hsd, pCardInfo); // 获取 SD 卡的信息 2、读数据 HAL_SD_ReadBlocks(hsd, aOldData, 7, 2, 3000); // SD卡的句柄、数据、块地址、块数量、超时ms 3、写数据 HAL_SD_WriteBlocks(hsd, aTestData, 7, 2, 3000) // SD卡的句柄、数据、块地址、块数量、超时ms 4、擦除数据 HAL_SD_Erase(hsd, 7, 8) // SD卡的句柄、块起始地址、块结束地址本篇通过DMA读写将使用以下两个读写函数和基础读写的函数名称近似只是多了后辍_DMA4、读取数据_通过DMA HAL_SD_ReadBlocks_DMA(hsd, aOldData, 7, 2); // 读取SD卡指定块的数据; 参数SD句柄、数据地址、块起始地址、需要读取的块数量; 5、写入数据_通过DMA HAL_SD_WriteBlocks_DMA(hsd, aTestData, 7, 2); // 向指定块写入数据; 参数SD句柄、数据地址、块起始地址、需要写入的块数量;注意机制的不同基础读、写函数 HAL_SD_ReadBlocks() 和 HAL_SD_WriteBlocks() 它俩是”死等式“执行的即读写完成、操作超时才能结束函数、并返回执行状态执行下一行代码。如果数据量大会卡程序。DMA读、写函数HAL_SD_ReadBlocks_DMA() 和 HAL_SD_WriteBlocks_DMA() 将按函数的参数配置、启动DMA后就会退出函数执行下一行代码。4、中断回调函数通过DMA读、写后有两种方式可以知道读写操作是否完成:通过HAL_SD_GetState(hsd)获取状态通过它俩的中断回调函数。本篇将使用中断回调函数的方式这样操作性更好;中断回调函数需要自行手写可以写在 it.c 或 main.c底部 它俩分别是1、DMA TX 传输完成 中断回调函数 void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { printf(写入完成\r\n); } 2、DMA RX 传输完成 中断回调函数 void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { printf(读取完成\r\n); }5、具体读写操作示例首先说明一点本示例示范如何通过DMA读写而示范代码中的状态判断只是提供一个思路这种写法是不高效的需按自身方案作出适合的修改。第一步新建两个变量作为DMA读写完成的标志。在main()的上方新建两个变量作为读写结束的标志volatile uint8_t myFlagSDReadReady 0; // 读取传输完成标志; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 volatile uint8_t myFlagSDWriteReady 0; // 写入传输完成标志; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。技巧这里用了volatile关键字修饰变量。因为示范代码中有几段 while (myFlagSDReadReady 0) 这样的空循环判断变量状态变化。而CubeMX生成的工程默认编译优化等级是3。编译时会把这一段中的变量误判为是不变的编译运行后就会导致循环体“卡死”。使用 volatile 修饰是告诉编译器不要优化它每次循环时都必须从内存中读一次这变量的真实值。编写完成后位置如下图第二步编写需要的两个中断回调函数F407 系列在main() 下方即/* USER CODE BEGIN 4 */下面添加这两个中断回调函数。/*DMA Tx传输完成中断回调函数*/ void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDWriteReady 1; // 写入传输完成标志; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 } /*DMA Rx传输完成中断回调函数*/ void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDReadReady 1; // 读取传输完成标志; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 }F103 系列上面在配置CubeMX时已提及F1的SDIO读写共用同一DMA通道因此操作与F4的稍有不同。首先在main.c的 /* USER CODE BEGIN 0 */里面添加一个引用extern DMA_HandleTypeDef hdma_sdio; // SDIO的DMA通道(单通道读写共用)然后在main() 下方即/* USER CODE BEGIN 4 */下面添加这两个中断回调函数。/*DMA Tx传输完成中断回调函数*/ void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDWriteReady 1; __HAL_DMA_DISABLE(hdma_sdio); // F103关键修复单DMA通道读写共用完成后强制关通道否则下次换向不生效 } /*DMA Rx传输完成中断回调函数*/ void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd) { myFlagSDReadReady 1; __HAL_DMA_DISABLE(hdma_sdio); // F103关键修复单DMA通道读写共用完成后强制关通道否则下次换向不生效 }编写完成后位置如下图第三步在 main()函数内/* USER CODE BEGIN 2 */ 注释下方编写读写代码有点点长建议直接复制。其实有效的读写只是简单的几行而已这里写了一堆只是为了展示。代码里已附详细注释整体流程是获取SD卡信息读取测试块的原数据写入测试擦除测试写回原数据/***************** SD卡读写通信测试 *****************/ /* 1、获取卡信息打印到串口助手 */ /* 2、读测试读出测试位置原数据保存在 aOldData[] */ /* 3、写测试在测试的块上写入指定数据 */ /* 读出刚才写入的块数据打印到串口助手观察 */ /* 4、擦除测试擦除指定块上的数据 */ /* 读出刚才擦除块的数据打印到串口助手观察 */ /* 5、写回原数据到指定位置 */ /* 读出刚才写入的块数据打印到串口助手观察 */ #define SD_TEST_SIZE 1024 // 测试数据的字节数刚好是2个块大小2x512 static uint8_t aOldData[SD_TEST_SIZE] {0}; // 用于存放旧数据先读出来测试完了再把旧数据写回去 static uint8_t aTestData[SD_TEST_SIZE] {0}; // 临时缓存用来存放测试数据 HAL_SD_CardInfoTypeDef pCardInfo {0}; // SD卡信息结构体 uint8_t status HAL_SD_GetCardState(hsd); // SD卡状态标志值 if (status HAL_SD_CARD_TRANSFER) { /* 1、获取卡信息打印到串口助手 */ HAL_SD_GetCardInfo(hsd, pCardInfo); // 获取 SD 卡的信息 printf(\r1、获取SD卡信息 ... \r\n); printf(卡类型%d \r\n, pCardInfo.CardType); // 类型返回0-SDSC、1-SDHC/SDXC、3-SECURED printf(卡版本%d \r\n, pCardInfo.CardVersion); // 版本返回0-CARD_V1、1-CARD_V2 printf(块数量%d \r\n, pCardInfo.BlockNbr); // 可用的块数量 printf(块大小%d \r\n, pCardInfo.BlockSize); // 每个块的大小; 单位字节 printf(卡容量%lluG \r\n, ((unsigned long long)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024); // 计算卡的容量 HAL_Delay(1000); // 重要稍作延时再开始读写测试; 避免有些仿真器烧录期间的多次复位短暂运行了程序导致下列读写数据不完整。 /* 2、读测试读出测试位置原数据保存在 aOldData[] */ printf(\r2、读取测试块的原数据 ... \r\n); memset(aOldData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady 0; // 读取传输完成标志。当DMA传输结束后在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_ReadBlocks_DMA(hsd, aOldData, 7, 2); // 读取SD卡指定块的数据; 参数SD句柄、数据地址、块起始地址、需要读取的块数量; while (myFlagSDReadReady 0); // 等待传输完成; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 for (uint32_t i 0; i SD_TEST_SIZE; i) // 打印 原始数据 printf(%X , aOldData[i]); printf(\r\n); /* 3-1、写测试在测试的块上写入数据 */ printf(\r3、SD卡 写入测试 ...\r\n); memset(aTestData, 0x8, SD_TEST_SIZE); // 整个数组填充指定值作为测试写入的数据 myFlagSDWriteReady 0; // 写入传输完成标志。当DMA传输结束后在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_WriteBlocks_DMA(hsd, aTestData, 7, 2); // 向指定块写入数据; 参数SD句柄、数据地址、块起始地址、需要写入的块数量; while (myFlagSDWriteReady 0); // 等待传输完成; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 printf(对指定块写入结束! \r写入的数据是\n); for (uint32_t i 0; i SD_TEST_SIZE; i) // 打印 写入的数据 printf(%X , aTestData[i]); printf(\r\n); /* 3-2、读出现在块内的数据 */ printf(\r现在块内的数据是\r\n); memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady 0; // 读取传输完成标志。当DMA传输结束后在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_ReadBlocks_DMA(hsd, aTestData, 7, 2); // 读SD卡数据块; 参数SD句柄、数据地址、块起始地址、读的块数量; while (myFlagSDReadReady 0); // 等待传输完成; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 for (uint32_t i 0; i SD_TEST_SIZE; i) // 打印 写入后块内现在数据 printf(%X , aTestData[i]); printf(\r\n); /* 4-1、擦除测试擦除指定块上的数据 */ printf(\r4、擦除块测试 ...\r\n); if (HAL_SD_Erase(hsd, 7, 8) HAL_OK) // 擦除SD卡上的数据; 参数SD结构体、块的起始地址、块的结束地址 { while (HAL_SD_GetCardState(hsd) ! HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束 printf(擦除 成功 \r\n); } else { printf(擦除 失败 \r\n); } /* 4-2、读取擦除后指定块上的数据 */ printf(擦除后现在块内的数据是\r\n); memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady 0; // 读取传输完成标志。当DMA传输结束后在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1. HAL_SD_ReadBlocks_DMA(hsd, aTestData, 7, 2); // 读SD卡数据块; 参数SD句柄、数据地址、块起始地址、读的块数量; while (myFlagSDReadReady 0); // 等待传输完成; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 for (uint32_t i 0; i SD_TEST_SIZE; i) // 打印 块内现在的数据 printf(%X , aTestData[i]); printf(\r\n); /* 5-1、写回测试块上的原始数据 */ printf(\r5、写回原数据 ...\r\n); //memset(aOldData, 0, SD_TEST_SIZE); // 这行是备用的为了测试后写入特定数据 myFlagSDWriteReady 0; // 写入传输完成标志。当DMA传输结束后在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1. HAL_SD_WriteBlocks_DMA(hsd, aOldData, 7, 2); // 向指定块写入数据; 参数SD句柄、数据地址、块起始地址、需要写入的块数量; while (myFlagSDWriteReady 0); // 等待传输完成; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 printf(写入结束! \n); /* 5-2、读取现在块内的数据 */ printf(现在块内的数据是: \r\n); memset(aTestData, 0, SD_TEST_SIZE); // 清0数组的数据 myFlagSDReadReady 0; // 读取传输完成标志。当DMA传输结束后在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1. HAL_SD_ReadBlocks_DMA(hsd, aTestData, 7, 2); // 读SD卡数据块; 参数SD句柄、数据地址、块起始地址、读的块数量; while (myFlagSDReadReady 0); // 等待传输完成; 重要CubeMX生成的Keil工程编译优化等级默认是3如果变量没有标记为易变的volatile编译器可能会认为其值在循环中不会改变从而导致优化后的代码无法正确检测到变量值的变化特别是在空循环体中。 for (uint32_t i 0; i SD_TEST_SIZE; i) // 打印 块内现在的数据 printf(%X , aTestData[i]); printf(\r\n\r\n); printf(SD卡 读写测试结束\r\n); }编写完成后位置如下图至此代码编写完成可以编译、烧录了。四、实验效果程序运行后串口助手输出如下如有错漏 望指正~~~

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号