b. SPI模式下对SD卡的操作

参考文献: SD Specifications Part 1 Physical Layer Simplified Specification Version 3.01 (见下载附录)

STM32的SPI设备简介:

STM32F107VC有3个SPI设备,SPI控制器在输出数据的同时采样输入数据,使用相同时钟线。

Master设备写操作的同时,读入寄存器同时采样填充,每次也需要清空寄存器。

Master设备的读操作,实际上是通过写数据输出时钟序列,采样MISO的信号。

SD卡简介:

SD卡的技术规范经过几次升级,与最初版本已有很大不同,本文基于Ver 3.01讨论

从容量上分

SD卡支持SPI的Mode0和Mode3

SD卡支持50MHz总线,STM32的APB2总线最高72MHz,SPI分频½为36Mhz,理论上所有SD卡都可以正常操作,实际上一些低版本的卡缺乏稳定性

插入信号CD:

CD线是可选的信号线,没有卡时为高电位,有卡插入时CD为低电位

CD Waiting

while ((*gpiob_idr)&0x4000) {}

电位稳定延迟:

CS线为高的状态下输出若干时钟,延迟利于电位稳定

Init Stable

sd_cs(1);

//sending dummy data few times

for (ii=0; ii<0xF; ii++) {

sd_bytedata(0xFF);

}

SD卡的准备状态,初始化操作:

SD卡从上电到可读写状态需要一定序列命令的操作,这个过程包括选择SPI模式和判断卡的版本以及供电操作

SD规范中的流程图

CMD0

SD卡上电后使用CMD0进入SPI模式,CMD0的返回值是1字节的R1,R1应该为空闲0x01

CMD0

sd_cs(0);

sd_command(cmd, cc);

res=sd_response();

sd_cs(1);

while ((rtn!=0x01)

CMD8

版本2.0以上的SD卡支持CMD8命令,包括大部分SDHC的卡和所有SDXC卡,早期的SDHC卡有可能仍属于V1.0卡

命令返回值R7,第一字节为R1,Ver1.0的卡对R1的“非法命令”位2置位,Ver2.0以上卡应返回0x01

CMD8

rtn = sd_cmd8_version_check(&sdif);

if ((rtn&0x04)==0x04) {

rtn=sd_v1_process(&sdif);

} else {

rtn=sd_v2_process(&sdif);

}

ACMD41和CMD1

ACMD41是为卡供电命令,供电前卡的状态为空闲(idle),R1的返回值为0x01,供电后为动作状态(ready)

一些早期的卡认为ACMD41是非法命令,只能用MMC的CMD1命令供电

ACMD41 Ready

if (rtn == 0x01) {

while (rtn==0x01) {

rtn = sd_send_cond_acmd41_v1();

}

}

if ((rtn&0x04)==0x04) { // invalid command

rtn = 0x01;

while (rtn==0x01) {

rtn = sd_cmd1_mmc_ready();

}

}

CMD58

CMD58读取卡的状态,一个重要的标志位CCS,会影响到读写操作中地址数据的设定

CCS为1时为高版本卡,数据地址为页单位,512字节为一页

CCS为0时,地址为以字节为单位实际地址

CCS置位与否也取决于ACMD41中对HCS:30bit的置位请求

CMD58 CCS

rtn = sd_read_ocr_cmd58(sd_inf->rx.r3);

sd_inf->ccs = (sd_inf->rx.r3[0]>>6)&0x01;

CMD9

取得卡容量等信息,CSO寄存器,CSO是16个字节的结构体,加上2字节的CRC,应读取18字节的内容

Read CSO

sd_cs(0);

sd_command(cmd, cc);

rtn = sd_response();

if (rtn == 0x00) {

if(sd_data_token()==0xFE) {

sd_data(cso, 20);

} else {

rtn = 0xFF;

}

}

sd_cs(1);

卡的信息也有版本区别,需要分别处理

CSO Version

sd_inf->csd_version = (cso[0]>>6)&0x3;

if (sd_inf->csd_version==0x0) {

sd_inf->block = cso[5]&0xF;

sd_inf->unit = cso[6]&0x3;

sd_inf->unit <<= 8;

sd_inf->unit |= cso[7];

sd_inf->unit <<= 2;

sd_inf->unit |= (cso[8]&0xC0)>>6;

sd_inf->multi = (cso[9]&0x3);

sd_inf->multi <<= 1;

sd_inf->multi |= (cso[10]&0x80)>>7;

sd_inf->page = sd_inf->unit + 1;

sd_inf->page <<= (sd_inf->multi + 2);

sd_inf->size = sd_inf->page << sd_inf->block;

} else {

sd_inf->block = cso[5]&0xF;

sd_inf->unit = cso[7]&0x3F;

sd_inf->unit <<= 8;

sd_inf->unit |= cso[8];

sd_inf->unit <<= 8;

sd_inf->unit |= cso[9];

sd_inf->page = sd_inf->unit * 1024;

sd_inf->size = sd_inf->page << sd_inf->block;

}

CMD24 CMD17

CMD24写数据

CMD17读数据

SD的读写都要以页单位进行,无论是否是新版本。

老版本的地址设定也要从页的起始处,一般为512的倍数

数据块紧接在命令块的后面。

数据块长度515个字节,起始标志字节为0xFE,实际数据512字节,2字节CRC

CRC使用CRC-16算法,不计算时可以设置为0xFF

写操作之后要等待非忙信号,忙信号使MISO为低电位,接收数据一直为0

CMD17 Read Block

sd_cs(0);

sd_command(cmd, cc);

rtn = sd_response();

if (rtn==0x00) {

rtn = sd_data_token();

if(rtn==0xFE) {

for (cc=0; cc<512; cc++) {

data[cc] = 0xAA;

data[cc] = sd_bytedata(0xFF);

}

sd_bytedata(0xFF);

sd_bytedata(0xFF);

}

rtn=0;

}

sd_cs(1);

CMD24 Write Block

sd_cs(0);

sd_command(cmd, cc);

rtn = sd_response();

if (rtn==0x00) {

sd_bytedata(0xFF);

sd_bytedata(0xFE);

for (cc=0; cc<512; cc++) {

sd_bytedata(data[cc]);

}

sd_bytedata(0xFF);

sd_bytedata(0xFF);

rtn = sd_response();

cc = 0;

while (rtn == 0x00) { //R1b busy

rtn = sd_bytedata(0xFF);

cc++;

}

}

sd_cs(1);

ACMD命令的发送实际上是两个命令,CMD55之后,片选信号需要重新复位后在发送

SD规范中没有找到相关描述,市面上一些卡也支持不重新复位

经过反复测试,以上方法适用于绝大多数情况