admin管理员组文章数量:1605636
基于FPGA的SD卡音乐播放器之SD卡篇
目录
前言
一、SD卡简介
二、SD卡特性
三、SD卡的class等级
四、SD卡工作模式
五、SD卡的SPI操作模式
六、SD卡命令分类
七、SD卡命令格式
八、SD卡常用命令
九、SD卡返回数据类型
十、SD卡初始化
十 一、SD卡读操作
总结
前言
这篇文章主要记录一下SD卡在该项目中的使用配置,用的是SPI接口。主要是关于SD卡的介绍、SD卡初始化、SD卡读操作。
提示:以下是本篇文章正文内容,均为作者本人原创,写文章实属不易,希望各位在转载时附上本文链接。
一、SD卡简介
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卡可通过卡片上面的标注进行区分,如图1左侧图片上面标注为“ MultiMediaCard”字母样式的为MMC卡,右侧图片上面标注为“SD”字母样式的为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卡特性
◎容量:32MB/64MB/128MB/256MB/512MB/1GByte ◎兼容规范版本 1.01 ◎卡上错误校正 ◎支持 CPRM ◎两个可选的通信协议:SD 模式和 SPI 模式 ◎可变时钟频率 0-25MHz ◎通信电压范围:2.0-3.6V 工作电压范围:2.0-3.6V ◎低电压消耗:自动断电及自动睡醒,智能电源管理 ◎无需额外编程电压 ◎卡片带电插拔保护 ◎正向兼容 MMC 卡 ◎高速串行接口带随即存取 ---支持双通道闪存交叉存取 ---快写技术:一个低成本的方案,能够超高速闪存访问和高可靠数据存储 ---最大读写速率:10Mbyte/s ◎最大 10 个堆叠的卡(20MHz,Vcc=2.7-3.6V) ◎数据寿命:10 万次编程/擦除 ◎CE 和 FCC 认证 ◎PIP 封装技术 ◎尺寸:24mm 宽×32mm 长×1.44mm 厚三、SD卡的class等级
存储器的class等级是一种很早就使用的标识,主要是标识存储卡的最低写入速度。
class后面的数字就代表了存储卡最低写入速度:
class 2 2MB/s
class 4 4MB/s
class 6 6MB/s
class 10 10MB/s
class等级在存储卡上的标志一般是一个未闭合的圆圈,内有一个数字。四、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卡接口定义以及各引脚功能说明如图2所示。既然讲到了SD卡,那这里也得提一下TF卡。TF卡也就是MicroSD卡,是一种极细小的快闪存储器卡,是由SanDisk(闪迪)公司发明,主要用于移动手机。MicroSD卡插入适配器(Adapter)可以转换成SD卡,其操作时序和SD卡是一样的。MicroSD卡接口定义以及各引脚功能说明如图 3所示。
标准SD卡2.0版本中,工作时钟频率可以达到50Mhz,在SDIO模式下采用4位数据位宽,理论上可以达到200Mbps(50Mx4bit)的传输速率;在SPI模式下采用1位数据位宽,理论上可以达到50Mbps的传输速率。因此SD卡在SDIO模式下的传输速率更快,同时其操作时序也更复杂。 对于使用SD卡读取音乐文件来说,SPI模式下的传输速度已经能够满足我们的需求,因此我们本项目采用SD卡的SPI模式来对SD卡进行读写测试。五、SD卡的SPI操作模式
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。 不同模式下的时序图如图4所示: 对于SD卡的SPI模式而言,采用的SPI的通信模式为模式3,即CPOL = 1,CPHA = 1,此时数据应当在下降沿变化,上升沿要保持稳定。在SD卡2.0版本协议中,SPI_CLK时钟频率可达50Mhz。六、SD卡命令分类
SD卡的命令分为标准命令(如CMD0)和应用相关命令(如ACMD41)。ACMD命令是特殊命令,发送方法同标准命令一样,但是在发送应用相关命令之前,必须先发送CMD55命令,告诉SD卡接下来的命令是应用相关命令,而非标准命令。七、SD卡命令格式
SD卡的命令格式由6个字节组成,发送数据时高位在前,SD卡的写入命令格式如图5所示: 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卡常用命令
SD卡的常用命令说明如图6所示。九、SD卡返回数据类型
发送完命令后,SD卡会返回响应命令的信息,不同的CMD命令会有不同类型的返回值,常用的返回值有R1类型、R3类型和R7类型(R7类型是 CMD8命令专用)。SD卡返回类型R1数据格式如图7所示:
由上图可知,SD卡返回类型R1格式共返回1个字节,最高位固定为0,其它位分别表示对应 状态的标志,高电平有效。 SD卡返回类型R3数据格式如图8所示: 由上图可知,SD卡返回类型R3格式共返回5个字节,首先返回的第一个字节为前面介绍的 R1的内容,其余字节为OCR(Operation Conditions Register,操作条件寄存器)寄存器的内 容。 SD卡返回类型R7数据格式如图9所示: 由上图可知,SD卡返回类型R7格式共返回5个字节,首先返回的第一个字节为前面介绍的 R1的内容,其余字节包含SD卡操作电压信息和校验字节等内容。其中电压范围是一个比较重要 的参数,其具体内容如下所示: Bit[11:8]:操作电压反馈 0:未定义 1:2.7V~3.6V 2:低电压 4:保留位 8:保留位 其它:未定义十、SD卡初始化
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卡,下面为其详细初始化步骤: 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步。以下为用verilog语言编写的SD卡的初始化代码:
module sd_init(
input clk_ref , //时钟信号
input clk_ref_180deg, //时钟信号,与clk_ref相位相差180度
input rst_n , //复位信号,低电平有效
input sd_miso , //SD卡SPI串行输入数据信号
output sd_clk , //SD卡SPI时钟信号
output reg sd_cs , //SD卡SPI片选信号
output reg sd_mosi , //SD卡SPI串行输出数据信号
output reg sd_init_done //SD卡初始化完成信号
);
//parameter define
//SD卡软件复位命令,由于命令号及参数为固定值,CRC也为固定值,CRC = 8'h95
parameter CMD0 = {8'h40,8'h00,8'h00,8'h00,8'h00,8'h95};
//接口状态命令,发送主设备的电压范围,用于区分SD卡版本,只有2.0及以后的卡才支持CMD8命令
//MMC卡及V1.x的卡,不支持此命令,由于命令号及参数为固定值,CRC也为固定值,CRC = 8'h87
parameter CMD8 = {8'h48,8'h00,8'h00,8'h01,8'haa,8'h87};
//告诉SD卡接下来的命令是应用相关命令,而非标准命令, 不需要CRC
parameter CMD55 = {8'h77,8'h00,8'h00,8'h00,8'h00,8'hff};
//发送操作寄存器(OCR)内容, 不需要CRC
parameter ACMD41= {8'h69,8'h40,8'h00,8'h00,8'h00,8'hff};
//时钟分频系数,初始化SD卡时降低SD卡的时钟频率,50M/250K = 200
parameter DIV_FREQ = 200;
//上电至少等待74个同步时钟周期,在等待上电稳定期间,sd_cs = 1,sd_mosi = 1
parameter POWER_ON_NUM = 5000;
//发送软件复位命令时等待SD卡返回的最大时间,T = 100ms; 100_000us/4us = 25000
//当超时计数器等于此值时,认为SD卡响应超时,重新发送软件复位命令
parameter OVER_TIME_NUM = 25000;
parameter st_idle = 7'b000_0001; //默认状态,上电等待SD卡稳定
parameter st_send_cmd0 = 7'b000_0010; //发送软件复位命令
parameter st_wait_cmd0 = 7'b000_0100; //等待SD卡响应
parameter st_send_cmd8 = 7'b000_1000; //发送主设备的电压范围,检测SD卡是否满足
parameter st_send_cmd55 = 7'b001_0000; //告诉SD卡接下来的命令是应用相关命令
parameter st_send_acmd41 = 7'b010_0000; //发送操作寄存器(OCR)内容
parameter st_init_done = 7'b100_0000; //SD卡初始化完成
//reg define
reg [7:0] cur_state ;
reg [7:0] next_state ;
reg [7:0] div_cnt ; //分频计数器
reg div_clk ; //分频后的时钟
reg [12:0] poweron_cnt ; //上电等待稳定计数器
reg res_en ; //接收SD卡返回数据有效信号
reg [47:0] res_data ; //接收SD卡返回数据
reg res_flag ; //开始接收返回数据的标志
reg [5:0] res_bit_cnt ; //接收位数据计数器
reg [5:0] cmd_bit_cnt ; //发送指令位计数器
reg [15:0] over_time_cnt ; //超时计数器
reg over_time_en ; //超时使能信号
//wire define
wire div_clk_180deg ; //时钟相位和div_clk相差180度
assign sd_clk = ~div_clk; //SD_CLK
assign div_clk_180deg = ~div_clk; //相位和DIV_CLK相差180度的时钟
//时钟分频,div_clk = 250KHz
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n) begin
div_clk <= 1'b0;
div_cnt <= 8'd0;
end
else begin
if(div_cnt == DIV_FREQ/2-1'b1) begin
div_clk <= ~div_clk;
div_cnt <= 8'd0;
end
else
div_cnt <= div_cnt + 1'b1;
end
end
//上电等待稳定计数器
always @(posedge div_clk or negedge rst_n) begin
if(!rst_n)
poweron_cnt <= 13'd0;
else if(cur_state == st_idle) begin
if(poweron_cnt < POWER_ON_NUM)
poweron_cnt <= poweron_cnt + 1'b1;
end
else
poweron_cnt <= 13'd0;
end
//接收sd卡返回的响应数据
//在div_clk_180deg(sd_clk)的上升沿锁存数据
always @(posedge div_clk_180deg or negedge rst_n) begin
if(!rst_n) begin
res_en <= 1'b0;
res_data <= 48'd0;
res_flag <= 1'b0;
res_bit_cnt <= 6'd0;
end
else begin
//sd_miso = 0 开始接收响应数据
if(sd_miso == 1'b0 && res_flag == 1'b0) begin
res_flag <= 1'b1;
res_data <= {res_data[46:0],sd_miso};
res_bit_cnt <= res_bit_cnt + 6'd1;
res_en <= 1'b0;
end
else if(res_flag) begin
//R1返回1个字节,R3 R7返回5个字节
//在这里统一按照6个字节来接收,多出的1个字节为NOP(8个时钟周期的延时)
res_data <= {res_data[46:0],sd_miso};
res_bit_cnt <= res_bit_cnt + 6'd1;
if(res_bit_cnt == 6'd47) begin
res_flag <= 1'b0;
res_bit_cnt <= 6'd0;
res_en <= 1'b1;
end
end
else
res_en <= 1'b0;
end
end
always @(posedge div_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle : begin
//上电至少等待74个同步时钟周期
if(poweron_cnt == POWER_ON_NUM) //默认状态,上电等待SD卡稳定
next_state = st_send_cmd0;
else
next_state = st_idle;
end
st_send_cmd0 : begin //发送软件复位命令
if(cmd_bit_cnt == 6'd47)
next_state = st_wait_cmd0;
else
next_state = st_send_cmd0;
end
st_wait_cmd0 : begin //等待SD卡响应
if(res_en) begin //SD卡返回响应信号
if(res_data[47:40] == 8'h01) //SD卡返回复位成功
next_state = st_send_cmd8;
else
next_state = st_idle;
end
else if(over_time_en) //SD卡响应超时
next_state = st_idle;
else
next_state = st_wait_cmd0;
end
//发送主设备的电压范围,检测SD卡是否满足
st_send_cmd8 : begin
if(res_en) begin //SD卡返回响应信号
//返回SD卡的操作电压,[19:16] = 4'b0001(2.7V~3.6V)
if(res_data[19:16] == 4'b0001)
next_state = st_send_cmd55;
else
next_state = st_idle;
end
else
next_state = st_send_cmd8;
end
//告诉SD卡接下来的命令是应用相关命令
st_send_cmd55 : begin
if(res_en) begin //SD卡返回响应信号
if(res_data[47:40] == 8'h01) //SD卡返回空闲状态
next_state = st_send_acmd41;
else
next_state = st_send_cmd55;
end
else
next_state = st_send_cmd55;
end
st_send_acmd41 : begin //发送操作寄存器(OCR)内容
if(res_en) begin //SD卡返回响应信号
if(res_data[47:40] == 8'h00) //初始化完成信号
next_state = st_init_done;
else
next_state = st_send_cmd55; //初始化未完成,重新发起
end
else
next_state = st_send_acmd41;
end
st_init_done : next_state = st_init_done; //初始化完成
default : next_state = st_idle;
endcase
end
//SPI模式中SD卡在div_clk_180deg(sd_clk)的上升沿锁存数据,因此在sd_clk的下降沿输出数据
//为了统一在alway块中使用上升沿触发,此处使用和sd_clk相位相差180度的时钟
always @(posedge div_clk or negedge rst_n) begin
if(!rst_n) begin
sd_cs <= 1'b1;
sd_mosi <= 1'b1;
sd_init_done <= 1'b0;
cmd_bit_cnt <= 6'd0;
over_time_cnt <= 16'd0;
over_time_en <= 1'b0;
end
else begin
over_time_en <= 1'b0;
case(cur_state)
st_idle : begin //默认状态,上电等待SD卡稳定
sd_cs <= 1'b1; //在等待上电稳定期间,sd_cs=1
sd_mosi <= 1'b1; //sd_mosi=1
end
st_send_cmd0 : begin //发送CMD0软件复位命令
cmd_bit_cnt <= cmd_bit_cnt + 6'd1;
sd_cs <= 1'b0;
sd_mosi <= CMD0[6'd47 - cmd_bit_cnt]; //先发送CMD0命令高位
if(cmd_bit_cnt == 6'd47)
cmd_bit_cnt <= 6'd0;
end
//在接收CMD0响应返回期间,片选CS拉低,进入SPI模式
st_wait_cmd0 : begin
sd_mosi <= 1'b1;
if(res_en) //SD卡返回响应信号
//接收完成之后再拉高,进入SPI模式
sd_cs <= 1'b1;
over_time_cnt <= over_time_cnt + 1'b1; //超时计数器开始计数
//SD卡响应超时,重新发送软件复位命令
if(over_time_cnt == OVER_TIME_NUM - 1'b1)
over_time_en <= 1'b1;
if(over_time_en)
over_time_cnt <= 16'd0;
end
st_send_cmd8 : begin //发送CMD8
if(cmd_bit_cnt<=6'd47) begin
cmd_bit_cnt <= cmd_bit_cnt + 6'd1;
sd_cs <= 1'b0;
sd_mosi <= CMD8[6'd47 - cmd_bit_cnt]; //先发送CMD8命令高位
end
else begin
sd_mosi <= 1'b1;
if(res_en) begin //SD卡返回响应信号
sd_cs <= 1'b1;
cmd_bit_cnt <= 6'd0;
end
end
end
st_send_cmd55 : begin //发送CMD55
if(cmd_bit_cnt<=6'd47) begin
cmd_bit_cnt <= cmd_bit_cnt + 6'd1;
sd_cs <= 1'b0;
sd_mosi <= CMD55[6'd47 - cmd_bit_cnt];
end
else begin
sd_mosi <= 1'b1;
if(res_en) begin //SD卡返回响应信号
sd_cs <= 1'b1;
cmd_bit_cnt <= 6'd0;
end
end
end
st_send_acmd41 : begin //发送ACMD41
if(cmd_bit_cnt <= 6'd47) begin
cmd_bit_cnt <= cmd_bit_cnt + 6'd1;
sd_cs <= 1'b0;
sd_mosi <= ACMD41[6'd47 - cmd_bit_cnt];
end
else begin
sd_mosi <= 1'b1;
if(res_en) begin //SD卡返回响应信号
sd_cs <= 1'b1;
cmd_bit_cnt <= 6'd0;
end
end
end
st_init_done : begin //初始化完成
sd_init_done <= 1'b1;
sd_cs <= 1'b1;
sd_mosi <= 1'b1;
end
default : begin
sd_cs <= 1'b1;
sd_mosi <= 1'b1;
end
endcase
end
end
endmodule
十 一、SD卡读操作
SD卡读写一次的数据量必须为512字节的整数倍,即对SD卡读写操作的最少数据量为512 个字节。我们可以通过命令CMD16来配置单次读写操作的数据长度,以使每次读写的数据量为(n*512)个字节(n≥1),本工程SD卡的读写操作使用SD卡默认配置,即单次读写操作的数据 量为512个字节。 SD卡的读操作时序图如图10所示。 SD卡的读操作流程如下: 1、 拉低片选CS引脚,发送命令CMD17(0x51)读取单个数据块,命令发送完成后等待 SD卡返回响应数据; 2、 SD卡返回正确响应数据0x00后,准备开始解析SD卡返回的数据头0xfe; 3、 解析到数据头0xfe后,接下来接收SD卡返回的512个字节的数据; 4、 数据解析完成后,接下来接收两个字节的CRC校验值。由于SPI模式下不对数据进行 CRC校验,可直接忽略这两个字节; 5、 校验数据接收完成后,等待8个时钟周期; 6、 拉高片选CS引脚,等待8个时钟周期后允许进行其它操作。以下为用verilog语言编写的SD卡的读操作代码:
module sd_read(
input clk_ref , //时钟信号
input clk_ref_180deg, //时钟信号,与clk_ref相位相差180度
input rst_n , //复位信号,低电平有效
//SD卡接口
input sd_miso , //SD卡SPI串行输入数据信号
output reg sd_cs , //SD卡SPI片选信号
output reg sd_mosi , //SD卡SPI串行输出数据信号
//用户读接口
input rd_start_en , //开始读SD卡数据信号
input [31:0] rd_sec_addr , //读数据扇区地址
output reg rd_busy , //读数据忙信号
output reg rd_val_en , //读数据有效信号
output reg [15:0] rd_val_data //读数据
);
//reg define
reg rd_en_d0 ; //rd_start_en信号延时打拍
reg rd_en_d1 ;
reg res_en ; //接收SD卡返回数据有效信号
reg [7:0] res_data ; //接收SD卡返回数据
reg res_flag ; //开始接收返回数据的标志
reg [5:0] res_bit_cnt ; //接收位数据计数器
reg rx_en_t ; //接收SD卡数据使能信号
reg [15:0] rx_data_t ; //接收SD卡数据
reg rx_flag ; //开始接收的标志
reg [3:0] rx_bit_cnt ; //接收数据位计数器
reg [8:0] rx_data_cnt ; //接收的数据个数计数器
reg rx_finish_en ; //接收完成使能信号
reg [3:0] rd_ctrl_cnt ; //读控制计数器
reg [47:0] cmd_rd ; //读命令
reg [5:0] cmd_bit_cnt ; //读命令位计数器
reg rd_data_flag ; //准备读取数据的标志
//wire define
wire pos_rd_en ; //开始读SD卡数据信号的上升沿
assign pos_rd_en = (~rd_en_d1) & rd_en_d0;
//rd_start_en信号延时打拍
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n) begin
rd_en_d0 <= 1'b0;
rd_en_d1 <= 1'b0;
end
else begin
rd_en_d0 <= rd_start_en;
rd_en_d1 <= rd_en_d0;
end
end
//接收sd卡返回的响应数据
//在clk_ref_180deg(sd_clk)的上升沿锁存数据
always @(posedge clk_ref_180deg or negedge rst_n) begin
if(!rst_n) begin
res_en <= 1'b0;
res_data <= 8'd0;
res_flag <= 1'b0;
res_bit_cnt <= 6'd0;
end
else begin
//sd_miso = 0 开始接收响应数据
if(sd_miso == 1'b0 && res_flag == 1'b0) begin
res_flag <= 1'b1;
res_data <= {res_data[6:0],sd_miso};
res_bit_cnt <= res_bit_cnt + 6'd1;
res_en <= 1'b0;
end
else if(res_flag) begin
res_data <= {res_data[6:0],sd_miso};
res_bit_cnt <= res_bit_cnt + 6'd1;
if(res_bit_cnt == 6'd7) begin
res_flag <= 1'b0;
res_bit_cnt <= 6'd0;
res_en <= 1'b1;
end
end
else
res_en <= 1'b0;
end
end
//接收SD卡有效数据
//在clk_ref_180deg(sd_clk)的上升沿锁存数据
always @(posedge clk_ref_180deg or negedge rst_n) begin
if(!rst_n) begin
rx_en_t <= 1'b0;
rx_data_t <= 16'd0;
rx_flag <= 1'b0;
rx_bit_cnt <= 4'd0;
rx_data_cnt <= 9'd0;
rx_finish_en <= 1'b0;
end
else begin
rx_en_t <= 1'b0;
rx_finish_en <= 1'b0;
//数据头0xfe 8'b1111_1110,所以检测0为起始位
if(rd_data_flag && sd_miso == 1'b0 && rx_flag == 1'b0)
rx_flag <= 1'b1;
else if(rx_flag) begin
rx_bit_cnt <= rx_bit_cnt + 4'd1;
rx_data_t <= {rx_data_t[14:0],sd_miso};
if(rx_bit_cnt == 4'd15) begin
rx_data_cnt <= rx_data_cnt + 9'd1;
//接收单个BLOCK共512个字节 = 256 * 16bit
if(rx_data_cnt <= 9'd255)
rx_en_t <= 1'b1;
else if(rx_data_cnt == 9'd257) begin //接收两个字节的CRC校验值
rx_flag <= 1'b0;
rx_finish_en <= 1'b1; //数据接收完成
rx_data_cnt <= 9'd0;
rx_bit_cnt <= 4'd0;
end
end
end
else
rx_data_t <= 16'd0;
end
end
//寄存输出数据有效信号和数据
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n) begin
rd_val_en <= 1'b0;
rd_val_data <= 16'd0;
end
else begin
if(rx_en_t) begin
rd_val_en <= 1'b1;
rd_val_data <= rx_data_t;
end
else
rd_val_en <= 1'b0;
end
end
//读命令
always @(posedge clk_ref or negedge rst_n) begin
if(!rst_n) begin
sd_cs <= 1'b1;
sd_mosi <= 1'b1;
rd_ctrl_cnt <= 4'd0;
cmd_rd <= 48'd0;
cmd_bit_cnt <= 6'd0;
rd_busy <= 1'b0;
rd_data_flag <= 1'b0;
end
else begin
case(rd_ctrl_cnt)
4'd0 : begin
rd_busy <= 1'b0;
sd_cs <= 1'b1;
sd_mosi <= 1'b1;
if(pos_rd_en) begin
cmd_rd <= {8'h51,rd_sec_addr,8'hff}; //写入单个命令块CMD17
rd_ctrl_cnt <= rd_ctrl_cnt + 4'd1; //控制计数器加1
//开始执行读取数据,拉高读忙信号
rd_busy <= 1'b1;
end
end
4'd1 : begin
if(cmd_bit_cnt <= 6'd47) begin //开始按位发送读命令
cmd_bit_cnt <= cmd_bit_cnt + 6'd1;
sd_cs <= 1'b0;
sd_mosi <= cmd_rd[6'd47 - cmd_bit_cnt]; //先发送高字节
end
else begin
sd_mosi <= 1'b1;
if(res_en) begin //SD卡响应
rd_ctrl_cnt <= rd_ctrl_cnt + 4'd1; //控制计数器加1
cmd_bit_cnt <= 6'd0;
end
end
end
4'd2 : begin
//拉高rd_data_flag信号,准备接收数据
rd_data_flag <= 1'b1;
if(rx_finish_en) begin //数据接收完成
rd_ctrl_cnt <= rd_ctrl_cnt + 4'd1;
rd_data_flag <= 1'b0;
sd_cs <= 1'b1;
end
end
default : begin
//进入空闲状态后,拉高片选信号,等待8个时钟周期
sd_cs <= 1'b1;
rd_ctrl_cnt <= rd_ctrl_cnt + 4'd1;
end
endcase
end
end
endmodule
总结
以上就是此次要分享的全部内容,本文简单介绍了什么是SD卡以及如何在基于FPGA的SD卡音乐播放器工程中用硬件语言完成对SD卡的初始化和读操作。
版权声明:本文标题:基于FPGA的SD卡音乐播放器之SD卡篇 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1728486988a1160273.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论