说明:
①测试的SD卡为高容量卡,支持SD卡2.0协议,容量为16G
②采用GPIO模拟SPI时序的方式对SD卡进行驱动,很方便移植到没有硬件SPI或者SDIO的MCU,对于这类MCU,只需要将对应的延时函数和GPIO配置换成自己的就可以,其他的都无需变动。
③对SPI有疑问或者的问题的,请移步之前写过的博文:
SD/TF卡驱动(一)--------SD卡相关简介
④如果内容有任何问题,恳请大家批评指正,谢谢。

一、 SD卡SPI初始化流程

(1)大致流程分析

流程图来自《SD卡2.0协议》P95页,SPI模式下的SD卡初始化流程,虚线框起来的部分不是必须要的流程。根据上面流程图,可以得到以下流程:

①上电后,将CS片选拉低,然后发送CMD0命令复位SD卡;
②主机发送CMD8命令,确认SD是否支持的协议版本;
③如果支持V1.X版本协议,则发送CMD58指令,确认电压是否兼容,兼容情况下发送ACMD41指令,确认是否已经准备好;
④如果支持V2.0协议,同样发送CMD58指令,确认电压是否兼容,兼容情况下发送ACMD41指令,确认是否已经准备好,待SD准备好之后,发送CMD58命令,确认SD卡是标准容量卡(2G或者2G以下)还是大容量卡(2G至32G(含))。
循环结束。

(2)初始化流程完整指令分析

前文已经说过,SD卡的每一条控制命令都是6个byte,且每一次发送完命令后,SD卡都会有相应的回复,那么接下来,我们需要确定两点:

①流程中命令的参数和CRC是多少?
②每条命令执行成功回复是什么?

下面就来解决这两个问题。上篇博文提到:

由此可以知道:

CMD0             回复R1
ACMD41          回复R3
CMD8/CMD58      回复R7

再来看看什么是R1、R3和R7:

如果看不清楚,请回到前面一篇博文,上面截图均来自上篇博文。

①R1比较简单,如果主机发送的时序和指令没有问题,不是返回0x00就是返回0x01②R3 5个byte,第一个字节为R1,不是0x00,就是0x01,剩下的四个字节为OCR寄存器的值(OCR寄存器请看前文),事实上有关CMD8命令的回复,在协议文档中已经有说明,如下③R7 也是5个byte,第一个字节为R1,不是0x00,就是0x01。第二个字节为命令版本、第三个字节保留、第四个字节为被接受的电压范围、第五个字节为模式检查(我也不晓得这是个啥东西,只知道它和CRC都是用来检查主机和卡之间通信的有效性)

主机发送CMD0命令,复位成功,SD卡应该回复0x01,我们将CMD0在协议文档中进行全局搜索,可以在P96页发现:

所以,最终CMD0的完整命令为:

0x00|0x40,0x00,0x00,0x00,0x00,0x95----->0x01(复位成功)

还剩下CMD8/CMD58/和ACMD41有待进一步确定回复值

CMD8和CMD58都是回复R7数据的格式,在文档P108页中,有如下说明:

由文档可以知道,如果CMD8发送成功,SD卡回复的五个字节应该是

0x01     R1
0x00        command version
0x00        Reserved bit
0x01        voltage range(VCA)
(Echo Back) check pattern

这个Echo Back是个什么鬼
复制这个Echo Back进行搜索,在文档的P40页,可以找到下面说明:

显而易见了,这个(Echo Back) check pattern就是0xAA了,事实上,从上面的文档说明里也可以得到CMD8的完整命令:0x80 | 0x40,0x00,0x00,0x01,0xAA,0x87(0x87为CRC,怎么算的自己找资料,实话实话我没找,哈哈哈)。
于是就有了CMD8的完整命令及回复:

0x08 | 0x40,0x00,0x00,0x01,0xAA,0x87---->0x01,0x00,0x00,0x01,0xAA

还剩下两个:CMD58和ACMD41
CMD58是READ_OCA,文档P106:

前面已经简单介绍过,这个命令用来确定电压范围和卡容量类型的,直接找到这个寄存器:

如果你的卡接接受电压范围为2.7-3.6V,发送CMD58命令,应该收到的回复是这样的:

0xC0
0xFF
0x80
0x00

所以,完整的CMD58命令及回复应该是:

0x3A|0x40,0x00,0x00,0x00,0x00,0x01--->0x00,0xC0,0xFF,0x80,0x00

最后一个ACMD41,这个命令用来启动初始化并检测初始化是否通过。必须要在发送第一个ACMD41命令之前发送CMD8命令。接收CMD8命令会拓展CMD58和ACMD41功能。
ACMD41参数中的HCS(高容量支持)和CMD58响应中的CCS(卡容量状态),只有在卡接收了CMD8后才能被激活。
ACMD41 R1响应中的“处于空闲状态”位通知主机ACMD41的初始化是否完成。将该位设置为“1”表示卡仍在初始化。将该位设置为“0”表示初始化完成。主机反复发出ACMD41,直到该位设置为“0”。

那么这个命令由SD卡初始化流程图可以知道,当SD卡为标准容量卡的时候,其参数为0,为高容量SD卡时,其HCS位为1,即此时其命令参数为:0x40,0x00,0x00,0x00,
所以,最后ACMD41的完整命令参数及回复为:

标准容量卡:0x29|0x40,0x00,0x00,0x00,0x00,0x01---->0x00(初始化完成)
高容量卡:0x29|0x40,0x40,x000,0x00,0x00,0x01----> 0x00(初始化完成)

最后,总结一下初始化流程中用到的完整指令以及回复:

CMD0:0x00|0x40,0x00,0x00,0x00,0x00,0x95----->0x01(复位成功)
CMD8:0x08 | 0x40,0x00,0x00,0x01,0xAA,0x87---->0x01,0x00,0x00,0x01,0xAA
ACMD41:标准容量卡:0x29|0x40,0x00,0x00,0x00,0x00,0x01---->0x00(初始化完成)高容量卡:0x29|0x40,0x40,x000,0x00,0x00,0x01----> 0x00(初始化完成)
CMD58:0x3A|0x40,0x00,0x00,0x00,0x00,0x01--->0x00,0xC0,0xFF,0x80,0x00

(3)SD卡SPI初始化代码思路梳理

这里需要说明一点,应该养成意识,在驱动一款设备时,一般设备上电后的一小段时间内都需要完成某些操作后才能接收指令,SD卡2.0协议中的P91页中,有如下说明:

现在整个初始化流程就出来了,用伪代码,表示如下:

Delay()   //等待SD卡上电稳定
Reply = 200
for(int I = 0;I < 20;i++) SPI_SendByte(0xff);  //内容随便发,但是必须>74个时钟周期。
//等待SD卡复位成功,CMD0命令,没有参数,
//且根据文档其CRC为0x95(文档P96),复位正常应该返回0x01
CS = 0while((ret = SendCmd(CMD0, 0, 0x95)) != 0x01 && Reply--);
if(ret == 0x01)  //复位成功
{Reply = 200While((ret = SendCmd(CMD8, 0x1AA, 0x87)) != 0x01 && Reply--);if(ret == 0x01){for(int i = 0;i < 4;i++)  buf[i] = SPI_ReadByte();if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V{Reply = 20000do{ret = SendCmd(ACMD41, 0x40000000, 0x01);} While(ret != 0x00 && Reply--);  //等待SD初始化完毕ret = SendCmd(CMD58, 0x00, 0x01);if(ret == 0x00){for(i=0;i<4;i++)buf[i]=SPI_ ReadByte ();//得到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);       //发送CMD55r1=SD_SendCmd(ACMD41,0,0X01);    //发送CMD41if(r1<=1){SD_Type=SD_TYPE_V1;retry=0XFFFE;do //等待退出IDLE模式{SD_SendCmd(CMD55,0,0X01);   //发送CMD55r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41}while(r1&&retry--);}}
}

OK,到这里,基本上就剩下写代码了,有关于SPI,你可以用GPIO模拟,也可以用MCU硬件自带的SPI,毫无疑问,同一个芯片硬件的SPI肯定是要快过GPIO模拟的SPI的,如果你对自己使用芯片不太了解,而且时间又很赶,在不影响用户使用的情况下,用GPIO模拟的SPI也没关系。

事实上,如果你按照上面的思路完成代码,在保证SPI时序不出问题的情况下,有大概率是卡死在SendCmd(ACMD41, 0x40000000, 0x01),细心的你可能会发现,别的地方是都CMD,为啥这里是ACMD,有啥区别?带着疑问,还是需要回到协议文档看看,复制ACMD41,搜索看看:
在文档P107页,有以下说明:

问题的答案就在这里,凡是以ACMD开头的都是特定应用程序命令,在发这些命令之前,需要发送CMD55,告知SD卡,接下来主机要发送ACMD命令,这里就不再分析CMD55命令了,直接在发ACMD41之前发送即可:SendCmd(CMD55, 0x00, 0x01)

所以,伪代码应该是下面这样:

Delay()   //等待SD卡上电稳定
Reply = 200
//等待SD卡复位成功,CMD0命令,没有参数,
//且根据文档其CRC为0x95(文档P96),复位正常应该返回0x01
While((ret = SendCmd(CMD0, 0, 0x95)) != 0x01 && Reply--);
if(ret == 0x01)  //复位成功
{Reply = 200while((ret = SendCmd(CMD8, 0x1AA, 0x87)) != 0x01 && Reply--);if(ret == 0x01){for(int i = 0;i < 4;i++)  buf[i] = SPI_ReadByte();if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V{Reply = 20000do{SendCmd(CMD55, 0x00, 0x01)ret = SendCmd(ACMD41, 0x40000000, 0x01);} While(ret != 0x00 && Reply--);  //等待SD初始化完毕ret = SendCmd(CMD58, 0x00, 0x01);if(ret == 0x00){for(i=0;i<4;i++)buf[i]=SPI_ ReadByte ();//得到OCR值if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC;   //检查CCS 标准容量卡else SD_Type=SD_TYPE_V2;         //高容量卡}}}else//SD V1.x{SD_SendCmd(CMD55,0,0x01);       //发送CMD55r1=SD_SendCmd(ACMD41,0,0x01);    //发送ACMD41if(r1<=1){SD_Type=SD_TYPE_V1;retry=0XFFFE;do //等待退出IDLE模式{SD_SendCmd(CMD55,0,0x01);   //发送CMD55r1=SD_SendCmd(CMD41,0,0x01);//发送CMD41}while(r1&&retry--);}}
}

(4)初始化代码分析

这里采用的GPIO模拟SPI时序来驱动SD卡的,再次说明,有关SPI时序分析和代码的编写,不清楚的可以我之前写过的东西:
SD/TF卡驱动(一)--------SD卡相关简介

uint8_t SD_Init(void)
{uint8_t r1;      // 存放SD卡的返回值uint16_t retry;  // 用来进行超时计数uint8_t buf[4];uint16_t i;SPI_GpioInit();for(i=0;i<20;i++)SPI_WriteReadByte(0XFF);//发送最少74个脉冲retry=20;do{r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态}while((r1!=0X01) && retry--);SD_Type=0;//默认无卡if(r1==0X01){printf("SD_SendCmd(CMD0,0,0x95) SUCCESS!\r\n");if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{//Get trailing return value of R7 respfor(i=0;i<4;i++)buf[i]=SPI_WriteReadByte(0XFF);  if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V{retry=0XFFFE;do{SD_SendCmd(CMD55,0,0X01);   //发送CMD55r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41}while(r1&&retry--);if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始{printf("SD_SendCmd(CMD41,0x40000000,0X01) SUCCESS!\r\n");for(i=0;i<4;i++)buf[i]=SPI_WriteReadByte(0XFF);//得到OCR值for(int i = 0;i < 4;i++){printf("buf[%d] %02X\r\n",i, buf[i]);}if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC;    //检查CCSelse SD_Type=SD_TYPE_V2;printf("SD_Type %02X\r\n", SD_Type);}}}else//SD V1.x/ MMC V3{SD_SendCmd(CMD55,0,0X01);       //发送CMD55r1=SD_SendCmd(CMD41,0,0X01);    //发送CMD41if(r1<=1){SD_Type=SD_TYPE_V1;retry=0XFFFE;do //等待退出IDLE模式{SD_SendCmd(CMD55,0,0X01);   //发送CMD55r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41}while(r1&&retry--);}else//MMC卡不支持CMD55+CMD41识别{SD_Type=SD_TYPE_MMC;//MMC V3retry=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();//取消片选if(SD_Type)return 0;else if(r1)return r1;return 0xFF;//其他错误
}

二、 SD卡读写流程分析

初始化SD卡过了,下面就是读写SD卡了,一样的,我们围绕文档,在文档P96页以及后面几页的内容里,详细介绍了如何读写SD卡,请仔细阅读文档。对命令的分析方式,和上面的初始化时候的思路一样,这里就不再详细叙述了,如果你初始化没问题,读写大概率是不会有问题的。

(1)SD卡读写

1.SD卡读
读SD卡有单块读,也有多块读,
单块读的流程如下:

流程总结如下:

①发送 CMD17;
②接收卡响应 R1;
③接收数据;
④接收 2 个字节的 CRC,如果不使用 CRC,这两个字节在读取后可以丢掉。

多块读的流程如下:

总结起来流程如下:

①发送 CMD18;
②接收卡响应 R1;
③接收数据;
④接收 2 个字节的 CRC,如果不使用 CRC,这两个字节在读取后可以丢掉。
⑤数据接收完成后发送 CMD12,停止数据接收,等待回应,结束

2.SD卡写
SD卡写和读类似,也有单块写,也有多块写,如下:
单块写:

①发送 CMD24;
②接收卡响应 R1;
③发送写数据起始令牌 0XFE;
④发送数据;
⑤发送 2 字节的伪 CRC;

多块写:

①发送 CMD25;
②接收卡响应 R1;
③发送写数据起始令牌 0XFE;
④发送数据;
⑤发送 2 字节的伪 CRC;
⑥判断SD卡响应
⑦结束指令

(2)SD卡读写代码分析

/** 向sd卡写入一个数据包的内容 512字节* 参数:buf:数据缓存区    cmd:指令* 返回值:0,成功;其他,失败;* */
uint8_t SD_SendBlock(uint8_t*buf,uint8_t cmd)
{uint16_t t;if(SD_WaitReady())return 1;//等待准备失效SPI_WriteReadByte(cmd);if(cmd!=0XFD)//不是结束指令{for(t=0;t<512;t++)SPI_WriteReadByte(buf[t]);//提高速度,减少函数传参时间SPI_WriteReadByte(0xFF);//忽略crcSPI_WriteReadByte(0xFF);t=SPI_WriteReadByte(0xFF);//接收响应if((t&0x1F)!=0x05)return 2;//响应错误}return 0;//写入成功
}/** 读SD卡* 参数:buf:数据缓存区*      sector:扇区*      cnt:扇区数* 返回值:0,ok;其他,失败.* */
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{uint8_t 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;//
}/** 写SD卡* 参数:buf:数据缓存区*      sector:扇区*      cnt:扇区数* 返回值:0,ok;其他,失败.* */
uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{uint8_t r1;uint8_t reply = 200;if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址if(cnt==1){r1=SD_SendCmd(CMD24,sector,0X01);//读命令if(r1==0)//指令发送成功{r1=SD_SendBlock(buf,0xFE);//写512个字节}}else{if(SD_Type!=SD_TYPE_MMC){do{SD_SendCmd(CMD55,0,0X01);
//            SD_SendCmd(CMD23,cnt,0X01);//发送指令r1 = SD_SendCmd(CMD22,0x00,0X01);}while(r1 != 0x00 && reply--);for(int i = 0;i < 4;i++)SPI_WriteReadByte(0xff);}r1=SD_SendCmd(CMD25,sector,0X01);//连续读命令if(r1==0){do{r1=SD_SendBlock(buf,0xFC);//接收512个字节buf+=512;}while(--cnt && r1==0);r1=SD_SendBlock(0,0xFD);//接收512个字节}}SD_DisSelect();//取消片选return r1;//
}

三、 完整代码

Sd_card.h

/** sd_card.h**  Created on: 2021年11月26日*      Author: heng.liao*/#ifndef HARDWARE_SD_CARD_H_
#define HARDWARE_SD_CARD_H_#include "sl_spidrv.h"
#include "em_gpio.h"
#include "sl_spidrv_spiflash_config.h"
#include "spi.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 CMD22   22
#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//数据写入回应字意义
extern uint8_t  SD_Type;         //SD卡的类型uint8_t SD_WaitReady(void);uint8_t SD_Init(void);
uint32_t SD_GetSectorCount(void);
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);
uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);#endif /* HARDWARE_SD_CARD_H_ */

Sd_card.c

/** sd_card.c**  Created on: 2021年11月26日*      Author: heng.liao*/#include "sd_card.h"
#include <stdio.h>
#include "spidrv.h"
#include "sl_udelay.h"uint8_t  SD_Type;/** 取消片选,释放SPI总线** 参数:void* 返回值:void* */
void SD_DisSelect(void)
{SD_CS_SET_GPIO_OUTPUT_STATUS(1);//SD_CS=1;SPI_WriteReadByte(0xff);//提供额外的8个时钟
}/** 等待SD卡准备好* 参数:void* 返回值:准备好了返回0  否则返回其他* */
uint8_t SD_WaitReady(void)
{uint32_t t=0;do{if(SPI_WriteReadByte(0XFF)==0XFF)return 0;//OKt++;}while(t<0XFF);//等待return 1;
}/** 选择SD卡,并且等待SD卡准备好* 参数:void* 返回值:0,成功;1,失败* */
uint8_t SD_Select(void)
{SD_CS_SET_GPIO_OUTPUT_STATUS(0);//SD_CS=0;if(SD_WaitReady()==0)return 0;//等待成功SD_DisSelect();return 1;//等待失败
}/** 向SD卡发送一个命令* 参数: u8 cmd   命令*      u32 arg  命令参数*      u8 crc   crc校验值* 返回值:SD卡返回的响应* */
uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{uint8_t r1;uint8_t Retry=0;SD_DisSelect();//取消上次片选if(SD_Select())//return 0XFF;//片选失效SD_CS_SET_GPIO_OUTPUT_STATUS(0);//发送SPI_WriteReadByte(cmd | 0x40);//分别写入命令SPI_WriteReadByte(arg >> 24);SPI_WriteReadByte(arg >> 16);SPI_WriteReadByte(arg >> 8);SPI_WriteReadByte(arg);SPI_WriteReadByte(crc);if(cmd==CMD12)SPI_WriteReadByte(0xff);//Skip a stuff byte when stop reading//等待响应,或超时退出Retry=0X1F;do{r1=SPI_WriteReadByte(0xFF);}while((r1&0X80) && Retry--);//返回状态值return r1;
}/** 等待SD卡回应* 参数:Response:要得到的回应值* 返回值:0,成功得到了该回应值*      其他,得到回应值失败* */
uint8_t SD_GetResponse(uint8_t Response)
{uint16_t Count=0xFFFF;//等待次数while ((SPI_WriteReadByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应if (Count==0)return 1;//得到回应失败else return 0;//正确回应
}/** 从sd卡读取一个数据包的内容* 参数:buf:数据缓存区*      len:要读取的数据长度.* 返回值:0,成功;其他,失败;* */
uint8_t SD_RecvData(uint8_t*buf,uint16_t len)
{if(SD_GetResponse(0xFE))return 1;//等待SD卡发回数据起始令牌0xFEwhile(len--)//开始接收数据{*buf=SPI_WriteReadByte(0xFF);buf++;}//下面是2个伪CRC(dummy CRC)SPI_WriteReadByte(0xFF);SPI_WriteReadByte(0xFF);return 0;//读取成功
}/** 获取SD卡的CID信息,包括制造商信息* 参数:u8 *cid_data(存放CID的内存,至少16Byte)* 返回值:0:NO_ERR   1:错误* */
uint8_t SD_GetCID(uint8_t *cid_data)
{uint8_t r1;//发CMD10命令,读CIDr1=SD_SendCmd(CMD10,0,0x01);if(r1==0x00){r1=SD_RecvData(cid_data,16);//接收16个字节的数据}SD_DisSelect();//取消片选if(r1)return 1;else return 0;
}/** 获取SD卡的CSD信息,包括容量和速度信息* 参数:u8 *cid_data(存放CID的内存,至少16Byte)** 返回值:0:NO_ERR*      1:错误* */
uint8_t SD_GetCSD(uint8_t *csd_data)
{uint8_t r1;r1=SD_SendCmd(CMD9,0,0x01);//发CMD9命令,读CSDif(r1==0){r1=SD_RecvData(csd_data, 16);//接收16个字节的数据}SD_DisSelect();//取消片选if(r1)return 1;else return 0;
}/** 获取SD卡的总扇区数(扇区数)** 参数:void** 返回值:0: 取容量出错*          其他:SD卡的容量(扇区数/512字节)* 每扇区的字节数必为512,因为如果不是512,则初始化不能通过.*/
uint32_t SD_GetSectorCount(void)
{uint8_t csd[16];uint32_t Capacity;uint8_t n;uint16_t csize;//取CSD信息,如果期间出错,返回0if(SD_GetCSD(csd)!=0) return 0;//如果为SDHC卡,按照下面方式计算if((csd[0]&0xC0)==0x40)  //V2.00的卡{csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;Capacity = (uint32_t)csize << 10;//得到扇区数}else//V1.XX的卡{n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;Capacity= (uint32_t)csize << (n - 9);//得到扇区数}return Capacity;
}/** SD卡初始化** 参数:void** 返回值:初始化成功返回0*      失败返回其他* */
uint8_t SD_Init(void)
{uint8_t r1;      // 存放SD卡的返回值uint16_t retry;  // 用来进行超时计数uint8_t buf[4];uint16_t i;SPI_GpioInit();for(i=0;i<20;i++)SPI_WriteReadByte(0XFF);//发送最少74个脉冲retry=20;do{r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态}while((r1!=0X01) && retry--);SD_Type=0;//默认无卡if(r1==0X01){printf("SD_SendCmd(CMD0,0,0x95) SUCCESS!\r\n");if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0{for(i=0;i<4;i++)buf[i]=SPI_WriteReadByte(0XFF);  //Get trailing return value of R7 respif(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V{retry=0XFFFE;do{SD_SendCmd(CMD55,0,0X01);   //发送CMD55r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41}while(r1&&retry--);if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始{printf("SD_SendCmd(CMD41,0x40000000,0X01) SUCCESS!\r\n");for(i=0;i<4;i++)buf[i]=SPI_WriteReadByte(0XFF);//得到OCR值for(int i = 0;i < 4;i++){printf("buf[%d] %02X\r\n",i, buf[i]);}if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC;    //检查CCSelse SD_Type=SD_TYPE_V2;printf("SD_Type %02X\r\n", SD_Type);}}}else//SD V1.x/ MMC V3{SD_SendCmd(CMD55,0,0X01);       //发送CMD55r1=SD_SendCmd(CMD41,0,0X01);    //发送CMD41if(r1<=1){SD_Type=SD_TYPE_V1;retry=0XFFFE;do //等待退出IDLE模式{SD_SendCmd(CMD55,0,0X01);   //发送CMD55r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41}while(r1&&retry--);}else//MMC卡不支持CMD55+CMD41识别{SD_Type=SD_TYPE_MMC;//MMC V3retry=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();//取消片选if(SD_Type)return 0;else if(r1)return r1;return 0xaa;//其他错误
}/** 向sd卡写入一个数据包的内容 512字节* 参数:buf:数据缓存区    cmd:指令* 返回值:0,成功;其他,失败;* */
uint8_t SD_SendBlock(uint8_t*buf,uint8_t cmd)
{uint16_t t;if(SD_WaitReady())return 1;//等待准备失效SPI_WriteReadByte(cmd);if(cmd!=0XFD)//不是结束指令{for(t=0;t<512;t++)SPI_WriteReadByte(buf[t]);//提高速度,减少函数传参时间SPI_WriteReadByte(0xFF);//忽略crcSPI_WriteReadByte(0xFF);t=SPI_WriteReadByte(0xFF);//接收响应if((t&0x1F)!=0x05)return 2;//响应错误}return 0;//写入成功
}/** 读SD卡* 参数:buf:数据缓存区*      sector:扇区*      cnt:扇区数* 返回值:0,ok;其他,失败.* */
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{uint8_t 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;//
}/** 写SD卡* 参数:buf:数据缓存区*      sector:扇区*      cnt:扇区数* 返回值:0,ok;其他,失败.* */
uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{uint8_t r1;uint8_t reply = 200;if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址if(cnt==1){r1=SD_SendCmd(CMD24,sector,0X01);//读命令if(r1==0)//指令发送成功{r1=SD_SendBlock(buf,0xFE);//写512个字节}}else{if(SD_Type!=SD_TYPE_MMC){do{SD_SendCmd(CMD55,0,0X01);
//            SD_SendCmd(CMD23,cnt,0X01);//发送指令r1 = SD_SendCmd(CMD22,0x00,0X01);}while(r1 != 0x00 && reply--);for(int i = 0;i < 4;i++)SPI_WriteReadByte(0xff);}r1=SD_SendCmd(CMD25,sector,0X01);//连续读命令if(r1==0){do{r1=SD_SendBlock(buf,0xFC);//接收512个字节buf+=512;}while(--cnt && r1==0);r1=SD_SendBlock(0,0xFD);//接收512个字节}}SD_DisSelect();//取消片选return r1;//
}

/*************************************************************************/

附:SPI源码(SPI模式0)

Spi.h

/** spi.h**  Created on: 2021年11月27日*      Author: lhsmd*/#ifndef HARDWARE_SPI_H_
#define HARDWARE_SPI_H_#include "em_gpio.h"
#include "em_cmu.h"// USART0 TX on PC00
#define SPI_MOSI_PORT               gpioPortC
#define SPI_MOSI_PIN                1// USART0 RX on PC01
#define SPI_MISO_PORT               gpioPortC
#define SPI_MISO_PIN               3// USART0 CLK on PC02
#define SPI_CLK_PORT              gpioPortC
#define SPI_CLK_PIN               0// USART0 CS on PC03
#define SPI_CS_PORT               gpioPortC
#define SPI_CS_PIN                2#define SD_CS_SET_GPIO_OUTPUT_STATUS(status)        {if(status == 1)   GPIO_PinOutSet(SPI_CS_PORT, SPI_CS_PIN);\else if(status == 0)  GPIO_PinOutClear(SPI_CS_PORT, SPI_CS_PIN);}#define SD_CLK_SET_GPIO_OUTPUT_STATUS(status)        {if(status == 1)   GPIO_PinOutSet(SPI_CLK_PORT, SPI_CLK_PIN);\else if(status == 0)  GPIO_PinOutClear(SPI_CLK_PORT, SPI_CLK_PIN);}#define SD_MOSI_SET_GPIO_OUTPUT_STATUS(status)        {if(status == 1)   GPIO_PinOutSet(SPI_MOSI_PORT, SPI_MOSI_PIN);\else if(status == 0)  GPIO_PinOutClear(SPI_MOSI_PORT, SPI_MOSI_PIN);}void SPI_GpioInit(void);
void SPI_WriteByte(uint8_t data);
uint8_t SPI_ReadByte(void);
uint8_t SPI_WriteReadByte(uint8_t data);#endif /* HARDWARE_SPI_H_ */

Spi.c

/** spi.c**  Created on: 2021年11月27日*      Author: lhsmd*/#include "spi.h"
#include "sl_udelay.h"static void SPI_Delay(uint32_t us)
{sl_udelay_wait(us);
}void SPI_GpioInit(void)
{CMU_ClockEnable(cmuClock_GPIO, true);  /* 使能GPIO时钟 */GPIO_PinModeSet(SPI_CS_PORT, SPI_CS_PIN, gpioModePushPull, 0);GPIO_PinModeSet(SPI_MOSI_PORT, SPI_MOSI_PIN, gpioModePushPull, 0);GPIO_PinModeSet(SPI_CLK_PORT, SPI_CLK_PIN, gpioModePushPull, 0);GPIO_PinModeSet(SPI_MISO_PORT, SPI_MISO_PIN, gpioModeInputPull, 0);SD_CS_SET_GPIO_OUTPUT_STATUS(1);SD_CLK_SET_GPIO_OUTPUT_STATUS(0);
}/*
* 函数名:void SPI_WriteByte(uint8_t data)
* 输入参数:data -> 要写的数据
* 输出参数:无
* 返回值:无
* 函数作用:模拟 SPI 写一个字节
*/
void SPI_WriteByte(uint8_t data)
{uint8_t i = 0;uint8_t temp = 0;for(i=0; i<8; i++){temp = ((data&0x80)==0x80)? 1:0;data = data<<1;SD_CLK_SET_GPIO_OUTPUT_STATUS(0); //SPI_CLK(0); //CPOL=0SD_MOSI_SET_GPIO_OUTPUT_STATUS(temp);//SPI_MOSI(temp);SPI_Delay(1);SD_CLK_SET_GPIO_OUTPUT_STATUS(1);//SPI_CLK(1); //CPHA=0SPI_Delay(1);}SD_CLK_SET_GPIO_OUTPUT_STATUS(0);//SPI_CLK(0);
}
/*
* 函数名:uint8_t SPI_ReadByte(void)
* 输入参数:
* 输出参数:无
* 返回值:读到的数据
* 函数作用:模拟 SPI 读一个字节
*/
uint8_t SPI_ReadByte(void)
{uint8_t i = 0;uint8_t read_data = 0xFF;for(i=0; i<8; i++){read_data = read_data << 1;SD_CLK_SET_GPIO_OUTPUT_STATUS(0);//SPI_CLK(0);SPI_Delay(1);SD_CLK_SET_GPIO_OUTPUT_STATUS(1);//SPI_CLK(1);SPI_Delay(1);if(GPIO_PinInGet(SPI_MISO_PORT, SPI_MISO_PIN)==1){read_data = read_data + 1;}}SD_CLK_SET_GPIO_OUTPUT_STATUS(0);//SPI_CLK(0);return read_data;
}/*
* 函数名:uint8_t SPI_WriteReadByte(uint8_t data)
* 输入参数:data -> 要写的一个字节数据
* 输出参数:无
* 返回值:读到的数据
* 函数作用:模拟 SPI 读写一个字节
*/
uint8_t SPI_WriteReadByte(uint8_t data)
{uint8_t i = 0;uint8_t temp = 0;uint8_t read_data = 0xFF;for(i=0;i<8;i++){temp = ((data&0x80)==0x80)? 1:0;data = data<<1;read_data = read_data<<1;SD_CLK_SET_GPIO_OUTPUT_STATUS(0);//SPI_CLK(0);SD_MOSI_SET_GPIO_OUTPUT_STATUS(temp);//SPI_MOSI(temp);SPI_Delay(1);SD_CLK_SET_GPIO_OUTPUT_STATUS(1);//SPI_CLK(1);SPI_Delay(1);if(GPIO_PinInGet(SPI_MISO_PORT, SPI_MISO_PIN)==1){read_data = read_data + 1;}}SD_CLK_SET_GPIO_OUTPUT_STATUS(0); //SPI_CLK(0);return read_data;
}

SD/TF卡驱动(二)--------SD卡程序初始化流程以及读写相关推荐

  1. linux sd卡驱动流程图,SD卡驱动(详细介绍,不明白的人可以仔细看看了.有流程图)-转-OpenEdv-开源电子网...

    ffice ffice" /> 一.SD/MMC卡介绍 1.1.什么是MMC卡 MMC:MMC就是MultiMediaCard的缩写,即多媒体卡.它是一种非易失性存储器件,体积小巧(2 ...

  2. 无法安装N卡驱动,N卡驱动安装程序闪退解决办法

    问题 朋友电脑需要459版本以上的驱动,但是无论如何无法打开驱动安装程序,或打开后卡在加载界面后闪退.在CSDN中找到有人说是证书问题,但是文章中的方法有点麻烦.所以自己用了别的方法.主要方法都是用正 ...

  3. SD/eMMC初始化流程、读写流程(dwc mshc)

    目录 1.芯片简介 1.1 模块与接口 1.2 SD/eMMC初始化流程 1.3 SD/eMMC数据传输流程 1.3.1 传输模式 2.调试问题 2.1 uboot 2.1.1 连续读4个以上bloc ...

  4. android从应用到驱动之—camera(1)---程序调用流程

    一.开篇 写博客还得写开篇介绍,可惜,这个不是我所擅长的.就按我自己的想法写吧. 话说camera模块,从上层到底层一共包含着这么几个部分: 1.apk------java语言 2.camera的ja ...

  5. RK3568平台开发系列讲解(设备驱动篇)V4L2程序实现流程

  6. S5PV210开发系列五 sd卡驱动实现

    SD卡(Secure Digital Memory Card)具有体积小.容量大.数据传输快.可插拔.安全性好等优点,被广泛应用于便携式设备上.例如作为数码相机的存储卡,作为手机.平板多媒体扩展卡用的 ...

  7. S5PV210开发系列五_sd卡驱动实现

    S5PV210开发系列五 sd卡驱动实现 象棋小子    1048272975 SD卡(Secure Digital Memory Card)具有体积小.容量大.数据传输快.可插拔.安全性好等优点,被 ...

  8. S3C2416裸机开发系列十六_sd卡驱动实现

    S3C2416裸机开发系列十六 sd卡驱动实现 象棋小子    1048272975 SD卡(Secure Digital Memory Card)具有体积小.容量大.数据传输快.可插拔.安全性好等优 ...

  9. spi总线 上层调用_spi总线的mmc卡驱动调试总结 | 学步园

    这周调试挂载在spi总线sd卡驱动,总结一下 因为这个涉及到2个驱动,spi总线驱动和sd卡驱动,sd卡设备挂载在spi总线上并不像设备挂载在i2c总线上, i2c总线提供设备挂载需要的借口函数att ...

最新文章

  1. latex 插图解释_大O符号-只需插图和视频即可解释
  2. 大数据架构+Lamba+Kappa+Unifield
  3. Codeforces Round 542 (Div. 2)
  4. cocoa mysql_基本MySQL查询
  5. 十大最急需IT技术人才榜:Java开发人员领跑
  6. 浙江师范大学python试卷_2014考研计算机真题试卷及答案(浙江师范大学考点)
  7. Java编码问题原因以及解决
  8. Asp.net 中 IHttpHandlerFactory接口 对应web.config 中的节点
  9. 20135210——信息安全系统设计基础第一周学习总结
  10. 单片机原理及应用 张鑫_单片机原理及应用 张鑫 课后习题答案 电子工业出版社 单片机原理及应用 张鑫 课后习题答案 电子工业出版社.doc...
  11. android超大屏触摸设备,世界上最大的安卓Android平板电脑具有98英寸的屏幕
  12. 【scratch案例教学】scratch端午节划龙舟比赛 scratch创意编程 少儿编程 边玩边学过个快乐端午节
  13. php毕业综合实践报告范文,php毕业实习报告
  14. 深入理解 Spring 事务原理
  15. 前端50个精美登录注册模板
  16. 解决ping值波动,一下20ms,一下上千ms的问题。
  17. 华为机试(Python)真题Od【A卷+B卷】
  18. telegram java,telegram_bot
  19. 企业项目文档库管理系统推荐
  20. Mac上Elasticsearch 安装及PHP使用

热门文章

  1. C语言改错题系列整理-非常容易0x1
  2. 操作成功失败html,固话呼叫转移设置不成功怎么办?
  3. 如何获取当前地址以及天气温度情况,适用于微信小程序(端午假期将至,祝愿大家端午快乐)
  4. Codeforces 1287C Garland
  5. 单片机c语言报错_asm啥意思,SPMC75系列单片机地C和ASM( - 控制/MCU - 电子发烧友网...
  6. bm24 bm25 sql 22 sql 24
  7. python 模拟器多开任务并行
  8. <汇编>七种寻址方式
  9. Webuploader教程(一)------简单实用上传功能
  10. 一招解决LoadRunner不能录制脚本(录制脚本为空)问题