SD卡初始化以及命令详解

SD卡是嵌入式设备中很常用的一种存储设备,体积小,容量大,通讯简单,电路简单所以受到很多设备厂商的欢迎,主要用来记录设备运行过程中的各种信息,以及程序的各种配置信息,很是方便,有这样几点是需要知道的

SD 卡是基于 flash 的存储卡。

SD 卡和 MMC 卡的区别在于初始化过程不同。SD卡并不是我们通常意义上的手机扩展卡,那种卡叫做TF卡,但是通讯以及驱动模式是类似的.

SD 卡的通信协议包括 SD 和 SPI 两类,SD卡上电之后默认处于SD状态。

SD 卡使用卡内智能控制模块进行 FLASH 操作控制,包括协议、安全算法、

数据存取、ECC 算法、缺陷处理和分析、电源管理、时钟管理。这些都不需要用户关系,这是SD卡厂商做的事情

驱动SD卡主要要实现读扇区,写扇区,初始化,获取SD卡相关配置信息这几个就可以了,

另外.SD卡本身只是一种数据介质,它不含有文件系统,文件系统是一种文件的组织格式,是独立于存储介质的一种规范

SD卡初始化以及命令详解_第1张图片

标准SD卡引脚序列

SD卡引脚功能表

SD卡初始化以及命令详解_第2张图片

TF卡引脚排序

SD卡初始化以及命令详解_第3张图片

TF卡引脚功能表

由此可见,TF卡比SD卡少了一个VSS引脚,也就是少了一个供电引脚

另外电路设计时若SD卡使用SPI模式,那么不用的几根数据线应加上上拉电阻,否者会因为这几根数据线的电流震荡引起电流损耗,造成电路上的不稳定

SD卡电路SPI驱动模式

 SD卡初始化以及命令详解_第4张图片

SD卡内部有五个我们可以读取的寄存器,分别如下

 SD卡初始化以及命令详解_第5张图片

要读取这些信息就需要与卡通讯,SD通讯是用命令+数据的形式进行的,命令格式如下

SD卡初始化以及命令详解_第6张图片

也就是说,一次SD卡命令发送一共要发送6个字节,对于SPI通讯而言,就是SPI总线上传送六个字节

字节 1 的最高 2 位固定为 01,低 6 位为命令号(比如 CMD16,

为 10000 即 16 进制的 0X10,完整的 CMD16,第一个字节为 01010000,即 0X10+0X40)。

字节 2~5 为命令参数,有些命令是没有参数的。对于没有参数的命令默认发送0即可

字节 6 的高七位为 CRC 值,最低位恒定为 1,crc计算遵循以下规律

GX为生成多项式,具体计算方法请查看CRC计算相关,不过有一点好处就是,在SPI驱动模式下,不需要CRC校验(默认SD卡在SPI模式下不开启CRC校验,在SD模式下默认开始CRC校验),所以我们只需要对CMD0进行CRC就可以了,后面的CRC都可以不管(因为在CMD0之前是SD模式,所以第一个命令需要,切换之后就不用了),而CMD0的CRC为0x95(加上了之后的一位1)

注:SPI模式下打开crc校验需要用到CMD59的保留命令,请查阅相关资料

SD卡的命令表如下所示(以下仅写出SPI模式的CMD)

SD卡初始化以及命令详解_第7张图片

CMD0 复位SD卡, 重置所有卡到 Idle状态,参数为0

CMD1 设置SD卡到ACTIVATE模式,也就是推出IDLE模式

SD卡初始化以及命令详解_第8张图片

CMD8 发送接口状态命令

CMD9 读取CSD寄存器

CMD10 读取CID寄存器

CMD12 在多块读取的时候请求停止读取

CMD13读取SD卡状态寄存器

SD卡初始化以及命令详解_第9张图片

CMD16 设置单个扇区的大小一般都设置为512字节一个扇区

CMD17 读取扇区命令

CMD18 读取多个扇区知道发送停止命令

CMD24 写扇区命令

CMD25 写多个扇区命令

SD卡初始化以及命令详解_第10张图片

CMD27 编辑CSD位

CMD28设置地址组保护位。写保护由卡配置数据的WP_GRP_SIZE 指定

CMD29清除保护位

CMD30 要求卡发送写保护状态,参数中有要查询的地址

CMD32 设置要擦除的第一个写数据块地址

CMD33 设置要擦除的最后一个写数据块地址

CMD38 擦除所有选中的块

 

CMD42 设置SD卡的解锁或者上锁

CMD55 告诉SD卡下一个命令式卡应用命令,不是标准命令

SD卡初始化以及命令详解_第11张图片

CMD56 应用相关的数据块读写命令

CMD58 读取OCR信息

CMD59 设置crc校验的使能与关闭(前面说到过)

ACMD13 发送SD卡状态

ACMD18保留作为 SD 安全应用(也就是这命令没用)

ACMD22发送写数据块的数目。响应为 32 位+CRC

ACMD23设置写前预擦除的数据块数目(用来加速多数据块写操作)。“1”=默认(一个块)(1)

不管是否使用 ACMD23,在多数据块写操作中都需要 STOP_TRAN(CMD12)命令

ACMD25 26 38 保留作为安全应用

SD卡初始化以及命令详解_第12张图片

ACMD41要求访问的卡发送它的操作条件寄存器(OCR)内容

ACMD42连接[1]/断开[0]卡上CD/DAT3(pin 1]的 50K 欧姆上拉电阻。上拉电阻可用来检测卡

ACMD43-49保留作为安全应用

ACMD51读取 SD 配置寄存器 SCR

 

ACMD命令,全称应该是application CMD,所以使用ACMD都需要在发送CMD55之后

发出命令后会收到相应的响应, 所有响应通过 CMD 线传输,响应以 MSB 开始,不同类型的响应长度根据类型不同而不同。

响应以起始位开始(通常为“0”),接着这是传输方向的位(卡为 0)。除了 R3 外其他

响应都有 CRC。每个响应都以结束位(通常为“1”)结束。,SD卡响应格式有多种

1.       R1响应

SD卡初始化以及命令详解_第13张图片

2.       R1b响应

多了一个忙数据

3.       R2响应

SD卡初始化以及命令详解_第14张图片

4.       R3响应(针对于read ocr的响应 CMD58)

SD卡初始化以及命令详解_第15张图片

5.       响应R4和R5都是正对于SD mode的响应

6.       针对CMD8命令的响应R7

 SD卡初始化以及命令详解_第16张图片

 

SD卡的初始化以及识别过程(为了方便起见,我们只检测响应的R1状态)

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

2.  上电延时(>74 个 CLK)(为了让卡正常启动)

3.  复位卡(CMD0),进入 IDLE 状态,检测R1的最低位,是否为闲置状态

4.  发送 CMD8,检查是否支持 2.0 协议,因为这个命令是在2.0的协议里面才添加的

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

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

具体请查看下图

SD卡初始化以及命令详解_第17张图片

以下是网络上找到的一份经我修改之后的SD卡驱动,不完全符合SD卡标准驱动,但是我用着一直还蛮正常,大家有兴趣可以看看改改

 

Spisd.c

#include "spisd.h"

//预定义SD卡类型
u8  SD_Type=0;//SD卡的类型 

//这部分应根据具体的连线来修改!
#define	SD_CS  PAout(4) //SD卡片选引脚

//data:要写入的数据
//返回值:读到的数据
static u8 SdSpiReadWriteByte(u8 data)
{
    return Spi1ReadWriteByte(data);
}

//SD卡初始化的时候,需要低速
static void SdSpiSpeedLow(void)
{
    Spi1SetSpeed(SPI_SPEED_256);//设置到低速模式  用于初始化,最高spi速度为400k	
}

//SD卡正常工作的时候,可以高速了
static void SdSpiSpeedHigh(void)
{
    Spi1SetSpeed(SPI_SPEED_4);//设置到高速模式	初始化完成之后进行,最高可到25M,不过一般不用
}


static void SdIOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA, ENABLE );	
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化A4
	
	SD_CS = 1;
	Spi1Init();//初始化SPI接口
	SdSpiSpeedLow();//初始化设置为低速
}

//等待卡准备好
//返回值:0,准备好了;其他,错误代码
static u8 SdWaitReady(void)
{
    u32 t=0;
    do
    {
        if(SdSpiReadWriteByte(0XFF)==0XFF)return 0;//OK
        t++;		  	
    }while(t<0XFFFFFF);//等待 
    return 1;
}

//取消选择,释放SPI总线
void SD_DisSelect(void)
{
    SD_CS=1;
    SdSpiReadWriteByte(0xff);//提供额外的8个时钟
}

//选择sd卡,并且等待卡准备OK
//返回值:0,成功;1,失败;
u8 SdSelect(void)
{
    SD_CS=0;
    if(SdWaitReady()==0)return 0;//等待成功
    SD_DisSelect();
    return 1;//等待失败
}

//等待SD卡回应
//Response:要得到的回应值
//返回值:0,成功得到了该回应值
//    其他,得到回应值失败    期待得到的回应值
u8 SdGetResponse(u8 Response)
{
    u16 Count=0xFFF;//等待次数	   						  
    while ((SdSpiReadWriteByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应  	  
    if (Count==0)return MSD_RESPONSE_FAILURE;//得到回应失败   
    else return MSD_RESPONSE_NO_ERROR;//正确回应
}

//从sd卡读取一个数据包的内容
//buf:数据缓存区
//len:要读取的数据长度.
//返回值:0,成功;其他,失败;
//0XFE数据起始令牌	
u8 SdRecvData(u8*buf,u16 len)
{			  	  
    if(SdGetResponse(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
    while(len--)//开始接收数据
    {
        *buf=SdSpiReadWriteByte(0xFF);
        buf++;
    }
    //下面是2个伪CRC(dummy CRC)
    SdSpiReadWriteByte(0xFF);
    SdSpiReadWriteByte(0xFF);									  					    
    return 0;//读取成功
}

//向sd卡写入一个数据包的内容 512字节
//buf:数据缓存区
//cmd:指令
//返回值:0,成功;其他,失败;	
u8 SdSendBlock(u8*buf,u8 cmd)
{	
    u16 t;		  	  
    if(SdWaitReady())return 1;//等待准备失效
    SdSpiReadWriteByte(cmd);
    if(cmd!=0XFD)//不是结束指令
    {
        for(t=0;t<512;t++)SdSpiReadWriteByte(buf[t]);//提高速度,减少函数传参时间
        SdSpiReadWriteByte(0xFF);//忽略crc
        SdSpiReadWriteByte(0xFF);
        t=SdSpiReadWriteByte(0xFF);//接收响应
        if((t&0x1F)!=0x05)return 2;//响应错误									  					    
    }						 									  					    
    return 0;//写入成功
}


//向SD卡发送一个命令
//输入: u8 cmd   命令 
//      u32 arg  命令参数
//      u8 crc   crc校验值	   
//返回值:SD卡返回的响应															  
u8 SdSendCmd(u8 cmd, u32 arg, u8 crc)
{
    u8 r1;	
    u8 Retry=0; 
    SD_DisSelect();//取消上次片选
    if(SdSelect())return 0XFF;//片选失效 
    //发送
    SdSpiReadWriteByte(cmd | 0x40);//分别写入命令
    SdSpiReadWriteByte(arg >> 24);
    SdSpiReadWriteByte(arg >> 16);
    SdSpiReadWriteByte(arg >> 8);
    SdSpiReadWriteByte(arg);	  
    SdSpiReadWriteByte(crc); 
    if(cmd==CMD12)SdSpiReadWriteByte(0xff);//Skip a stuff byte when stop reading
    //等待响应,或超时退出
    Retry=0X1F;
    do
    {
        r1=SdSpiReadWriteByte(0xFF);
    }while((r1&0X80) && Retry--);	 
    //返回状态值
    return r1;
}	

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

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

//获取SD卡的总扇区数(扇区数)   
//返回值:0: 取容量出错 
//其他:SD卡的容量(扇区数/512字节)
//每扇区的字节数必为512,因为如果不是512,则初始化不能通过.														  
u32 SdGetSectorCount(void)
{
    u8 csd[16];
    u32 Capacity;  
    u8 n;
    u16 csize;  					    
    //取CSD信息,如果期间出错,返回0
    if(SdGetCSD(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;
}


//初始化SD卡
//返回值:0,正常.
//其他,不正常.
u8 SdInitialize(void)
{
    u8 r1;      // 存放SD卡的返回值
    u16 retry;  // 用来进行超时计数
    u8 buf[4];  
    u16 i;
    
    SdIOInit();		//初始化IO 
    for(i=0;i<10;i++)SdSpiReadWriteByte(0XFF);//发送最少74个脉冲  ,这里发送了80个脉冲
    retry=20;
    do
    {
        r1=SdSendCmd(CMD0,0,0x95);//进入IDLE状态
    }while((r1!=0X01) && retry--);
    SD_Type=0;//默认无卡
    if(r1==0X01)
    {
        if(SdSendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
        {
            for(i=0;i<4;i++)buf[i]=SdSpiReadWriteByte(0XFF);	//Get trailing return value of R7 resp
            if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
            {
                retry=0XFFFE;
                do
                {
                    SdSendCmd(CMD55,0,0X01);	//发送CMD55
                    r1=SdSendCmd(CMD41,0x40000000,0X01);//发送CMD41
                }while(r1&&retry--);
                if(retry&&SdSendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
                {
                    for(i=0;i<4;i++)buf[i]=SdSpiReadWriteByte(0XFF);//得到OCR值
                    if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC;    //检查CCS
                    else SD_Type=SD_TYPE_V2;   
                }
            }
        }else//SD V1.x/ MMC	V3
        {
            SdSendCmd(CMD55,0,0X01);		//发送CMD55
            r1=SdSendCmd(CMD41,0,0X01);	//发送CMD41
            if(r1<=1)
            {		
                SD_Type=SD_TYPE_V1;
                retry=0XFFFE;
                do //等待退出IDLE模式
                {
                    SdSendCmd(CMD55,0,0X01);	//发送CMD55
                    r1=SdSendCmd(CMD41,0,0X01);//发送CMD41
                }while(r1&&retry--);
            }else
            {
                SD_Type=SD_TYPE_MMC;//MMC V3
                retry=0XFFFE;
                do //等待退出IDLE模式
                {											    
                    r1=SdSendCmd(CMD1,0,0X01);//发送CMD1
                }while(r1&&retry--);  
            }
            if(retry==0||SdSendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;//错误的卡
        }
    }
    SD_DisSelect();//取消片选
    SdSpiSpeedHigh();//高速
    if(SD_Type)return 0;
    else if(r1)return r1; 	   
    return 0xaa;//其他错误
}



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

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

Spisd.h

#ifndef __SPISD_H_
#define __SPISD_H_

#include "spi.h"
#include "delay.h"
#include "common.h"
#include "ioremap.h"

// SD卡类型定义  
#define SD_TYPE_ERR     0X00
#define SD_TYPE_MMC     0X01
#define SD_TYPE_V1      0X02
#define SD_TYPE_V2      0X04
#define SD_TYPE_V2HC    0X06	

// 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

// SD卡中的响应有许多种,R1为标准响应,最为常用。与R1响应相似的还有R1b、R2和R3。
// R1响应在除SEND_STATUS外其它命令后发送,也是最高位先发送,共1个字节。最高位为0。响应说明如下:
// 0x01:空闲状态
// 0x02:擦除错误
// 0x04:命令错误
// 0x08:CRC通信错误
// 0x10:擦除次序错误
// 0x20:地址错误
// 0x40:参数错误

#define MSD_RESPONSE_NO_ERROR      0x00    //无错误
#define MSD_IN_IDLE_STATE          0x01    //空闲状态
#define MSD_ERASE_RESET            0x02    //擦除错误
#define MSD_ILLEGAL_COMMAND        0x04    //命令错误
#define MSD_COM_CRC_ERROR          0x08    //CRC通信错误
#define MSD_ERASE_SEQUENCE_ERROR   0x10    //擦除次序错误
#define MSD_ADDRESS_ERROR          0x20    //地址错误
#define MSD_PARAMETER_ERROR        0x40    //参数错误
#define MSD_RESPONSE_FAILURE       0xFF    //这次命令根本是失败的,没有任何回应

u8 SdInitialize(void);

u8 SdGetCID(u8 *cid_data);

u8 SdGetCSD(u8 *csd_data);

u32 SdGetSectorCount(void);

u8 SdReadDisk(u8*buf,u32 sector,u8 cnt);

u8 SdWriteDisk(u8*buf,u32 sector,u8 cnt);


#endif

https://www.cnblogs.com/dengxiaojun/p/4279439.html

你可能感兴趣的:(SD卡)