打印
[应用相关]

【转】数码相框工程手记(二)【SD卡底层驱动 SPI模式】

[复制链接]
2858|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
figo20042005|  楼主 | 2012-7-11 10:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
硬件平台:stm32
编译环境:MDK401
驱动方式:SPI总线
下载 (12.88 KB)
2010-11-8 19:50



编写的函数如下:
1、 u8 SD_Init()                                                         SD卡初始化(复位和激活)
2、 u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc)      向SD卡发送一个命令
3、 u32 SD_GetCapacity(void)                                     获取SD卡的容量
4、 u8 SD_GetCID(u8 *cid_data)                                 获取SD卡的CID信息,包括制造商信息
5、 u8 SD_GetCSD(u8 *csd_data)                               获取SD卡的CSD信息,包括容量和速度信息
6、 u8 SD_ReadMultiBlock(u32 sector, u8 *buffer, u8 count)   读SD卡的多个block
7、 u8 SD_ReadSingleBlock(u32 sector, u8 *buffer)       读SD卡的一个block
8、 u8 SD_ReceiveData(u8 *data, u16 len, u8 release)    从SD卡中读回指定长度的数据,放置在给定位置
9、 u8 SD_WaitReady(void)                                          等待SD卡Ready
10、u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count)  写入SD卡的N个block
11、u8 SD_WriteSingleBlock(u32 sector, const u8 *data)  写入SD卡的一个block

SD卡命令   推荐SD卡命令共分为12类,分别为class0到class11,不同的SDd卡,主控根据其功能,支持不同的命令集 如下:
Class0 (卡的识别、初始化等基本命令集)
CMD0:复位SD 卡.
CMD1:读OCR寄存器.
CMD9:读CSD寄存器.
CMD10:读CID寄存器.
CMD12:停止读多块时的数据传输
CMD13:读 Card_Status 寄存器
Class2 (读卡命令集):
CMD16:设置块的长度
CMD17:读单块.
CMD18:读多块,直至主机发送CMD12为止 .
Class4(写卡命令集) :
CMD24:写单块.
CMD25:写多块.
CMD27:写CSD寄存器 .
Class5 (擦除卡命令集):
CMD32:设置擦除块的起始地址.
CMD33:设置擦除块的终止地址.
CMD38: 擦除所选择的块.
Class6(写保护命令集):
CMD28:设置写保护块的地址.
CMD29:擦除写保护块的地址.
CMD30: Ask the card for the status of the write protection bits
class7:卡的锁定,解锁功能命令集  
class8:申请特定命令集 。
class10 -11 :保留
其中 class1,    class3,class9:SPI模式不支持
SD卡命令格式如下:
下载 (48.37 KB)
2010-11-8 19:45


解释:6个字节,第一个字节最高两个BIT为01,2-5BYTE为参数,还有一个字节的CRC校验

SD卡SPI模式硬件连接:(典型连接)
下载 (30.29 KB)
2010-11-8 19:50


SD卡两种模式,引角使用情况注意:本帖所用SPI模式
下载 (78.52 KB)
2010-11-8 19:48



SPI时序如下:
下载 (57.03 KB)
2010-11-8 19:48


下载 (48.2 KB)
2010-11-8 19:48


SD卡时序如下:
下载 (76.34 KB)
2010-11-8 19:48


下载 (72.82 KB)
2010-11-8 19:48


下载 (19.86 KB)
2010-11-8 19:48


下载 (108.18 KB)
2010-11-8 19:48


下载 (104.43 KB)
2010-11-8 19:48



时序分析(这里我举一个例子)

SD卡读扇区时序
1、拉低CS,选中SD卡
2、发送命令17(从本帖的上面查)
3、命令发送完毕后不断的读取输出线,读到0xFE,表示命令成功
4、准备接收数据,0xFE是数据的开始,此后就可以接收512个字节了   (当然在初始化的时候有用命令16设置块的大小为512
5、数据接收完毕,拉高CS线
6、最后等8个时钟,你可以随便发一个BYTE就可以了(严格按照时序,大家别偷懒哦)

SD卡读扇区时序
  • u8 SD_ReceiveData(u8 *data, u16 len, u8 release)
  • {
  • u16 retry;
  • u8 r1;
  • SD_CS_Reset();  // 启动一次传输
  • //等待SD卡发回数据起始令牌0xFE
  • retry = 0;
  • do
  • {
  • r1 = SPI_ReadWriteByte(0xFF);
  • retry++;
  • if(retry>200) //200次等待后没有应答,退出报错
  • {
  • SD_CS_Set();
  • return 1;
  • }
  • }while(r1 != 0xFE);
  • //开始接收数据
  • while(len--)
  • {
  • *data = SPI_ReadWriteByte(0xFF);
  • data++;
  • }
  • //下面是2个伪CRC(dummy CRC)
  • SPI_ReadWriteByte(0xFF);
  • SPI_ReadWriteByte(0xFF);
  • //按需释放总线,将CS置高
  • if(release == RELEASE)
  • {
  • //传输结束
  • SD_CS_Set();
  • SPI_ReadWriteByte(0xFF);
  • }
  • return 0;
  • }

复制代码


SD_SendCommand
  • u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc)
  • {
  • unsigned char r1;
  • unsigned char Retry = 0;
  • // SPI_ReadWriteByte(0xff);
  • //片选端置低,选中SD卡
  • SD_CS_Reset();
  • //发送
  • SPI_ReadWriteByte(cmd | 0x40); //分别写入命令
  • SPI_ReadWriteByte(arg >> 24);
  • SPI_ReadWriteByte(arg >> 16);
  • SPI_ReadWriteByte(arg >> 8);
  • SPI_ReadWriteByte(arg);
  • SPI_ReadWriteByte(crc);
  • //等待响应,或超时退出
  • while((r1 = SPI_ReadWriteByte(0xFF))==0xFF)
  • {
  • Retry++;
  • if(Retry > 200)
  • {
  • break;
  • }
  • }
  • //关闭片选
  • //在总线上额外增加8个时钟,让SD卡完成剩下的工作
  • SPI_ReadWriteByte(0xFF);
  • SD_CS_Set();
  • //返回状态值
  • return r1;
  • }

复制代码


SD卡初始化代码:
这里我不敢保证大家的卡都能初始化成功,我自己的是可以,我发送CMD0和CMD1就可以初始化啦
网上有更兼容的初始化代码,大家可以看看哦!
调试的时候大家看返回值就可以了,哪里没成功就修改哪里哦!
  • u8 SD_Init()
  • {
  • unsigned char time,temp,i;
  • SD_CS_Set(); //关闭片选
  • for(i=0;i<0x0A;i++) //初始时,首先要发送最少74个时钟信号,这是必须的!!!
  • {
  • SPI_ReadWriteByte(0xff); //120个时钟
  • }
  • SD_CS_Reset(); //打开片选
  • time=0;
  • do
  • {
  • temp=SD_SendCommand(CMD0, 0 ,0x95);//写入CMD0  复位SD卡
  • time++;
  • if(time==200)
  • {
  • SD_CS_Set(); //关闭片选
  • }
  • }while(temp!=0x01);
  • time=0;
  • do
  • {
  • temp=SD_SendCommand(CMD1, 0 , 0xff); //写入CMD1 激活SD卡
  • time++;
  • if(time==200)
  • {
  • SD_CS_Set(); //关闭片选
  • }
  • }while(temp!=0);
  • SPI_SetSpeed(1);
  • temp = SD_SendCommand(CMD59, 0, 0x01);
  • if(temp != 0x00)
  • {
  • return temp; //命令错误,返回r1
  • }
  • temp=SD_SendCommand(CMD16,512,0xff);
  • if(temp!=0x00)
  • {
  • return temp ;
  • //命令错误,返回r1
  • }
  • SD_CS_Set(); //关闭片选
  • SPI_ReadWriteByte(0xff); //按照SD卡的操作时序在这里补8个时钟
  • return 0;
  • }

复制代码


写入SD卡的N个block
  • u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count)
  • {
  • u8 r1;
  • u16 i;
  • //设置为高速模式
  • SPI_SetSpeed(SPI_SPEED_HIGH);
  • //如果不是SDHC,给定的是sector地址,将其转换成byte地址
  • if(SD_Type != SD_TYPE_V2HC)
  • {
  • sector = sector<<9;
  • }
  • //如果目标卡不是MMC卡,启用ACMD23指令使能预擦除
  • if(SD_Type != SD_TYPE_MMC)
  • {
  • r1 = SD_SendCommand(ACMD23, count, 0x00);
  • }
  • //发多块写入指令
  • r1 = SD_SendCommand(CMD25, sector, 0x00);
  • if(r1 != 0x00)
  • {
  • return r1; //应答不正确,直接返回
  • }
  • //开始准备数据传输
  • SD_CS_Reset();
  • //先放3个空数据,等待SD卡准备好
  • SPI_ReadWriteByte(0xff);
  • SPI_ReadWriteByte(0xff);
  • //--------下面是N个sector写入的循环部分
  • do
  • {
  • //放起始令牌0xFC 表明是多块写入
  • SPI_ReadWriteByte(0xFC);
  • //放一个sector的数据
  • for(i=0;i<512;i++)
  • {
  • SPI_ReadWriteByte(*data++);
  • }
  • //发2个Byte的dummy CRC
  • SPI_ReadWriteByte(0xff);
  • SPI_ReadWriteByte(0xff);
  • //等待SD卡应答
  • r1 = SPI_ReadWriteByte(0xff);
  • if((r1&0x1F)!=0x05)
  • {
  • SD_CS_Set(); //如果应答为报错,则带错误代码直接退出
  • return r1;
  • }
  • //等待SD卡写入完成
  • if(SD_WaitReady()==1)
  • {
  • SD_CS_Set(); //等待SD卡写入完成超时,直接退出报错
  • return 1;
  • }
  • //本sector数据传输完成
  • }while(--count);
  • //发结束传输令牌0xFD
  • r1 = SPI_ReadWriteByte(0xFD);
  • if(r1==0x00)
  • {
  • count = 0xfe;
  • }
  • if(SD_WaitReady())
  • {
  • while(1)
  • {
  • }
  • }
  • //写入完成,片选置1
  • SD_CS_Set();
  • SPI_ReadWriteByte(0xff);
  • return count; //返回count值,如果写完则count=0,否则count=1
  • }

复制代码



后记:
关于SPI读写,我就不发了,大家自己写就是了,当然读扇区,写扇区都是差不多的,只是命令不同,所以我只列举了其中一个,大家把函数都写好。IO设置好,初始化成功后,就可以读一个扇区了,大家取出来,可以用WINHEX对比一下数据是否一致,一般命令成功后就没有什么大问题的。

祝大家成功!如果本帖对你有任何帮助,你要支持我哦!
如果有任何不懂的地方,请联系我   E
沙发
tao0127| | 2012-7-18 18:01 | 只看该作者
讲得很细,很好的入门资料,没人顶可惜了点。。

使用特权

评论回复
板凳
boyie| | 2012-7-18 20:32 | 只看该作者
非常好 楼主有心了

使用特权

评论回复
地板
dfsa| | 2012-7-18 21:23 | 只看该作者
文字说明好像少了点,不过还是不错的

使用特权

评论回复
5
txcy| | 2012-7-18 21:36 | 只看该作者
对SD卡底层驱动的开发应该很有帮助

使用特权

评论回复
6
无冕之王| | 2012-7-18 21:46 | 只看该作者
以前看过的匠人手记比这个写的要好

使用特权

评论回复
7
zzf119| | 2013-1-8 22:47 | 只看该作者
请问楼主,我现在用stm32控制sdhc卡,初始化都ok了,读sdhc卡的数据能读出来,但是读的数据和用winhex查看的内容不一致,,如cmd17的参数为0x00004000,用winhex看该地址的内容和sd卡读出的不一样了,求解

使用特权

评论回复
8
阿泥巴| | 2013-12-6 10:27 | 只看该作者
我顶!!!好人呀,感谢分享!

使用特权

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

本版积分规则

个人签名:need to study

16

主题

1576

帖子

3

粉丝