FPGAベースのSDカード音楽プレーヤーのSDカード

FPGAベースのSDカード音楽プレーヤーのSDカード

目次

序文

1.SDカードの紹介

2.SDカードの特徴

3.SDカードのクラスレベル

4. SD カードの動作モード

 5. SD カードの SPI 動作モード

 6、SDカードコマンド分類

7. SD カードのコマンドフォーマット

8. SDカード共通コマンド

9. SDカードの返却データ型

10.SDカードの初期化

11.SDカードの読み込み操作

要約する


序文

        この記事では、SPI インターフェイスを使用して、このプロジェクトでの SD カードの使用と構成を主に記録します。主にSDカードの紹介、SDカードの初期化、SDカードの読み込み操作について。


ヒント: 以下はこの記事のテキストです, すべて著者自身のオリジナルです. 記事を書くのは簡単ではありません. 再投稿する際には、この記事へのリンクを添付してください.

1.SDカードの紹介

        SD カードの完全な英語名はSecure Digital 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カードは、ストレージ容量の観点から、SDカード、SDHCカード(Secure Digital High Capacity、大容量セキュアデジタルカード)の3つのレベルに分けられます.およびSDXCカード(SD eXtended Capacity、容量を拡張したセキュアメモリーカード)。SD カードは MMC カードに基づいて開発され、FAT12/FAT16 ファイル システムを使用し、SD カードは SD1.0 プロトコル仕様を採用し、SD カードの最大ストレージ容量は 2GB であると規定されています; SDHC カードは大容量ストレージ SD カードです。、FAT32ファイルシステムを使用して、SDHCカードはSD2.0プロトコル仕様を採用しています.SDHCカードのストレージ容量は2GBから32GBまでの範囲であると規定されています.SDXCカードは新しく提案された規格であり、FATファイルとは異なります. SD カードおよび SDHC カードで使用されるシステム SDXC カードは、拡張 FAT ファイル システムである exFAT ファイル システムを使用します。SDXC カードは SD3.0 プロトコル仕様を採用しており、SDXC カードのストレージ容量は 32GB から 2TB (2048GB) の範囲であると規定されており、一般的にミドルからハイエンドの一眼レフ カメラや高解像度カメラで使用されています。

2.SDカードの特徴

◎容量:32MB/64MB/128MB/256MB/512MB/1GByte
◎仕様バージョン1.01対応
◎カード内誤り訂正
◎CPRM対応
◎オプションの2つの通信プロトコル:SDモードとSPIモード
◎可変クロック周波数 0~25MHz
◎通信電圧範囲:2.0~3.6V
動作電圧範囲:2.0~3.6V
◎低電圧消費:自動電源オフと自動ウェイクアップ、インテリジェントな電源管理
◎追加のプログラミング電圧は不要
◎カード活線挿抜保護
◎MMCカードとの上位互換
◎ランダムアクセスの高速シリアルインターフェース
---デュアルチャンネルフラッシュインターリーブアクセスをサポート
——高速書き込み技術:超高速フラッシュメモリアクセスと信頼性の高いデータストレージを実現する低コストソリューション
---最大読み書き速度:10Mbyte/s
◎最大10枚スタック可能(20MHz、Vcc=2.7~3.6V)
◎データ寿命:100,000回の書き込み/消去
◎CEおよびFCC認証
◎PIP包装技術
◎寸法:幅24mm×長さ32mm×厚さ1.44mm

3.SDカードのクラスレベル

        メモリのクラスレベルは、主にメモリカードの最低書き込み速度を識別するために、古くから使用されてきた識別です。

クラスの後の数字は、メモリ カードの最小書き込み速度を表します。

クラス 2 2MB/秒

クラス4 4MB/秒

クラス6 6MB/秒

クラス10 10MB/秒

メモリ カードのクラス レベルのマークは、通常、内側に数字が入った閉じていない円です。

4. SD カードの動作モード

        SD カードには合計 9 本のピン ラインがあり、SDIO モードまたはSPI モードで動作します。SDIO モードでは、6 つの信号線 CLK、CMD、および DAT[3:0] が共有されます; SPI モードでは、CS (SDIO_DAT[3])、CLK (SDIO_CLK) MISO (SDIO_DAT[0])、MOSI (SDIO_CMD) 4本の信号線。SD カード インターフェイスの定義と各ピンの機能説明を図 2 に示します。
図 2 SD カードのピン定義と機能説明

        SDカードについて言及したので、TFカードもここで言及する必要があります。MicroSD カードとも呼ばれる TF カードは、SanDisk が発明した非常に小さなフラッシュ メモリ カードで、主に携帯電話で使用されています。MicroSDカードはアダプタ(Adapter)を差し込むことでSDカードに変換でき、操作手順はSDカードと同じです。MicroSD カード インターフェイスの定義と各ピンの機能説明を図 3 に示します。

図3 TFカードのピン定義と機能説明

        標準 SD カードのバージョン 2.0 では、動作クロック周波数は 50Mhz に達することができます.SDIO モードでは、4 ビットのデータ ビット幅が使用され、理論的には 200Mbps (50Mx4bit) の伝送速度に達することができます; SPI モードでは、1-理論的にはビットデータのビット幅を使用し、転送速度は50Mbpsに達します。そのため、SDIO モードでの SD カードの転送速度は速くなり、その動作タイミングはより複雑になります。SD カードを使用して音楽ファイルを読み取る場合、SPI モードでの転送速度はすでにニーズを満たすことができるため、このプロジェクトでは SD カードの SPI モードを使用して SD カードの読み取りと書き込みをテストします。

 5. SD カードの SPI 動作モード

        SDカードの読み取りおよび書き込み操作では、SDカードのSPIモードが使用されます。つまり、SPIプロトコルが読み取りおよび書き込み操作に使用されます。SPIもIICもチップ上でよく使われる通信プロトコルで、IICに比べてSPIは通信速度が速い反面、占有するピン数が多くなります. 次にSPIプロトコルと送信タイミングについて理解しましょう.
        SPI (Serial Peripheral interface) は Motorola によって定義されたシリアル周辺デバイス インターフェイスです. 高速, 全二重, 同期通信バスです. 4 つの信号線しか必要としないため、ピンを節約し、PCB レイアウトを容易にします. この使いやすい機能により、FLASH や AD コンバータなどの SPI 通信プロトコルを統合するチップがますます増えています。
        SPI の通信原理は比較的単純で、マスター/スレーブ方式で動作し、通常、マスター デバイス (ここでは FPGA を指します) と 1 つ以上のスレーブ デバイス (ここでは SD カードを指します) があります。SPI 通信には、SPI_CS、SPI_CLK、SPI_MOSI、SPI_MISO の 4 つのラインが必要です。このうち、SPI_CS、SPI_CLK、SPI_MOSI はマスターからスレーブへ出力され、SPI_MISO はスレーブからマスターへ出力されます。SPI_CS は、チップが選択されているかどうかを制御するために使用されます。つまり、チップ セレクト信号が有効な場合にのみ (SD カードではロー レベルが有効)、チップの動作は有効です。SPI_CLK は、によって生成される同期クロックです。同期用のホスト データ; SPI_MOSI および SPI_MISO は、ホストによって送受信されるデータ ピンです。
         一般的に、SPI 通信には 4 つの異なるモードがあり、異なるスレーブ デバイスは工場出荷時にモードの 1 つとして設定されており、ユーザーがモードを変更することはできません。マスターとスレーブは同じモードで通信する必要があります。そうしないと、データが正しく受信されません。SPI の通信モードは、CPOL (クロック極性) と CPHA (クロック位相) によって決定され、次の 4 つの通信モードがあります。
モード 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 に達することがあります。

 6、SDカードコマンド分類

         SD カードのコマンドは、標準コマンド(CMD0 など) とアプリケーション関連のコマンド(ACMD41 など)に分けられます。ACMDコマンドは特殊なコマンドであり、送信方法は標準コマンドと同じですが、アプリ関連コマンドを送信する前にCMD55コマンドを送信し、次のコマンドがアプリ関連コマンドであることをSDカードに伝える必要があります。 、標準コマンドではありません。

7. SD カードのコマンドフォーマット

         SD カードのコマンド フォーマットは 6 バイトで構成され、データを送信するときに上位ビットが最初に来ます. SD カードの書き込みコマンド フォーマットを図 5 に示します。
図5 SDカードのコマンドフォーマット
         Byte1: コマンドワードの最初のバイトはコマンド番号 (CMD0、CMD1 など) で、フォーマットは「0 1 xxxxxx」です。
コマンド番号の最上位ビットは常に 0 で、これはコマンド番号の開始ビットであり、2 番目の上位ビットは常に 1 であり、これはコマンド番号の送信ビットであり、下位 6 ビットは
特定のコマンド番号 (CMD55、8'd55 = 8'b0011_0111 など、コマンド番号は 0 1 1 1 0 1 1 1 = 0x77)。
        Byte2~Byte5: コマンドパラメータ、一部のコマンドパラメータは予約ビットです。パラメータの内容が定義されていない場合、予約ビットを設定する必要があります
は 0 です。
        Byte6: 最初の 7 ビットは CRC (Cyclic Redundancy Check) チェック ビットで、最後のビットはストップ ビット 0 です。SPI モードでの SD カードのデフォルト
CRC チェックを有効にするかどうかは、SDIO モードで CRC チェックを有効にします。つまり、SPI モードでは、CRC チェック ディジットを送信する必要がありますが、SD カードは CRC チェック ディジットを読み取るときに自動的に無視するため、すべてのチェック ディジットを 1 に設定できます。なお、SD カードの電源投入時はデフォルトで SDIO モードに設定されており、SD カードから CMD0 に返された応答コマンドを受信すると、チップセレクト CS をプルダウンして SPI モードに移行します。したがって、CMD0 コマンドを送信するとき、SD カードは SDIO モードであり、CRC チェックを有効にする必要があります。また、CMD8 の CRC チェックは常に有効であり、CRC チェックも有効にする必要があります。これら 2 つのコマンドを除いて、他のコマンドの CRC はチェックされない場合があります。

8. SDカード共通コマンド

SD カードの一般的に使用されるコマンドを図 6 に示します。
図 6 SD カード共通コマンドの説明

9. 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 (操作条件レジスタ、操作条件レジスタ) レジスタの内容
許可する。
        SD カードのリターン タイプ R7 データ フォーマットを図 9 に示します。
図 9 R7 データ形式
         上の図からわかるように、SD カードの戻り型R7 形式は合計 5 バイトを返し、最初に返されるバイトは以前に紹介したものです。
R1 の内容、残りのバイトには SD カードの動作電圧情報とチェック バイトが含まれます。その中で、電圧範囲はより重要です
パラメータ、具体的な内容は次のとおりです。
Bit[11:8]: 動作電圧フィードバック
0: 未定義
1:2.7V~3.6V
2: 低電圧
4: 予約ビット
8: 予約ビット
その他:未定

10.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 カードは正常に初期化され、それ以外の場合はエラーカードと判断。
         市場に出回っているほとんどの SD カードは SD2.0 プロトコルを採用しており、詳細な初期化手順は次のとおりです。
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

11.SDカードの読み込み操作

         SD カードが一度に読み書きできるデータ量は、512 バイトの整数倍でなければなりません。つまり、SD カードが読み書きできるデータの最小量は 512 バイトです。CMD16 をコマンドすることで、1 回の読み取りと書き込み操作のデータ長を構成できるため、毎回の読み取りと書き込みのデータ量は (n*512) バイト (n≥1) になります。このプロジェクトでは、SD を使用します カードのデフォルト設定、つまり、1 回の読み取りおよび書き込み操作のデータ
量は 512 バイトです。
        SD カード読み取り動作のタイミング図を図 10 に示します。
図 10 SD カードの読み取り動作シーケンス図
SD カードの読み取り操作プロセスは次のとおりです。
1. チップ セレクト CS ピンをプルダウンし、コマンド CMD17 (0x51) を送信して 1 つのデータ ブロックを読み取り、コマンドが送信された後に待機します。
SD カードは応答データを返します。
2. SD カードが正しい応答データ 0x00 を返すと、SD カードから返されたデータ ヘッダー 0xfe の解析を開始する準備が整います。
3. データ ヘッダー 0xfe に解析した後、SD カードから返された 512 バイトのデータを受信します。
4. データ解析が完了すると、次の 2 バイトの CRC チェック値が受信されます。SPIモードではデータを処理しないため
CRC チェック。これらの 2 バイトは直接無視できます。
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

要約する

        今回共有する内容は以上で、FPGAベースのSDカード音楽プレーヤープロジェクトで、SDカードとは何か、ハードウェア言語を使ってSDカードの初期化と読み込み操作を完了する方法を簡単に紹介します。

おすすめ

転載: blog.csdn.net/m0_66360845/article/details/126481784