打印
[STM32F1]

STM32 基础篇——SD 卡实验

[复制链接]
楼主: aizaixiyuanqian
手机看帖
扫描二维码
随时随地手机跟帖
21
aizaixiyuanqian|  楼主 | 2018-3-20 16:23 | 只看该作者 回帖奖励 |倒序浏览
然后进入 SPI 模式。也就是在片选信号为低电平时,发送 CMD0。

使用特权

评论回复
22
aizaixiyuanqian|  楼主 | 2018-3-20 16:27 | 只看该作者
发送 CMD8。如果是非法命令,那么该卡是 SD 卡 V1.0 协议(MMC
卡也是得到非法回应)。如果得到正确回应,说明该卡是 SD 卡 V2.0
或者更高版本的协议。

使用特权

评论回复
23
aizaixiyuanqian|  楼主 | 2018-3-20 16:27 | 只看该作者
当 SD 卡是 V2.0 协议或者更高版本时:
a) CMD8 正确回应,读取返回值中的“提供电压”和“模式检测”,
看看是否匹配,否则卡出错。
b) 从上面的流程我们看到这里应该是在读取 CMD58 中的返回值,
以确定匹配电压,可是在 CMD8 的时候,我们也有检测过,所
以这里可以省略。
c) 发送 ACMD41。要注意,ACMD41 是特定的应用命令,所以发
送 ACMD41 的时候,要先发送 CMD55 告诉 SD 卡,接下来的命
令是特定的应用命令。
d) 发送 CMD58,读取 CCS,查看 SD 卡是高容量卡(2G 及 2G 以
上为高容量卡)还是标准卡。

使用特权

评论回复
24
aizaixiyuanqian|  楼主 | 2018-3-20 16:33 | 只看该作者
当 SD 卡是 V1.0 协议的时候:
a) 发送 CMD58,读取电压检测。如果检测电压出错,表示卡出错。
(也可以省略。)
b) 发送 ACMD41。要注意,ACMD41 是特定的应用命令,所以发
送 ACMD41 的时候,要先发送 CMD55 告诉 SD 卡,接下来的命
令是特定的应用命令。

使用特权

评论回复
25
aizaixiyuanqian|  楼主 | 2018-3-20 16:33 | 只看该作者
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
}
}
/* 发送 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[i] = 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
是否设置成功。
/* 检测是 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[i] = 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 卡的初始化。

使用特权

评论回复
26
aizaixiyuanqian|  楼主 | 2018-3-20 16:35 | 只看该作者
SD 卡读取数据操作
SD 卡读取数据有两个命令,一个命令是 CMD17:读取一个扇区(一般
为 512 字节);另一个是 CMD18:读取多个扇区。

使用特权

评论回复
27
aizaixiyuanqian|  楼主 | 2018-3-20 16:35 | 只看该作者
单个扇区读取的步骤为:
1) 发送 CMD17。(命令参数是读取扇区地址)
2) 检测卡响应是否发送成功。
3) 等待起始令牌 0xFE。
4) 接收一个扇区的数据。
5) 接收两个字节的 CRC 效验。

使用特权

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

使用特权

评论回复
29
aizaixiyuanqian|  楼主 | 2018-3-20 16:36 | 只看该作者
读取扇区的程序实现如下:
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;
}

使用特权

评论回复
30
aizaixiyuanqian|  楼主 | 2018-3-20 16:37 | 只看该作者
这个程序还调用一个 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;
}

使用特权

评论回复
31
Nivans| | 2018-3-21 10:54 | 只看该作者
aizaixiyuanqian 发表于 2018-3-20 16:20
CMD8
CMD8 的应答是 R7,它返回的数据类型如下

楼主有IDE硬盘接口的资料吗?

使用特权

评论回复
32
aizaixiyuanqian|  楼主 | 2018-3-23 14:36 | 只看该作者
Nivans 发表于 2018-3-21 10:54
楼主有IDE硬盘接口的资料吗?

这个没有,可以去相关产品那里去找下

使用特权

评论回复
33
aizaixiyuanqian|  楼主 | 2018-3-23 14:36 | 只看该作者
SD 卡写入数据也有两个命令,一个命令是 CMD24:写入一个扇区(一
般为 512 字节);另一个是 CMD25:写入多个扇区。(MMC 卡写入多个扇区
之前要先试用特定命令 CMD23 擦除扇区,写入特定命令之前,记得要先发
送命令 CMD55。)

使用特权

评论回复
34
aizaixiyuanqian|  楼主 | 2018-3-23 14:37 | 只看该作者
写入一个扇区的操作步骤为:
1) 发送 CMD24(命令参数是写入扇区地址)
2) 检测返回值,查看 CMD24 是否发送成功。
3) 然后发送若干个时钟,同时读取返回值,知道返回值不是 0xFF
4) 发送起始令牌 0xFE。
5) 发送一个扇区的数据。
6) 发送 2 个字节的 CRC 效验。
7) 读取返回值,判断是否写入成功。

使用特权

评论回复
35
aizaixiyuanqian|  楼主 | 2018-3-23 14:39 | 只看该作者
发送 CMD25(命令参数是写入扇区地址)。如果是 MMC 卡,就先
发送 CMD55 和 CMD23 擦除扇区。

使用特权

评论回复
36
aizaixiyuanqian|  楼主 | 2018-3-23 14:40 | 只看该作者
检测返回值,查看 CMD25 是否发送成功。
然后一个扇区一个扇区的写入数据,这个部分的步骤为:
a) 然后发送若干个时钟,同时读取返回值,知道返回值不是 0xFF
b) 发送起始令牌 0xFE。
c) 发送一个扇区的数据。
d) 发送 2 个字节的 CRC 效验。
e) 读取返回值,判断是否写入成功。
f)  如果没有写入完成,那么返回 a)继续写入;如果写入完成,那么
跳到下一步。

使用特权

评论回复
37
aizaixiyuanqian|  楼主 | 2018-3-23 14:41 | 只看该作者
然后发送若干个时钟,同时读取返回值,知道返回值不是 0xFF
发送结束令牌 0xFD。

使用特权

评论回复
38
aizaixiyuanqian|  楼主 | 2018-3-23 14:42 | 只看该作者
写多个扇区
int8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint8_t num)
{
uint8_t i;
if(SD_TYPE != SD_TYPE_V2HC)
{
sector <<= 9; //转换位字节地址
}
SPI2_SetSpeed(SPI_BaudRatePrescaler_2);
/* 只写一个扇区 */
if(num == 1)
{
/* 发送写命令 */
i = 0;
while(SD_WriteCmd(SD_CMD24, sector, 0x01) != 0);
{
i++;
if(i > 100)
{
return 0xFF; //命令无反应,表明发送命令失败
}
}
if(SD_WriteData(buf, 0xFE) != 0)
{
return 0xFF;
}
}

使用特权

评论回复
39
aizaixiyuanqian|  楼主 | 2018-3-23 14:48 | 只看该作者
/* 写多个扇区 */
else
{
if(SD_TYPE == SD_TYPE_MMC)  //如果是 MMC 卡
{
SD_WriteCmd(SD_CMD55, 0, 0X01);
SD_WriteCmd(SD_CMD23, num, 0X01); //写入前先擦除 num 个
扇区里面的数据
}
/* 发送连续写命令 */
i = 0;
while(SD_WriteCmd(SD_CMD25, sector, 0x01) != 0);
{
i++;
if(i > 100)
{
return 0xFF; //命令无反应,表明发送命令失败
}
}

使用特权

评论回复
40
aizaixiyuanqian|  楼主 | 2018-3-23 14:49 | 只看该作者
本帖最后由 aizaixiyuanqian 于 2018-3-23 14:51 编辑

/* 开始写数据 */
while(num--)
{
if(SD_WriteData(buf, 0xFC) != 0)
{
return 0xFF;
}
buf += 512;
}
/* 发送若干个时钟,等待 SD 卡准备好 */
i = 0;
while(SPI2_WriteReadData(0xFF) != 0xFF)
{
if(i > 0x0FFF)
{
return 0xFF;
}
}/* 发送结束命令 */
SPI2_WriteReadData(0xFD);
}
SD_CS_SET;
SPI2_WriteReadData(0xFF);
return 0;
}

使用特权

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

本版积分规则