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 Previous Reference To ...;  将跳转到函数内部;

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

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

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

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

3、编写测试代码

在 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数组的数据
        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");
    }

完成后,位置如下图:


七、实验效果

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型GUI系列论文阅读 DAY2续:《一个具备规划、长上下文理解和程序合成能力的真实世界Web代理》

摘要 预训练的大语言模型&#xff08;LLMs&#xff09;近年来在自主网页自动化方面实现了更好的泛化能力和样本效率。然而&#xff0c;在真实世界的网站上&#xff0c;其性能仍然受到以下问题的影响&#xff1a;(1) 开放领域的复杂性&#xff0c;(2) 有限的上下文长度&#xff…

【ESP32】ESP32连接JY61P并通过WIFI发送给电脑

前言 手头上有个ESP32&#xff0c;发现有wifi功能&#xff0c;希望连接JY61P并通过WIFI把姿态数据发送给电脑 1.采用Arduino IDE编译器&#xff1b;需要安装ESP32的开发板管理器&#xff1b; 2.电脑接受数据是基于python的&#xff1b; 1. ESP32 连接手机WIFI #include <…

C语言程序设计十大排序—冒泡排序

文章目录 1.概念✅2.冒泡排序&#x1f388;3.代码实现✅3.1 直接写✨3.2 函数✨ 4.总结✅ 1.概念✅ 排序是数据处理的基本操作之一&#xff0c;每次算法竞赛都很多题目用到排序。排序算法是计算机科学中基础且常用的算法&#xff0c;排序后的数据更易于处理和查找。在计算机发展…

【Elasticsearch】腾讯云安装Elasticsearch

Elasticsearch 认识Elasticsearch安装Elasticsearch安装Kibana安装IK分词器分词器的作用是什么&#xff1f;IK分词器有几种模式&#xff1f;IK分词器如何拓展词条&#xff1f;如何停用词条&#xff1f; 认识Elasticsearch Elasticsearch的官方网站如下 Elasticsearch官网 Ela…

Django学习笔记(安装和环境配置)-01

Django学习笔记(安装和环境配置)-01 一、创建python环境 1、可以通过安装Anaconda来创建一个python环境 # 创建一个虚拟python环境 conda create -n django python3.8 # 切换激活到创建的环境中 activate django2、安装django # 进入虚拟环境中安装django框架 pip install …

python创建一个httpServer网页上传文件到httpServer

一、代码 1.server.py import os from http.server import SimpleHTTPRequestHandler, HTTPServer import cgi # 自定义请求处理类 class MyRequestHandler(SimpleHTTPRequestHandler):# 处理GET请求def do_GET(self):if self.path /:# 响应200状态码self.send_response(2…

一个软件分发和下载的网站源码,带多套模板

PHP游戏应用市场APP软件下载平台网站源码手机版 可自行打包APP&#xff0c;带下载统计&#xff0c;带多套模板&#xff0c;带图文教程 代码下载&#xff1a;百度网盘

前端面试题-问答篇-5万字!

1. 请描述CSS中的层叠&#xff08;Cascade&#xff09;和继承&#xff08;Inheritance&#xff09;规则&#xff0c;以及它们在实际开发中的应用。 在CSS中&#xff0c;层叠&#xff08;Cascade&#xff09;和继承&#xff08;Inheritance&#xff09;是两个关键的规则&#x…

面试:Hadoop,块,HDFS的优缺点,HDFS的读写流程

Hadoop CDH会简化Hadoop的安装 Hue主要用于数据分析和处理&#xff0c;而CM(Cloudera Manager)则主要用于集群的管理和运维。 HDFS HDFS的块 块是 HDFS 系统当中的最小存储单位, 在hadoop2.0和3.0中默认128MB 在HDFS上的文件会被拆分成多个块&#xff0c;每个块作为独立的单…

Stable Diffusion 3.5 模型在 Linux 上的部署指南

文章目录 前言-参考资料如下一. ComfyUI安装二.模型下载2.1 安装GGUF和T5 xxl编码模型2.2 安装ComfyUI辅助插件2.3 启动ComfyUI2.4 基础ComfyUI和SD3.5配置2.5 demo 前言-参考资料如下 ComfyUI WIKI教程 sd3.5 github 尝试过sd集成ollama&#xff0c;但是sd在ollama上无法良好…

移远通信多模卫星通信模组BG95-S5获得Skylo网络认证,进一步拓展全球卫星物联网市场

近日&#xff0c;全球领先的物联网整体解决方案供应商移远通信正式宣布&#xff0c;其支持“卫星蜂窝”多模式的高集成度NTN卫星通信模组BG95-S5已成功获得NTN网络运营商Skylo的网络认证。BG95-S5也成为了获得该认证的最新款移远卫星通信模组。 BG95-S5模组顺利获得Skylo认证&a…

C语言之饭店外卖信息管理系统

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 C语言之饭店外卖信息管理系统 目录 设计题目设计目的设计任务描述设计要求输入和输出要求验…

【数学建模美赛速成系列】O奖论文绘图复现代码

文章目录 引言折线图 带误差棒得折线图单个带误差棒得折线图立体饼图完整复现代码 引言 美赛的绘图是非常重要得&#xff0c;这篇文章给大家分享我自己复现2024年美赛O奖优秀论文得代码&#xff0c;基于Matalab来实现&#xff0c;可以直接运行出图。 折线图 % MATLAB 官方整理…

wordpress安装完后台无格式解决方法(样式加载不出来)

刚安装的wordpress,进入后台后,没有样式。 1.如果ip进入,可能一切正常 2.域名进入,遇到这种情况概率大(经过了nginx代理) 正常访问文章的话是没问题的,只是管理后台存在这样的代码,样式没加载出来。 美国随机地址生成器:美国随机地址生成器(随机地址生成器 - 生成全…

R语言基础| 回归分析

写在前面 R语言拥有丰富的数据处理、统计分析和机器学习工具包&#xff0c;涵盖了从简单的描述统计到复杂的模型建立的各个方面。再加上数据的处理可以完美的衔接后续的可视化&#xff0c;这使得它成为处理各种类型和规模的数据集的理想选择。回归分析是统计学中一种用于探究自…

数据结构-ArrayList和顺序表

1.线性表 线性表是n个具有相同类型的数据元素所组成的有限序列&#xff0c;当n0时&#xff0c;线性表为一个空表。 常见的线性表&#xff1a;顺序表&#xff0c;链表&#xff0c;栈和队列... 线性表在逻辑上是线性结构&#xff0c;可以说是连续的一条直线。但是在物理结构上…

计算机视觉算法实战——人类情感识别(主页有源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​​​​​​​​​​​​​​​​ 1. 引言✨✨ 人类情感识别&#xff08;Facial Expression Recognition, FER&#xff09;是计算机视觉领…

08_游戏启动逻辑

1.GameRoot.cs 控制 服务层Svc.cs 和业务层Sys.cs 的初始化 创建脚本GameRoot.cs&#xff08;游戏入口 已进入就初始化各个系统&#xff09; 创建资源加载服务.cs Res 将服务层Svc设置成单例类所以需要挂载在GameRoot身上&#xff0c;这样就可以通过GameRoot来调各个服务 接…

当使用 npm 时,出现 `certificate has expired` 错误通常意味着请求的证书已过期。

当使用 npm 时&#xff0c;出现 certificate has expired 错误通常意味着请求的证书已过期。这可能是由于以下几种情况&#xff1a; 网络代理问题&#xff1a;如果使用了网络代理&#xff0c;代理服务器的证书可能过期或配置有误。系统时间错误&#xff1a;系统时间不准确可能导…

2024年,我的技术探索与成长之路

2024年&#xff0c;我的技术探索与成长之路 2024年已经过去&#xff0c;作为一名技术爱好者和写作者&#xff0c;我回顾了过去一年在博客上记录的点滴&#xff0c;感慨良多。这一年&#xff0c;我不仅见证了技术的飞速发展&#xff0c;也在不断学习和实践中找到了自己的成长方向…