STM32_SD卡的SDIO通信_基础读写

本篇将使用CubeMX+Keil,  创建一个SD卡读写的工程。

目录

一、SD卡要点速读

二、SDIO要点速读

三、SD卡座接线原理图

四、CubeMX新建工程

五、CubeMX 生成 SD卡的SDIO通信部分

六、Keil 编辑工程代码

七、实验效果

实现效果,如下图:


一、SD卡 速读

SD卡,全称Secure Digital Memory Card(安全数码卡),是嵌入式设备上常用的一种存储介质。

1、尺寸大小 分类

按卡的大小分类,可以为3种:

  • 标准SD卡 :体积较大,卡侧带写保护开关;常见于相机和摄像机中,用于存储高分辨率照片和视频;
  • mini SD卡 :现在较少看到,已逐渐被microSD卡取代;
  • Micro SD卡:旧称 TF卡,2004年更名为 Micro SD Card, 常用于扩展手机和平板电脑的存储空间。

每种卡形状大小不一,但功能一样:遵循相同的 SD卡协议、相同的命令集、相同的块大小(512)。只需确保SDIO引脚配置正确,并且遵循SD卡协议发送正确的命令,程序即可通用。

都是SD卡,但习惯上,标准SD叫SD卡,Micro SD叫TF卡。

目前,STM32开发板、Linux开发板 等,预留的卡座,一般是TF卡座,因为它占用空间最少。

2、卡的容量及标准 分类

在SD卡的表面丝印上,会有HC、XC等字样,表示它所使用的存储标准。

  • SD: 早期的版本,基本停用,最高 2GB, 分区格式为 FAT12(FAT)、FAT16。
  • SDHC:容量范围 2GB ~ 32GB, 分区格式为 FAT32。
  • SDXC:容量范围 32GB ~ 2TB, 分区格式为 exFAT。
  • SDUC:容量范围 2TB ~ 128TB, 分区格式为 exFAT。

3、SD卡的传输速度

SD卡的可变时钟频率:0~25MHz。当运行在25M+数据带宽4位时,最大理论传输速度是12.5MB/s。

而操作中,会明显低于理论速度,其受限于不同品牌的芯片优化、制造工芯、采用标准等。

SD卡是Flash存储,读写速度特点是:读快、写慢。

SD卡的最低写入速度,用Class等级来标识。

在表面丝印上,一般会有Class字样,它后面的数表示最低写入速度,单位是:MB/s。

或者,会用一个外面带半圆的数字表示。

  • Class 2:2MB/s
  • Class 4:4MB/s
  • Class 6:6MB/s
  • Class 10:10MB/s

附:常用的SD卡读写速率参考,非严谨值。

SD卡容量文件系统写入速度读取速度
32G(SDHC)FAT322MB/s8MB/s
32G(SDHC)exFAT3.5MB/s8.5MB/s
64G(SDXC)exFAT4MB/s8.5MB/s

4、SD卡的使用寿命

一般是指:擦除的最大次数。

写入数据时需要先擦除扇区内容。读数据是不影响使用寿命的,写数据才会影响使用寿命。

因此,应避免频繁地对同一地址(扇区)进行写数据。如:使用程序每隔一秒保存一次数据到同一地址,这是不妥当的。

  • TLC:1000~3000次
  • MLC:3000 ~1万次
  • SLC:可达10万次

擦写次数对使用寿命影响较小,而更容易直接“致死”的是:带电插拔,很容易坏卡,主要是静电原因!


二、SDIO要点速读

原理比较复杂,有兴趣的请自行csdn搜更详细的技术文档,或STM32的官方文档。

  • SD卡的读写通信操作,可以用 SPI、SDIO,本示例使用SDIO。
  • SDIO接口是在SD内存卡接口的基础上发展起来的;
  • SDIO接口除了能读写SD内存卡,还能连接其它SDIO接口的设备;
  • 常用的STM32F103C8,没有SDIO接口,F103系列R型号起,才带SDIO;
  • STM32F4系列芯片,带更完善的SDIO主机接口,能与MMC卡、SD卡、SDI/0卡、EC-ATA设备进行通信;
  • 三种总线模式:1-bit、4-bit、8-bit(不常用);


三、SD卡座接线原理图

STM32的SDIO外设与SD卡通信,通用接线如下图。

注:当使用弹簧式SD卡座,会有第9个脚(CD), 可不接。它用于判断SD卡是否插入,当插入SD卡时,此脚输出低电平。


四、CubeMX新建工程

建议复制一个已带UART1、printf的工程,这样更省时。

如果没有,可参考以下步骤。

1、新建一个普通的工程

新手可参考如下图解,老司机请直接跳过。

【STM32+CubeMX】 新建工程_STM32F407

2、为工程添加UART1通信、printf输出

用于把SD卡的测试信息,(通过USB转TTl),输出到串口助手观察。

如果,你已知晓如何通过printf输出信息,自行添加,跳过即可。

USART1 DMA发送、DMA空闲中断 接收不定长数据

UART1 快速实现移植、通信 ( bsp_UART.c 、bsp_UART.h)


五、CubeMX 配置 SD卡的SDIO 初始化

通过 CubeMX配置SDIO, 极度简单。

本节为方便测试,只使用普通的读写方式,后续篇章再添加DMA、FATFS等方式。

1、使能SDIO

  • Mode:选择SD的四线模式,即 SD 4 bits Wide bus.
  • 参数部分:F4系列不用修改配置,默认即可。F103系列,需把时钟分频系数修改为 6,即SDIOCLK Clock divide factor这一项,由默认0改为6, 不然会通信失败。

2、时钟设置

进入时钟树配置页面。

这时可能会弹出一个询问窗:是否自动配置所需时钟?

选择:NO ,手动修改即可。

如果Yes,它将针对已使能的SDIO进行必须值的配置,而系统时钟值,会被修改为其它值。不推荐。

F4系列,如果板上是25M的晶振,用如下参数值;要是8M的晶振,修改晶振、分频两处为8即可。

重点:箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟。

这个时钟,必须是 48M ! 

好了,已完成配置。

重新生成工程,即可!


六、Keil 编辑工程代码

1、打开keil 工程,先重新编译一次。

  • 正常情况,编译是0 Error的。
  • 如果有Error,  应该是新建工程时,路径、名称有中文了,重新开建工程,用英文即可。

2、重要修改:SD卡的初始化,使用 1-bit 模式

CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下: 

  • 编译后,右击 main.c 文件中函数 MX_SDIO_SD_Init(), 
  • 在弹出菜单中:Go To Definition Of ...;  将跳转到SD卡初始化函数;

跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后,

把下图位置中的 4B,改为 1B ;

它下面还有一个4B,不用修改,只修改刚才那个即可。不要改错位置了!

重点:每次重新生成后,都要手动修改一次。如果不修改,初始化过程会导致程序卡死。

3、编写 读写测试 代码

SD卡的基础读写函数比较简单,常用的函数共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卡的句柄、数据、块地址、块数量、超时ms3、写数据
HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000)  //  SD卡的句柄、数据、块地址、块数量、超时ms4、擦除数据
HAL_SD_Erase(&hsd, 7, 8)  //  SD卡的句柄、块起始地址、块结束地址

在 main函数内的 /* USER CODE BEGIN 2 */ 注释下方,编写以下代码(可复制):

示例代码里已附详细注释,比较容易理解,流程是:

  • 获取SD卡信息
  • 读取测试块的原数据 
  • 写入测试
  • 擦除测试
  • 写回原数据
    /***************** SD卡读写通信测试 *****************//* 1、获取卡信息,打印到串口助手                    *//* 2、读测试:读出测试位置原数据,保存在 aOldData[] *//* 3、写测试:在测试的块上,写入指定数据            *//*    读出刚才写入的块数据,打印到串口助手观察      *//* 4、擦除测试:擦除指定块上的数据                  *//*    读出刚才擦除块的数据,打印到串口助手观察      *//* 5、写回原数据到指定位置                          *//*    读出刚才写入的块数据,打印到串口助手观察      */    #define SD_TEST_SIZE    1024                                       // 测试数据的字节数,刚好是2个块大小:2x512static 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-SECUREDprintf("卡版本:%d \r\n", pCardInfo.CardVersion);              // 版本返回:0-CARD_V1、1-CARD_V2printf("块数量:%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数组的数据if (HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000) == HAL_OK)   // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                // 打印 原数据printf("%X ",  aOldData[i]);printf("\r\n");}else{printf("SD卡 读测试 失败!\n");            }/* 3-1、写测试:在测试的块上写入数据 */printf("\r3、SD卡 写入测试 ...\r\n");memset(aTestData, 0x8, SD_TEST_SIZE);                          // 为数组准备要写入的测试数据:整个数组填充指定值if (HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK) // 向SD卡写入数据块; 参数:SD结构体、数据地址、块起始地址、写入的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束printf("对指定块写入结束! \r写入的数据是:\n");for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                // 打印 写入的数据printf("%X ",  aTestData[i]);printf("\r\n");}else{printf("SD卡 写测试 失败!\n");}/* 3-2、读出刚才写测试的块内数据 */printf("\r现在块内的数据是:\r\n");memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据if (HAL_SD_ReadBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK)  // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                // 打印 写入后块内现在数据printf("%X ",  aTestData[i]);printf("\r\n");}else{printf("SD卡 读测试 失败!\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数组的数据if (HAL_SD_ReadBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK)  // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                // 打印 块内现在的数据printf("%X ",  aTestData[i]);printf("\r\n");}else{printf("SD卡 读测试 失败!\n");}/* 5-1、写回测试块上的原数据 */printf("\r5、写回原数据 ...\r\n");//memset(aOldData, 1, SD_TEST_SIZE);if (HAL_SD_WriteBlocks(&hsd, aOldData, 7, 2, 3000) == HAL_OK)  // 向SD卡写入数据块; 参数:SD结构体、数据地址、块起始地址、写入的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束printf("写入结束! \n");}else{printf("SD卡 写回原数据 失败!\n");}        /* 5-2、读取,写入后的数据 */printf("现在块内的数据是: \r\n");memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据if (HAL_SD_ReadBlocks(&hsd, aTestData, 7, 2, 3000) == HAL_OK)  // 读SD卡数据块; 参数:SD结构体、数据地址、块起始地址、读的块数量、超时时间;{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                // 打印 块内现在的数据printf("%X ",  aTestData[i]);printf("\r\n\r\n");}else{printf("SD卡 读测试 失败! \r\n");}printf("SD卡 读写测试结束!\r\n");}

完成后,位置如下图:


七、实验效果

程序运行后,串口助手输出如下:

如有错漏 ,望指正~~~!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/69004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深度学习】2.视觉问题与得分函数

计算机视觉任务 可以通过神经网络搜索是什么类别的动物。 图像实际就是含有数值的三维矩阵。 像素值从0-255可以表示亮度递增的参数。数字越大&#xff0c;像素点越亮。 最后的3表示三个颜色通道&#xff0c;常见的如JPG、RGB等。 现实场景容易发生各种遮蔽现象。 计算机判断…

JVM面试题解,垃圾回收之“分代回收理论”剖析

一、什么是分代回收 我们会把堆内存中的对象间隔一段时间做一次GC&#xff08;即垃圾回收&#xff09;&#xff0c;但是堆内存很大一块&#xff0c;内存布局分为新生代和老年代、其对象的特点不一样&#xff0c;所以回收的策略也应该各不相同 对于“刚出生”的新对象&#xf…

从根源分析,调试,定位和解决MacOS ld: unsupported tapi file type ‘!tapi-tbd‘ in YAML file

你要是遇到同样错误&#xff0c;找一圈都没有解决&#xff0c;建议认真读一下本文&#xff0c;这个应该是最终极的解决办法&#xff0c;从原理上剖析了产生的原因&#xff0c;同时给出来了调试和定位的办法。 maccos使用brew安装了一个gcc14, 结果编译一个最简单的程序都报错&a…

单片机-STM32 IIC通信(OLED屏幕)(十一)

一、屏幕的分类 1、LED屏幕&#xff1a; 由无数个发光的LED灯珠按照一定的顺序排列而成&#xff0c;当需要显示内容的时候&#xff0c;点亮相关的LED灯即可&#xff0c;市场占有率很高&#xff0c;主要是用于户外&#xff0c;广告屏幕&#xff0c;成本低。 LED屏是一种用发光…

Windows cmd常用命令

文章目录 Windows cmd常用命令一、引言二、文件和目录操作1、查看和切换目录2、文件和目录的创建与删除 三、系统信息与网络配置1、系统信息2、网络配置 四、使用示例五、总结 Windows cmd常用命令 一、引言 Windows 命令提示符&#xff08;cmd&#xff09;是一个强大的工具&a…

snippets router pinia axios mock

文章目录 补充VS Code 代码片段注册自定义组件vue routerpinia删除vite创建项目时默认的文件axiosmock3.0.x版本的 viteMockServe 补充 为文章做补充&#xff1a;https://blog.csdn.net/yavlgloss/article/details/140063387 VS Code 代码片段 为当前项目创建 Snippets {&quo…

Spring MVC中HandlerInterceptor和Filter的区别

目录 一、处理阶段 二、功能范围 三、参数访问 四、配置方式 五、使用场景说明 在Spring MVC中&#xff0c;HandlerInterceptor和Filter都是用于拦截请求的重要组件&#xff0c;但它们在多个方面存在显著的差异。本文将详细解析这两种拦截机制的区别&#xff0c;并结合使用…

在K8S中,如何使用EFK实现日志的统一管理?

在Kubernetes中&#xff0c;EFK是一种常见的日志统一管理方案。EFK堆栈允许你收集、存储、搜素、分析和可视化容器应用程序的日志。下面是如何在Kubernetes中使用EFK实现日志统一管理的详细步骤&#xff1a; 部署Elasticsearch elasticsearch是一个分布式、RESTful风格的搜索…

神经网络基础 | 给定条件下推导对应的卷积层参数

神经网络基础 | 给定条件下推导对应的卷积层参数 按照 PyTorch 文档中 给定的设置&#xff1a; H o u t ⌊ H i n 2 padding [ 0 ] − dilation [ 0 ] ( kernel_size [ 0 ] − 1 ) − 1 stride [ 0 ] 1 ⌋ H_{out} \left\lfloor\frac{H_{in} 2 \times \text{padding}[0]…

欧拉(Euler 22.03)安装ProxySQL

下载离线安装包 proxysql-2.0.8-1-centos7.x86_64.rpm 链接: https://pan.baidu.com/s/1R-SJiVUEu24oNnPFlm9wRw 提取码: sa2w离线安装proxysql yum localinstall -y proxysql-2.0.8-1-centos7.x86_64.rpm 启动proxysql并检查状态 systemctl start proxysql 启动proxysql syste…

Sharding-JDBC 5.4.1+SpringBoot3.4.1+MySQL8.4.1 使用案例

最近在升级 SpringBoot 项目&#xff0c;原版本是 2.7.16&#xff0c;要升级到 3.4.0 &#xff0c;JDK 版本要从 JDK8 升级 JDK21&#xff0c;原项目中使用了 Sharding-JDBC&#xff0c;版本 4.0.0-RC1&#xff0c;在升级 SpringBoot 版本到 3.4.0 之后&#xff0c;服务启动失败…

MacOS/C/C++下怎样进行软件性能分析(CPU/GPU/Memory)

在macOS环境下进行C/C软件性能分析&#xff0c;可以使用多种工具和技术来测量和优化CPU、GPU和内存的性能。macOS提供了丰富的性能分析工具&#xff0c;如Instruments、gprof、Perf、以及NVIDIA和Intel的专用工具。下面详细介绍了实现思想和操作方法。 1. 性能分析的目标 CPU…

WPS计算机二级•幻灯片的基础操作

听说这是目录哦 PPT的正确制作步骤&#x1f6e3;️认识PPT界面布局&#x1f3dc;️PPT基础操作 快捷键&#x1f3de;️制作PPT时 常用的快捷技巧&#x1f3d9;️快速替换PPT的 文本字体&#x1f303;快速替换PPT 指定文本内容&#x1f305;能量站&#x1f61a; PPT的正确制作步…

vue3组件传值具体使用

问&#xff1a; left.vue文件调用接口获取了后端返回的urlLink字段&#xff0c;我该怎么传递给总的父组件index.vue中&#xff0c;我需要点击父组件的一个按钮来触发跳转&#xff1f; 回答&#xff1a; 在 Vue 3 中使用 TypeScript 和 setup 语法糖时&#xff0c;可以通过 e…

easyexcel读取写入excel easyexceldemo

1.新建springboot项目 2.添加pom依赖 <name>excel</name> <description>excelspringboot例子</description><parent> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId&…

考研机试题:打印数字菱形

描述 打印一个由数字 0∼n 构成的菱形。 其中 n 位于正中心&#xff0c;数字靠近边缘时逐个递减&#xff0c;直至为 0。 例如&#xff0c;当 n5时&#xff0c;图形如下所示&#xff1a; 00 1 00 1 2 1 00 1 2 3 2 1 00 1 2 3 4 3 2 1 0 0 1 2 3 4 5 4 3 2 1 00 1 2 3 4 3 2 …

Neural networks 神经网络

发展时间线 基础概念 多层神经网络结构 神经网络中一个网络层的数学表达 TensorFlow实践 创建网络层 神经网络的创建、训练与推理 推理 推理可以理解为执行一次前向传播 前向传播 前向传播直观数学表达 前向传播直观数学表达的Python实现 前向传播向量化实现 相关数学知识…

【探索 Kali Linux】渗透测试与网络安全的终极操作系统

探索 Kali Linux&#xff1a;渗透测试与网络安全的终极操作系统 在网络安全领域&#xff0c;Kali Linux 无疑是最受欢迎的操作系统之一。无论是专业的渗透测试人员、安全研究人员&#xff0c;还是对网络安全感兴趣的初学者&#xff0c;Kali Linux 都提供了强大的工具和灵活的环…

AR智慧点巡检系统探究和技术方案设计

一、项目背景 随着工业生产规模的不断扩大和设备复杂度的提升&#xff0c;传统的人工点巡检方式效率低下、易出错&#xff0c;难以满足现代化企业对设备运行可靠性和安全性的要求。AR&#xff08;增强现实&#xff09;技术的发展为点巡检工作带来了新的解决方案&#xff0c;通…

AI如何帮助解决生活中的琐碎难题?

引言&#xff1a;AI已经融入我们的日常生活 你有没有遇到过这样的情况——早上匆忙出门却忘了带钥匙&#xff0c;到了公司才想起昨天的会议资料没有打印&#xff0c;或者下班回家还在纠结晚饭吃什么&#xff1f;这些看似微不足道的小事&#xff0c;往往让人疲惫不堪。而如今&a…