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

news2024/11/30 6:35:16

一、​  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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26269.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】堆的拓展延伸 —— 堆排序 和 TopK问题

文章目录前言堆排序TopK问题结语前言 上篇博客&#xff0c;我们实现了堆。那么堆到底有什么应用情景&#xff1f;今天的内容就是堆的两个应用&#xff0c;堆排序和TopK问题。话不多说&#xff0c;我们这就开始。 堆排序 堆排序&#xff0c;是根据堆的结构而设计出的一种排序…

Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks

原文链接&#xff1a;https://aclanthology.org/D19-1410.pdf 概述 问题&#xff1a; BERT和RoBERT模型在进行语义匹配的时候&#xff0c;需要将每个可能的组合都输入到模型中&#xff0c;会带来大量的计算&#xff08;因为BERT模型对于句子对的输入&#xff0c;使用[SEP]来标记…

C++11、17、20的内存管理-指针、智能指针和内存池从基础到实战(中)

C11、17、20的内存管理-指针、智能指针和内存池从基础到实战&#xff08;中&#xff09;第三章 分配器allocator和new重载1、重载operator的new和delete包括数组如果我们访问的是一个数组2、类成员操作符new重载和放置placement_newplacement new&#xff08;放置内存&#xff…

并发编程(三)原子性(1)

【认识原子性】&#xff1a; 一个小程序认识原子性&#xff1a; package T05_YuanZiXing;import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class T00_00_IPlusPlus {private …

Android源码相关面试专题

Android源码相关面试专题 1、Android属性动画实现原理 工作原理&#xff1a;在一定时间间隔内&#xff0c;通过不断对值进行改变&#xff0c;并不断将该值赋给对象的属性&#xff0c;从而实现该对象在该属性上的动画效果。 正在上传…重新上传取消 1&#xff09;ValueAnimato…

Do Transformers Really Perform Bad for Graph Representation?

Do Transformers Really Perform Bad for Graph Representation? 论文中提出了Graphormer&#xff0c;它建立在标准的Transformer架构之上&#xff0c;并且在广泛地图表示学习任务重获得了优异的成绩。同时&#xff0c;作者也提出了一些简单但是有效的结构编码方法来帮助Grap…

【支付宝生态质量验收与检测技术】

如何验收和检测海量的支付宝生态小程序的质量&#xff0c;是一个很重要的课题。本次分享会简单介绍如何通过平台化的方式在小程序入驻环节进行准入验收&#xff0c;以及使用前端自动化测试技术和智能化算法对小程序质量进行检测。希望能对小程序质量的验收和测试提供参考。讲师…

计算机网络-应用层(应用层概述,网络应用模型(C/S模型,P2P模型),DNS域名协议)

文章目录1. 应用层概述2. 网络应用模型3. 域名系统&#xff08;DNS&#xff09;1. 应用层概述 应用层概述&#xff1a;应用层对应用程序的通信提供服务。 应用层协议定义&#xff1a; 应用进程交换的报文类型&#xff0c;请求还是响应各种报文类型的语法&#xff0c;如报文中…

分布式锁-简单入门

状态不是很好&#xff0c;记一下以前学过的分布式锁吧。 样例简介 不谈大概念&#xff0c;就是简单入门以及使用。 为什么要用分布式锁呢&#xff1f; 假设我需要一个定时操作&#xff0c;每天在某个点&#xff0c;我要处理一批数据&#xff0c;要先从数据库中查询出来&…

云计算-Hadoop-2.7.7 最小化集群的搭建(3台)

云计算-Hadoop-2.7.7 最小化集群的搭建&#xff08;3台&#xff09; 文章目录云计算-Hadoop-2.7.7 最小化集群的搭建&#xff08;3台&#xff09;一、环境依赖下载二、部署概要三、hadoop101模板机配置1. 更新 & 升级2. 安装好用的vim VimForCpp3. 安装必要依赖4. 关闭防火…

nginx配置https访问 生成ssl自签名证书,浏览器直接访问

问题 nginx配置自签名ssl证书&#xff0c;来支持https访问nginx&#xff0c;在浏览器中访问nginx时&#xff0c;提示有风险。而访问其他各大网站时&#xff0c;也是使用了https协议&#xff0c;为什么可以直接访问&#xff0c;而不提示有风险呢&#xff1f; 解疑 先从ssl证书…

MyBatis--动态SQL

Emp类 1.if标签 通过test属性中的表达式判断标签中的内容是否有效 (是否会拼接到SQL中) 接口 映射 测试 2.Where标签 where标签的三个作用 若where标签中有条件成立 , 会自动生成where关键字会自动将where标签中内容前多余的and去掉 , 但是其中内容后多余的and无法去掉若where标…

mysql explain和DESC性能分析

mysql explain和DESC 根据执行时间去只可以粗略的判断sql的性能&#xff0c;我们如果想去查看一条sql语句的性能还需要explain去查看sql的执行计划。 EXPLAIN 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息&#xff0c;包括在 SELECT 语句执行过程中表如何连接和连接的…

如何做好供应商绩效管理?

供应商绩效管理是一种商业行为&#xff0c;用于衡量、分析和管理供应商的绩效。供应商管理专业人员寻求削减成本&#xff0c;减轻风险并推动持续改进。企业可使用供应商管理系统来监测供应商的绩效水平。 供应商绩效管理最佳实践 所有企业都必须发展核心竞争力&#xff0c;有…

【Linux 网络编程 】

Linux 网络编程背景知识&#xff1a;主机字节序列和网络字节序列IP地址的转换API网络编程接口网络节序与主机节序转换函数IP地址转换函数数据读写TCP编程编程步骤&#xff1a;客户端链接服务端成功的条件多线程实现服务端并发多进程实现服务端并发注意&#xff1a;UDP编程编程步…

自动化测试基础简介(本质)

目录 前言 1.自动化基础 2.分层的自动化测试 2.1 单元自动化测试 2.2 接口自动化测试 2.3 UI自动化测试 3.适合自动化的项目 4.自动化测试模型 4.1线性测试 4.2模块化与类库 4.3数据驱动测试 4.4关键字驱动测试 5.POM设计模式 总结 前言 随着软件系统规模的日益…

应对Redis缓存污染问题,你应该知道这些内容

前言 我们在使用Redis做为缓存时&#xff0c;能加速我们对于热点数据的查询。但是如果缓存中有大量的数据不再热门了&#xff0c;从而占据着大量的内存空间&#xff0c;那么我们的Redis性能就会收到很大影响。该如何解决这个问题呢&#xff1f;本文给你答案。 什么是缓存污染…

kafka开发环境搭建

1 kafka开发环境 1.1 安装Java环境 1.1.1 下载linux下的安装包 登陆网址https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 下载完成后&#xff0c;Linux默认下载位置在当前目录下的Download或下载文件夹下&#xff0c;通过命令cd ~/…

轻松学习string类常用接口(附模拟实现)

目录 String的常用接口说明(最常用的) string类对象的容量操作 string类对象的访问及遍历操作 string类对象的修改操作 string类非成员函数 深浅拷贝 简介&#xff1a;Cstring 是C中的字符串。 字符串对象是一种特殊类型的容器&#xff0c;专门设计来操作的字符序列。 不像…

MySQL 全文检索的实现

微信搜「古时的风筝」&#xff0c;还有更多技术干货 这有朋友聊到他们的系统中要接入全文检索&#xff0c;这让我想起了很久以前为一个很古老的项目添加搜索功能的事儿。 一提到全文检索&#xff0c;我们首先就会想到搜索引擎。也就是用一个词、一段文本搜索出匹配的内容。一般…