您当前的位置: 首页 >  fpga开发

正点原子

暂无认证

  • 1浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【正点原子FPGA连载】第四十六章SD卡读写测试实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

正点原子 发布时间:2021-11-23 11:06:54 ,浏览量:1

1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子FPGA感兴趣的同学可以加群讨论:994244016 4)关注正点原子公众号,获取最新资料更新 在这里插入图片描述

第四十六章SD卡读写测试实验

SD存储卡是一种基于半导体快闪记忆器的新一代记忆设备。它具有体积小、传输速度快、支持热插拔等优点,在便携式装置领域得到了广泛的应用,如数码相机、多媒体播放器、笔记本电脑等。本章我们将使用FPGA开发板学习如何对SD卡(本章使用的是TF卡也就是MicroSD卡,以下简称SD卡)进行读写操作并进行SD卡的读写测试实验。 本章包括以下几个部分: 4545.1简介 45.2实验任务 45.3硬件设计 45.4程序设计 45.5下载验证

46.1简介

SD卡的英文全称是Secure Digital Card,即安全数字卡(又叫安全数码卡),是在MMC卡(Multimedia Card,多媒体卡)的基础上发展而来,具有两个突出的优点:更高的安全性和更快的读写速度。SD卡和MMC卡的长度和宽度都是32mm x 24mm,不同的是,SD卡的厚度为2.1mm,而MMC卡的厚度为1.4mm,SD卡比MMC卡略厚,以容纳更大容量的存贮单元,同时SD卡比MMC卡触点引脚要多,且在侧面多了一个写保护开关。SD卡与MMC卡保持着向上兼容,也就是说,MMC卡可以被新的SD设备存取,兼容性则取决于应用软件,但SD卡却不可以被MMC设备存取。SD卡和MMC卡可通过卡片上面的标注进行区分,如下图左侧图片上面标注为“MultiMediaCard”字母样式的为MMC卡,右侧图片上面标注为“SD”字母样式的为SD卡。 在这里插入图片描述 图 46.1.1 MMC外观图(左)和SD卡外观图(右) 上图中右侧图片的SD卡实际上为SDHC卡,SD卡从存储容量上分为3个级别,分别为:SD卡、SDHC卡(Secure Digital High Capacity,高容量安全数字卡)和SDXC卡(SD eXtended Capacity,容量扩大化的安全存储卡)。SD卡在MMC卡的基础上发展而来,使用FAT12/FAT16文件系统,SD卡采用SD1.0协议规范,该协议规定了SD卡的最大存储容量为2GB;SDHC卡是大容量存储SD卡,使用FAT32文件系统,SDHC卡采用SD2.0协议规范,该协议规定了SDHC卡的存储容量范围为2GB至32GB;SDXC卡是新提出的标准,不同于SD卡和SDHC卡使用的FAT文件系统,SDXC卡使用exFAT文件系统,即扩展FAT文件系统。SDXC卡采用SD3.0协议规范,该协议规定了SDXC卡的存储容量范围为32GB至2TB(2048GB),一般用于中高端单反相机和高清摄像机。 下表为不同类型的SD卡采用的协议规范、容量等级及读写速度。 表 46.1.1 SD卡的类型、协议规范、容量等级及支持的文件系统 在这里插入图片描述 不同协议规范的SD卡有着不同速度等级的表示方法。在SD1.0协议规范中(现在用的较少),使用“X”表示不同的速度等级;在SD2.0协议规范中,使用SpeedClass表示不同的速度等级;SD3.0协议规范使用UHS(Ultra High Speed)表示不同的速度等级。SD2.0规范中对SD卡的速度等级划分为普通卡(Class2、Class4、Class6)和高速卡(Class10);SD3.0规范对SD卡的速度等级划分为UHS速度等级1和3而在SD4.0中则是UHS-II,它不仅速度等级大大加快,接口也有所变化。不同等级的读写速度和应用如下图所示。 在这里插入图片描述

图 46.1.2 SD卡不同速度等级表示法 SD卡共有9个引脚线,可以工作在SDIO模式或者SPI模式。在SDIO模式下,共用到CLK、CMD、DAT[3:0]六根信号线;在SPI模式下,共用到CS(SDIO_DAT[3])、CLK(SDIO_CLK)、MISO(SDIO_DAT[0])、MOSI(SDIO_CMD)四根信号线。SD卡接口定义以及各引脚功能说明如下图所示。 在这里插入图片描述

图 46.1.3 SD卡接口定义以及各引脚功能说明 市面上除标准SD卡外,还有MicroSD卡(原名TF卡,就是本次实验所使用的SD卡),是一种极细小的快闪存储器卡,是由SanDisk(闪迪)公司发明,主要用于移动手机。MicroSD卡插入适配器(Adapter)可以转换成SD卡,其操作时序和SD卡是一样的。MicroSD卡接口定义以及各引脚功能说明如下图所示。 在这里插入图片描述

图 46.1.4 MicroSD卡接口定义以及各引脚功能说明 标准SD卡2.0版本中,工作时钟频率可以达到50Mhz,在SDIO模式下采用4位数据位宽,理论上可以达到200Mbps(50Mx4bit)的传输速率;在SPI模式下采用1位数据位宽,理论上可以达到50Mbps的传输速率。因此SD卡在SDIO模式下的传输速率更快,同时其操作时序也更复杂。对于使用SD卡读取音乐文件和图片来说,SPI模式下的传输速度已经能够满足我们的需求,因此我们本章采用SD卡的SPI模式来对SD卡进行读写测试。 SD卡在正常读写操作之前,必须先对SD卡进行初始化,SD卡的初始化过程就是向SD中写入命令,使其工作在预期的工作模式。在对SD卡进行读写操作时同样需要先发送写命令和读命令,因此SD卡的命令格式是学习SD卡的重要内容。SD卡的命令格式由6个字节组成,发送数据时高位在前,SD卡的写入命令格式如下图所示: 在这里插入图片描述

图 46.1.5 SD卡命令格式 Byte1:命令字的第一个字节为命令号(如CMD0、CMD1等),格式为“0 1 x x x x x x”。命令号的最高位始终为0,是命令号的起始位;次高位始终为1,是命令号的发送位;低6位为具体的命令号(如CMD55,8’d55 = 8’b0011_0111,命令号为 0 1 1 1 0 1 1 1 = 0x77)。 Byte2~Byte5:命令参数,有些命令参数是保留位,没有定义参数的内容,保留位应设置为0。 Byte6:前7位为CRC(循环冗余校验)校验位,最后一位为停止位0。SD卡在SPI模式下默认不开启CRC校验,在SDIO模式下开启CRC校验。也就是说在SPI模式下,CRC校验位必须要发,但是SD卡会在读到CRC校验位时自动忽略它,所以校验位全部设置为1即可。需要注意的是,SD卡上电默认是SDIO模式,在接收SD卡返回CMD0的响应命令时,拉低片选CS,进入SPI模式。所以在发送CMD0命令的时候,SD卡处于SDIO模式,需要开启CRC校验。另外CMD8的CRC校验是始终启用的,也需要启用CRC校验。除了这两个命令,其它命令的CRC可以不做校验。 SD卡的命令分为标准命令(如CMD0)和应用相关命令(如ACMD41)。ACMD命令是特殊命令,发送方法同标准命令一样,但是在发送应用相关命令之前,必须先发送CMD55命令,告诉SD卡接下来的命令是应用相关命令,而非标准命令。发送完命令后,SD卡会返回响应命令的信息,不同的CMD命令会有不同类型的返回值,常用的返回值有R1类型、R3类型和R7类型(R7类型是CMD8命令专用)。SD卡的常用命令说明如下表所示。 表 46.1.2 SD卡常用命令说明 在这里插入图片描述 SD卡返回类型R1数据格式如下图所示: 在这里插入图片描述

图 46.1.6 SD卡返回类型R1数据格式 由上图可知,SD卡返回类型R1格式共返回1个字节,最高位固定为0,其它位分别表示对应状态的标志,高电平有效。 SD卡返回类型R3数据格式如下图所示: 在这里插入图片描述

图 46.1.7 SD卡返回类型R3数据格式 由上图可知,SD卡返回类型R3格式共返回5个字节,首先返回的第一个字节为前面介绍的R1的内容,其余字节为OCR(Operation Conditions Register,操作条件寄存器)寄存器的内容。 SD卡返回类型R7数据格式如下图所示: 在这里插入图片描述

图 46.1.8 SD卡返回类型R7数据格式 由上图可知,SD卡返回类型R7格式共返回5个字节,首先返回的第一个字节为前面介绍的R1的内容,其余字节包含SD卡操作电压信息和校验字节等内容。其中电压范围是一个比较重要的参数,其具体内容如下所示: Bit[11:8]:操作电压反馈 0:未定义 1:2.7V~3.6V 2:低电压 4:保留位 8:保留位 其它:未定义 SD卡在正常读写操作之前,必须先对SD卡进行初始化,使其工作在预期的工作模式。SD卡1.0版本协议和2.0版本协议在初始化过程中有区别,只有SD2.0版本协议的SD卡才支持CMD8命令,所以响应此命令的SD卡可以判断为SD2.0版本协议的卡,否则为SD1.0版本协议的SD卡或者MMC卡;对于CMD8无响应的情况,可以发送CMD55 + ACMD41命令,如果返回0,则表示SD1.0协议版本卡初始化成功,如果返回错误,则确定为MMC卡;在确定为MMC卡后,继续向卡发送CMD1命令,如果返回0,则MMC卡初始化成功,否则判断为错误卡。 由于市面上大多采用SD2.0版本协议的SD卡,接下来我们仅介绍SD2.0版本协议的初始化流程,以下提到的SD卡均代表基于SD2.0版本协议的SDHC卡,其详细初始化步骤如下: 1,SD卡完成上电后,主机FPGA先对从机SD卡发送至少74个以上的同步时钟,在上电同步期间,片选CS引脚和MOSI引脚必须为高电平(MOSI引脚除发送命令或数据外,其余时刻都为高电平); 2,拉低片选CS引脚,发送命令CMD0(0x40)复位SD卡,命令发送完成后等待SD卡返回响应数据; 3,SD卡返回响应数据后,先等待8个时钟周期再拉高片选CS信号,此时判断返回的响应数据。如果返回的数据为复位完成信号0x01,在接收返回信息期间片选CS为低电平,此时SD卡进入SPI模式,并开始进行下一步,如果返回的值为其它值,则重新执行第2步; 4,拉低片选CS引脚,发送命令CMD8(0x48)查询SD卡的版本号,只有SD2.0版本的卡才支持此命令,命令发送完成后等待SD卡返回响应数据; 5,SD卡返回响应数据后,先等待8个时钟周期再拉高片选CS信号,此时判断返回的响应数据。如果返回的电压范围为4’b0001即2.7V~3.6V,说明此SD卡为2.0版本,进行下一步,否则重新执行第4步; 6,拉低片选CS引脚,发送命令CMD55(0x77)告诉SD卡下一次发送的命令是应用相关命令,命令发送完成后等待SD卡返回响应数据; 7,SD卡返回响应数据后,先等待8个时钟周期再拉高片选CS信号,此时判断返回的响应数据。如果返回的数据为空闲信号0x01,开始进行下一步,否则重新执行第6步。 8,拉低片选CS引脚,发送命令ACMD41(0x69)查询SD卡是否初始化完成,命令发送完成后等待SD卡返回响应数据; 9,SD卡返回响应数据后,先等待8个时钟周期再拉高片选CS信号,此时判断返回的响应数据。如果返回的数据为0x00,此时初始化完成,否则重新执行第6步。 SD卡上电复位及初始化命令时序如下图所示: 在这里插入图片描述

图 46.1.9 SD卡复位时序图 在这里插入图片描述

图 46.1.10 SD卡初始化时序图 至此,SD卡完成了复位以及初始化操作,进入到SPI模式的读写操作。需要注意的是:SD卡在初始化的时候,SPI_CLK的时钟频率不能超过400KHz,在初始化完成之后,再将SPI_CLK的时钟频率切换至SD卡的最大时钟频率。尽管目前市面上的很多SD卡支持以较快的时钟频率进行初始化,为了能够兼容更多的SD卡,在SD卡初始化的时候时钟频率不能超过400KHz。 SD卡读写一次的数据量必须为512字节的整数倍,即对SD卡读写操作的最少数据量为512个字节。我们可以通过命令CMD16来配置单次读写操作的数据长度,使每次读写的数据量为(n*512)个字节(n≥1),本次SD卡的读写操作使用SD卡默认配置,即单次读写操作的数据量为512个字节。 SD卡初始化完成后,即可对SD卡进行读写测试,SD卡的读写测试是先向SD卡中写入数据,再从SD卡中读出数据,并验证数据的正确性。SD卡的写操作时序图如下图所示: 在这里插入图片描述

图 46.1.11 SD卡写操作时序图 SD卡的写操作流程如下: 1、拉低片选CS引脚,发送命令CMD24(0x58)读取单个数据块,命令发送完成后等待SD卡返回响应数据; 2、SD卡返回正确响应数据0x00后,等待至少8个时钟周期,开始发送数据头0xfe; 3、发送完数据头0xfe后,接下来开始发送512个字节的数据; 4、数据发送完成后,发送2个字节的CRC校验数据。由于SPI模式下不对数据进行CRC校验,直接发送两个字节的0xff即可; 5、校验数据发送完成后,等待SD卡响应; 6、SD卡返回响应数据后会进入写忙状态(MISO引脚为低电平),即此时不允许其它操作。当检测到MISO引脚为高电平时,SD卡此时退出写忙状态; 7、拉高CS引脚,等待8个时钟周期后允许进行其它操作。 SD卡的读操作时序图如下图所示: 在这里插入图片描述

图 46.1.12 SD卡读操作时序图 SD卡的读操作流程如下: 1、拉低片选CS引脚,发送命令CMD17(0x51)读取单个数据块,命令发送完成后等待SD卡返回响应数据; 2、SD卡返回正确响应数据0x00后,准备开始解析SD卡返回的数据头0xfe; 3、解析到数据头0xfe后,接下来接收SD卡返回的512个字节的数据; 4、数据解析完成后,接下来接收两个字节的CRC校验值。由于SPI模式下不对数据进行CRC校验,可直接忽略这两个字节; 5、校验数据接收完成后,等待8个时钟周期; 6、拉高片选CS引脚,等待8个时钟周期后允许进行其它操作。 在前面介绍的SD卡读写操作中,使用的是SD卡的SPI模式,即采用SPI协议进行读写操作。SPI和IIC都是芯片上常用的通信协议,SPI相比于IIC具有更高的通信速率,但同时占用更多的引脚线,接下来我们了解一下SPI的协议及传输时序。 SPI(Serial Peripheral interface)是由摩托罗拉公司定义的一种串行外围设备接口,是一种高速、全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于PCB的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了SPI通信协议,如FLASH、AD转换器等。 SPI的通信原理比较简单,它以主从方式工作,通常有一个主设备(此处指FPGA)和一个或多个从设备(此处指SD卡)。SPI通信需要四根线,分别为SPI_CS、SPI_CLK、SPI_MOSI和SPI_MISO。其中SPI_CS、SPI_CLK和SPI_MOSI由主机输出给从机,而SPI_MISO由从机输出给主机。SPI_CS用于控制芯片是否被选中,也就是说只有片选信号有效时(对于SD卡来说是低电平有效),对芯片的操作才有效;SPI_CLK是由主机产生的同步时钟,用于同步数据;SPI_MOSI和SPI_MISO是主机发送和接收的数据脚。 一般而言,SPI通信有4种不同的模式,不同的从设备在出厂时被厂家配置为其中一种模式,模式是不允许用户修改的。主设备和从设备必须在同一模式下进行通信,否则数据会接收错误。SPI的通信模式是由CPOL(时钟极性)和CPHA(时钟相位)来决定的,四种通信模式如下: 模式0:CPOL = 0,CPHA = 0; 模式1:CPOL = 0,CPHA = 1; 模式2:CPOL = 1,CPHA = 0; 模式3:CPOL = 1,CPHA = 1。 CPOL控制着SPI_CLK的时钟极性,时钟极性变化如下图所示: 在这里插入图片描述

图 46.1.13 SPI_CLK时钟极性 由上图可知,当CPOL = 1时,SPI_CLK在空闲时为高电平,发起通信后的第一个时钟沿为下降沿;CPOL = 0时,SPI时钟信号SPI_CLK空闲时为低电平,发起通信后的第一个时钟沿为上升沿。 CPHA用于控制数据与时钟的对齐模式,其不同模式下的时序图如下图所示: 在这里插入图片描述

图 46.1.14 不同CPHA模式下的时序图 由上图可知,当CPHA=0时,数据在时钟的第一个变化沿之前就已经改变,并且保持稳定,也就意味着在时钟的第一个变化沿锁存数据;当CPHA=1时,时钟的第一个变化沿(上升沿或者下降沿)数据开始改变,那么也就意味着时钟的第2个变化沿(与第一个变化沿相反)锁存数据。 对于SD卡的SPI模式而言,采用的SPI的通信模式为模式3,即CPOL=1,CPHA=1,在SD卡2.0版本协议中,SPI_CLK时钟频率可达50Mhz。 以上是SD卡简介部分的全部内容,在这里还需要补充下FAT文件系统的知识。如果对SD卡的读写测试像EEPROM一样仅仅是写数据,读数据并验证数据的正确性的话,是不需要FAT文件系统的。而SD卡经常被用来在Windows操作系统上存取数据,必须使用Windows操作系统支持的FAT文件系统才能在电脑上正常使用。 FAT(File Allocation Table,文件分配表)是Windows操作系统所使用的一种文件系统,它的发展过程经历了FAT12、FAT16、FAT32三个阶段。FAT文件系统用“簇”作为数据单元,一个“簇”由一组连续的扇区组成,而一个扇区由512个字节组成。簇所包含的扇区数必须是2的整数次幂,其扇区个数最大为64,即32KB(512Byte * 64 = 32KB)。所有的簇从2开始进行编号,每个簇都有一个自己的地址编号,用户文件和目录都存储在簇中。 FAT文件系统的基本结构依次为:分区引导记录、文件分配表(FAT表1和FAT表2)、根目录和数据区。 分区引导记录:分区引导记录区通常占用分区的第一个扇区,共512个字节。包含四部分内容:BIOS参数记录块BPB(BIOS Parameter Block)、磁盘标志记录表、分区引导记录代码区和结束标志0x55AA。 文件分配表(FAT表1和FAT表2):文件在磁盘上以簇为单位存储,但是同一个文件的数据并不一定完整地存放在磁盘的一个连续的区域内,往往会分成若干簇,FAT表就是记录文件存储中簇与簇之间连接的信息,这就是文件的链式存储。对于FAT16文件系统来说,每个簇用16Bit来表示文件分配表,而对于FAT32文件系统,使用32Bit来表示文件分配表,这是两者之间的最重要区别。 根目录:根目录是文件或者目录的首簇号。在FAT32文件系统中,不再对根目录的位置做硬性规定,可以存储在分区内可寻址的任意簇内。不过通常根目录是最早建立的(格式化就生成了)目录表,所以我们看到的情况基本上都是根目录首簇紧邻FAT2,占簇区顺序上的第1个簇(即2号簇)。 数据区:数据区紧跟在根目录后面,是文件等数据存放的地方,占用大部分的磁盘空间。 46.2实验任务 本节实验任务是使用FPGA开发板向SD卡指定的扇区地址中写入512个字节的数据,写完后将数据读出,并验证数据是否正确。 46.3硬件设计 我们的新起点FPGA开发板上有一个SD卡插槽,用于插入SD卡,其原理图如图 46.3.1所示:

图 46.3.1 SD卡接口原理图 本次实验,我们使用SD卡的SPI模式,只用到了SDIO_D3(SPI_CS)、SDIO_CMD(SPI_MOSI),SDIO_SCK(SPI_SCK)和SDIO_D0(SPI_MISO)引脚,而其它两个引脚是在SD卡的SDIO模式下用到的。 本实验中各端口信号的管脚分配如下表所示。 表 46.3.1 SD卡读写测试实验管脚分配 在这里插入图片描述

对应的TCL约束语句如下所示: #系统时钟和复位 set_location_assignment PIN_M2 -to sys_clk set_location_assignment PIN_M1 -to sys_rst_n

#SD(TF)卡 set_location_assignment PIN_J2 -to sd_clk set_location_assignment PIN_C2 -to sd_cs set_location_assignment PIN_D1 -to sd_mosi set_location_assignment PIN_K1 -to sd_miso

#LED set_location_assignment PIN_D11 -to led[0] set_location_assignment PIN_C11 -to led[1] set_location_assignment PIN_E10 -to led[2] set_location_assignment PIN_F9 -to led[3] 46.4程序设计 通过前面介绍的SD卡初始化、写操作以及读操作可知,SD卡的这个三个操作是相互独立且不能同时进行的,因此我们可以将SD卡的初始化、写操作以及读操作分别划分为三个独立的模块,最后将这三个模块例化在SD卡的控制器模块中,便于在其它工程项目中使用。下图是实验的系统框图,时钟模块为各个模块提供驱动时钟,SD卡测试数据产生模块产生测试数据写入SD卡,写完后从SD卡中读出数据,最终读写测试结果由LED显示模块通过控制LED灯的显示状态来指示。 在这里插入图片描述

图 46.4.1 SD卡读写测试系统框图 顶层模块的原理图如下图所示: 在这里插入图片描述

图 46.4.2 顶层模块原理图 FPGA顶层模块(top_sd_rw)例化了以下四个模块:时钟模块(pll_clk)、SD卡测试数据产生模块(data_gen)、SD卡控制器模块(sd_ctrl_top)和LED显示模块(led_alarm)。 顶层模块(top_sd_rw):顶层模块完成了对其它四个模块的例化,SD卡测试数据产生模块产生的开始写入信号及数据连接至SD卡控制器模块,数据写完后从SD卡控制器中读出数据,并验证数据的正确性,将验证的结果连接至LED显示模块。 时钟模块(pll_clk):时钟模块通过调用时钟IP核来实现,总共输出两个时钟,频率都是50Mhz,但两个时钟相位相差180度。我们知道,SD卡的SPI通信模式为CPOL=1,CPHA=1;即SPI_CLK在空闲时为高电平,数据发送是在时钟的第一个边沿,也就是SPI_CLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。为了在程序代码中统一使用上升沿,我们使用两个相位相差180度的时钟来对SD卡进行操作。 SD卡测试数据产生模块(data_gen):SD卡测试数据产生模块产生的开始写入信号和数据写入SD卡控制器模块中,数据写完后从SD卡控制器中读出数据,并验证数据的正确性,将验证的结果发送给LED显示模块。 SD卡控制器模块(sd_ctrl_top):SD卡控制器模块例化了SD卡初始化模块(sd_init)、SD卡写数据模块(sd_write)和SD卡读数据模块(sd_read)。SD卡初始化模块完成对SD卡的上电初始化操作;SD卡写数据模块完成对SD卡的写操作;SD卡读数据模块完成对SD卡的读操作。由于这三个模块都操作了SD卡的引脚信号,且这三个模块在同一时间内不会同时操作,所以此模块实现了对其它三个模块的例化以及选择SD卡的引脚连接至其中某一个模块。 LED显示模块(led_alarm):LED显示模块将SD卡测试数据产生模块输出的验证结果值,通过控制LED灯的显示状态来指示。 顶层模块的代码如下:

1   module top_sd_rw(
2       input               sys_clk     ,  //系统时钟
3       input               sys_rst_n   ,  //系统复位,低电平有效
4       
5       //SD卡接口
6       input               sd_miso     ,  //SD卡SPI串行输入数据信号
7       output              sd_clk      ,  //SD卡SPI时钟信号
8       output              sd_cs       ,  //SD卡SPI片选信号
9       output              sd_mosi     ,  //SD卡SPI串行输出数据信号
10     
11      //LED
12      output      [3:0]   led            //LED灯
13      );
14  
15  //wire define
16  wire             clk_ref        ;
17  wire             clk_ref_180deg ;
18  wire             rst_n          ;
19  wire             locked         ;
20  wire             wr_start_en    ;      //开始写SD卡数据信号
21  wire     [31:0]  wr_sec_addr    ;      //写数据扇区地址    
22  wire     [15:0]  wr_data        ;      //写数据            
23  wire             rd_start_en    ;      //开始写SD卡数据信号
24  wire     [31:0]  rd_sec_addr    ;      //读数据扇区地址    
25  wire             error_flag     ;      //SD卡读写错误的标志
26  wire             wr_busy        ;      //写数据忙信号
27  wire             wr_req         ;      //写数据请求信号
28  wire             rd_busy        ;      //读忙信号
29  wire             rd_val_en      ;      //数据读取有效使能信号
30  wire     [15:0]  rd_val_data    ;      //读数据
31  wire             sd_init_done   ;      //SD卡初始化完成信号
32  
33  //*****************************************************
34  //**                    main code
35  //*****************************************************
36  
37  assign  rst_n = sys_rst_n & locked;
38  
39  //时钟IP核
40  pll_clk pll_clk_inst (
41      .areset     (1'b0),
42      .inclk0     (sys_clk),
43      .c0         (clk_ref),
44      .c1         (clk_ref_180deg),
45      .locked     (locked)
46      );
47       
48  //产生SD卡测试数据  
49  data_gen u_data_gen(
50      .clk             (clk_ref),
51      .rst_n           (rst_n),
52      .sd_init_done    (sd_init_done),
53      .wr_busy         (wr_busy),
54      .wr_req          (wr_req),
55      .wr_start_en     (wr_start_en),
56      .wr_sec_addr     (wr_sec_addr),
57      .wr_data         (wr_data),
58      .rd_val_en       (rd_val_en),
59      .rd_val_data     (rd_val_data),
60      .rd_start_en     (rd_start_en),
61      .rd_sec_addr     (rd_sec_addr),
62      .error_flag      (error_flag)
63      );     
64  
65  //SD卡顶层控制模块
66  sd_ctrl_top u_sd_ctrl_top(
67      .clk_ref           (clk_ref),
68      .clk_ref_180deg    (clk_ref_180deg),
69      .rst_n             (rst_n),
70      //SD卡接口
71      .sd_miso           (sd_miso),
72      .sd_clk            (sd_clk),
73      .sd_cs             (sd_cs),
74      .sd_mosi           (sd_mosi),
75      //用户写SD卡接口
76      .wr_start_en       (wr_start_en),
77      .wr_sec_addr       (wr_sec_addr),
78      .wr_data           (wr_data),
79      .wr_busy           (wr_busy),
80      .wr_req            (wr_req),
81      //用户读SD卡接口
82      .rd_start_en       (rd_start_en),
83      .rd_sec_addr       (rd_sec_addr),
84      .rd_busy           (rd_busy),
85      .rd_val_en         (rd_val_en),
86      .rd_val_data       (rd_val_data),    
87      
88      .sd_init_done      (sd_init_done)
89      );
90  
91  //led警示 
92  led_alarm #(
93      .L_TIME      (25'd25_000_000)
94      )  
95     u_led_alarm(
96      .clk            (clk_ref),
97      .rst_n          (rst_n),
98      .led            (led),
99      .error_flag     (error_flag)
100     ); 
101 
102 endmodule

SD卡控制器模块输出的sd_init_done(SD卡初始化完成信号)连接至SD卡测试数据产生模块,只有在SD卡初始化完成之后(sd_init_done为高电平),才能对SD卡进行读写测试。SD卡控制器模块将SD卡的初始化以及读写操作封装成方便用户调用的接口,SD卡测试数据产生模块只需对SD卡控制器模块的用户接口进行操作即可完成对SD卡的读写操作。 在代码的第93行定义了一个参数(L_TIME),用于在读写测试错误时控制LED闪烁的时间,其单位是1个时钟周期。因为输入的时钟频率为50Mhz,周期为20ns,所以20 * 25’d25_000_000 = 500ms,因此LED灯在读写错误时每500ms闪烁一次。 SD卡控制器模块的代码如下:

1   module sd_ctrl_top(
2       input                clk_ref       ,  //时钟信号
3       input                clk_ref_180deg,  //时钟信号,与clk_ref相位相差180度
4       input                rst_n         ,  //复位信号,低电平有效
5       //SD卡接口
6       input                sd_miso       ,  //SD卡SPI串行输入数据信号
7       output               sd_clk        ,  //SD卡SPI时钟信号    
8       output  reg          sd_cs         ,  //SD卡SPI片选信号
9       output  reg          sd_mosi       ,  //SD卡SPI串行输出数据信号
10      //用户写SD卡接口
11      input                wr_start_en   ,  //开始写SD卡数据信号
12      input        [31:0]  wr_sec_addr   ,  //写数据扇区地址
13      input        [15:0]  wr_data       ,  //写数据                  
14      output               wr_busy       ,  //写数据忙信号
15      output               wr_req        ,  //写数据请求信号    
16      //用户读SD卡接口
17      input                rd_start_en   ,  //开始读SD卡数据信号
18      input        [31:0]  rd_sec_addr   ,  //读数据扇区地址
19      output               rd_busy       ,  //读数据忙信号
20      output               rd_val_en     ,  //读数据有效信号
21      output       [15:0]  rd_val_data   ,  //读数据    
22      
23      output               sd_init_done     //SD卡初始化完成信号
24      );
25  
26  //wire define
27  wire                init_sd_clk   ;       //初始化SD卡时的低速时钟
28  wire                init_sd_cs    ;       //初始化模块SD片选信号
29  wire                init_sd_mosi  ;       //初始化模块SD数据输出信号
30  wire                wr_sd_cs      ;       //写数据模块SD片选信号     
31  wire                wr_sd_mosi    ;       //写数据模块SD数据输出信号 
32  wire                rd_sd_cs      ;       //读数据模块SD片选信号     
33  wire                rd_sd_mosi    ;       //读数据模块SD数据输出信号 
34  
35  //*****************************************************
36  //**                    main code
37  //*****************************************************
38  
39  //SD卡的SPI_CLK  
40  assign  sd_clk = (sd_init_done==1'b0)  ?  init_sd_clk  :  clk_ref_180deg;
41  
42  //SD卡接口信号选择
43  always @(*) begin
44      //SD卡初始化完成之前,端口信号和初始化模块信号相连
45      if(sd_init_done == 1'b0) begin     
46          sd_cs = init_sd_cs;
47          sd_mosi = init_sd_mosi;
48      end    
49      else if(wr_busy) begin
50          sd_cs = wr_sd_cs;
51          sd_mosi = wr_sd_mosi;   
52      end    
53      else if(rd_busy) begin
54          sd_cs = rd_sd_cs;
55          sd_mosi = rd_sd_mosi;       
56      end    
57      else begin
58          sd_cs = 1'b1;
59          sd_mosi = 1'b1;
60      end    
61  end    
62  
63  //SD卡初始化
64  sd_init u_sd_init(
65      .clk_ref            (clk_ref),
66      .rst_n              (rst_n),
67      
68      .sd_miso            (sd_miso),
69      .sd_clk             (init_sd_clk),
70      .sd_cs              (init_sd_cs),
71      .sd_mosi            (init_sd_mosi),
72      
73      .sd_init_done       (sd_init_done)
74      );
75  
76  //SD卡写数据
77  sd_write u_sd_write(
78      .clk_ref            (clk_ref),
79      .clk_ref_180deg     (clk_ref_180deg),
80      .rst_n              (rst_n),
81      
82      .sd_miso            (sd_miso),
83      .sd_cs              (wr_sd_cs),
84      .sd_mosi            (wr_sd_mosi),
85      //SD卡初始化完成之后响应写操作    
86      .wr_start_en        (wr_start_en & sd_init_done),  
87      .wr_sec_addr        (wr_sec_addr),
88      .wr_data            (wr_data),
89      .wr_busy            (wr_busy),
90      .wr_req             (wr_req)
91      );
92  
93  //SD卡读数据
94  sd_read u_sd_read(
95      .clk_ref            (clk_ref),
96      .clk_ref_180deg     (clk_ref_180deg),
97      .rst_n              (rst_n),
98      
99      .sd_miso            (sd_miso),
100     .sd_cs              (rd_sd_cs),
101     .sd_mosi            (rd_sd_mosi),    
102     //SD卡初始化完成之后响应读操作
103     .rd_start_en        (rd_start_en & sd_init_done),  
104     .rd_sec_addr        (rd_sec_addr),
105     .rd_busy            (rd_busy),
106     .rd_val_en          (rd_val_en),
107     .rd_val_data        (rd_val_data)
108     );
109 
110 endmodule

SD卡控制器模块例化了SD卡初始化模块(sd_init)、SD卡写数据模块(sd_write)和SD卡读数据模块(sd_read)。由于这三个模块都驱动着SD卡的引脚,因此在代码的第43行开始的always块中,用于选择哪一个模块连接至SD卡的引脚。 在代码的第40行,init_sd_clk用于初始化SD卡时提供较慢的时钟,在SD卡初始化完成之后,再将较快的时钟clk_ref_180deg赋值给sd_clk。sd_clk从上电之后,是一直都有时钟的,而我们在前面说过SPI_CLK的时钟在空闲时为高电平或者低电平。事实上,为了简化设计,sd_clk在空闲时提供时钟也是可以的,其是否有效主要由片选信号来控制。 在这里主要介绍下SD卡控制器模块的使用方法。当外部需要对SD卡进行读写操作时,首先要判断sd_init_done(SD卡初始化完成)信号,该信号拉高之后才能对SD卡进行读写操作;在对SD卡进行写操作时,只需给出wr_start_en(开始写SD卡数据信号)和wr_sec_addr(写数据扇区地址),此时SD卡控制器模块会拉高wr_busy信号,开始对SD卡发起写入命令;在命令发起成功后SD卡控制器模块会输出wr_req(写数据请求)信号,此时我们给出wr_data(写数据)即可将数据写入SD卡中;待所有数据写入完成后,wr_busy信号拉低,即可再次发起读写操作。SD卡的读操作是给出rd_start_en(rd_start_en)和rd_sec_addr(读数据扇区地址),此时SD卡控制器会拉高rd_busy(读数据忙)信号,开始对SD卡发起读出命令;在命令发起成功后SD卡控制器模块会输出rd_val_en(读数据有效)信号和rd_val_data(读数据),待所有数据读完之后,拉低rd_busy信号。需要注意的是,SD卡单次写入和读出的数据量为512个字节,因为接口封装为16位数据,所以单次读写操作会有256个16位数据。 SD卡初始化模块完成对SD卡的上电初始化操作,我们在SD卡的简介部分已经详细的介绍了SD卡的初始化流程,我们只需要按照SD卡的初始化步骤即可完成SD卡的初始化。由SD卡的初始化流程可知,其步骤非常适合状态机编写,其状态跳转图如图 46.4.3所示。 在这里插入图片描述

图 46.4.3 SD卡初始化状态跳转图 由上图可知,我们把SD卡初始化过程定义为7个状态,分别为st_idle(初始状态)、st_send_cmd0(发送软件复位命令)、st_wait_cmd0(等待SD卡响应)、st_send_cmd8(发送CMD8命令)、st_send_cmd55(发送CMD55命令)、st_send_acmd41(发送ACMD41命令)以及st_init_done(SD卡初始化完成)。因为SD卡的初始化只需要上电后执行一次,所以在初始化完成之后,状态机一直处于st_init_done状态。 SD卡初始化模块的部分代码如下所示:

91  //接收sd卡返回的响应数据
92  //在div_clk_180deg(sd_clk)的上升沿锁存数据
93  always @(posedge div_clk_180deg or negedge rst_n) begin
94      if(!rst_n) begin
95          res_en             
关注
打赏
1665308814
查看更多评论
0.1159s