打印

【连载】STM32开发指南--第四十四章 SD卡实验

[复制链接]
6607|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
正点原子|  楼主 | 2013-4-4 23:02 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子 于 2013-4-4 23:04 编辑

第四十四章  SD卡实验

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有U盘,FLASH芯片,SD卡等。他们各有优点,综合比较,最适合单片机系统的莫过于SD卡了,它不仅容量可以做到很大(32Gb以上),而且支持SPI接口,方便移动,并且有几种体积的尺寸可供选择(标准的SD卡尺寸,以及TF卡尺寸等),能满足不同应用的要求。
只需要4个IO口即可外扩一个最大达32GB以上的外部存储器,容量从几十M到几十G选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。
ALIENTKE 战舰STM32开发板自带了标准的SD卡接口,可使用STM32自带的SPI/SDIO接口驱动(通过跳线帽选择驱动方式),本章我们使用SPI驱动,最高通信速度可达18Mbps,每秒可传输数据2M字节以上,对于一般应用足够了。在本章中,我们将向大家介绍,如何在ALIENTEK战舰STM32开发板上实现SD卡的读取。本章分为如下几个部分:
44.1 SD卡简介
44.2 硬件设计
44.3 软件设计
44.4 下载验证

44.1 SD卡简介
SD卡(Secure Digital Memory Card)中文翻译为安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪**器的新一代**设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。大小犹如一张邮票的SD**卡,重量只有2克,但却拥有高**容量、快速数据传输率、极大的移动灵活性以及很好的安全性。按容量分类,可以将SD卡分为3类:SD卡、SDHC卡、SDXC卡。如表44.1.1所示:
容量

命名

简称

0~2G

Standard Capacity SD Memory Card

SDSC或SD

2G~32G

High Capacity SD Memory Card

SDHC

32G~2T

Extended Capacity SD Memory Card

SDXC

表44.1.1 SD卡按容量分类
SD卡和SDHC卡协议基本兼容,但是SDXC卡,同这两者区别就比较大了,本章我们讨论的主要是SD/SDHC卡(简称SD卡)。
SD卡一般支持2种操作模式:
1,SD卡模式(通过SDIO通信);
2,SPI模式;
主机可以选择以上任意一种模式同SD卡通信,SD卡模式允许4线的高速数据传输。SPI模式允许简单的通过SPI接口来和SD卡通信,这种模式同SD卡模式相比就是丧失了速度。
SD卡的引脚排序如下图44.1.1所示:


图44.1.1 SD卡引脚排序图
SD卡引脚功能描述如表45.1.2所示:

表45.1.2 SD卡引脚功能表
SD卡只能使用3.3V的IO电平,所以,MCU一定要能够支持3.3V的IO端口输出。注意:在SPI模式下,CS/MOSI/MISO/CLK都需要加10~100K左右的上拉电阻。
SD卡有5个寄存器,如表45.1.3所示:
名称

宽度

描述

CID

128

卡标识寄存器

RCA

16

相对卡地址(Relative card address)寄存器:本地系统中卡的地址,动态变化,在主机初始化的时候确定
*SPI模式中没有

CSD

128

卡描述数据:卡操作条件相关的信息数据

SCR

64

SD配置寄存器:SD卡特定信息数据

OCR

32

操作条件寄存器

表45.1.3 SD卡相关寄存器
关于这些寄存器的详细描述,请参考光盘相关SD卡资料。我们在这里就不描述了。接下来,我们看看SD卡的命令格式,如表45.1.4所示:
字节1

字节2--5

字节6

7

6

5      0

31       0

7       1

0

0

1

command

命令参数

CRC

1

表45.1.4 SD卡命令格式
SD卡的指令由6个字节组成,字节1的最高2位固定为01,低6位为命令号(比如CMD16,为10000即16进制的0X10,完整的CMD16,第一个字节为01010000,即0X10+0X40)。
字节2~5为命令参数,有些命令是没有参数的。
字节6的高七位为CRC值,最低位恒定为1。
SD卡的命令总共有12类,分为Class0~Class11,本章,我们仅介绍几个比较重要的命令,如表45.1.5所示:
命令

参数

回应

描述

CMD0(0X00)

NONE

R1

复位SD卡

CMD8(0X08)

VHS+Check pattern

R7

发送接口状态命令

CMD9(0X09)

NONE

R1

读取卡特定数据寄存器

CMD10(0X0A)

NONE

R1

读取卡标志数据寄存器

CMD16(0X10)

块大小

R1

设置块大小(字节数)

CMD17(0X11)

地址

R1

读取一个块的数据

CMD24(0X18)

地址

R1

写入一个块的数据

CMD41(0X29)

NONE

R3

发送给主机容量支持信息和激活
卡初始化过程

CMD55(0X37)

NONE

R1

告诉SD卡,下一个是特定应用命令

CMD58(0X3A)

NONE

R3

读取OCR寄存器

表45.1.5 SD卡部分命令
上表中,大部分的命令是初始化的时候用的。表中的R1、R3和R7等是SD卡的回应,SD卡和单片机的通信采用发送应答机制,如图45.1.2所示:
图45.1.2 SD卡命令传输过程
       每发送一个命令,SD卡都会给出一个应答,以告知主机该命令的执行情况,或者返回主机需要获取的数据。SPI模式下,SD卡针对不同的命令,应答可以使R1~R7,R1的应答,各位描述如表45.1.6所示:



R1响应格式


7

6

5

4

3

2

1

0

含义

开始位
始终为0

参数
错误

地址
错误

擦除序列
错误

CRC错误

非法
命令

擦除
复位

闲置
状态

表45.1.6 R1响应各位描述
       R2~R7的响应,我们就不介绍了,请的大家参考SD卡2.0协议。接下来,我们看看SD卡初始化过程。因为我们使用的是SPI模式,所以先得让SD卡进入SPI模式。方法如下:在SD卡收到复位命令(CMD0)时,CS为有效电平(低电平)则SPI模式被启用。不过在发送CMD0之前,要发送>74个时钟,这是因为SD卡内部有个供电电压上升时间,大概为64个CLK,剩下的10个CLK用于SD卡同步,之后才能开始CMD0的操作,在卡初始化的时候,CLK时钟最大不能超过400Khz!。
接着我们看看SD卡的初始化,SD卡的典型初始化过程如下:
1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
2、上电延时(>74个CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58和CMD1等);
6、取消片选,发多8个CLK,结束初始化
这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。
SD卡读取数据,这里通过CMD17来实现,具体过程如下:
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌0XFE;
4、接收数据;
5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个CLK;
以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下:
1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;
以上就是一个典型的写SD卡过程。关于SD卡的介绍,我们就介绍到这里,更详细的介绍请参考光盘SD卡的参考资料(SD卡2.0协议)。
44.2 硬件设计
本章实验功能简介:开机的时候先初始化SD卡,如果SD卡初始化完成,则提示LCD初始化成功。按下KEY0,读取SD卡扇区0的数据,然后通过串口发送到电脑。如果没初始化通过,则在LCD上提示初始化失败。 同样用DS0来指示程序正在运行。
本实验用到的硬件资源有:
1)  指示灯DS0
2)  KEY0按键
3)  串口
4)  TFTLCD模块
5)  SD卡
前面四部分,在之前的实例已经介绍过了,这里我们介绍一下战舰STM32开发板板载的SD卡接口和STM32的连接关系,如图44.2.1所示:

图44.2.1 SD卡接口与STM32连接原理图
       我们用跳线帽将P10的SD_DT3、SD_CMD、SD_SCK、SD_DT0分别同P12的SD_CS、SPI2_MOSI、SPI2_SCK、SPI2_MISO连接起来,即实现SD卡的SPI模式连接。硬件连接示意图如图44.2.2所示:

图44.2.2 SD卡SPI方式硬件连接示意图
       将图中所示的4处,用跳线帽短接,接口实现SD卡与STM32的SPI连接。最后,你还得自备一个SD卡,将其插入板子下面的SD卡接口。
实验39 SD卡实验.rar (194.65 KB)

《STM32开发指南》第四十四章 SD卡实验.rar (686.87 KB)
沙发
正点原子|  楼主 | 2013-4-4 23:02 | 只看该作者
44.3 软件设计
打开上一章的工程,首先在HARDWARE文件夹下新建一个SD的文件夹。然后新建一个MMC_SD.C和MMC_SD.H的文件保存在SD文件夹下,并将这个文件夹加入头文件包含路径。
打开MMC_SD.C文件,在该文件里面,我们输入与SD卡相关的操作代码,这里由于篇幅限制,我们不贴出所有代码,仅介绍两个最重要的函数,第一个是SD_Initialize函数,该函数源码如下:                                             
//初始化SD卡
u8 SD_Initialize(void)
{
    u8 r1;      // 存放SD卡的返回值
    u16 retry;  // 用来进行超时计数
    u8 buf[4];
       u16 i;
       SD_SPI_Init();             //初始化IO
      SD_SPI_SpeedLow();    //设置到低速模式
      for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XFF);//发送最少74个脉冲
       retry=20;
       do
       {
              r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态
       }while((r1!=0X01) && retry--);
      SD_Type=0;//默认无卡
       if(r1==0X01)
       {
              if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
              {
                     for(i=0;i<4;i++)buf=SD_SPI_ReadWriteByte(0XFF);//得到R7相应值      
                     if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
                     {
                            retry=0XFFFE;
                            do
                            {
                                   SD_SendCmd(CMD55,0,0X01);   //发送CMD55
                                   r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41
                            }while(r1&&retry--);
                            if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
                            {
                                   for(i=0;i<4;i++)buf=SD_SPI_ReadWriteByte(0XFF);//得到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);          //发送CMD55
                     r1=SD_SendCmd(CMD41,0,0X01);     //发送CMD41
                     if(r1<=1)
                     {           
                            SD_Type=SD_TYPE_V1;
                            retry=0XFFFE;
                            do //等待退出IDLE模式
                            {
                                   SD_SendCmd(CMD55,0,0X01);   //发送CMD55
                                   r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41
                            }while(r1&&retry--);
                     }else//MMC卡不支持CMD55+CMD41识别
                     {
                            SD_Type=SD_TYPE_MMC;//MMC V3
                            retry=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();//取消片选
       SD_SPI_SpeedHigh();//高速
       if(SD_Type)return 0;
       else if(r1)return r1;         
       return 0xaa;//其他错误
}                                                                                                                                
该函数先设置与SD相关的IO口及SPI初始化,然后发送CMD0,进入IDLE状态,并设置SD卡为SPI模式通信,然后判断SD卡类型,完成SD卡的初始化,注意该函数调用的SD_SPI_Init等函数,实际是对SPI2的相关函数进行了一层封装,方便移植。另外一个要介绍的函数是SD_ReadDisk,该函数用于从SD卡读取一个扇区的数据(这里一般为512字节),该函数代码如下:                                                                        
//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
       u8 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;//
}
此函数先发送CMD17命令,然后读取一个扇区的数据,详细见代码,这里我们就不多介绍了。保存MMC_SD.C文件,并加入到HARDWARE组下,然后打开MMC_SD.H,在该文件里面输入如下代码:
#ifndef _MMC_SD_H_
#define _MMC_SD_H_      
#include "sys.h"   
#include <stm32f10x_map.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 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
//数据写入回应字意义
#define MSD_DATA_OK                      0x05
#define MSD_DATA_CRC_ERROR            0x0B
#define MSD_DATA_WRITE_ERROR         0x0D
#define MSD_DATA_OTHER_ERROR         0xFF
//SD卡回应标记字
#define MSD_RESPONSE_NO_ERROR        0x00
#define MSD_IN_IDLE_STATE                 0x01
#define MSD_ERASE_RESET                 0x02
#define MSD_ILLEGAL_COMMAND        0x04
#define MSD_COM_CRC_ERROR            0x08
#define MSD_ERASE_SEQUENCE_ERROR   0x10
#define MSD_ADDRESS_ERROR             0x20
#define MSD_PARAMETER_ERROR        0x40
#define MSD_RESPONSE_FAILURE          0xFF            
//这部分应根据具体的连线来修改!
//战舰STM32开发板使用的是PD2作为SD卡的CS脚.
#define    SD_CS  PDout(2) //SD卡片选引脚           extern u8  SD_Type;//SD卡的类型
//函数申明区
u8 SD_SPI_ReadWriteByte(u8 data);
void SD_SPI_SpeedLow(void);
void SD_SPI_SpeedHigh(void);
u8 SD_WaitReady(void);                                         //等待SD卡准备
u8 SD_GetResponse(u8 Response);                           //获得相应
u8 SD_Initialize(void);                                            //初始化
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt);                     //读块
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt);              //写块
u32 SD_GetSectorCount(void);                              //读扇区数
u8 SD_GetCID(u8 *cid_data);                   //读SD卡CID
u8 SD_GetCSD(u8 *csd_data);                  //读SD卡CSD
#endif
该部分代码主要是一些命令的宏定义以及函数声明,在这里我们设定了SD卡的CS管脚为PD2。保存MMC_SD.H,就可以在主函数里面编写我们的应用代码了,打开test.c文件,在该文件中修改main函数如下:      
int main(void)
{           
       u8 key; u8 t=0; u8 *buf;                    
       u32 sd_size;
      Stm32_Clock_Init(9);           //系统时钟设置
       uart_init(72,9600);             //串口初始化为9600
       delay_init(72);                         //延时初始化
       LED_Init();                        //初始化与LED连接的硬件接口
       LCD_Init();                       //初始化LCD
       usmart_dev.init(72);             //初始化USMART      
      KEY_Init();                         //按键初始化
      FSMC_SRAM_Init();           //初始化外部SRAM
       mem_init(SRAMIN);            //初始化内部内存池                             
      POINT_COLOR=RED;//设置字体为红色
       LCD_ShowString(60,50,200,16,16,"WarShip STM32");   
       LCD_ShowString(60,70,200,16,16,"SD CARD TEST");   
       LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
       LCD_ShowString(60,110,200,16,16,"2012/9/17");
       LCD_ShowString(60,130,200,16,16,"KEY0:Read Sector 0");      
      while(SD_Initialize())//检测不到SD卡
       {
              LCD_ShowString(60,150,200,16,16,"SD Card Error!");
              delay_ms(500);                                 
              LCD_ShowString(60,150,200,16,16,"Please Check! ");
              delay_ms(500);
              LED0=!LED0;//DS0闪烁
       }
      POINT_COLOR=BLUE;//设置字体为蓝色
       //检测SD卡成功                                                                           
       LCD_ShowString(60,150,200,16,16,"SD Card OK    ");
       LCD_ShowString(60,170,200,16,16,"SD Card Size:     MB");
       sd_size=SD_GetSectorCount();//得到扇区数
       LCD_ShowNum(164,170,sd_size>>11,5,16);//显示SD卡容量(MB)
       while(1)
       {
              key=KEY_Scan(0);
              if(key==KEY_RIGHT)//KEY0按下了
              {
                     buf=mymalloc(0,512);                 //在内部内存池,申请512字节内存
                     if(SD_ReadDisk(buf,0,1)==0)      //读取0扇区的内容
                     {   
                            LCD_ShowString(60,190,200,16,16,"USART1 Sending Data...");
                            printf("SECTOR 0 DATA:\r\n");
                            for(sd_size=0;sd_size<512;sd_size++)printf("%x ",buf[sd_size]);
//打印0扇区数据         
                            printf("\r\nDATA ENDED\r\n");
                            LCD_ShowString(60,190,200,16,16,"USART1 Send Data Over!");
                     }
                     myfree(0,buf);       //释放内存      
              }  
              t++;
              delay_ms(10);
              if(t==20)
              {
                     LED0=!LED0;
                     t=0;
              }
       }  
}
这里我们通过SD_GetSectorCount函数来得到SD卡的扇区数,间接得到SD卡容量,然后在液晶上显示出来,接着我们通过按键KEY0控制读取SD卡的扇区0,然后把读到的数据通过串口打印出来。这里,我们对上一章学过的内存管理小试牛刀,稍微用了下,以后我们会尽量使用内存管理来设计。
44.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,可以看到LCD显示如图44.4.1所示的内容(默认SD卡已经接上了):


图44.4.1 程序运行效果图

打开串口调试助手,按下KEY0就可以看到从开发板发回来的数据了,如图44.4.2所示:

44.4.2 串口收到的SD卡扇区0内容


       这里请大家注意,不同的SD卡,读出来的扇区0是不尽相同的,所以不要因为你读出来的数据和图44.4.2不同而感到惊讶。

使用特权

评论回复
板凳
xylvhp| | 2013-6-6 12:34 | 只看该作者
好东西收了,第一个回复

使用特权

评论回复
地板
pentral0311| | 2013-6-6 13:08 | 只看该作者
MARK

使用特权

评论回复
5
lin34337151| | 2013-7-1 10:09 | 只看该作者
刚去你们公司网站看了,支持,不错。

使用特权

评论回复
6
菜鸟宅男| | 2013-9-18 11:43 | 只看该作者
学习一下

使用特权

评论回复
7
l308152569| | 2014-1-2 15:47 | 只看该作者
谢谢,学习了

使用特权

评论回复
8
与时俱进| | 2014-3-31 23:04 | 只看该作者
学习SD卡。

使用特权

评论回复
9
kflkr| | 2014-7-2 17:56 | 只看该作者
谢谢,学习了!

使用特权

评论回复
10
wuyuang| | 2015-8-1 07:46 | 只看该作者
mark

使用特权

评论回复
11
戈卫东| | 2015-8-1 08:04 | 只看该作者
多谢了。。。。。最近准备用这个。。。。

使用特权

评论回复
12
qbl易拉罐| | 2015-8-22 13:59 | 只看该作者
asdasddsdddd

使用特权

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

本版积分规则

个人签名:我的STM32开发板店铺:http://openedv.taobao.com 我的技术论坛论坛:www.openedv.com

91

主题

264

帖子

71

粉丝