基于FPGA的SD卡音乐播放器之SD卡篇

基于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卡。

图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卡特性

◎容量: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所示。

图2 SD卡引脚定义及功能说明

        既然讲到了SD卡,那这里也得提一下TF卡。TF卡也就是MicroSD卡,是一种极细小的快闪存储器卡,是由SanDisk(闪迪)公司发明,主要用于移动手机。MicroSD卡插入适配器(Adapter)可以转换成SD卡,其操作时序和SD卡是一样的。MicroSD卡接口定义以及各引脚功能说明如图 3所示。

图3 TF卡引脚定义及功能说明

        标准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所示:

图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所示:

图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卡常用命令

SD卡的常用命令说明如图6所示。

图6 SD卡常用命令说明

九、SD卡返回数据类型

        发送完命令后,SD卡会返回响应命令的信息,不同的CMD命令会有不同类型的返回值,常用的返回值有R1类型、R3类型和R7类型(R7类型是 CMD8命令专用)

        SD卡返回类型R1数据格式如图7所示:

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

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

图9 R7数据格式
         由上图可知,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所示。

图10 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个时钟周期后允许进行其它操作。

 以下为用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卡的初始化和读操作

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340