打印
[STM32F1]

ST分享大集结 stm32 SD卡分享

[复制链接]
3819|56
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 hanzhen654 于 2017-11-23 12:12 编辑

SD 卡的原理图,采用SPI总线。

1.png (88.02 KB )

1.png
沙发
hanzhen654|  楼主 | 2017-11-23 11:59 | 只看该作者
SD 卡又两种操作方式,一种是 SDIO 模式,还有一种是 SPI 模式,我使用 SPI 模式。SD 卡部分的跳帽跳到 IO 部分。

使用特权

评论回复
板凳
hanzhen654|  楼主 | 2017-11-23 12:04 | 只看该作者
SD 卡简介
SD 卡是由 MMC 卡的基础上发展而来的,它是一种基于半导体快闪**器的新一代**设备,被广泛应用于各种便携设备,如手机、数码相机、多媒体播放器等。SD 卡可以分为三类:SD 卡、SDHC 卡、SDXC 卡:它的通信协议也分为两种,一种是 V1.0 版本和 V2.0 版本,所以不同的版本操作过程中会有一定的差异。对于 SD 卡和 SDHC 卡来说,协议基本是兼容的,不过 SDXC 卡,区别就大了。在这里我们主要讨论的是 SD 卡和 SDHC 卡。而SD 卡的通信接口也有两种模式,一种是 SDIO 模式,还有一种是 SPI 模式,这里我们主要讨论的是 SPI 模式下的 SD 卡。使用 STM32 的 SPI 驱动 SD 卡,最高通信速度可达 18Mbps,每秒可传输数据 2M 字节以上,对于一般的应用足够了。

2.png (81.76 KB )

2.png

使用特权

评论回复
地板
hanzhen654|  楼主 | 2017-11-23 12:07 | 只看该作者
SD 卡的操作
要操作 SD 卡,最重要的就是如何初始化 SD 卡、读 SD 卡、写 SD 卡。其他的读取 SD 卡的其他型号信息等,都是次要的,只有偶尔才会用到。而学习初始化 SD 卡,读 SD 卡之前,我们要先学会怎么向 SD 写命令。
1. SD 卡写命令操作

SD 卡的命令格式如下:如图3
长度为 48 位,一共分为第一个字节的最高 2 位必须是 01。接下来 6 位是命令号,比如:CMD0 其实就是 0x00;CMD16 其实是 0x10。第 2 个字节到第 5个字节是命令参数,命令参数是特定的命令才会有的。剩下第 6 个字节是一个 7位的 CRC 效验和最低位为 1 组成。


3.png (42.13 KB )

3.png

使用特权

评论回复
5
hanzhen654|  楼主 | 2017-11-23 12:10 | 只看该作者
接下来我们来看一下 SD 卡的一些命令参数 见图4
从图4我们可以看出,每个命令后面都会有一个回应,所以 SD 卡和单片机之间的通信是采用发送应答机制。它的应答类型有 R1~R7,一共 7 种。不同的命令,长度可能不一样,下面我比较 R1 和 R2。我们可以看出,R1 的长度为 48 位,而 R2 的长度为 136 位。不过虽然回应的长度不同,不过呢,它们的应答都有一个特点,那就是最高两位都为00(大家可以通过查看《SD 卡 2.0 协议》等对比),这个时候表示命令发送成功。

4.png (150.17 KB )

4.png

使用特权

评论回复
6
hanzhen654|  楼主 | 2017-11-23 12:14 | 只看该作者
从上面的介绍,我们就可以总结出,我们写命令的操作步骤为:
1) 如果上次片选没有取消,那么先取消片选,取消片选之后提供格外
的 8 个时钟周期,方便 SD 卡准备好。
2) 选择片选。
3) 检测 SD 卡是否准备好,也就是一直发送 0xFF,直到接收到非 0xFF
为止。
4) 发送 6 个字节的命令格式。包括 1 个字节命令序号,4 个字节的命令
参数,1 个字节的 CRC 效验。注意第 1 个字节最高两位为 01。第 6
个字节最低位为 1。
5) 如果是 CMD12 停止数据传输命令的话,多发送 8 个时钟周期。也就
是发送 0xFF 一次,为了避免影响之后检测是否发送成功。
6) 检测是否发送命令成功。因为我们不知道回应有多少个字节,所以
一直读取回应数据,直到最高两位为 00 时,说明发送成功。

使用特权

评论回复
7
hanzhen654|  楼主 | 2017-11-23 12:15 | 只看该作者
程序实现如下
static uint8_t SD_WriteCmd(uint8_t cmd, uint32_t dat, uint8_t crc)
{
uint8_t r1 = 0;
uint16_t i = 0;
//--复位 SD 卡,取消上次片选--//
SD_CS_SET;
SPI2_WriteReadData(0xFF); //额外提供 8 个时钟
SD_CS_CLR;
while(SPI2_WriteReadData(0xFF) != 0xFF) //等待卡是否准备好
{
i++;
if(i > 100)
{
return 0xFF; //等待失败返回
}
}
//--发送数据--//
SPI2_WriteReadData(cmd | 0x40);
SPI2_WriteReadData(dat >> 24); //发送 Dat 的最高 8 位
SPI2_WriteReadData(dat >> 16);
SPI2_WriteReadData(dat >> 8);
SPI2_WriteReadData(dat & 0x00FF);
SPI2_WriteReadData(crc & 0x01);
if(cmd == SD_CMD12) //如果是停止数据传输命令,额外多发
一个时钟
{
SPI2_WriteReadData(0xFF);
}
i = 0;
do
{
r1 = SPI2_WriteReadData(0xFF);
i++;
if(i > 100)
{
return 0xFF;
}
}
while((r1 & 0x80) != 0); //发送成功的最高位是 0
return r1;
}

使用特权

评论回复
8
mmuuss586| | 2017-11-23 12:45 | 只看该作者

不错,谢谢分享;

使用特权

评论回复
9
21ic小管家| | 2017-11-23 13:39 | 只看该作者
回复你的帖子名称 和链接到活动帖里面哦~~

使用特权

评论回复
10
hanzhen654|  楼主 | 2017-11-23 22:24 | 只看该作者
好的,感谢小管家。

使用特权

评论回复
11
hanzhen654|  楼主 | 2017-11-23 22:24 | 只看该作者
mmuuss586 发表于 2017-11-23 12:45
不错,谢谢分享;

不客气,大家都应该懂得分享。

使用特权

评论回复
12
hanzhen654|  楼主 | 2017-11-23 22:29 | 只看该作者
SD 卡初始化操作
在《SD 卡 2.0 协议.pdf》中,有 SPI 模式的初始化流程,它的流程如下:它这里分为两种类型的卡初始化,一种 SD 卡 V1.0 协议的初始化和 SD卡 V2.0 协议的初始化。如图5,我们可以看出初始化的时候我们要用到CMD0、CMD8、ACMD41、CMD58。其中 CMD8、CMD58 都要查看相应应答,这里我们来讲一下这些命令的应答。

5.png (262.05 KB )

5.png

使用特权

评论回复
13
hanzhen654|  楼主 | 2017-11-23 22:32 | 只看该作者
1.CMD8
CMD8 的应答是 R7,它返回的数据类型如下:在这里,我们要确定 SD 卡的电压是否匹配(即:[19:16]voltage supplied)以及“模式检测”(即[15:8]check pattern)。提供电压的一般的返回是:它的 4 位是 0001b 的时候,就是匹配电压了。而“模式检测”它的正确返回值是:10101010b。所以我们要读取的 voltage supplide 就是返回值的第 4 个字节的低四位。(注意返回的时候,它是先从高字节返回的。)而要读取的 check pattern 是第5 个字节。
2) CMD58。
CMD58 的返回值是 R3,R3 的数据类型如下:其中我们要读取的就是 OCR,而 OCR 的结构为:所以我们知道,我们要读取的 CCS 是在返回值的第 2 个字节(OCR的第 30 位,注意的是它返回的时候是从高字节开始返回的,所以是第 2个字节,第 1 个字节是 R1);而要确认电压是在返回值的第 5 个字节的
第 7 位。
现在我们就来看一下初始化的具体步骤为:
1) 上电之后初始化之后,进行至少 74 个时钟周期的上电延时,这个是必须的(具体大家可以查看数据手册复位部分)。
2) 然后进入 SPI 模式。也就是在片选信号为低电平时,发送 CMD0。
3) 发送 CMD8。如果是非法命令,那么该卡是 SD 卡 V1.0 协议(MMC卡也是得到非法回应)。如果得到正确回应,说明该卡是 SD 卡 V2.0或者更高版本的协议。
4) 当 SD 卡是 V2.0 协议或者更高版本时:
a) CMD8 正确回应,读取返回值中的“提供电压”和“模式检测”,看看是否匹配,否则卡出错。
b) 从上面的流程我们看到这里应该是在读取 CMD58 中的返回值,以确定匹配电压,可是在 CMD8 的时候,我们也有检测过,所以这里可以省略。
c) 发送 ACMD41。要注意,ACMD41 是特定的应用命令,所以发送 ACMD41 的时候,要先发送 CMD55 告诉 SD 卡,接下来的命令是特定的应用命令。
d) 发送 CMD58,读取 CCS,查看 SD 卡是高容量卡(2G 及 2G 以上为高容量卡)还是标准卡。当 SD 卡是 V1.0 协议的时候:
a) 发送 CMD58,读取电压检测。如果检测电压出错,表示卡出错。(也可以省略。)
b) 发送 ACMD41。要注意,ACMD41 是特定的应用命令,所以发送 ACMD41 的时候,要先发送 CMD55 告诉 SD 卡,接下来的命
令是特定的应用命令。

使用特权

评论回复
14
hanzhen654|  楼主 | 2017-11-23 22:32 | 只看该作者
SD 卡的初始化步骤就如上述,而程序实现如下:
int8_t SD_Init(void)
{
uint8_t r1, buf[4];
uint16_t i = 0;
SD_GPIO_Config();
SPI2_SetSpeed(SPI_BaudRatePrescaler_256);
/* 将 SD 卡通信模式转换为 SPI 模式,上电默认是用 DAT0 作数据线 */
/* 接到 CMD0 时,CS 信号有效,SPI 模式启动 */
for(i=0; i<0x0F; i++)//初始时,先发送至少 74 个时钟,这个是必须的。
{
SPI2_WriteReadData(0xFF); //发送 8*16 个
}
/* 当读取到 0x01 的时候表示初始化成功 */
i = 0;
while(SD_WriteCmd(SD_CMD0, 0, 0x95) != 0x01)
{
i++;
if(i > 100)
{
return 0xFF; //初始化失败返回 0
}
}

使用特权

评论回复
15
hanzhen654|  楼主 | 2017-11-23 22:36 | 只看该作者
/* 发送 CMD8,检测是否 SD V2.0 */
i = 0;
do
{
i++;
if(i > 100)  //若是发送超过次数跳出循环管
{
break;
}
r1 = SD_WriteCmd(SD_CMD8, 0x01AA, 0x87);
}
while(r1 != 0x01); //发送 CMD8
if(r1 == 0x01) //如果 CMD8 有回应说明是 SD V2.0 协议
{
/* 读取 CMD8 的返回值,检测是否支持电压 */
/* 读取 CMD8 的返回值,检测是否支持电压 */
for(i=0; i<4; i++)
{
buf = SPI2_WriteReadData(0xFF);
}
/* 卡电压不支持电压,返回错误 */
if((buf[2] != 0x01) || (buf[3] != 0xAA))
{
return 0xFF;
}
/* 初始化 SD 卡 */
i = 0;
do
{
i++;
if(i > 100)
{
return 0xFF; //返回失败
}
SD_WriteCmd(SD_CMD55, 0, 0x01);
r1 = SD_WriteCmd(SD_CMD41, 0x40000000, 0x01);
}
while(r1 != 0); //写入 CMD41 成功了,这里省略读取 CMD41
是否设置成功。

使用特权

评论回复
16
hanzhen654|  楼主 | 2017-11-23 22:38 | 只看该作者
/* 检测是 SDHC 卡还是 SD 卡 */
i = 0;
while(SD_WriteCmd(SD_CMD58, 0, 0x01) != 0)
{
i++;
if(i > 100)
{
SD_TYPE = SD_TYPE_ERR;
break;
}
}
/* 读取 OCR */
for(i=0; i<4; i++)
{
buf = SPI2_WriteReadData(0xFF);
}
if(buf[0] & 0x40)
{
SD_TYPE = SD_TYPE_V2HC;
}
else
{
SD_TYPE = SD_TYPE_V2;
}
}
else //否则就是 SD V1.0 或者 MMC V3
{
SD_WriteCmd(SD_CMD55, 0x00, 0x01);
r1 = SD_WriteCmd(SD_CMD41, 0x00, 0x01);
if(r1 <= 1) //对 CMD41 有回应说明是 SD V1.0
{
SD_TYPE = SD_TYPE_V1; //是 V1.0 卡
i = 0;
do
{
if(i > 100)
{
return 0xFF;
}
SD_WriteCmd(SD_CMD55, 0x00, 0x01);
r1 = SD_WriteCmd(SD_CMD41, 0x00, 0x01);
}
while(r1 != 0);
}
else //没有回应说明是 MMC V3
{
SD_TYPE = SD_TYPE_MMC;  //卡类型是 MMC 卡
i = 0;
while(SD_WriteCmd(SD_CMD1, 0, 0x01) != 0)
{
i++;
if(i > 100)
{
return 0xFF;
}
}
}
}
SD_CS_SET;  //取消片选
SPI2_WriteReadData(0xFF);
return 0;
}
以上代码是对 MMC 卡的初始化。

使用特权

评论回复
17
hanzhen654|  楼主 | 2017-11-23 23:10 | 只看该作者
SD 卡读取数据操作
SD 卡读取数据有两个命令,一个命令是 CMD17:读取一个扇区(一般
为 512 字节);另一个是 CMD18:读取多个扇区。
单个扇区读取的步骤为:
1) 发送 CMD17。(命令参数是读取扇区地址)
2) 检测卡响应是否发送成功。
3) 等待起始令牌 0xFE。
4) 接收一个扇区的数据。
5) 接收两个字节的 CRC 效验。
多个扇区读取的步骤为:
1) 发送 CMD18。(命令参数是读取扇区地址)
2) 检测卡响应是否发送成功。
3) 然后一个扇区一个扇区的接收数据,这个部分的步骤为:
a) 等待起始令牌 0xFE。
b) 接收一个扇区的数据。
c) 接收两个字节的 CRC 效验。
d) 接收完一个扇区,如果没有结束,返回 a)继续接收;接收结束,
跳到下一步。
4) 发送停止命令 CMD12。

使用特权

评论回复
18
hanzhen654|  楼主 | 2017-11-23 23:11 | 只看该作者
读取扇区的程序实现如下:
int8_t SD_ReadDisk(uint8_t *buf, uint32_t sector, uint8_t num)
{
uint16_t i;
if(SD_TYPE != SD_TYPE_V2HC)
{
sector <<= 9; //转换位字节地址
}
SPI2_SetSpeed(SPI_BaudRatePrescaler_2);
if(num == 1)
{
/* 发送读取命令 */
i = 0;
while(SD_WriteCmd(SD_CMD17, sector, 0x01) != 0);
{
i++;
if(i > 100)
{
return 0xFF; //命令无反应,表明发送命令失败
}
}
/* 接收数据 */
if(SD_ReadData(buf) != 0)
{
return 0xFF;
}
}
else
{
/* 发送连续读取命令 */
i = 0;
while(SD_WriteCmd(SD_CMD18, sector, 0x01) != 0);
{
i++;
if(i > 100)
{
return 0xFF; //命令无反应,表明发送命令失败
}
}
/* 接收数据 */
while(num--)
{
/* 接收数据 */
if(SD_ReadData(buf) != 0)
{
return 0xFF;
}
buf += 512;
}
SD_WriteCmd(SD_CMD12, 0, 0x01); //发送停止信号
}
/* 取消片选 */
SD_CS_SET;
SPI2_WriteReadData(0xFF);
return 0;
}

使用特权

评论回复
19
hanzhen654|  楼主 | 2017-11-23 23:11 | 只看该作者
这个程序还调用一个 SD_ReadData()函数,这个函数是接收一个扇区数据的
函数,具体代码如下:
static int8_t SD_ReadData(uint8_t *buf)
{
uint16_t i;
/* 等待起始令牌 0XFE */
i = 0;
while(SPI2_WriteReadData(0xFF) != 0xFE)
{
i++;
if(i > 0x0FFF)
{
return 0xFF;
}
}
/* 接收数据 */
for(i=0; i<512; i++)
{
*buf = SPI2_WriteReadData(0xFF);
buf++;
}
/* 读完数据再读两位 CRC 效验,但是我们可以不需要它们 */
SPI2_WriteReadData(0xFF);
SPI2_WriteReadData(0xFF);
return 0;
}

使用特权

评论回复
20
minibmw888| | 2017-11-24 10:19 | 只看该作者
感谢楼主分享!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

73

主题

1766

帖子

2

粉丝