完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 正点原子运营官 于 2020-4-13 12:11 编辑
1)实验平台:正点原子STM32mini开发板 2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第三十三章 SD 卡实验 很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘,FLASH 芯片,SD 卡等。他们各有优点,综合比较,最适合单片机系统的莫过于 SD 卡了,它不仅容量可以做到很大(32Gb 以上),而且支持 SPI 接口,方便移动,并且有几种体积的尺寸可供选择(标准的 SD 卡尺寸,以及 TF 卡尺寸等),能满足不同应用的要求。只需要 4 个 IO 口即可外扩一个最大达 32GB 以上的外部存储器,容量从几十 M 到几十 G选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。ALIENTKE MiniSTM32 开发板自带了标准的 SD 卡接口(在背面),可使用 STM32 自带的 SPI 接口驱动,本章我们使用 SPI 驱动,最高通信速度可达 18Mbps,每秒可传输数据 2M 字节以上,对于一般应用足够了。在本章中,我们将向大家介绍,如何在 ALIENTEK MiniSTM32开发板上实现 SD 卡的读取。本章分为如下几个部分: 33.1 SD 卡简介 33.2 硬件设计 33.3 软件设计 33.4 下载验证 33.1 SD 卡简介 SD 卡(Secure Digital Memory Card)中文翻译为安全数码卡,它是在 MMC 的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD 卡由日本松下、东芝及美国 SanDisk公司于 1999 年 8 月共同开发研制。大小犹如一张邮票的 SD 记忆卡,重量只有 2 克,但却拥 有高记忆容量、快速数据传输率、极大的移动灵活性以及很好的安全性。按容量分类,可以将SD 卡分为 3 类:SD 卡、SDHC 卡、SDXC 卡。如表 33.1.1 所示: 表 33.1.1 SD 卡按容量分类 SD 卡和 SDHC 卡协议基本兼容,但是 SDXC 卡,同这两者区别就比较大了,本章我们讨论的主要是 SD/SDHC 卡(简称 SD 卡)。 SD 卡一般支持 2 种操作模式: 1,SD 卡模式(通过 SDIO 通信); 2,SPI 模式; 主机可以选择以上任意一种模式同 SD 卡通信,SD 卡模式允许 4 线的高速数据传输。SPI模式允许简单的通过 SPI 接口来和 SD 卡通信,这种模式同 SD 卡模式相比就是丧失了速度。 SD 卡的引脚排序如下图 33.1.1 所示: 图 33.1.1 SD 卡引脚排序图 SD 卡引脚功能描述如表 33.1.2 所示: 表 33.1.2 SD 卡引脚功能表 SD 卡只能使用 3.3V 的 IO 电平,所以,MCU 一定要能够支持 3.3V 的 IO 端口输出。注意:在 SPI 模式下,CS/MOSI/MISO/CLK 都需要加 10~100K 左右的上拉电阻。SD 卡有 5 个寄存器,如表 33.1.3 所示: 表 33.1.3 SD 卡相关寄存器 关于这些寄存器的详细描述,请参考光盘相关 SD 卡资料。我们在这里就不描述了。接下来,我们看看 SD 卡的命令格式,如表 33.1.4 所示: 表 33.1.4 SD 卡命令格式 SD 卡的指令由 6 个字节组成,字节 1 的最高 2 位固定为 01,低 6 位为命令号(比如 CMD16,为 10000B 即 16 进制的 0X10,完整的 CMD16,第一个字节为 01010000,即 0X10+0X40)。字节 2~5 为命令参数,有些命令是没有参数的。 字节 6 的高七位为 CRC 值,最低位恒定为 1。SD 卡的命令总共有 12 类,分为 Class0~Class11,本章,我们仅介绍几个比较重要的命令, 如表 33.1.5 所示: 表 33.1.5 SD 卡部分命令 上表中,大部分的命令是初始化的时候用的。表中的 R1、R3 和 R7 等是 SD 卡的回应,SD卡和单片机的通信采用发送应答机制,如图 33.1.2 所示: 图 33.1.2 SD 卡命令传输过程 每发送一个命令,SD 卡都会给出一个应答,以告知主机该命令的执行情况,或者回主机需要获取的数据。SPI 模式下,SD 卡针对不同的命令,应答可以是 R1~R7,R1 的应答,各位描述如表 33.1.6 所示:表 33.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 卡的介绍,我们就介绍到这里,更详细的介绍请参考光盘资料→ 7,硬件资料→SD 卡资料→ SD 卡 V2.0 协议。 33.2 硬件设计 本章实验功能简介:开机的时候先初始化 SD 卡,如果 SD 卡初始化完成,则提示 LCD 初始化成功。按下 KEY0,读取 SD 卡扇区 0 的数据,然后通过串口发送到电脑。如果没初始化通过,则在 LCD 上提示初始化失败。 同样用 DS0 来指示程序正在运行。 本实验用到的硬件资源有: 1) 指示灯 DS0 2) KEY0 按键 3) 串口 4) TFTLCD 模块 5) SD 卡 前面四部分,在之前的实例已经介绍过了,这里我们介绍一下 MiniSTM32 开发板板载的SD 卡接口和 STM32 的连接关系,如图 33.2.1 所示: 图33.2.1 SD卡接口与STM32连接原理图 从图中可以看出,SD卡通过4根信号线与STM32连接,SD卡的片选(SD_CS)连接PA3,SD卡的SPI接口,连接在STM32的SPI1上面,硬件连接就这么简单,这里要注意的是SPI1被3 个外设共用了:SD卡、W25Q64和NRF24L01,在使用SD卡的时候,必须禁止其他外设的片选,以防干扰。 33.3 软件设计 打开上一章的工程,由于本章还需要用到SPI功能,所以,先添加spi.c。然后,在HARDWARE文件夹下新建一个 SD 的文件夹。然后新建一个 MMC_SD.C 和 MMC_SD.H 的文件保存在 SD 文件夹下,并将这个文件夹加入头文件包含路径。 打开 MMC_SD.C 文件,在该文件里面,我们输入与 SD 卡相关的操作代码,这里由于篇幅限制,我们不贴出所有代码,仅介绍两个最重要的函数,第一个是 SD_Initialize 函数,该函 数源码如下: //初始化 SD 卡 u8 SD_Init(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); //Get trailing return value of R7 resp 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等函数,实际是对 SPI1 的相关函数进行了一层封装,方便移植。另外一个要介绍的函数是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/CMD18 命令,然后读取一个/多个扇区的数据,详细见代码,这里我们就不多介绍了。保存 MMC_SD.C 文件,并加入到 HARDWARE 组下, 然后打开 MMC_SD.H,在该文件里面输入如下代码: #ifndef _MMC_SD_H_ #define _MMC_SD_H_ #include "sys.h" #include // 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 //这部分应根据具体的连线来修改! //MiniSTM32 开发板使用的是 PA3 作为 SD 卡的 CS 脚. #define SD_CS PAout(3) //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 管脚为 PA3。保存 MMC_SD.H,就可以在主函数里面编写我们的应用代码了,打开 main.c,输入如下代 码: //读取 SD 卡的指定扇区的内容,并通过串口 1 输出 //sec:扇区物理地址编号 void SD_Read_Sectorx(u32 sec) { u8 *buf; u16 i; buf=mymalloc(512); //申请内存 if(SD_ReadDisk(buf,sec,1)==0) //读取 0 扇区的内容 { LCD_ShowString(60,190,200,16,16,"USART1 Sending Data..."); printf("SECTOR 0 DATA:rn"); for(i=0;i<512;i++)printf("%x ",buf);//打印 sec 扇区数据 printf("rnDATA ENDEDrn"); LCD_ShowString(60,190,200,16,16,"USART1 Send Data Over!"); } myfree(buf);//释放内存 } int main(void) { u8 key; u8 t=0; u32 sd_size; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 LED_Init(); //初始化 LED KEY_Init(); //初始化按键 LCD_Init(); //初始化 LCD mem_init(); //初始化内存池 POINT_COLOR=RED; //设置字体为红色 LCD_ShowString(60,50,200,16,16,"Mini 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,"2019/11/16"); 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 卡容量 while(1) { key=KEY_Scan(0); if(key==KEY0_PRES)SD_Read_Sectorx(0);//KEY0 按,读取 SD 卡扇区 0 的内容 delay_ms(10);t++; if(t==20) { LED0=!LED0; t=0; } } } 这里总共 2 个函数,其中 SD_Read_Sectorx 用于读取 SD 卡指定扇区的数据,并将读到的数据通过串口 1 输出。然后 main 函数则通过 SD_GetSectorCount 函数来得到 SD 卡的扇区数,间接得到 SD 卡容量,然后在液晶上显示出来,接着我们通过按键 KEY0 控制读取 SD 卡的扇区 0,然后把读到的数据通过串口打印出来。另外,我们对上一章学过的内存管理小试牛刀,稍微用了下,以后我们会尽量使用内存管理来设计。最后,我们将 SD_Read_Sectorx 函数加入 USMART 控制,这样,我们就可以通过串口调试助手,读取 SD 卡任意一个扇区的数据,方便大家测试。 33.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,可以看到LCD 显示如图 33.4.1 所示的内容(默认 SD 卡已经接上了): 图 33.4.1 程序运行效果图 打开串口调试助手,按下 KEY0 就可以看到从开发板发回来的数据了,如图 33.4.2 所示: 图 33.4.2 串口收到的 SD 卡扇区 0 内容 这里请大家注意,不同的 SD 卡,读出来的扇区 0 是不尽相同的,所以不要因为你读出来的数据和图 33.4.2 不同而感到惊讶。 |
|
相关推荐
|
|
使用stm32l451片子,对ad7606进行3通道100k采样值跳动问题?
884 浏览 1 评论
STM32进行DMA数据转运,为什么只有A组最后一位数据被转运到B组去?
1429 浏览 2 评论
1748 浏览 3 评论
1348 浏览 0 评论
嵌入式开发 Win11安装ST-Link 提示 设备描述符请求失败 怎么处理?
1476 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-22 09:00 , Processed in 0.405470 second(s), Total 35, Slave 28 queries .
Powered by 电子发烧友网
© 2015 www.ws-dc.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号