从0开始编写SD卡底层驱动代码(适用于任何单片机的通用代码)

SD卡在嵌入式、单片机开发里使用的很多。主要用来存储图片、字库、等比较占用内存的数据。

SD卡支持两种通信协议:SPI和SDIO。 SPI协议是最简单,不依赖单片机本身硬件的协议,实现非常容易。

那么这篇文章就带大家,从开始认识SD卡,学习SD卡的指令,了解SPI协议,从0开始自己写出SD卡的驱动代码。

学会之后,可以用在任何单片机,任何嵌入式系统上。

当前文章就是介绍通过模拟SPI协议驱动SD卡,读写SD卡。 不依赖于任何特定的单片机型号,也不依赖单片机的本身硬件,是一个完全通用的代码。

不管你是51单片机、还是 STM32、还是树莓派、还是其他单片机,使用起来是没区别的。

单片机只是一个执行器,你学的是软件程序逻辑。

文章目录

一、从0开始学习SD卡指令与时序

1.1 SD卡引脚接口功能介绍

【1】SD卡引脚接口图

image-20240524092405034

image-20240524092419291

图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方式。

【2】SPI方式驱动SD卡介绍

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

1.2 MMC卡、SD卡介绍

【1】SD卡和MMC两者间区别

SD卡和MMC(多媒体卡)似乎可以使用同一个插槽,两者间有什么区别呢?
不过,虽说外型几乎一致,但还是有点差异的。MMC比SD卡要薄一些。

image-20240524092918251

图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】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,因为这个值不太大,占用内存不太多,适合单片机使用。

【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信息

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

【1】取消选中SD卡(SDCardCancelCS)

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

image-20240524093224222

【2】选中SD卡(SDCardSelectCS)

(1).选中片选 (2).等待SD卡忙状态
说明: 向SD卡发送0xFF,如果SD卡也返回0xFF就表示SD卡已经准备好,如果返回不是0xFF就表示SD卡还没有准备好,需要等待。

image-20240524093333131

【3】向SD卡发送操作命令cmd

将要发送的命令 |0x40 发给SD卡。 示例: cmd | 0x40
命令是8位数据。

image-20240524093248826

【4】向SD卡发送命令参数

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

image-20240524093358542

【5】发送CRC校验

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

image-20240524093419396

image-20240524093430766

【6】等待SD卡响应

向SD卡发送0xFF数据,如果SD卡返回的数据最高位为0,就是表示SD卡响应完成,否则就继续发送0xFF,再判断,直到SD卡响应成功。
SD卡响应之后,完成一次命令发送,并将返回的数据当做返回值返回去。
推荐: 使用do{…}while()循环结构,更加方便判断。

image-20240524093446478

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

【1】SDCard_CMD0:卡复位命令

image-20240524093811728

【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卡,如果复位不成功,则表示是无法识别的卡。

image-20240524093830650

image-20240524093847292

image-20240524093900214

image-20240524093915028

鉴别到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 寄存器的结构描述:

image-20240524093934165

这是时序图:

image-20240524094001922

【3】SDCard_CMD9: 获取SD卡的CSD信息

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

CMD9的命令:

image-20240524094233010

【4】SDCard_CMD17: 设置单个读取的扇区

image-20240524094248099

【5】SDCard_CMD18: 设置读扇区(连续读扇区使用)

image-20240524094300407

【6】SDCard_CMD12: 停止数据传输

image-20240524094314408

【7】SDCard_CMD24: 设置写单个扇区

image-20240524094326513

【8】4.8 SDCard_CMD55

image-20240524094339138

【9】SDCard_CMD23: 多扇区写入前预先擦除块数量

image-20240524094352996

【10】SDCard_CMD25: 设置写多个扇区

image-20240524094404164

【11】SDCard_CMD41

image-20240524094413367

【12】SDCard_CMD58

image-20240524094422827

1.5 SD卡SPI接口命令

【1】SPI接口时序

image-20240524094950294

这是:SPI时序图

image-20240524095004664

数据先发/先收高位

从图上得知(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;
}

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

【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)。

image-20240524095030033

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

(1)等待SD卡发回数据起始令牌0xFE
向SD卡发送0xFF,如果SD卡返回0xFE就表示等待成功。

image-20240524095041353

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

image-20240524095054708

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

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

写一个扇区步骤:

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

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

image-20240524095109122

写多个扇区的步骤:

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

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

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

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

image-20240524095120686

图5-5-2

【6】写结束指令0xFD,完成写入。

image-20240524095130356

【7】取消片选

CS=0;

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

【1】读取一个扇区的步骤

(1)发送CM17命令,设置读取的扇区

(2)接着进行接收SD卡返回的数据包。
每次固定接收512字节,以扇区为单位。

(3)取消片选,完成数据读取
说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。

读取多个扇区的步骤:

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

(2)接着循环接收SD卡返回的数据包

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

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

(4)取消片选,完成数据读取
说明: 取消片选之后,需要再额外发送8个时钟信号,结束本次操作。

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

二、完整代码

下面是编写好的完整SD卡驱动代码。

2.1 sd_card.c

#include "sd_card.h"

/*
函数功能: SD卡使用的GPIO初始化
硬件连接: 

MISO--PA4
MOSI--PA5
CS----PA6
SCK---PA7
VCC---5V
GND---GND

*/
void SDCard_GPIO_Init(void)
{
	/*1. 开时钟*/
	RCC->APB2ENR|=1<<2;
	/*2. 配置模式*/
	GPIOA->CRL&=0x0000FFFF;
	GPIOA->CRL|=0x33380000;
	/*3. SD卡进行上拉*/
	SDCARD_CS=1;
	SDCARD_SCK=1;
}

/*
函数功能: 底层的SPI接口时序
*/
u8 SDCard_SPI_ReadWriteByte(u8 tx_data)
{
	u8 i;
	u8 rx_data=0;
	for(i=0;i<8;i++)
	{
		 /*1. 发送1位数据*/
			SDCARD_SCK=0;
			if(tx_data&0x80)SDCARD_MOSI=1;
			else SDCARD_MOSI=0;
			SDCARD_SCK=1;
			tx_data<<=1;
		
		 /*2. 读取1位数据*/
		 rx_data<<=1;
		 if(SDCARD_MISO)rx_data|=0x01;
	}
	return rx_data;
}


/*
函数功能: SD卡初始化
*/
u8 SDCard_Init(void)
{
    u8 i;
    u8 state;
    /*1. GPIO初始化*/
    SDCard_GPIO_Init();
    /*2. 发送74个时钟信号:唤醒SD卡*/
    for(i=0;i<10;i++)SDCard_SPI_ReadWriteByte(0xFF);
    /*3. 发送卡复位命令,等待复位成功*/
    do
    {
            state=SDCard_WriteCMD(SDCard_CMD0,0,0x95);
    }while(state!=0x01);
    /*4. 鉴别2.0版本的SD卡*/
    if(SDCard_WriteCMD(SDCard_CMD8,0x1AA,0x87)==0x01)
    {
            /*4.1 等待V2.0卡复位成功*/
            do
            {
                SDCard_WriteCMD(SDCard_CMD55,0,0x01);
                state=SDCard_WriteCMD(SDCard_CMD41,0x40000000,0x01);
            }while(state);
            
            /*4.2 判断是否是高速卡*/
            if(SDCard_WriteCMD(SDCard_CMD58,0,0x01)==0)
            {
                        state=SDCard_SPI_ReadWriteByte(0xFF);
                        if(state&0x40)
                        {
                                printf("当前SD卡是高速SD卡!\n");
                                state=0; //高速SD卡
                        }
            }		
    }
    /*5. 结束初始化*/
    SDCARD_CS=1;
    SDCard_SPI_ReadWriteByte(0xFF); //额外发送8个时钟信号
    return state;
}


/*
函数功能: 向SD卡写命令
函数参数:
返 回 值: 接收的返回结果
*/
u8 SDCard_WriteCMD(u8 cmd,u32 data,u8 crc)
{
		u8 state;
		SDCARD_CS=1; //取消片选
	  SDCard_SPI_ReadWriteByte(0xFF); //发送8个时钟
		SDCARD_CS=0; //选中SD卡
		while(SDCard_SPI_ReadWriteByte(0xFF)!=0xFF){} //等待SD卡准备好
		SDCard_SPI_ReadWriteByte(cmd|0x40); //发送命令
		SDCard_SPI_ReadWriteByte(data>>24); //发送数据24~31
		SDCard_SPI_ReadWriteByte(data>>16); //发送数据23~16
		SDCard_SPI_ReadWriteByte(data>>8); //发送数据15~8
		SDCard_SPI_ReadWriteByte(data>>0); //发送数据7~0
		SDCard_SPI_ReadWriteByte(crc); //发送CRC校验值
		if(cmd==SDCard_CMD12)SDCard_SPI_ReadWriteByte(0xFF);
		
		do
		{
			state=SDCard_SPI_ReadWriteByte(0xFF);
		}while(state&0x80);  //等待SD卡响应
		return state;
}

/*
函数功能: 向SD卡写一个数据包
*/
void SDCard_WriteDataPack(u8 cmd,u8 *buff)
{
		u32 i=0;
		/*1. 等待SD卡忙状态*/
		while(SDCard_SPI_ReadWriteByte(0xFF)!=0xFF){}
		/*2. 发送命令*/
		SDCard_SPI_ReadWriteByte(cmd);
		/*3. 判断是否是结束命令*/
		if(cmd!=0xFD)
		{
				for(i=0;i<512;i++)
				{
						SDCard_SPI_ReadWriteByte(buff[i]);
				}
				SDCard_SPI_ReadWriteByte(0xFF);
				SDCard_SPI_ReadWriteByte(0xFF);
				SDCard_SPI_ReadWriteByte(0xFF);
		}
}

/*
函数功能: 从SD卡读取一个数据包
*/
void SDCard_ReadDataPack(u32 len,u8 *buff)
{
		u32 i=0;
	 /*1. 等待 SD 卡发回数据起始令牌 0xFE*/
	 while(SDCard_SPI_ReadWriteByte(0xFF)!=0xFE){}
	 /*2. 循环读取数据*/
	 for(i=0;i<len;i++)
	 {
			buff[i]=SDCard_SPI_ReadWriteByte(0xFF);
	 }
	 /*3. 读完数据发送两个伪 CRC*/
	 SDCard_SPI_ReadWriteByte(0xFF);
	 SDCard_SPI_ReadWriteByte(0xFF);
}

/*
函数功能: 获取SD卡的总扇区数量
说明: 1个扇区是512字节
*/
u32 SDCard_GetSectorCount(void)
{
	u8 csd[16];
	u32 csize=0;
  u32 Capacity=0;	
	/*1. 发送读取CSD寄存器命令*/
	SDCard_WriteCMD(SDCard_CMD9,0,0x01);
	/*2. 连续读取16个字节信息*/
	SDCard_ReadDataPack(16,csd);
	/*3. 判断是否是V2.0版本的SD卡*/
	if((csd[0]&0xC0)==0x40)
	{
			csize=csd[9]+(csd[8]<<8)+1;
		  Capacity=csize<<10;//得到总扇区数
	}
	return Capacity;
}

/*
函数功能: 写扇区的函数
函数参数: 
u8*buf      :写的数据
u32 sector  :扇区的起始位置
u32 cnt     :扇区的数量
说明: 每次写最小单位1个扇区
*/
void SDCard_WriteData(u8*buf,u32 sector,u32 cnt)
{
		u32 i;
		if(cnt==1) //写一个扇区的情况
		{
			/*1. 设置要写的扇区位置(只是写一个扇区)*/
			SDCard_WriteCMD(SDCard_CMD24,sector,0x01);
			/*2. 写数据*/
			SDCard_WriteDataPack(0xFE,buf);//调用一次就发送512字节
		}
		else //写多个扇区的情况
		{
				SDCard_WriteCMD(SDCard_CMD55,0,0x01);
				//多扇区写入前预先擦除块数量(设置写入扇区数量)
				SDCard_WriteCMD(SDCard_CMD23,0,0x01);
			  //设置写多个扇区 (设置扇区的位置)
		   	SDCard_WriteCMD(SDCard_CMD25,sector,0x01);
			  //循环发送数据包
				for(i=0;i<cnt;i++)
			  {
						SDCard_WriteDataPack(0xFC,buf);//调用一次就发送512字节
						buf+=512;
				}
				//发送结束指令
				SDCard_WriteDataPack(0xFD,buf);
		}
		//取消片选
		SDCARD_CS=1;
		SDCard_SPI_ReadWriteByte(0xFF);
}

/*
函数功能: 读扇区的函数
函数参数: 
u8*buf      :存放读取的数据
u32 sector  :扇区的起始位置
u32 cnt     :扇区的数量
说明: 每次读512字节,最小单位1个扇区
*/
void SDCard_ReadData(u8*buf,u32 sector,u32 cnt)
{
	 u32 i=0;
	 if(cnt==1) //读取单个扇区
	 {
		  //设置读取的扇区 读取的扇区首地址
		  SDCard_WriteCMD(SDCard_CMD17,sector,0x01);
		  //读取数据
		  SDCard_ReadDataPack(512,buf);
	 }
	 else  //读取多个扇区
	 {
			//设置读取的扇区 读取的扇区首地址
		  SDCard_WriteCMD(SDCard_CMD18,sector,0x01);
		  for(i=0;i<cnt;i++)
		  {
					SDCard_ReadDataPack(512,buf);
				  buf+=512;
			}
			//停止数据传输
			SDCard_WriteCMD(SDCard_CMD12,0,0x01);
	 }
	  //取消片选
		SDCARD_CS=1;
		SDCard_SPI_ReadWriteByte(0xFF);
}

2.1 sd_card.h

#ifndef _SD_CARD_
#define _SD_CARD_
#include "stm32f10x.h"
#include "sys.h"
#include "usart.h"

/*
MISO--PA4
MOSI--PA5
CS----PA6
SCK---PA7
VCC---5V
GND---GND
*/


//SD卡接口定义
#define SDCARD_MISO PAin(4)
#define SDCARD_MOSI PAout(5)
#define SDCARD_CS  PAout(6)
#define SDCARD_SCK PAout(7)

//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,设置多扇区写入前预先擦除(设置写入的扇区数量)
#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 信息

//函数声明
void SDCard_GPIO_Init(void);
u8 SDCard_SPI_ReadWriteByte(u8 tx_data);
u8 SDCard_WriteCMD(u8 cmd,u32 data,u8 crc);
u8 SDCard_Init(void);
void SDCard_WriteDataPack(u8 cmd,u8 *buff);
void SDCard_ReadDataPack(u32 len,u8 *buff);
u32 SDCard_GetSectorCount(void);
void SDCard_ReadData(u8*buf,u32 sector,u32 cnt);
void SDCard_WriteData(u8*buf,u32 sector,u32 cnt);
#endif
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的LPC2294 SD卡驱动代码,仅供参考: ``` #include "LPC2294.h" #include <stdio.h> #include "sdcard.h" // SD卡初始化 void sdcard_init() { // 设置SPI时钟速度 S0SPCCR = 8; // 设置SPI接口为主模式 S0SPCR = (1<<5)|(1<<4)|(1<<2); // 发送80个时钟脉冲 spi_send_dummy(10); // 发送CMD0命令,复位SD卡 if (send_command(CMD0, 0) != R1_IDLE_STATE) { printf("SD卡复位失败\n"); return; } // 发送CMD8命令,检查SD卡是否支持高速模式 if (send_command(CMD8, 0x1AA) == R1_IDLE_STATE) { printf("SD卡支持高速模式\n"); } // 进行初始化序列 if (init_sequence() != 0) { printf("SD卡初始化失败\n"); return; } printf("SD卡初始化成功\n"); } // 发送命令 int send_command(uint8_t cmd, uint32_t arg) { uint8_t response; uint8_t retry = 0; // 等待SD卡不忙 while (spi_send_dummy(1) != 0xFF && retry < 0xFF) { retry++; } // 发送命令 spi_send_byte(0x40 | cmd); spi_send_byte(arg >> 24); spi_send_byte(arg >> 16); spi_send_byte(arg >> 8); spi_send_byte(arg); // 发送CRC校验码 if (cmd == CMD0) spi_send_byte(0x95); else if (cmd == CMD8) spi_send_byte(0x87); else spi_send_byte(0xFF); // 接收响应 response = spi_send_dummy(1); return response; } // 进行初始化序列 int init_sequence() { uint8_t response; uint8_t retry = 0; // 发送CMD55和ACMD41命令,激活SD卡 do { send_command(CMD55, 0); response = send_command(ACMD41, 0x40000000); retry++; } while (response != R1_READY_STATE && retry < 0xFF); // 检查SD卡是否已经初始化完成 if (retry == 0xFF || send_command(CMD58, 0) != R1_READY_STATE) { return -1; } return 0; } // 发送一个字节到SD卡 void spi_send_byte(uint8_t byte) { S0SPDR = byte; while (!(S0SPSR & 0x80)); } // 发送多个字节到SD卡 uint8_t spi_send_dummy(int count) { uint8_t response; while (count--) { S0SPDR = 0xFF; while (!(S0SPSR & 0x80)); response = S0SPDR; } return response; } ``` 需要注意的是,这只是一个简单的驱动代码,只实现了SD卡的初始化和命令发送功能。在实际的应用中,可能需要更丰富的功能和更复杂的代码来实现文件读写等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DS小龙哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值