STM32开发_利用SPI协议读写SD卡介绍SD卡SPI时序

Posted DS小龙哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32开发_利用SPI协议读写SD卡介绍SD卡SPI时序相关的知识,希望对你有一定的参考价值。

一、​  SD卡引脚接口功能介绍

1.1 SD卡引脚

目录

一、​  SD卡引脚接口功能介绍

1.1 SD卡引脚接口图

1.2 SPI方式驱动SD卡介绍

1.3 开发板接口定义

二、MMC卡、SD卡介绍

2.1 SD卡和MMC两者间区别

2.2 SD卡版本说明

2.3 SD卡常用的指令表

三、向SD卡发送命令的步骤介绍(SendSDCardCmd)

3.1 取消选中SD卡(SDCardCancelCS)

3.2 选中SD卡(SDCardSelectCS)

3.3 向SD卡发送操作命令cmd

3.4 向SD卡发送命令参数

3.5 发送CRC校验

3.6 等待SD卡响应

四、SD卡的寄存器与操作命令介绍

4.1 SDCard_CMD0:卡复位命令

4.2 SDCard_CMD8:检测是否是2.0版本的SD卡

4.3 SDCard_CMD9: 获取SD卡的CSD信息

4.4 SDCard_CMD17: 设置单个读取的扇区

4.5 SDCard_CMD18: 设置读扇区(连续读扇区使用)

4.6 SDCard_CMD12: 停止数据传输

4.7 SDCard_CMD24: 设置写单个扇区

4.8 SDCard_CMD55

4.9 SDCard_CMD23: 多扇区写入前预先擦除块数量

4.10 SDCard_CMD25: 设置写多个扇区

4.11 SDCard_CMD41

4.12 SDCard_CMD58

五、SD卡SPI接口命令

5.1 SPI接口时序

5.2 SPI模式下: SD卡初始化步骤(SDCardDeviceInit)

5.3 SPI模式下: 向SD卡发送数据包步骤(SDCardSendData)

5.4 SPI模式下: 从SD卡读取数据包步骤(SDCardRecvData)

5.5 SPI模式下: 向SD卡指定扇区写数据(SDCardWriteData)

5.6 SPI模式下: 从SD卡读取指定扇区数据(SDCardReadData)

5.7 SPI模式下: 获取SD卡的总扇区数(GetSDCardSectorCount)

六、示例代码

6.1 sdcard.c文件

6.2 sdcard.h文件


接口图

图1-1 SD卡引脚图

图1-2 SD卡引脚说明

SD卡支持两种总线方式:SD方式与SPI方式。其中SD方式采用6线制,使用CLK、CMD、DAT0~DAT3进行数据通信。而SPI方式采用4线制,使用CS、CLK、DataIn、DataOut进行数据通信。

SD方式时的数据传输速度与SPI方式要快,采用单片机对SD卡进行读写时一般都采用SPI模式。采用不同的初始化方式可以使SD卡工作于SD方式或SPI方式。

1.2 SPI方式驱动SD卡介绍

SD卡的SPI通信接口使其可以通过SPI通道进行数据读写。

从应用的角度来看,采用SPI接口的好处在于,很多单片机内部自带SPI控制器,不光给开发上带来方便,同时也见降低了开发成本。然而,它也有不好的地方,如失去了SD卡的性能优势,要解决这一问题,就要用SD方式,因为它提供更大的总线数据带宽。SPI接口的选用是在上电初始时向其写入第一个命令时进行的。以下介绍SD卡的驱动方法,只实现简单的扇区读写。

1.3 开发板接口定义

根据当前所用开发板原理图为例,SD卡卡槽的接口与STM32 IO口对应如下:

PC11 片选 SDCardCS

PC12 时钟 SDCardSCLK

PD2 输出 SPI_MOSI--主机输出从机输入

PC8 输入 SPI_MISO--主机输入从机输出




SD卡与开发板的SPI方式接线关系如下:

DATA0---PC8-----OUT---MISO---主机输入从机输出

DATA1---PC9

DATA2---PC10

DATA3---PC11----CS

CLK-----PC12-------SCLK

CMD-----PD2------INPUT--MOSI--主机输出从机输入

二、MMC卡、SD卡介绍

2.1 SD卡和MMC两者间区别

SD卡和MMC(多媒体卡)似乎可以使用同一个插槽,两者间有什么区别呢?

不过,虽说外型几乎一致,但还是有点差异的。MMC比SD卡要薄一些。

图2-1 MMC卡与SD卡

首先得从MMC卡的发展谈起。 MMC卡是由西门子设计,和SanDisk合作开发的小型存储卡标准。 在1997年,作为使用闪存的存储卡(I / O卡或ROM卡都可以)开始发售,日立和NEC,摩托罗拉,诺基亚等共同建立了MMCA(多媒体卡协会)。 并促进了标准​​化和市场推广。

SanDisk公司,也是在94年提出小型闪存卡(以下简称CF)的厂商,但是CF在用于紧凑型概念的产品时,采用了和广泛使用的PC卡的ATA兼容的接口。 这种设计消除了不需要的信号线,管脚数也由68针减少到50针,电气方面可以相互兼容,并且被设计为仅仅通过简单的适配器就可以安装在PC卡插槽中。 然而,CF虽然了PC卡容易替换的好处,但由于管脚的数量巨大,宽度达到43毫米,这样就不太能减下去。 这个在涉及到​​移动电话时,你将无法容纳CF。 因此,在新的设计中,硬件兼容性被舍弃,只致力于小型化的MMC。

CF卡和PC卡的接口,多个并行传输的地址信号和数据信号,各种控制线紧密被布置一起。规格上就是PC的扩展总线这样的接口,但在MMC中,据传输方式变更为串行传输,地址的指定和各种控制也用的是通过一个串行接口交换数据包的方式。 其结果是,主要的信号变成三个:数据,命令,时钟,接口可以降低到仅7针。 接口也从两排的针/socket这种面接触类型变换成小型的薄卡片。体积比的话,CF是PC卡(基于Ⅰ型)的三分之一,而MMC的话则是十四分之一,这么看来MMC已经变得非常紧凑。

以MMC为基础实现了安全(安全性)功能的是东芝,松下,SanDisk三家公司共同研发的SD卡。 该标准本身不是MMC卡的扩展,而是另一种标准,虽然该标准成立了另一个叫SDA(SD卡协会)组织,但它的一大特色是被设计成能够和MMC卡共享插槽 。SD卡的表面积和MMC卡是相同大小的,但是厚度比1.4毫米的MMC增大了0.7毫米,变成2.1毫米。 然而,SD卡的左右部分和MMC卡的厚度一样的,为1.4毫米,所以MMC卡可以直接插入SD卡插槽。(相反,SD卡不能插入MMC卡插槽) 接口的规格也是在MMC卡的管脚排列基础上添加的两条信号线到两侧,传输方法因为和MMC相兼容,也可以从SD卡host访问到MMC。 记录数据的逻辑规范,因为它们用的是相同的FAT文件系统,只要是它被用作简单的记录媒体那就是兼容的。

然而,实际上SD卡主机端的应用程序能否使用的MMC上的数据,因为是涉及到安全和文件格式的问题,所以是由应用程序决定。 特别是用到安全性的情况下,基本上没有兼容性。 SD卡的版权保护机制用到的松下和东芝倡导的是CPRM(内容保护可记录媒体)。 此外,作为MMC卡的安全版本,MMCA发布了安全MMC的版本,它是与MMC完全兼容的更高的标准,但是这里用到是的日立倡导的UDAC MB(Universal Distribution with Access Control-Media Base)的版权保护机制,所以与SD卡不兼容。 此外,现在还没有支持UDAC-MB和CPRM的商品。

此外,SD里添加的两条信号线都是用于数据的信号线。MMC中只有一个数据信号通道,但在SD中MMC中的7号管脚(数据信号)和一号管脚(在MMC中未使用),加上新加的8,9号管脚一共4个通道可以使用,这样就能达到更高的传输速度。 MMC的传输时钟最大是20MHz(时钟可变),所以传输速度最大为20Mbps(2.5MB/s)。 虽说这是和闪存读出速度相当的速度,做为存储卡的规格来说是够了,但是用到I/O卡的情况下,它可能是不够的。 而用到所有四个管脚的SD卡,目前可达到80Mbps(10MB / s)速度。

2.2 SD卡版本说明

SD卡版本:SD V1.X(即SD标准卡)最大容量2GB

SD V2.0 2.0版本的标准卡,最多2GB

SD V2.0HC 2.0高容量卡,最多32GB

说明: 本程序主要针对SD卡2.0 HC 2.0高容量卡协议进行说明。

SD卡默认操作的扇区大小是512字节。扇区大小,可以通过指令设置。就算不是512,也可以通过指令设置成512,因为这个值不太大,占用内存不太多,适合单片机使用。

2.3 SD卡常用的指令表

#define SDCard_CMD0 0 //卡复位

#define SDCard_CMD8 8 //命令8 ,SEND_IF_COND

#define SDCard_CMD9 9 //命令9 ,读CSD数据

#define SDCard_CMD12 12 //命令12,停止数据传输

#define SDCard_CMD13 16 //命令16,设置扇区大小 应返回0x00

#define SDCard_CMD17 17 //命令17,读扇区

#define SDCard_CMD18 18 //命令18,读多个扇区

#define SDCard_CMD23 23 //命令23,设置多扇区写入前预先擦除block

#define SDCard_CMD24 24 //命令24,写扇区

#define SDCard_CMD25 25 //命令25,写多个扇区

#define SDCard_CMD41 41 //命令41,应返回0x00

#define SDCard_CMD55 55 //命令55,应返回0x01

#define SDCard_CMD58 58 //命令58,读OCR信息

三、向SD卡发送命令的步骤介绍(SendSDCardCmd)

3.1 取消选中SD卡(SDCardCancelCS)

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

图3-1 时序图

3.2 选中SD卡(SDCardSelectCS)

(1).选中片选 (2).等待SD卡忙状态

说明: 向SD卡发送0xFF,如果SD卡也返回0xFF就表示SD卡已经准备好,如果返回不是0xFF就表示SD卡还没有准备好,需要等待。

图3-2 时序图

3.3 向SD卡发送操作命令cmd

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

命令是8位数据。

图3-3 时序图

3.4 向SD卡发送命令参数

命令参数是32位数据,SPI每次发送8位,需要发送4次,先发送最高8位,依次再发送低位。

图3-4 时序图

3.5 发送CRC校验

CRC是8位数据。

注意: 如果发送的是CMD12命令(停止数据传输),在发送CRC校验之后,需要再发送一个0xFF数据。

图3-5-1 时序图

图3-5-2 时序图

3.6 等待SD卡响应

向SD卡发送0xFF数据,如果SD卡返回的数据最高位为0,就是表示SD卡响应完成,否则就继续发送0xFF,再判断,直到SD卡响应成功。

SD卡响应之后,完成一次命令发送,并将返回的数据当做返回值返回去。

推荐: 使用do…while()循环结构,更加方便判断。

图3-6 时序图

四、SD卡的寄存器与操作命令介绍

4.1 SDCard_CMD0:卡复位命令

图 4-1 时序图

4.2 SDCard_CMD8:检测是否是2.0版本的SD卡

发送只有V2.0版的SD卡才具有的命令CMD8,然后检测返回值: 返回值若是0x01,则表示此卡为V2.0卡,然后再发送循环命令CMD55+CMD41,直到返回0x00,确定SD2.0卡初始化成功;

然后再发送CMD58命令,接收返回的OCR寄存器的数据,其中第31位用于判断V2.0的卡是否为SDHC类型。 

若返回值不为0x01,则进一步判断是V1.0卡还是MMC卡:先发送循环命令CMD55+CMD41进行复位,如果复位不成功则考虑是MMC卡,如果复位成功,则为V1.0卡。在复位不成功的情况下,再使用CMD1进行复位,如果复位成功,则表明是MMC卡,如果复位不成功,则表示是无法识别的卡。

图4-2-1 时序图

图4-2-2 时序图

图4-2-3 时序图

图4-2-4 时序图

鉴别到v2.0版本之后,可以读取OCR 寄存器的值,继续判断是否是V2.0高速卡。

SD卡响应命令成功,可以继续接收4字节的OCR寄存器值;

OCR寄存器的第30位(CCS)指示了卡的类型是否为SDHC,此位为1则为SDHC,为0则为SDSC。

OCR 寄存器,储存了卡的 VDD 电压轮廓图。任何标准的 SD 卡主控制器可以使用 2V 至 3.6V 的工作电压来让 SD 卡能执行这个电压识别操作(CMD1)。而访问存储器的阵列操作无论如何都需要 2.7V 至 3.6V 的工作电压。OCR 寄存器显示了在访问卡的数据时所需要的电压范围。

OCR 寄存器的结构描述:

图4-2-5 时序图

图4-2-6 时序图

4.3 SDCard_CMD9: 获取SD卡的CSD信息

CSD包括容量和速度信息,存放CID的内存,至少16Byte

CMD9的命令:

图4-3

4.4 SDCard_CMD17: 设置单个读取的扇区

图4-4

4.5 SDCard_CMD18: 设置读扇区(连续读扇区使用)

图4-5

4.6 SDCard_CMD12: 停止数据传输

图4-6

4.7 SDCard_CMD24: 设置写单个扇区

图4-7

4.8 SDCard_CMD55

图4-8

4.9 SDCard_CMD23: 多扇区写入前预先擦除块数量

图4-9

4.10 SDCard_CMD25: 设置写多个扇区

图4-10

4.11 SDCard_CMD41

图4-11

4.12 SDCard_CMD58

图4-12

五、SD卡SPI接口命令

5.1 SPI接口时序

图5-1 SPI时序图

图5-2 SPI时序图

数据先发/先收高位

从图上得知(SD卡在SPI模式下): 下降沿写数据、时钟的上升沿读数据。

示例:

u8 SDCardReadWriteOneByte(u8 DataTx)
		 
    u8 i;
    u8 data=0;
    for(i=0;i<8;i++)
    
        SDCARD_SCK=0;
        if(DataTx&0x80)SDCARD_MOSI=1;
        else SDCARD_MOSI=0;
        SDCARD_SCK=1;
        DataTx<<=1;
        
        data<<=1;
        if(SDCARD_MISO)data|=0x01;
    
    return data;

5.2 SPI模式下: SD卡初始化步骤(SDCardDeviceInit)

SD卡的初始化是非常重要的,只有进行了正确的初始化,才能进行后面的各项操作。在初始化过程中,SPI的时钟不能太快,否则会造初始化失败。在初始化成功后,应尽量提高SPI的速率。在刚开始要先发送至少74个时钟信号,这是必须的。如果接到复位命令(CMD0)时,CS信号有效(低电平),SPI模式启用。

详细步骤:

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个时钟信号,结束本次操作。

5.3 SPI模式下: 向SD卡发送数据包步骤(SDCardSendData)

每次发送数据包默认为512字节。

1. 等待SD卡忙状态

向SD卡发送一个0xFF数据,如果SD卡也原路返回0xFF就表示SD卡处于闲置状态。

2. 发送(开始传输)/(结束传输)的标志

写一个扇区的情况下发送0xFE开始传输数据。

写多个扇区的情况下发送0xFC开始传输数据。

写多个扇区的情况下,连续发送数据完成之后,发送0xFD结束数据发送。

3. 如果不是结束标志(0xFD),就是表示发送的是正常的数据包,就进行循环发送512字节的数据。

注意: 每次发送数据包的单位是按扇区为单位的,也就是512字节,一包数据长度固定为512字节。

4. 数据发送完之后,再接着发送0xFF忽略CRC校验(连续发送3个0xFF)。

图5-3-1

5.4 SPI模式下: 从SD卡读取数据包步骤(SDCardRecvData)

1、等待SD卡发回数据起始令牌0xFE

向SD卡发送0xFF,如果SD卡返回0xFE就表示等待成功。

图5-4-1

2、收到返回的数据起始令牌之后就可以连续读取数据了(接收的数量以传入的cnt为准),读完数据发送两个伪CRC

图5-4-2

5.5 SPI模式下: 向SD卡指定扇区写数据(SDCardWriteData)

封装的函数原型: SDCardWriteData(u8*buf,u32 sector,u32 cnt)

写一个扇区步骤:

1、发送CMD24命令,设置要写的扇区。(写单个扇区使用CMD24命令)

2、接着向SD卡写数据包(参考5.3小节)。

图5-5-1

写多个扇区的步骤:

1、发送CMD55命令(正常应返回0x01)

2、​ 发送CMD23命令(设置多扇区写入前预先擦除N个block)---写入的数量

3、​ 发送CMD25命令,设置写入的扇区位置。(设置写多个扇区)

4、 接着向SD卡写数据包(参考5.3小节)。

图5-5-2

5、写结束指令0xFD,完成写入。

图5-5-3

6、 取消片选

5.6 SPI模式下: 从SD卡读取指定扇区数据(SDCardReadData)

读取一个扇区的步骤:

1、​ 发送CM17命令,设置读取的扇区

2、 接着进行接收SD卡返回的数据包。(参考5.4小节)

每次固定接收512字节,以扇区为单位。

3、 取消片选,完成数据读取

说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。

读取多个扇区的步骤:

1、 发送CMD18命令,设置读取的扇区(连续读多个扇区使用)

2、​ 接着循环接收SD卡返回的数据包。(参考5.4小节)

每次固定接收512字节,以扇区为单位。

3、 发送CMD12指令,停止数据传输

4、 取消片选,完成数据读取

说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。

5.7 SPI模式下: 获取SD卡的总扇区数(GetSDCardSectorCount)

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;//得到总扇区数

六、示例代码

6.1 sdcard.c文件

#include "sdcard.h"			   

/*
函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节
函数参数:data是要写入的数据
返 回 值:读到的数据
*/
u8 SDCardReadWriteOneByte(u8 DataTx)
		 
    u8 i;
    u8 data=0;
    for(i=0;i<8;i++)
    
        SDCARD_SCK=0;
        if(DataTx&0x80)SDCARD_MOSI=1;
        else SDCARD_MOSI=0;
        SDCARD_SCK=1;
        DataTx<<=1;
        
        data<<=1;
        if(SDCARD_MISO)data|=0x01;
    
    return data;



//4种: 边沿两种、电平是两种
/*
函数功能:底层SD卡接口初始化

本程序SPI接口如下:
PC11  片选 SDCardCS
PC12  时钟 SDCardSCLK
PD2   输出 SPI_MOSI--主机输出从机输入
PC8   输入 SPI_MISO--主机输入从机输出
*/
void SDCardSpiInit(void)

  /*1. 开启时钟*/
 	RCC->APB2ENR|=1<<5;		    //使能PORTD时钟
	RCC->APB2ENR|=1<<4;		    //使能PORTC时钟
  
  /*2. 配置GPIO口模式*/
  GPIOC->CRH&=0xFFF00FF0;
  GPIOC->CRH|=0x00033008;
  
  GPIOD->CRL&=0xFFFFF0FF;
  GPIOD->CRL|=0x00000300;
  
  /*3. 上拉*/
  GPIOC->ODR|=1<<8;
  GPIOC->ODR|=1<<11;
  GPIOC->ODR|=1<<12;
  GPIOD->ODR|=1<<2;


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



/*
函数功能:向sd卡写入一个数据包的内容 512字节
函数参数:
					buf 数据缓存区
					cmd 指令
返 回 值:0表示成功;其他值表示失败;
*/
u8 SDCardSendData(u8*buf,u8 cmd)
	
	u16 t;		  	  
	while(SDCardReadWriteOneByte(0xFF)!=0xFF)  //等待忙状态
	SDCardReadWriteOneByte(cmd);
	if(cmd!=0xFD)//不是结束指令
	
		  for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间
	    SDCardReadWriteOneByte(0xFF); //忽略crc
	    SDCardReadWriteOneByte(0xFF);
		  SDCardReadWriteOneByte(0xFF); //接收响应								  					    
							 									  					    
  return 0;//写入成功



/*
函数功能:向SD卡发送一个命令
函数参数:
				u8 cmd   命令 
				u32 arg  命令参数
				u8 crc   crc校验值	
返回值:SD卡返回的响应
*/												  
u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc)

	u8 r1;	
	SDCARD_CS=1; //取消上次片选
 	SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
	SDCARD_CS=0; //选中SD卡
	while(SDCardReadWriteOneByte(0xFF)!=0xFF);//等待成功

	//发送数据
	SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令
	SDCardReadWriteOneByte(arg >> 24);
	SDCardReadWriteOneByte(arg >> 16);
	SDCardReadWriteOneByte(arg >> 8);
	SDCardReadWriteOneByte(arg);	  
	SDCardReadWriteOneByte(crc); 
	
	if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
	do
	
		r1=SDCardReadWriteOneByte(0xFF);
	while(r1&0x80);	  //等待响应,或超时退出
	return r1;



/*
函数功能:获取SD卡的总扇区数(扇区数)   
返 回 值:
				0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节)
说   明:
				每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过.	
*/
u32 GetSDCardSectorCount(void)

    u8 csd[16];
    u32 Capacity=0;  
	  u16 csize;
		//获取SD卡的CSD信息,包括容量和速度信息,存放CID的内存,至少16Byte
		SendSDCardCmd(SDCard_CMD9,0,0x01);//发SDCard_CMD9命令,读CSD
	  SDCardRecvData(csd,16);//接收16个字节的数据 
		SDCARD_CS=1;//取消片选
		SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
    if((csd[0]&0xC0)==0x40)  //SDHC卡,按照下面方式计算
    	
			csize=csd[9]+(csd[8]<<8)+1;
			Capacity=csize<<10;//得到扇区数	 		   
    
    return Capacity;



/*
函数功能: 初始化SD卡
返 回 值: 非0表示初始化失败!
*/
u8 SDCardDeviceInit(void)

  u8 r1=0;      // 存放SD卡的返回值
  u8 data;  
	u16 i;
	/*1. 初始化底层IO口*/
	SDCardSpiInit();
	
	/*2. 发送最少74个脉冲*/
 	for(i=0;i<10;i++)SDCardReadWriteOneByte(0xFF);
	
	/*3. 进入闲置状态*/
	do
	
		r1=SendSDCardCmd(SDCard_CMD0,0,0x95);
	while(r1!=0x01);
	
	/*4. 鉴别是否是2.0版本的SD卡*/
	if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==0x01)
	
			do
			
				SendSDCardCmd(SDCard_CMD55,0,0x01);	    //发送SDCard_CMD55
				r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0x01);//发送SDCard_CMD41
			while(r1);
			
			if(SendSDCardCmd(SDCard_CMD58,0,0x01)==0)//鉴别SD2.0卡版本开始
			
				data=SDCardReadWriteOneByte(0xFF);//得到OCR值
				if(data&0x40)
				
						r1=0; //高速卡
									
			
	 
	SDCARD_CS=1;//取消片选
 	SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
	return r1;          //其他错误



/*
函数功能:读SD卡
函数参数:
				buf:数据缓存区
				sector:扇区
				cnt:扇区数
返回值:
				0,ok;其他,失败.
说  明:
				SD卡一个扇区大小512字节
*/
void SDCardReadData(u8*buf,u32 sector,u32 cnt)

	u32 i=0;
	if(cnt==1)
	
		SendSDCardCmd(SDCard_CMD17,sector,0x01);//读扇区
		SDCardRecvData(buf,512);			//接收512个字节	   
	
	else
	
		SendSDCardCmd(SDCard_CMD18,sector,0x01);//连续读命令
		for(i=0;i<cnt;i++)
		
			SDCardRecvData(buf,512);//接收512个字节	 
			buf+=512;  
		
		SendSDCardCmd(SDCard_CMD12,0,0x01);	//停止数据传输
	   
	SDCARD_CS=1;//取消片选
 	SDCardReadWriteOneByte(0xff);//提供额外的8个时钟();



/*
函数功能:向SD卡写数据
函数参数:
				buf:数据缓存区
				sector:起始扇区
				cnt:扇区数
说  明:
				SD卡一个扇区大小512字节
*/
void SDCardWriteData(u8*buf,u32 sector,u32 cnt)

	u32 i=0;
	if(cnt==1)
	
		SendSDCardCmd(SDCard_CMD24,sector,0x01);//写单个扇区
		SDCardSendData(buf,0xFE);//写512个字节	   
	
	else
	
		SendSDCardCmd(SDCard_CMD55,0,0x01);	
		SendSDCardCmd(SDCard_CMD23,cnt,0x01);   //设置多扇区写入前预先擦除N个block
 		SendSDCardCmd(SDCard_CMD25,sector,0x01);//写多个扇区
		for(i=0;i<cnt;i++)
		
			SDCardSendData(buf,0xFC);//写512个字节	 
			buf+=512;  
		
		SDCardSendData(0,0xFD);//写结束指令
	
	SDCARD_CS=1;
 	SDCardReadWriteOneByte(0xff);//提供额外的8个时钟

6.2 sdcard.h文件

#ifndef SD_H_
#define SD_H_	 
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "usart.h"

/*----------------------------------------------
本程序SPI接口如下:
PC11  片选 SDCardCS
PC12  时钟 SDCardSCLK
PD2   输出 SPI_MOSI--主机输出从机输入
PC8   输入 SPI_MISO--主机输入从机输出
------------------------------------------------*/
#define SDCARD_CS PCout(11)
#define SDCARD_SCK PCout(12)
#define SDCARD_MOSI PDout(2)
#define SDCARD_MISO PCin(8)

// SD卡指令表  	   
#define SDCard_CMD0    0       //卡复位
#define SDCard_CMD8    8       //命令8 ,SEND_IF_COND
#define SDCard_CMD9    9       //命令9 ,读CSD数据
#define SDCard_CMD12   12      //命令12,停止数据传输
//#define SDCard_CMD13   16      //命令16,设置扇区大小 应返回0x00
#define SDCard_CMD17   17      //命令17,读扇区
#define SDCard_CMD18   18      //命令18,读多个扇区
#define SDCard_CMD23   23      //命令23,设置多扇区写入前预先擦除N个block
#define SDCard_CMD24   24      //命令24,写扇区
#define SDCard_CMD25   25      //命令25,写多个扇区
#define SDCard_CMD41   41      //命令41,应返回0x00
#define SDCard_CMD55   55      //命令55,应返回0x01
#define SDCard_CMD58   58      //命令58,读OCR信息

//函数声明              
u8 SDCardReadWriteOneByte(u8 data);                 //底层接口,SPI读写字节函数
u8 SDCardDeviceInit(void);							            //初始化
void SDCardReadData(u8*buf,u32 sector,u32 cnt);		  //读块(扇区)
void SDCardWriteData(u8*buf,u32 sector,u32 cnt);		//写块(扇区)
u8 SDCardSendData(u8*buf,u8 cmd);  									//发送数据包
u8 SDCardRecvData(u8*buf,u16 len);									//接收数据包
u32 GetSDCardSectorCount(void);   					        //读扇区数
#endif

以上是关于STM32开发_利用SPI协议读写SD卡介绍SD卡SPI时序的主要内容,如果未能解决你的问题,请参考以下文章

Linux下驱动开发_块设备驱动开发(硬件上采用SD卡+SPI协议)

Linux下驱动开发_块设备驱动开发(硬件上采用SD卡+SPI协议)

STM32通过SPI读写SD卡,初始化失败

嵌入式用STM32F103完成对SD卡的数据读取

嵌入式20STM32F103完成对SD卡的数据读取详细操作

STM32使用SDIO进行SD卡读写-初步认识SD卡