(一)常用卡的认识
在学习这个内容之前,作为生活小白的我对于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卡单块数据块读取流程
-
发送CMD16指令,设置数据块大小
-
等待CMD16响应(R1)
-
发送CMD17指令,开始读取数据
-
等待CMD17响应(R1)
-
读一个数据块的数据
其中注意的是:CMD16设置的数据块大小,一般是512字节,此设置直接决定了SD卡的块大小,SD卡默认的块大小自动失效
2.SD卡多块数据块读取流程
-
发送CMD16指令,设置数据块大小
-
等待CMD16响应(R1)
-
发送 CMD18指令,开始读数据块
-
等待CMD18响应(R1)
-
读一个数据块的数据
-
读两个数据块的数据
-
读n个数据块的数据
-
发送CMD12指令,结束数据块读取
-
等待CMD12响应(R1)
-
结束多块数据块读取
(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;//更新块号读取下一个段
}