SD卡简介

本质是nandflash+控制芯片。SD卡系列有三种:SD卡,MiniSD卡和MicroSD卡(TF卡)。特点:容量大,高安全性,体积小,传输速率块,接口简单。

SD卡主要规格参数如图:

脚位数对应实卡的金手指数

SD卡 的存储结构:

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

CID:卡标识寄存器,128bit

提供制造商ID,应用ID,产品名称,版本,序列号,制造日等信息

RCA:相对卡地址寄存器,16bit,仅SDIO模式下有,初始化时动态地由卡建立,经主机核准

OCR:操作条件寄存器, 32bit

主要是SD卡的操作电压等信息,关注15~23位可以知道SD卡支持的电压范围,SD卡支持范围中相应位是置1的,还要关注30位的CCS,卡容量状态,为1是SDHC卡,为0是SDSC卡

SCR:SD配置寄存器,64比特

关注DAT Bus widths supported ,SD_BUS_WIDTHS位,设置有几根数据,0设置1位(DAT0),2设置4位(DAT0~3),默认值是0

CSD:卡特定数据寄存器,128比特

提供SD卡操作条件相关信息和数据,主要看CSD_STRUCTURE位,可以区分版本,0是1.0版本,1是2.0版本

SD卡驱动方式

可通过SPI接口或者SDIO接口,不同接口SD卡引脚功能不一样。

SD卡引脚对应这两种接口的引脚功能定义:

TF卡8pin,SD卡9pin,TF卡只比SD卡少一个电源引脚。注:S电源,I输入,O推挽输出,PP复用推挽。SD卡和micorSD只有引脚和形状大小不同,内部结构是类似的,操作时序也完全相同,可以使用相同的代码驱动。

SDIO接口通信线:CLK/CMD/DAT0/DAT3/DAT1/DAT2

CMD:命令线,SDIO主机通过该线发送命令控制SD卡,若命令要求响应,SD卡也是通过该线传输响应信息

DAT0~3:数据线,用于接收或发送数据。SD卡可将DAT0拉低表示处于忙状态(SPI接口的MISO也有该特性)

SPI接口通信线:CLK/MOSI/MISO/CS

SD卡认识

SD卡命令和响应:

SD卡有多种命令和响应,他们的格式定义及含义在SD卡协议2.0的第三和第四章有详细介绍,发送命令时主机只能通过CMD引脚发送给SD卡,串行逐位发送时先发送最高位(MSB),然后是次高位这样类推。SD卡强制性类命令有,基本命令,块读取命令,块写入命令,擦除命令,加锁命令,特定于应用命令。

SD卡命令格式:

6个字节组成,固定48位,发送数据时高位在前。

起始位(47)固定0,停止位(0)固定是1,传输方向(46)为1表命令,0则表响应,注意使用SDIO 接口驱动,CRC7校验值必须正确,而SPI接口驱动,CRC7校验默认关闭即为伪CRC。

SD卡总共有12类命令,分为CLASS0~CLASS11。常用的命令有以下这些:

大部分的命令是初始化的时候用的,应用相关命令(ACMD),通用命令(CMD),发送ACMD前需先发CMD55        

SD卡响应:

分短响应(48bit)和长响应(136bit),R1,R1b,R3,R6,R7属于短响应,R2属于长响应,五类响应

响应格式:举例SDIO接口下的R1响应

R1响应:通过40~45这6位(响应内容中的commandindex)可获知响应的是哪个命令

R2响应:通过1~127位,CID寄存器内容作为CMD2的响应,CSD寄存器内容作为CMD9的响应

R3响应:8~39位,OCR寄存器的值作为ACMD41的响应

R7响应:16~19位,专用于命令CMD8的响应,返回卡支持电压范围和检测模式

R6响应:24~39位,专用于命令CMD3的响应(RCA响应)卡相对地址

SD卡操作模式和卡状态:

无效模式,卡识别模式(识别总线上的SD卡类型)和数据传输模式(读写操作),每个操作模式下SD卡都有几种状态。状态之间通过命令控制实现切换卡状态的切换

SD卡有两种数据模式,一种是常规的8位宽,即一次按一字节传输,另一种是一次按512字节传输

SD卡操作步骤

(1)SD初始化:

主机上电后所有卡处于空闲状态,主机也可通过CMD0软复位进入空闲状态(无效卡不会复位)

主机与SD卡通信之前需先确定双方在互相支持的电压范围内,SD卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。空闲状态下发送CMD8用于验证卡接口操作条件的(主要是电压支持),即主机给SD卡发其当前支持的工作电压,如果SD卡不支持主机电压就无响应返回空闲状态,还可用于区分卡类型因为CMD8是SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡

空闲状态下发ACMD41主机看SD卡的OCR寄存器。对于对CMD8有响应的卡,把ACMD41命令的HCS位设置为1,可以测试卡的容量类型,如果卡响应(R3响应)的CCS位为1说明为高容量SD卡,否则为标准卡。卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。ACMD41是应用特定命令,发送该命令之前必须先发CMD55。对COM8不响应的卡在发CMD55时如果不支持CMD55可说明为MMC卡或不能识别卡此时可再发CMD1命令激活MMC卡如果有响应则为MMC卡,CMD55有响应为V1.0版本卡

准备状态下发送CMD2获取卡(V1.x卡或V2.0卡)的CID寄存器,处于准备状态的卡在发送CID之后就进入识别状态,卡识别模式,之后主机就发送CMD3获取卡相对地址,卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。对SD卡读写之前需要识别卡的种类

CMD7用于选卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是处于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。如果主机发送读操作命令,卡会进入发送数据状态,传输完成或者发CMD12中止会回到传输状态则是数据接收状态,还会进入编程状态,因为SD卡写前要预擦除,编程完可发CMD7取消卡进入卡断开连接状态,然后再进入待机状态

(2)写操作:

SD卡单块数据块写入流程:发送CMD16指令设置数据块大小,发送CMD13指令查询卡状态,等待READY_FOR_DATA位=1,发送CMD2指令开始写入数据,写一个数据块的数据。

SD卡多块数据块写入流程:发送CMD16指令设置数据块大小,发送ACMD23指令预擦除数据块,发送CMD25指令开始写数据块,写入第一个数据块的数据~第N个数据块的数据,发送 CMD12指令结束数据块写入,发送CMD13指令查询卡状态,等待SD卡写入过程结束,完成多块多块数据的写入。

(3)读操作:

SD卡单块数据块读取流程:发送CMD16指令设置数据块大小,等待CMD16的R1响应,发送CMD17指令开始读数据块,等待CMD17响应R1,读一个数据块的数据

SD卡多块数据读取流程:发送CMD16等待响应,发送CMD18指令等待响应,读第一个数据块的数据~读第n个数据块的数据,发送CMD12指令结束数据块读取等待CMD12响应R1,结束多块数据块读取。

SDIO接口简介

SDIO,全称为安全数字输入/输出接口,多媒体卡(MMC卡),SD存储卡都有SDIO设备。

SDIO框图:

STM32F1的SDIO控制器包含2个部分:SDIO适配器和AHB总线接口,其功能框图如图

SDIO适配器提供SD卡特有功能:产生时钟,发送命令,接收响应,双向数据传输

SDIO适配器里边有控制单元,负责电源管理和时钟管理功能。

有命令通道:控制命令发送并接收响应,有命令通道状态机(CPSM)

数据通道:负责主机和卡之间传输数据,有数据通道状态机(DPSM)

数据FIFO,具有发送和接收数据缓冲器。TXACT和RXACT标志表明当前发送还是接收状态。读空FIFO后继续读会下溢,写满FIFO后继续写会上溢,可以开启硬件流控解决这个问题。

SDIO时钟:

从框图可看出,有三个时钟。

卡时钟(SDIO_CK):SD卡是指频率可以在0MHz至25MHz间变化,通信时钟,每个时钟周期在命令和数据线上传输1位命令或数据。计算公式:SDIO_CK=SDIOCLK/(2+CLKDIV)。注意:在SD卡刚刚初始化的时候,其时钟频率(SDIO_CK)是不能超过400Khz的,否则可能无法完成初始化。在初始化以后,就可以设置时钟频率到最大了(但不可超过SD 卡的最大操作时钟频率)。复位后默认情况下SDIO_D0用于数据传输。初始化后主机可以改变数据总线的宽度(通过ACMD6命令设置)。

SDIO适配器时钟(SDIOCLK):

该时钟用于驱动 SDIO 适配器,其频率等于 48MHz,并用于产生 SDIO_CK 时钟

AHB总线时钟(HCLK/2):该时钟用于驱动SDIO的AHB总线接口

SDIO命令与响应

SDIO命令格式:

SDIO 的命令分为应用相关命令(ACMD)和通用命令(CMD)两部分,应用相关命令(ACMD)

的发送,必须先发送通用命令(CMD55),然后才能发送应用相关命令(ACMD)。

所有的命令都是由 STM32F1 发出,其中开始位、传输位、CRC7 和结束位由 SDIO 硬件控

制,我们需要设置的就只有命令索引和参数部分。命令索引在SDIO_CMD寄存器里面设置,命令参数则由寄存器SDIO_ARG设置

SDIO响应格式:

短响应的:

命令索引存放在SDIO_RESPCMD寄存器,参数则存放在SDIO_RESP1寄存器里面。在收到 R1 响应后,我们可以从 SDIO_RESPCMD 寄存器和 SDIO_RESP1 寄存器分别读出 命令索引和卡状态信息。

长响应的:

则仅留CID/CSD位域,存放在SDIO_RESP1~SDIO_RESP4等4个寄存器。

SDIO数据传输:

对于 SDI/SDIO 存储器,数据是以数据块的形式传输的,而对于 MMC 卡,数据是以数据块或者数据流的形式传输。

SDIO(多)数据块读操作:

SDIO(多)数据块写操作:

数据块写操作同数据块读操作基本类似,只是数据块写的时候,多了一个繁忙判断,新的

数据块必须在 SD 卡非繁忙的时候发送。这里的繁忙信号由 SD 卡拉低 SDIO_D0,以表示繁忙,

SDIO 硬件自动控制,不需要我们软件处理。

SDIO适配器寄存器

SDIO电源控制寄存器(SDIO_POWER):

让SDIO上电,开启卡时钟

SDIO时钟控制寄存器(SDIO_CLKCR):

当SDIO_CK频率过快时可能会导致SD卡失败,此时建议降低频率试试

SDIO命令寄存器SDIO_CMD):

一般都开启CPSM(命令状态机),位6,7按需设置

SDIO命令响应寄存器SDIO_RESPCMD):

该寄存器为32位,但只有低6位有效用于存储最后收到的命令响应中的命令索引

SDIO响应寄存器组(SDIO_RESP1~SDIO_RESP4):

共由 4 个 32 位寄存器组成,用于存放接收到的卡响应部分信息

SDIO数据定时器寄存器SDIO_DTIMER:

用于存储以卡总线时钟(SDIO_CK)为周期的数据超时时间,一个计数器将从SDIO_DTIMER寄存器加载数值,并在数据通道状态机(DPSM)进入Wait_R或繁忙状态时进行递减计数,当DPSM处在这些状态时,如果计数器减为0,则设置超时标志

SDIO数据长度寄存器(SDIO_DLEN):

低25位有效,用于设置需要传输的数据字节长度

SDIO数据控制寄存器(SDIO_DCTRL):

SDIO状态寄存器(STA):

SDIO数据FIFO寄存器(SDIO_FIFO):

数据 FIFO 寄存器包括接收和发送 FIFO,由一组连续的 32 个地址上的 32 个寄存器组成。我们要从 SD 卡读数据,就必须读 SDIO_FIFO寄存器,要写数据到 SD 卡,则要写 SDIO_FIFO 寄存器。SDIO 将这 32 个地址分为 16 个一组, 发送接收各占一半。我们每次读写的时候,最多就是读取发送 FIFO 或写入接收 FIFO 的一半 大小的数据,也就是 8 个字(32 个字节),这里特别提醒,我们操作 SDIO_FIFO(不论读出还 是写入)必须是以 4 字节对齐的内存进行操作,否则将导致出错!

SDIO相关HAL库驱动:

__HSL_RCC_SDIO_CLK_ENABLE  用于SDIO时钟使能

HAL_SD_Init,HAL_SD_Msplint用于初始SD卡和SDIO的底层硬件

HAL_SD_ConfigWideBusOperation用于配置总线宽度

HAL_SD_ReadBlocks 读取块数据

HAL_SD_WriteBlocks  写入块数据

SDIO_SendCommand函数用于发送,SDIO_GetResponse用于接收响应

SDIO外设相关结构体:

SD_InitTypeDef,SDIO_CmdInitTypedef,SDIO_DataInitTypeDef,Hal_SD_CardInfoTypeDef

驱动文件:halsd.c封装初始化,读写,擦除等驱动接口操作

halsdmmc对接SDIO寄存器与SD卡通信

代码部分:

SD卡初始化:

SD_HandleTypeDef        SDCARD_Handler;     		//SD卡句柄
HAL_SD_CardInfoTypeDef  SDCardInfo;                 //SD卡信息结构体
//SD卡初始化
//返回值:0 初始化正确;其他值,初始化错误
u8 SD_Init(void)
{
    u8 SD_Error;
    
    //初始化时的时钟不能大于400KHZ 
    SDCARD_Handler.Instance=SDIO;
    SDCARD_Handler.Init.ClockEdge=SDIO_CLOCK_EDGE_RISING;          		//上升沿     
    SDCARD_Handler.Init.ClockBypass=SDIO_CLOCK_BYPASS_DISABLE;     		//不使用bypass模式,直接用HCLK进行分频得到SDIO_CK
    SDCARD_Handler.Init.ClockPowerSave=SDIO_CLOCK_POWER_SAVE_DISABLE;   //空闲时不关闭时钟电源
    SDCARD_Handler.Init.BusWide=SDIO_BUS_WIDE_1B;                       //1位数据线
    SDCARD_Handler.Init.HardwareFlowControl=SDIO_HARDWARE_FLOW_CONTROL_ENABLE;//开启硬件流控
    SDCARD_Handler.Init.ClockDiv=SDIO_TRANSFER_CLK_DIV;            		//SD传输时钟频率最大25MHZ
	
    SD_Error=HAL_SD_Init(&SDCARD_Handler);
    if(SD_Error!=HAL_OK) return 1;
	
    SD_Error=HAL_SD_ConfigWideBusOperation(&SDCARD_Handler,SDIO_BUS_WIDE_4B);//使能宽总线模式
    if(SD_Error!=HAL_OK) return 2;
    return 0;
}
//SDMMC底层驱动,时钟使能,引脚配置,DMA配置
//此函数会被HAL_SD_Init()调用
//hsd:SD卡句柄
void HAL_SD_MspInit(SD_HandleTypeDef *hsd)
{
//    DMA_HandleTypeDef TxDMAHandler,RxDMAHandler;
    GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_SDIO_CLK_ENABLE();    //使能SDIO时钟
//    __HAL_RCC_DMA2_CLK_ENABLE();    //使能DMA2时钟 
    __HAL_RCC_GPIOC_CLK_ENABLE();   //使能GPIOC时钟
    __HAL_RCC_GPIOD_CLK_ENABLE();   //使能GPIOD时钟
    
    //PC8,9,10,11,12
    GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;      //推挽复用
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);     //初始化
    
    //PD2
    GPIO_Initure.Pin=GPIO_PIN_2;            
    HAL_GPIO_Init(GPIOD,&GPIO_Initure);     //初始化
}

判断SD卡是否可以传输数据:

#define SD_TRANSFER_OK     	((uint8_t)0x00)
#define SD_TRANSFER_BUSY   	((uint8_t)0x01) 
//判断SD卡是否可以传输(读写)数据
//返回值:SD_TRANSFER_OK 传输完成,可以继续下一次传输
//		 SD_TRANSFER_BUSY SD卡正忙,不可以进行下一次传输
u8 SD_GetCardState(void)
{
  return((HAL_SD_GetCardState(&SDCARD_Handler)==HAL_SD_CARD_TRANSFER )?SD_TRANSFER_OK:SD_TRANSFER_BUSY);
}

获取卡信息:

//得到卡信息
//cardinfo:卡信息存储区
//返回值:错误状态
u8 SD_GetCardInfo(HAL_SD_CardInfoTypeDef *cardinfo)
{
    u8 sta;
    sta=HAL_SD_GetCardInfo(&SDCARD_Handler,cardinfo);
    return sta;
}

读SD卡:

#define SD_TIMEOUT 			((uint32_t)100000000)  	//超时时间
//SD_ReadDisk/SD_WriteDisk函数专用buf,当这两个函数的数据缓存区地址不是4字节对齐的时候,
//需要用到该数组,确保数据缓存区地址是4字节对齐的.
__align(4) u8 SDIO_DATA_BUFFER[512];
//读SD卡
//buf:读数据缓存区
//sector:扇区地址
//cnt:扇区个数	
//返回值:错误状态;0,正常;其他,错误代码;
u8 SD_ReadDisk(u8* buf,u32 sector,u32 cnt)
{
    u8 sta=HAL_OK;
	u32 timeout=SD_TIMEOUT;
	long long lsector=sector;
    INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
	sta=HAL_SD_ReadBlocks(&SDCARD_Handler, (uint8_t*)buf,lsector,cnt,SD_TIMEOUT);//多个sector的读操作
	
	//等待SD卡读完
	while(SD_GetCardState()!=SD_TRANSFER_OK)
    {
		if(timeout-- == 0)
		{	
			sta=SD_TRANSFER_BUSY;
		}
    }
    INTX_ENABLE();//开启总中断
    return sta;
}
//关闭所有中断
void INTX_DISABLE(void)
{		  
	__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
	__ASM volatile("cpsie i");		  
}

写SD卡:

//写SD卡
//buf:写数据缓存区
//sector:扇区地址
//cnt:扇区个数	
//返回值:错误状态;0,正常;其他,错误代码;	
u8 SD_WriteDisk(u8 *buf,u32 sector,u32 cnt)
{   
    u8 sta=HAL_OK;
	u32 timeout=SD_TIMEOUT;
	long long lsector=sector;
    INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
	sta=HAL_SD_WriteBlocks(&SDCARD_Handler,(uint8_t*)buf,lsector,cnt,SD_TIMEOUT);//多个sector的写操作

	//等待SD卡写完
	while(SD_GetCardState()!=SD_TRANSFER_OK)
    {
		if(timeout-- == 0)
		{	
			sta=SD_TRANSFER_BUSY;
		}
    }    
	INTX_ENABLE();//开启总中断
    return sta;
}

作者:白菜小周

物联沃分享整理
物联沃-IOTWORD物联网 » STM32的SD卡读取

发表回复