这里写目录标题
一、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卡通信协议
以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 发送命令
- 初始化与 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卡资料如下,感兴趣可以下载学习。