SPI接口操作SD卡

一、SD卡简介

1.1 SD卡和TF卡有哪些区别呢?

SD卡和TF卡主要有以下区别和联系:

卡体尺寸不同,SD卡要比TF卡大(TF卡称作Micro SD card);
管教定义不同;SD卡管脚要比TF卡多;
TF卡插入卡套可作为SD卡使用;反之则不行。
在嵌入式领域,尤其是小设备,通常使用TF卡。下文中除特殊说明外,用SD卡统一称呼而不做区分。

1.2 容量分类

按容量分类,SD卡分为:SDSC卡(小于等于2GB),SDHC卡(大于2GB且小于等于32GB),SDXC卡(大于32GB且小于等于2TB)。其中SDSC卡和SDHC卡协议兼容,而与SDXC卡有很大的差异。本文中主要讨论前者。

1.3 工作电压范围

高电压SD卡工作电压范围:2.73.6V;双电压SD卡工作电压范围:低电压范围(T.B.D)和2.73.6V。

1.4 速度等级

SD卡V2.0标准协议定义了4个速度等级,来表示卡的最小速率。

Class 0 - 这种卡不定义具体性能,代表了这种规范出来之前的所有卡。
Class 2 - 最小2MB/s的性能。
Class 4 - 最小4MB/s的性能。
Class 6 - 最小6MB/s的性能。
Class 8 - 最小8MB/s的性能。
Class 10 - 最小10MB/s的性能。
高容量SD卡应该支持速度等级规格,并且最小要到Class 2。

注意:性能单位表示的是1000x1000字节/秒,而数据大小的MB单位指的是1024x1024字节。这是因为最大SD总线速度是由最大SD时钟频率决定的,而数据大小是基于存储范围。

SD卡引脚图
SD卡引脚说明

二、SD卡通信协议

以STM32F103 SPI协议读取SD卡为例。

2.1 SPI协议请求命令格式

在这里插入图片描述
SD卡的命令由三部分组成:Command,Command Argument和CRC。

2.1.1 取消选中的SD

发送新的命令之前,需要取消之前的片选,额外发多 8个 CLK (发送0xFF无效数据),结束之前的操作。

2.1.2 发送0xFF

2.1.3 发送操作命令cmd

将要发送的命令 |0x40 发给SD卡。 示例: cmd | 0x40

// SD卡指令表  	   
#define CMD0    0       //卡复位
#define CMD1    1
#define CMD8    8       //命令8 ,SEND_IF_COND
#define CMD9    9       //命令9 ,读CSD数据
#define CMD10   10      //命令10,读CID数据
#define CMD12   12      //命令12,停止数据传输
#define CMD16   16      //命令16,设置SectorSize 应返回0x00
#define CMD17   17      //命令17,读sector
#define CMD18   18      //命令18,读Multi sector
#define CMD23   23      //命令23,设置多sector写入前预先擦除N个block
#define CMD24   24      //命令24,写sector
#define CMD25   25      //命令25,写Multi sector
#define CMD41   41      //命令41,应返回0x00
#define CMD55   55      //命令55,应返回0x01
#define CMD58   58      //命令58,读OCR信息
#define CMD59   59      //命令59,使能/禁止CRC,应返回0x00

在这里插入图片描述

2.1.4 发送命令参数

在这里插入图片描述

2.1.5 发送CRC校验

在这里插入图片描述###

2.1.6 等待回复

在这里插入图片描述

2.1.7 检测SD卡指令示例,检测SD卡的协议波形

在这里插入图片描述

2.2 SPI模式下代码操作步骤

以spi 读取sd为例。

2.2.1 发送命令

  1. 初始化与 SD卡连接的硬件条件(MCU的 SPI配置,IO口配置等等)
    2. 向总线最少发送74个脉冲,为了让SD卡正常启动 (唤醒SD卡) (解释: 就是时钟线至少需要74个跳变,向MOSI发送0xFF数据即可,这是无效数据)
    3. 复位卡(CMD0),进入 IDLE(闲置)状态。 说明: 最后的返回值等于0x01就表示复位成功。
    4. 发送 CMD8,检查是否支持 2.0协议,因为这个命令是在2.0的协议里面才添加的 说明: 发送 CMD8命令之后,返回值等于0x01表示就是2.0版本的SD卡。
    5. 如果是2.0版本的SD卡,就需要循环发送CMD55+ CMD41命令等待2.0卡初始化成功,如果CMD41命令的返回值等于0就表示卡复位成功。(先发CMD55,再发CMD41)
    6. 2.0卡初始化成功后,再发送CMD58命令,继续判断是否是高速卡。 说明: CMD58命令返回值等于0,表示执行成功。然后就可以读取4字节的OCR
    寄存器的值。OCR寄存器的第30位(CCS)指示了卡的类型是否为SDHC,此位为1则为SDHC,为0则为SDSC。
    如果只是为了判断是否是高速卡,可以只读取1个字节数据即可,因为SD返回的数据先返回的是高位数据(24~31),后面的数据可以不读取。
    7. 取消片选,结束初始化。 说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。
//向SD卡发送一个命令
//输入: u8 cmd   命令 
//      u32 arg  命令参数
//      u8 crc   crc校验值	   
//返回值:SD卡返回的响应															  
u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)
{
    u8 r1;	
	u8 Retry=0; 
	SD_DisSelect();//取消上次片选
	if(SD_Select())return 0XFF;//片选失效 
	//发送
    SD_SPI_ReadWriteByte(cmd | 0x40);//分别写入命令
    SD_SPI_ReadWriteByte(arg >> 24);
    SD_SPI_ReadWriteByte(arg >> 16);
    SD_SPI_ReadWriteByte(arg >> 8);
    SD_SPI_ReadWriteByte(arg);	  
    SD_SPI_ReadWriteByte(crc); 
	if(cmd==CMD12)SD_SPI_ReadWriteByte(0xff);//Skip a stuff byte when stop reading
    //等待响应,或超时退出
	Retry=0X1F;
	do
	{
		r1=SD_SPI_ReadWriteByte(0xFF);
	}while((r1&0X80) && Retry--);	 
	//返回状态值
    return r1;
}		    

2.2.2 获取SD卡的CID信息,包括制造商信息

//获取SD卡的CID信息,包括制造商信息
//输入: u8 *cid_data(存放CID的内存,至少16Byte)	  
//返回值:0:NO_ERR
//		 1:错误														   
u8 SD_GetCID(u8 *cid_data)
{
    u8 r1;	   
    //发CMD10命令,读CID
    r1=SD_SendCmd(CMD10,0,0x01);
    if(r1==0x00)
	{
		r1=SD_RecvData(cid_data,16);//接收16个字节的数据	 
    }
	SD_DisSelect();//取消片选
	if(r1)return 1;
	else return 0;
}							

2.2.3 获取SD卡的CSD信息,包括容量和速度信息

//获取SD卡的CSD信息,包括容量和速度信息
//输入:u8 *cid_data(存放CID的内存,至少16Byte)	    
//返回值:0:NO_ERR
//		 1:错误														   
u8 SD_GetCSD(u8 *csd_data)
{
    u8 r1;	 
    r1=SD_SendCmd(CMD9,0,0x01);//发CMD9命令,读CSD
    if(r1==0)
	{
    	r1=SD_RecvData(csd_data, 16);//接收16个字节的数据 
    }
	SD_DisSelect();//取消片选
	if(r1)return 1;
	else return 0;
}  

2.2.4 获取SD卡的总扇区数(扇区数)

1、 发送CMD9命令,读取CSD信息
2、 连续接收16个字节数据包。(参考5.4小节)
3、 取消片选,完成读取
4、 判断是否是v2.0 SDHC高速卡。
使用读取的第一个字节数据csd[0]&0xC0判断是否等于0x40,如果等于0x40就是v2.0高速卡。
5、 如果是v2.0 SDHC高速卡就按照以下公式计算得到扇区数量

csize=csd[9]+(csd[8]<<8)+1;
Capacity=csize<<10;//得到总扇区数
//获取SD卡的总扇区数(扇区数)   
//返回值:0: 取容量出错 
//       其他:SD卡的容量(扇区数/512字节)
//每扇区的字节数必为512,因为如果不是512,则初始化不能通过.														  
u32 SD_GetSectorCount(void)
{
    u8 csd[16];
    u32 Capacity;  
    u8 n;
	u16 csize;  					    
	//取CSD信息,如果期间出错,返回0
    if(SD_GetCSD(csd)!=0) return 0;	    
    //如果为SDHC卡,按照下面方式计算
    if((csd[0]&0xC0)==0x40)	 //V2.00的卡
    {	
		csize = csd[9] + ((u16)csd[8] << 8) + 1;
		Capacity = (u32)csize << 10;//得到扇区数	 		   
    }else//V1.XX的卡
    {	
		n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
		csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
		Capacity= (u32)csize << (n - 9);//得到扇区数   
    }
    return Capacity;
}

2.2.5 读SD卡

读取一个扇区的步骤:

1、​ 发送CM17命令,设置读取的扇区
2、 接着进行接收SD卡返回的数据包。(参考5.4小节)
每次固定接收512字节,以扇区为单位。
3、 取消片选,完成数据读取
说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。
读取多个扇区的步骤:
1、 发送CMD18命令,设置读取的扇区(连续读多个扇区使用)
2、​ 接着循环接收SD卡返回的数据包。(参考5.4小节)
每次固定接收512字节,以扇区为单位
3、 发送CMD12指令,停止数据传输
4、 取消片选,完成数据读取
说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。

//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
	u8 r1;
	if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
	if(cnt==1)
	{
		r1=SD_SendCmd(CMD17,sector,0X01);//读命令
		if(r1==0)//指令发送成功
		{
			r1=SD_RecvData(buf,512);//接收512个字节	   
		}
	}else
	{
		r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令
		do
		{
			r1=SD_RecvData(buf,512);//接收512个字节	 
			buf+=512;  
		}while(--cnt && r1==0); 	
		SD_SendCmd(CMD12,0,0X01);	//发送停止命令
	}   
	SD_DisSelect();//取消片选
	return r1;//
}

2.2.6 写SD卡

//写SD卡
//buf:数据缓存区
//sector:起始扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
	u8 r1;
	if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址
	if(cnt==1)
	{
		r1=SD_SendCmd(CMD24,sector,0X01);//读命令
		if(r1==0)//指令发送成功
		{
			r1=SD_SendBlock(buf,0xFE);//写512个字节	   
		}
	}else
	{
		if(SD_Type!=SD_TYPE_MMC)
		{
			SD_SendCmd(CMD55,0,0X01);	
			SD_SendCmd(CMD23,cnt,0X01);//发送指令	
		}
 		r1=SD_SendCmd(CMD25,sector,0X01);//连续读命令
		if(r1==0)
		{
			do
			{
				r1=SD_SendBlock(buf,0xFC);//接收512个字节	 
				buf+=512;  
			}while(--cnt && r1==0);
			r1=SD_SendBlock(0,0xFD);//接收512个字节 
		}
	}   
	SD_DisSelect();//取消片选
	return r1;//
}	


上传的SD卡资料如下,感兴趣可以下载学习。
在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值