【STM32】SD卡

(一)常用卡的认识

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

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

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

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

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

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

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

(二)SD卡介绍

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

1.种类

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*),*代表数字表示最低写入速度。

【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;//更新块号读取下一个段
        }

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

相关推荐
PegasusYu6 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
文弱书生65610 小时前
输出比较简介
stm32
黑客呀13 小时前
[系统安全]Rootkit基础
stm32·单片机·系统安全
小A15913 小时前
STM32完全学习——使用SysTick精确延时(阻塞式)
stm32·嵌入式硬件·学习
楚灵魈13 小时前
[STM32]从零开始的STM32 HAL库环境搭建
stm32·单片机·嵌入式硬件
小A15913 小时前
STM32完全学习——使用标准库点亮LED
stm32·嵌入式硬件·学习
code_snow16 小时前
STM32--JLINK使用、下载问题记录
stm32·单片机·嵌入式硬件
youcans_18 小时前
【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识
stm32·单片机·嵌入式硬件·电机控制·foc
YuCaiH21 小时前
【STM32】MPU6050简介
笔记·stm32·单片机·嵌入式硬件
linux_carlos1 天前
#lwIP 的 Raw API 使用指南
stm32·单片机·mcu·物联网·rtdbs