SD卡(Secure
Digital Memory Card)中文翻译为安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。大小犹如一张邮票的SD记忆卡,重量只有2克,但却拥有高记忆容量、快速数据传输率、极大的移动灵活性以及很好的安全性。按容量分类,可以将SD卡分为3类:SD卡、SDHC卡、SDXC卡。如表44.1.1所示:
容量
|
命名
|
简称
|
0~2G
|
Standard Capacity SD Memory
Card
|
SDSC或SD
|
2G~32G
|
High Capacity SD Memory
Card
|
SDHC
|
32G~2T
|
Extended Capacity SD Memory
Card
|
SDXC
|
表44.1.1
SD卡按容量分类
SD卡和SDHC卡协议基本兼容,但是SDXC卡,同这两者区别就比较大了,本章我们讨论的主要是SD/SDHC卡(简称SD卡)。
SD卡一般支持2种操作模式:
1,SD卡模式(通过SDIO通信);
2,SPI模式;
主机可以选择以上任意一种模式同SD卡通信,SD卡模式允许4线的高速数据传输。SPI模式允许简单的通过SPI接口来和SD卡通信,这种模式同SD卡模式相比就是丧失了速度。
SD卡的引脚排序如下图44.1.1所示:
图44.1.1 SD卡引脚排序图
SD卡引脚功能描述如表45.1.2所示:
表45.1.2 SD卡引脚功能表
SD卡只能使用3.3V的IO电平,所以,MCU一定要能够支持3.3V的IO端口输出。注意:在SPI模式下,CS/MOSI/MISO/CLK都需要加10~100K左右的上拉电阻。
SD卡有5个寄存器,如表45.1.3所示:
名称
|
宽度
|
描述
|
CID
|
128
|
卡标识寄存器
|
RCA
|
16
|
相对卡地址(Relative card address)寄存器:本地系统中卡的地址,动态变化,在主机初始化的时候确定
*SPI模式中没有
|
CSD
|
128
|
卡描述数据:卡操作条件相关的信息数据
|
SCR
|
64
|
SD配置寄存器:SD卡特定信息数据
|
OCR
|
32
|
操作条件寄存器
|
表45.1.3 SD卡相关寄存器
关于这些寄存器的详细描述,请参考光盘相关SD卡资料。我们在这里就不描述了。接下来,我们看看SD卡的命令格式,如表45.1.4所示:
字节1
|
字节2--5
|
字节6
|
7
|
6
|
5 0
|
31 0
|
7 1
|
0
|
0
|
1
|
command
|
命令参数
|
CRC
|
1
|
表45.1.4 SD卡命令格式
SD卡的指令由6个字节组成,字节1的最高2位固定为01,低6位为命令号(比如CMD16,为10000即16进制的0X10,完整的CMD16,第一个字节为01010000,即0X10+0X40)。
字节2~5为命令参数,有些命令是没有参数的。
字节6的高七位为CRC值,最低位恒定为1。
SD卡的命令总共有12类,分为Class0~Class11,本章,我们仅介绍几个比较重要的命令,如表45.1.5所示:
命令
|
参数
|
回应
|
描述
|
CMD0(0X00)
|
NONE
|
R1
|
复位SD卡
|
CMD8(0X08)
|
VHS+Check pattern
|
R7
|
发送接口状态命令
|
CMD9(0X09)
|
NONE
|
R1
|
读取卡特定数据寄存器
|
CMD10(0X0A)
|
NONE
|
R1
|
读取卡标志数据寄存器
|
CMD16(0X10)
|
块大小
|
R1
|
设置块大小(字节数)
|
CMD17(0X11)
|
地址
|
R1
|
读取一个块的数据
|
CMD24(0X18)
|
地址
|
R1
|
写入一个块的数据
|
CMD41(0X29)
|
NONE
|
R3
|
发送给主机容量支持信息和激活
卡初始化过程
|
CMD55(0X37)
|
NONE
|
R1
|
告诉SD卡,下一个是特定应用命令
|
CMD58(0X3A)
|
NONE
|
R3
|
读取OCR寄存器
|
表45.1.5 SD卡部分命令
上表中,大部分的命令是初始化的时候用的。表中的R1、R3和R7等是SD卡的回应,SD卡和单片机的通信采用发送应答机制,如图45.1.2所示:
图45.1.2 SD卡命令传输过程
每发送一个命令,SD卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据。SPI模式下,SD卡针对不同的命令,应答可以使R1~R7,R1的应答,各位描述如表45.1.6所示:
R1响应格式
|
位
|
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
含义
|
开始位
始终为0
|
参数
错误
|
地址
错误
|
擦除序列
错误
|
CRC错误
|
非法
命令
|
擦除
复位
|
闲置
状态
|
表45.1.6 R1响应各位描述
R2~R7的响应,我们就不介绍了,请的大家参考SD卡2.0协议。接下来,我们看看SD卡初始化过程。因为我们使用的是SPI模式,所以先得让SD卡进入SPI模式。方法如下:在SD卡收到复位命令(CMD0)时,CS为有效电平(低电平)则SPI模式被启用。不过在发送CMD0之前,要发送>74个时钟,这是因为SD卡内部有个供电电压上升时间,大概为64个CLK,剩下的10个CLK用于SD卡同步,之后才能开始CMD0的操作,在卡初始化的时候,CLK时钟最大不能超过400Khz!。
接着我们看看SD卡的初始化,SD卡的典型初始化过程如下:
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),在完成了初始化之后,就可以开始读写数据了。
SD卡读取数据,这里通过CMD17来实现,具体过程如下:
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌0XFE;
4、接收数据;
5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个CLK;
以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下:
1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;
以上就是一个典型的写SD卡过程。关于SD卡的介绍,我们就介绍到这里,更详细的介绍请参考光盘SD卡的参考资料(SD卡2.0协议)。
本章实验功能简介:开机的时候先初始化SD卡,如果SD卡初始化完成,则提示LCD初始化成功。按下KEY0,读取SD卡扇区0的数据,然后通过串口发送到电脑。如果没初始化通过,则在LCD上提示初始化失败。 同样用DS0来指示程序正在运行。
本实验用到的硬件资源有:
1) 指示灯DS0
2) KEY0按键
3) 串口
4) TFTLCD模块
5) SD卡
前面四部分,在之前的实例已经介绍过了,这里我们介绍一下战舰STM32开发板板载的SD卡接口和STM32的连接关系,如图44.2.1所示:
图44.2.1 SD卡接口与STM32连接原理图
我们用跳线帽将P10的SD_DT3、SD_CMD、SD_SCK、SD_DT0分别同P12的SD_CS、SPI2_MOSI、SPI2_SCK、SPI2_MISO连接起来,即实现SD卡的SPI模式连接。硬件连接示意图如图44.2.2所示:
图44.2.2 SD卡SPI方式硬件连接示意图
将图中所示的4处,用跳线帽短接,接口实现SD卡与STM32的SPI连接。最后,你还得自备一个SD卡,将其插入板子下面的SD卡接口。
打开上一章的工程,首先在HARDWARE文件夹下新建一个SD的文件夹。然后新建一个MMC_SD.C和MMC_SD.H的文件保存在SD文件夹下,并将这个文件夹加入头文件包含路径。
打开MMC_SD.C文件,在该文件里面,我们输入与SD卡相关的操作代码,这里由于篇幅限制,我们不贴出所有代码,仅介绍两个最重要的函数,第一个是SD_Initialize函数,该函数源码如下:
//初始化SD卡
u8 SD_Initialize(void)
{
u8 r1; // 存放SD卡的返回值
u16 retry; // 用来进行超时计数
u8 buf[4];
u16 i;
SD_SPI_Init(); //初始化IO
SD_SPI_SpeedLow(); //设置到低速模式
for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XFF);//发送最少74个脉冲
retry=20;
do
{
r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态
}while((r1!=0X01) &&
retry--);
SD_Type=0;//默认无卡
if(r1==0X01)
{
if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{
for(i=0;i<4;i++)buf=SD_SPI_ReadWriteByte(0XFF);//得到R7相应值
if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
{
retry=0XFFFE;
do
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41
}while(r1&&retry--);
if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
{
for(i=0;i<4;i++)buf=SD_SPI_ReadWriteByte(0XFF);//得到OCR值
if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC; //检查CCS
else
SD_Type=SD_TYPE_V2;
}
}
}else//SD V1.x/ MMC V3
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0,0X01); //发送CMD41
if(r1<=1)
{
SD_Type=SD_TYPE_V1;
retry=0XFFFE;
do //等待退出IDLE模式
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41
}while(r1&&retry--);
}else//MMC卡不支持CMD55+CMD41识别
{
SD_Type=SD_TYPE_MMC;//MMC V3
retry=0XFFFE;
do //等待退出IDLE模式
{
r1=SD_SendCmd(CMD1,0,0X01);//发送CMD1
}while(r1&&retry--);
}
if(retry==0||SD_SendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;
//错误的卡
}
}
SD_DisSelect();//取消片选
SD_SPI_SpeedHigh();//高速
if(SD_Type)return 0;
else if(r1)return r1;
return 0xaa;//其他错误
}
该函数先设置与SD相关的IO口及SPI初始化,然后发送CMD0,进入IDLE状态,并设置SD卡为SPI模式通信,然后判断SD卡类型,完成SD卡的初始化,注意该函数调用的SD_SPI_Init等函数,实际是对SPI2的相关函数进行了一层封装,方便移植。另外一个要介绍的函数是 SD_ReadDisk,该函数用于从SD卡读取一个扇区的数据(这里一般为512字节),该函数代码如下:
//读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;//
}
此函数先发送CMD17命令,然后读取一个扇区的数据,详细见代码,这里我们就不多介绍了。保存MMC_SD.C文件,并加入到HARDWARE组下,然后打开MMC_SD.H,在该文件里面输入如下代码:
#ifndef _MMC_SD_H_
#define _MMC_SD_H_
#include "sys.h"
#include
<stm32f10x_map.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
//数据写入回应字意义
#define MSD_DATA_OK
0x05
#define MSD_DATA_CRC_ERROR
0x0B
#define MSD_DATA_WRITE_ERROR
0x0D
#define MSD_DATA_OTHER_ERROR
0xFF
//SD卡回应标记字
#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
#define MSD_ERASE_SEQUENCE_ERROR 0x10
#define MSD_ADDRESS_ERROR
0x20
#define MSD_PARAMETER_ERROR 0x40
#define MSD_RESPONSE_FAILURE 0xFF
//这部分应根据具体的连线来修改!
//战舰STM32开发板使用的是PD2作为SD卡的CS脚.
#define SD_CS PDout(2) //SD卡片选引脚 extern u8
SD_Type;//SD卡的类型
//函数申明区
u8 SD_SPI_ReadWriteByte(u8 data);
void SD_SPI_SpeedLow(void);
void SD_SPI_SpeedHigh(void);
u8
SD_WaitReady(void); //等待SD卡准备
u8 SD_GetResponse(u8
Response); //获得相应
u8
SD_Initialize(void); //初始化
u8 SD_ReadDisk(u8*buf,u32 sector,u8
cnt); //读块
u8 SD_WriteDisk(u8*buf,u32 sector,u8
cnt); //写块
u32 SD_GetSectorCount(void);
//读扇区数
u8 SD_GetCID(u8 *cid_data);
//读SD卡CID
u8 SD_GetCSD(u8 *csd_data);
//读SD卡CSD
#endif
该部分代码主要是一些命令的宏定义以及函数声明,在这里我们设定了SD卡的CS管脚为PD2。保存MMC_SD.H,就可以在主函数里面编写我们的应用代码了,打开test.c文件,在该文件中修改main函数如下:
int main(void)
{
u8 key; u8 t=0; u8 *buf;
u32 sd_size;
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600);
//串口初始化为9600
delay_init(72);
//延时初始化
LED_Init();
//初始化与LED连接的硬件接口
LCD_Init();
//初始化LCD
usmart_dev.init(72);
//初始化USMART
KEY_Init();
//按键初始化
FSMC_SRAM_Init(); //初始化外部SRAM
mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip
STM32");
LCD_ShowString(60,70,200,16,16,"SD CARD
TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2012/9/17");
LCD_ShowString(60,130,200,16,16,"KEY0:Read
Sector 0");
while(SD_Initialize())//检测不到SD卡
{
LCD_ShowString(60,150,200,16,16,"SD
Card Error!");
delay_ms(500);
LCD_ShowString(60,150,200,16,16,"Please Check! ");
delay_ms(500);
LED0=!LED0;//DS0闪烁
}
POINT_COLOR=BLUE;//设置字体为蓝色
//检测SD卡成功
LCD_ShowString(60,150,200,16,16,"SD Card
OK ");
LCD_ShowString(60,170,200,16,16,"SD Card
Size: MB");
sd_size=SD_GetSectorCount();//得到扇区数
LCD_ShowNum(164,170,sd_size>>11,5,16);//显示SD卡容量(MB)
while(1)
{
key=KEY_Scan(0);
if(key==KEY_RIGHT)//KEY0按下了
{
buf=mymalloc(0,512);
//在内部内存池,申请512字节内存
if(SD_ReadDisk(buf,0,1)==0) //读取0扇区的内容
{
LCD_ShowString(60,190,200,16,16,"USART1 Sending Data...");
printf("SECTOR 0
DATA:\r\n");
for(sd_size=0;sd_size<512;sd_size++)printf("%x ",buf[sd_size]);
//打印0扇区数据
printf("\r\nDATA
ENDED\r\n");
LCD_ShowString(60,190,200,16,16,"USART1 Send Data Over!");
}
myfree(0,buf);
//释放内存
}
t++;
delay_ms(10);
if(t==20)
{
LED0=!LED0;
t=0;
}
}
}
这里我们通过SD_GetSectorCount函数来得到SD卡的扇区数,间接得到SD卡容量,然后在液晶上显示出来,接着我们通过按键KEY0控制读取SD卡的扇区0,然后把读到的数据通过串口打印出来。这里,我们对上一章学过的内存管理小试牛刀,稍微用了下,以后我们会尽量使用内存管理来设计。
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,可以看到LCD显示如图44.4.1所示的内容(默认SD卡已经接上了):
图44.4.1 程序运行效果图
打开串口调试助手,按下KEY0就可以看到从开发板发回来的数据了,如图44.4.2所示:
图44.4.2 串口收到的SD卡扇区0内容
这里请大家注意,不同的SD卡,读出来的扇区0是不尽相同的,所以不要因为你读出来的数据和图44.4.2不同而感到惊讶。