当前位置: 首页 > article >正文

【STM32】SD卡

(一)常用卡的认识

在学习这个内容之前,作为生活小白的我对于SD卡、TF卡、SIM卡毫无了解,晕头转向。

SD卡:Secure Digital Card的英文缩写,直译就是“安全数字卡”。一般用于大一些的电子设备比如:电脑、数码相机、AV等,做外存储器用。

TF卡:T-Flash卡,又叫micro SD卡,即微型SD卡。TF卡内存比较小,一般用于手机、mp4等小型数码电子产品的外存储器用。

tf卡是什么卡?-百度经验

SIM卡:用户身份识别卡,差不多就是手机卡,智能卡

SIM卡我们非常熟知,当做手机卡等在用,那TF卡和SD卡又有什么区别呢?

(1)卡的外形不同:SD卡比TF卡的尺寸要大,SD卡上有一个lock开关,即写保护开关,而TF卡没有

(2)转换代替:TF卡插入适配器可以转换成SD卡,但是SD卡一般无法转换成TF卡

img

(二)SD卡介绍

特点:容量大、高安全性、体积小

1.种类

SD存储容量等级分为四种

塔尔杰塔斯微型SD梅约雷斯

SDSC---标准容量SD卡【第一张卡,利用 FAT32 文件系统 因此它不能超过 2 GB的存储空间 ,目前它们已退出市场。】

最高2GB

SDHC---高容量SD存储卡【他们的能力范围从 2 GB至32 GB ,也因存储容量低而退出当前市场。 它是第一个使用 exFAT文件系统

2GB-32GB

SDXC---容量扩大化的安全存储卡(基于NAN闪存技术)【今天最常用的,因为它可以达到 2 TB的存储空间

32GB-2TB

SDUC---超容量SD存储卡【如果 2 TB 的容量对您来说太少,那么您可以选择 SDUC,但是它们目前稀有且昂贵,到时候还是将取代 SDXC】

2TB-128TB

注意:STM32最大仅支持32GB SD卡

2.文件形式

如果你对SD卡跟EEPROM或者NOR FLASH操作,读写数据并验证数据的准确性,则不需要FAT文件系统

但是,SD卡经常被用在Windows操作系统上存取数据,就就得使用操作系统支持的FAT文件系统

3.速度等级

SD卡还根据读写速度划分为不同的速度等级,以满足不同应用场景的需求:

写入速度等级有三个参数表示,分别是普通的速度等级Speed Class(C),超高速速度等级UHS Speed Class(U),视频速度等级Video Speed Class*(V),代表数字表示最低写入速度。

img

【C】普通的速度等级Speed Class ​ 有Class 2、Class 4、Class 6和Class 10,简写为C2、C4、C6、C10,分别表示最低写入速度为2MB/s、4MB/s、6MB/s、10MB/s。C10代表可用于高速或更高速的模式。

【U】超高速速度等级UHS Speed Class ​ UHS Speed Class 1和UHS Speed Class 3,简写为U1、U3。U1代表最低写入速度为10M/S,U3代表最低写入速度为30M/S。UHS与U1/U3(UHS Speed Class)不同,不要弄混了。一个是接口标准,一个是速度

【V】视频速度等级Video Speed Class ​ 有V6、V10、V30、V60和V90,V后面的数字表示最低写入速度,单位MB/s,V6表示最低写入速度为6MB/s,V90表示最低写入速度为90MB/s。这个参数对录制视频来说非常重要,录制不同画质的视频对存储卡视频速度等级也不一样。如果要录制4k画质视频,必须要用V30以上的存储卡。

(三)SD卡的驱动介绍

1.SD卡引脚以及接口

9pinSD卡内部结构

SD 卡允许不同的接口来访问它的内部存储单元。最常见的是 SDIO 模式SPI 模式,根据这两种接口模式,我们也列出 SD 卡引脚对应于这两种不同的电路模式的引脚功能定义

microSD 引脚,比 SD 卡少了一个电源引脚 VSS2,其它的引脚功能类似,操作时序也完全相同,所以可以用完全相同的代码驱动

2.SD卡寄存器

SD卡有8个寄存器,但是不能直接进行读写操作,需要通过命令来控制。SD卡根据收到的命令要求对内部寄存器进行修改

3.SD命令以及响应

一个完整的 SD 卡操作过程是:主机(单片机等)发起“命令”--》SD 卡根据命令的内容决定是否发送响应信息及数据等,如果是数据读/写操作,主机还需要发送停止读/写数据的命令来结束本次操作,这意味着主机发起命令指令后,SD 卡可以没有响应、数据等过程,这取决于命令的含义。

(1)命令格式

SD卡命令格式由6个字节所组成,发送数据时高位在前,SD卡的写入命令格式如下:

SD 卡的命令固定为 48 位,由 6 个字节组成,字节 1 的最高 2 位固定为 01,低 6 位为命令 号(比如 CMD16,为 10000B 即 16 进制的 0X10,完整的 CMD16,第一个字节为 01010000, 即 0X10+0X40)。字节 2~5为命令参数,有些命令是没有参数的。字节 6 的高七位为 CRC 值, 最低位恒定为 1

(2)SD卡命令

SD 卡的命令总共有 12 类,分为 Class0~Class11,以下为一些重要命令

上图中,大部分的命令是初始化的时候用的,而表中的 R1、R1b、R2、R3、R6 和 R7 等是 SD卡的应答信号。在主机发送有响应的命令后,SD卡都会给出相对应的应答,以告知主机 该命令的执行情况,或者返回主机需要获取的数据。

(2)SD卡响应

响应,分为长响应136bit、短响应48bit。R1、R1b、 R3、R6 和 R7 属于短响应,而 R2 属于长响应(若使用SDLO接口时,响应通过cmd线传输)

(3)响应接口格式

响应因使用接口不同,格式也不同

(4)卡模式

在SD卡系统(主机和SD卡)定义了两种操作模式:卡识别模式(识别总线上的SD卡类型)和数据传输模式(读写操作)

系统复位后,主机和SD卡都处于卡识别模式,主机在总线上找设备。当SD卡被识别后,SD卡也进入数据传输模式,而主机在总线上是所有卡都被识别后也进入数据传输模式

只关注两个模式:卡识别模式和数据传输模式就好了。

系统复位之后,主机处于卡识别模式,寻找总线上可用的 SDIO 设备,对 SD 卡进行数据读写之前需要识别卡的种类: V1.0 标准卡、V2.0 标准卡、V2.0 高容量卡或者不被识别卡;同时,SD 卡也处于卡识别模式, 直到被主机识别到,即当 SD 卡在卡识别状态接收到 CMD3 (SEND_RCA)命令后,SD 卡就进 入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。

卡识别模式下,主机会复位所有处于“卡识别模式”的 SD 卡,确认其工作电压范围, 识别 SD 卡类型,并且获取 SD 卡的相对地址(卡相对地址较短,便于寻址)。在卡识别过程中, 要求 SD 卡工作在识别时钟频率 FOD 的状态下

  • 上电

  • CMD0【软件复位】

  • CMD8【主机询问SD卡是否支持电压范围】(V2.0卡支持该命令,V1.x和MMC卡不支持)

    • 如果R1响应,ACMD41

      • R3响应【判断是高容量还是标准】(响应R3中返回OCR寄存器:CCS1 高容量V2.0卡,CCS0 标准V2.0卡)

    • 如果R1不响应,ACMD41【用于判断是V1.x卡还是MMC卡,其中MMC不支持】

      • R1响应,则为V1.x卡

      • R1不响应,CMD1【判断MMC卡或其他不能被识别的卡,其中CMD1激活MMC卡】

        • 响应则是MMC卡

主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送 GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不 会复位。 主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。SD 卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。

SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。

SD_SEND_OP_COND(ACMD41)命令可以识别或拒绝不匹配它的电压范围的卡。 ACMD41 命令的 VDD 电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。

ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡 在发送 CID之后就进入识别状态。之后主机就发送 SEND_RELATIVE_ADDR(CMD3)命令,让 卡自己推荐一个相对地址(RCA)并响应命令。这个 RCA 是 16bit 地址,而 CID 是 128bit 地址, 使用 RCA 简化通信。卡在接收到 CMD3 并发出响应后就进入数据传输模式,并处于待机状态, 主机在获取所有卡 RCA 之后也进入数据传输模式。

下图为数据传播模式卡状态转换

(5)数据块读取流程

1.SD卡单块数据块读取流程

  1. 发送CMD16指令,设置数据块大小

  2. 等待CMD16响应(R1)

  3. 发送CMD17指令,开始读取数据

  4. 等待CMD17响应(R1)

  5. 读一个数据块的数据

    其中注意的是:CMD16设置的数据块大小,一般是512字节,此设置直接决定了SD卡的块大小,SD卡默认的块大小自动失效

2.SD卡多块数据块读取流程

  1. 发送CMD16指令,设置数据块大小

  2. 等待CMD16响应(R1)

  3. 发送 CMD18指令,开始读数据块

  4. 等待CMD18响应(R1)

  5. 读一个数据块的数据

  6. 读两个数据块的数据

  7. 读n个数据块的数据

  8. 发送CMD12指令,结束数据块读取

  9. 等待CMD12响应(R1)

  10. 结束多块数据块读取

(5)数据传输格式

SD卡有两种数据模式,一种是常规的 8位宽,即一次按一字节传输,另一种是一次按 512 字节传输,我们只介绍前面一种。当按 8-bit 连续传输时,每次传输从最低字节开始,每字节从最高位(MSB)开始发送,当使用一条数据线时,只能通过 DAT0** 进行数据传输

当使用 4 线模式传输 8-bit 结构的数据时,数据仍按 MSB 先发送的原则,DAT[3:0]的高位发送高数据位,低位发送低数据位。其特点为可提升传输速率。

(四)STM32驱动SD卡代码解析

1.原理图:

(1)SDIO_CK ---> 产生时钟

[SDIO_CK = SDIOCLK【48MHZ】 / ( 2 + CLKDIV ) SD卡初始化时,SDIO_CK不可超过400Khz,初始化完成之后,可设为最大操作频率(但不能超过25Mhz) ]  

(2)SDIO_CMD ---> 发送命令、接收响应

(3)SDIO_D ---> 双向传输数据

2.STM32F4 HAL函数

HAL库有为我们准备专门驱动SD卡的代码,主要存放在 stm32f4xx_hal_sd.c/h 下。

(1)HAL_SD_Init函数

初始化SIOD外设操作,其声明如下

HAL_StatusTypeDef HAL_SD_Init(SD_HandleTypeDef *hsd)

形参是一个SD卡的句柄,精简后如下

typedef struct
{
  SD_TypeDef                   *Instance;        /*!< SD 相关寄存器基地址           */
  SD_InitTypeDef               Init;             /*!<  SDIO 初始化变量             */
  HAL_LockTypeDef              Lock;             /*!< 互斥锁,用于解决外设访问冲突    */
  uint8_t                      *pTxBuffPtr;      /*!< SD 发送数据指针              */
  uint32_t                     TxXferSize;       /*!< SD 发送缓存按字节数的大小      */
  uint8_t                      *pRxBuffPtr;      /*!< SD 接收数据指针              */
  uint32_t                     RxXferSize;       /*!< SD 接收缓存按字节数的大小      */
  __IO uint32_t                Context;          /*!<  HAL 库对 SD 卡的操作阶段     */
  __IO HAL_SD_StateTypeDef     State;            /*!<  SD 卡操作状态               */ 
  __IO uint32_t                ErrorCode;        /*!< SD 卡错误代码                */
  DMA_HandleTypeDef            *hdmatx;          /*!< SD DMA 数据发送指针          */
  DMA_HandleTypeDef            *hdmarx;          /*!<  SD DMA 数据接收指针         */
  HAL_SD_CardInfoTypeDef       SdCard;           /*!< SD 卡信息                   */
  uint32_t                     CSD[4];           /*!<  保存 SD 卡 CSD 寄存器信息    */
  uint32_t                     CID[4];           /*!< 保存 SD 卡 CID 寄存器信息     */
}SD_HandleTypeDef;

其中的

typedef struct
{
  uint32_t CardType;                     /*!< 存储卡类型标记:标准卡、高速卡           */
  uint32_t CardVersion;                  /*!<  存储卡版本            */
  uint32_t Class;                        /*!< 卡类型  */
  uint32_t RelCardAdd;                   /*!< 卡相对地址  */
  uint32_t BlockNbr;                     /*!< 卡存储块数 */
  uint32_t BlockSize;                    /*!< SD 卡每个存储块大小    */
  uint32_t LogBlockNbr;                  /*!<  以块表示的卡逻辑容量*/
  uint32_t LogBlockSize;                 /*!< 以字节为单位的逻辑块大小 */
}HAL_SD_CardInfoTypeDef;

(2)HAL_SD_ConfigWideBusOperation 函数

SD 卡上电后默认使用 1 位数据总线进行数据传输,卡如果允许,可以在初始化完成后重新设置 SD 卡的数据位宽以加快数据传输过程,以下是可修改的总线宽度值

#define SDIO_BUS_WIDE_1B ((uint32_t)0x00000000U)
#define SDIO_BUS_WIDE_4B SDIO_CLKCR_WIDBUS_0
#define SDIO_BUS_WIDE_8B SDIO_CLKCR_WIDBUS_1

(3) HAL_SD_ReadBlocks 函数

SD 卡初始化后从 SD 卡的指定扇区读一定数量的数据:

HAL_StatusTypeDef HAL_SD_ReadBlocks (SD_HandleTypeDef *hsd, uint8_t *pData,
uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);

(4)HAL_SD_WriteBlocks 函数

SD 卡初始化后,在 SD 卡的指定扇区写入一定数量的数据

HAL_StatusTypeDef HAL_SD_WriteBlocks (SD_HandleTypeDef *hsd, uint8_t *pData,
uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);

(5)HAL_SD_GetCardInfo 函数

SD 卡初始化后,根据设备句柄读 SD 卡的相关状态信息

HAL_StatusTypeDef HAL_SD_GetCardInfo(SD_HandleTypeDef *hsd,HAL_SD_CardInfoTypeDe*pCardInfo);
3.SDIO驱动SD卡关键代码
(1)使能SDIO和相关GPIO时钟,设置好GPIO工作模式
(2)初始化SDIO
(3)初始化SD卡

sd_init 函数:填充SDMMC结构体的控制句柄,然后使用HAL库的 HAL_SD_Init 初始化函数即可,在此过程中 HAL_SD_Init 会调用 HAL_SD_MspInit 函数回调函数,根据外设的情况,我们可以设置数据总线宽度为 4 位

(4)SD卡读取扇区数据

上我们使用它来对 HAL 库的读函数 HAL_SD_ReadBlocks进行了二次封装,并在最后加入了状态判断以使后续操作(实际上这部分代码也可以省略),直接根据读函数返回值自己作其它处理。为了保护 SD 卡的数据操作,我们在进行操作时暂时关闭了中断以防止数据读过程发生意外。

(5)SD卡写入扇区数据

我们使用它来对 HAL 库的写函数 HAL_SD_WriteBlocks 进行了二次封装,并在最后加入了状态判断以使后续操作(实际上这部分代码也可以省略),直接根据写函数返回值自己作其它处理。为了保护 SD 卡的数据操作,我们在进行操作时暂时关闭了中断以防止数据写过程发生意外。

4.SPI驱动SD卡步骤
(1)SD卡初始化

SPI操作模式下:在SD卡收到复位命令时,CS为有效电平(低电平),则SPI模式被启用,在发送CMD之前要先发送74个时钟,64个为内部供电上升时间,10个用于SD卡同步;之后才能开始CMD操作,在初始化时CLK时钟不能超过400KHz

(1)初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置)

(2)上电延时(>74个CLK)

(3)复位卡(CMD0),进入IDLE状态

(4)发送CMD8,检查是否支持2.0协议

(5)根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58和CMD1等)

(6)取消片选,发多8个CLK,结束初始化

这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。

(2)SD卡读扇区数据

(1)发送CMD17

(2)接收卡响应R1

(3)接收数据起始令牌0XFE

(4)接收数据

(5)接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉

(6)禁止片选之后,发多8个CLK

(3)SD卡写入数据

(1)发送CMD24

(2)接收卡响应R**1**

(3)发送写数据起始令牌0XFE

(4)发送数据

(5)发送2字节的伪CRC

(6)禁止片选之后,发多8个CLK

5.SDIO驱动SD卡实验

打开SDIO

打开中断

打开usart1

打开其中断

关键也就这两个操作,其它操作都很简单,就是配配时钟,这里要提到一个小地方:一定要打开debug当中的Serial,否则程序烧录一次之后就会识别烧录器失败!!!但也不要怕,本人一早到解决方法:BOOT0用杜邦线接3V3,然后按一下开发板上的reset键,亲测有效!!!

生成完工程之后,为了串口调试方便,在usart.c当中加入以下

#include "stdio.h"
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
 
/**
  * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}

接着在main.c文件声明SD对象

HAL_SD_CardInfoTypeDef pCardInfo;

声明

#define BLOCK_SIZE   512                            //一个块的字节数
#define NUMBERS_PER_CHUNK 512                                           //多少个数据存一次
#define INT_SIZE 1                                                      //一个数值占几个字节
#define BUFFER_SIZE (NUMBERS_PER_CHUNK * INT_SIZE)  //存一次sd卡的数组的大小(其实就是512)
 
uint8_t buffer_TX[NUMBERS_PER_CHUNK];                           // 用于暂时存储需要发送到SD卡的数据
uint8_t buffer_RX[NUMBERS_PER_CHUNK];                           // 用于接收从SD卡中读取来的数据  

插上SD卡 在main当中添加查看SD卡信息

    HAL_SD_GetCardInfo(&hsd,&pCardInfo);
    printf("SD卡类型:%u\r\n",pCardInfo.CardType);          //卡的类型
    printf("SD卡版本:%u\r\n",pCardInfo.CardVersion); //卡的版本
    printf("SD卡块数:%u\r\n",pCardInfo.BlockNbr);    //SD卡块数
    printf("SD卡每块大小:%u\r\n",pCardInfo.BlockSize);//每一块的大小

(1)读取SD卡中的数据

 
            for(int chunk=0;chunk<1;chunk++)
        {
            //计算读取的块数量
            uint32_t blocksToRead = BUFFER_SIZE/BLOCKSIZE;
            
            //从SD卡读取数量
            if(HAL_SD_ReadBlocks(&hsd,(uint8_t *)buffer_RX,blockNum,1,1000)==HAL_OK)
            {
                while(HAL_SD_GetCardState(&hsd)!=HAL_SD_CARD_TRANSFER);//返回到传输状态退出
                
                for(int i=0;i<NUMBERS_PER_CHUNK;i++)
                {
                    printf("%d\t",buffer_RX[i]);
                }
                printf("\n读取成功!\n");
            }
            else
            {
                printf("读取失败!\n");
            }
            blockNum +=1;//更新块号读取下一个段
        }

由于篇幅过长,可以看下一篇文章具体讲解以及更多玩法!


http://www.kler.cn/a/369348.html

相关文章:

  • 微信小程序——消息订阅
  • java :String 类
  • 【TIMM库】是一个专门为PyTorch用户设计的图像模型库 python库
  • 自由学习记录(15)
  • springboot基本概念
  • Maven 介绍与核心概念解析
  • SpringBoot支付回调枚举+策略+工厂模式
  • python构建flask服务用于视频文件的处理后返回
  • 基于单片机的家用多功能衣柜控制系统设计
  • Excel重新踩坑4:快捷键;逻辑函数;文本函数;日期相关函数;查找与引用函数;统计类函数;数组公式
  • ESP32 S3 怎么开发基于ESP-RTC的音视频实时交互的应用,用语AI陪伴的领域
  • 爬虫爬取数据时,如何解决由于验证码通常是动态生成的,直接通过URL下载可能会遇到验证码内容不一致的问题?( ̄︶ ̄)↗
  • 微服务架构学习笔记
  • 移植FreeRTOS实时操作系统(基于STM32F429)
  • 十、Linux 故障排除专业案例分享
  • 如何理解PostgreSQL全页写?
  • nginx和apache的区别
  • 组件通信八种方式(vue3)
  • 2024年下教师资格证面试报名详细流程❗
  • Vue应用中使用xlsx库实现Excel文件导出的完整指南
  • 详解Java之Spring MVC篇一
  • uniapp路由权限拦截守卫
  • 24下软考信息系统监理师,快背,都是精华知识点!
  • spring高手之路
  • react18中的受控与非受控组件及ref的使用
  • 配置 SSH 无需密码连接服务器及为 IP 指定自定义域名