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

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


序文

        WM8731 チップと SD カードの使用については以前に紹介しましたが、これら 2 つを使用して FPGA ベースの SD カード音楽プレーヤーを完成させ、ボタンを追加して音楽再生効果の制御を完了する方法を次に示します。


ヒント: 以下は、この記事の本文の内容です. 記事を書くのは簡単ではありません. 参考になれば幸いです.

1.WAV形式

         信号がサンプリングされた後、量子化され、エンコードされます。量子化ビット数は、サンプリング ポイントの値を表すために使用されるビット数を表します. 一般的に使用されるのは、8 ビット (低品質)、12 ビット、16 ビット (高品質) などです. 16 ビットが最も一般的なサンプリング精度です. WM8731がサポートするビット数は、16ビット、20ビット、24ビット、32ビットで、弊社では16ビットを使用しています。ここでのエンコーディングとは、ソース エンコーディング、つまりデータ圧縮を指します。トーン用
音楽には、WAV などの非圧縮音楽形式と、MP3 などの圧縮音楽形式があります。
         非圧縮音楽形式の WAV ファイルは、Microsoft によって導入されたオーディオ ストレージ形式である wave ファイルであり、主に Windows プラットフォームでオーディオ ソースを保存するために使用されます。WAVファイルは、音声波形のバイナリデータを格納したもので、圧縮されていないため容量が大きくなります。WAV ファイルの占有容量の計算式は[(サンプリング周波数×量子化桁数×チャンネル数)÷8]×時間(秒)で、単位はByteです。理論的には、サンプリング周波数と量子化ビットが高いほど優れていますが、より多くのディスク容量が必要になります。一般的な WAV フォーマット(つまり CD 品質の WAV)は、サンプリング周波数 44100Hz、量子化桁数 16Bit、2 チャンネルで、1 分間の音楽を保存するのに 10.34MB の WAV サウンド ファイルが必要であり、多くのスペースを占有します。 、しかし、それはデコードせずに変換することができます. 直接再生.
        デジタル音楽ファイル形式の規格として、WAV形式は容量が大きく使い勝手が悪い。したがって、 通常はMP3 または WMA 形式に圧縮します。圧縮方式には、可逆圧縮と非可逆圧縮があります。MP3、OGG は非可逆圧縮です
圧縮、圧縮されたオーディオを元に戻すと、オーディオは実際には異なります。もちろん、人間の耳がそのような微妙な違いを識別することは困難です。したがって、MP3 および OGG 形式を圧縮された状態から復元すると、損失が発生します。APEやFLACなどのオーディオフォーマットが復元されても、元の音質を損なうことなく維持できます。したがって、APE と FLAC は、高音の質感を失うことなく圧縮および復元できます。これらの圧縮オーディオ形式はすべて独自の圧縮アルゴリズムを使用するため、音楽を再生する場合は、まず対応するアルゴリズムを使用して解凍する必要があります。
        このプロジェクトは、SD カードに保存された音楽を FPGA 経由で読み取り、再生用に WM8731 に出力するものです。圧縮された音楽形式を対応する解凍アルゴリズムで処理せずに使用すると、ノイズが発生するため、このプロジェクトでは非圧縮の WAV 形式を使用します。一般的なWAVフォーマットの音楽サンプリングレートは44.1kHzですが、WM8731でサポートされているサンプリングレートには44.1kHzがないため、SDカードに格納されている音楽サンプリングレートをWM8731でサポートされている48kHzに変換しない場合は変換する必要があります。元の 44.1kHz を直接使用して再生すると、再生速度が速くなり、再生速度の 1.088 倍に相当し、変換は音楽フォーマット変換ソフトウェアを使用できます。このプロジェクトで使用する WAV 形式の音楽のサンプリング レートは 48kHz、量子化ビットは 16 ビットです。

2. FIFO モジュール

        データを読み取るSDカードのクロック周波数は50MHzですが、WM8731のサンプリング周波数は48Kまたは96Kしかなく、2つの周波数が一致せず、読み取り速度が遅いため、FIFOモジュールはSDカードから読み取ったデータを一時的に保存する役割を果たします。 DA 変換速度より速いため、読み出したデータを一時的に格納するための FIFO モジュールが必要です。FIFO モジュールのサイズは 1024 16 ビットに設定されています.16 ビットに設定すると、読み取りデータを 2 バイトごとに 1 ワードにつなぎ合わせることができ、WM8731 の使用に便利です。

FIFO モジュールの構成を図 1 に示します。

図 1 FIFO モジュールの構成

 3.オーディオSDカードインタラクティブモジュール

        オーディオ-SD カード インタラクション モジュールは、SD カードから FIFO へのオーディオ データの読み取りを制御し、FIFO がいっぱいになるか空で読み取られるのを防ぎ、FIFO から読み取られたオーディオ データを処理して再生のために WM8731 に送信するために使用されます。 . winhex ソフトウェアで表示するには、曲の開始アドレスに注意してください。

        以下は、オーディオ SD カード インタラクション モジュールのコードです。

module audio_sd_ctrl(
	input                  mode           ,      //当前歌曲播放模式
    input                  sd_clk         ,      // SD卡时钟信号
    input                  aud_bclk       ,      // WM8731位时钟信号
    input                  rst_n          ,      // 复位信号
    input                  sd_init_done   ,      // SD卡初始化完成
    input                  rd_busy        ,      // 读忙信号
    input                  tx_done        ,
    input          [15:0]  music_data     ,      // 音乐数据
    input          [ 9:0]  wrusedw_cnt    ,      // fifo内剩余写入的字数
	input          [ 4:0]  select         ,      //选择播放哪首音乐
    output   reg           rd_start_en    ,      // 开始读出使能
    output   reg   [31:0]  rd_sec_addr    ,      // 读SD卡扇区地址
    output   reg   [15:0]  dac_data       ,      // 音频数据
	output   reg           playdone              //当前歌曲播放完标志
	 
);

//reg define
reg    [ 1:0]   flow_cnt       ;                 // 状态流计数
reg    [19:0]   rd_sec_cnt     ;                 // 读扇区次数计数器
reg             rd_busy_d0     ;                 // 读忙信号打拍d0
reg             rd_busy_d1     ;                 // 读忙信号打拍d1
reg    [24:0]   START_ADDR=25'd10496     ;       // 音乐存放的起始地址
reg    [19:0]   AUDIO_SEC=20'd79076      ;       // 音乐占用的扇区数

//wire define
wire            neg_rd_busy    ;                 // 读忙信号的下降沿

assign  neg_rd_busy = rd_busy_d1 & (~rd_busy_d0);  // 采读忙信号下降沿

//以下操作循环赋值便于当前歌曲播放完后根据歌曲模式调整歌曲播放下一首还是循环播放
reg  rst1=1,rst2=1;
wire rstnege;
assign rstnege=rst2&(!rst1);
reg [1:0] num=2'b0;

always @(posedge sd_clk)
begin
	rst1 <= rst_n;
    rst2 <= rst1;
end

always @(posedge sd_clk)
	if(rstnege)
		num<=2'b0;
	else if(num<4)
		num<=num+1;

//歌曲选择,SD卡中32首歌曲的起始地址以及占用的扇区数,需视实际情况修改
always @(posedge sd_clk) 
if(num<4)
begin
	case(select)
		5'd0:  begin  START_ADDR<=25'd10496   ;AUDIO_SEC<=20'd79076  ;  end
		5'd1:  begin  START_ADDR<=25'd89600   ;AUDIO_SEC<=20'd99018  ;  end
		5'd2:  begin  START_ADDR<=25'd188672  ;AUDIO_SEC<=20'd104912 ;  end
		5'd3:  begin  START_ADDR<=25'd293632  ;AUDIO_SEC<=20'd85467  ;  end
		5'd4:  begin  START_ADDR<=25'd379136  ;AUDIO_SEC<=20'd74344  ;  end
		5'd5:  begin  START_ADDR<=25'd2216192 ;AUDIO_SEC<=20'd76292  ;  end
		5'd6:  begin  START_ADDR<=25'd859904  ;AUDIO_SEC<=20'd61837  ;  end
		5'd7:  begin  START_ADDR<=25'd2556864 ;AUDIO_SEC<=20'd66882  ;  end
		
		5'd8:  begin  START_ADDR<=25'd1987584 ;AUDIO_SEC<=20'd94012  ;  end
		5'd9:  begin  START_ADDR<=25'd2292544 ;AUDIO_SEC<=20'd94077  ;  end
		5'd10: begin  START_ADDR<=25'd453504  ;AUDIO_SEC<=20'd63007  ;  end
		5'd11: begin  START_ADDR<=25'd791936  ;AUDIO_SEC<=20'd67917  ;  end
		5'd12: begin  START_ADDR<=25'd1679360 ;AUDIO_SEC<=20'd99596  ;  end
		5'd13: begin  START_ADDR<=25'd1080000 ;AUDIO_SEC<=20'd102338 ;  end
		5'd14: begin  START_ADDR<=25'd2746880 ;AUDIO_SEC<=20'd107172 ;  end
		5'd15: begin  START_ADDR<=25'd1593856 ;AUDIO_SEC<=20'd85475  ;  end
		
		5'd16: begin  START_ADDR<=25'd1394368 ;AUDIO_SEC<=20'd106058 ;  end
		5'd17: begin  START_ADDR<=25'd1289984 ;AUDIO_SEC<=20'd104327 ;  end
		5'd18: begin  START_ADDR<=25'd1500480 ;AUDIO_SEC<=20'd93340  ;  end
		5'd19: begin  START_ADDR<=25'd2386624 ;AUDIO_SEC<=20'd105090 ;  end
		5'd20: begin  START_ADDR<=25'd2081600 ;AUDIO_SEC<=20'd67992  ;  end
		5'd21: begin  START_ADDR<=25'd690880  ;AUDIO_SEC<=20'd100999 ;  end
		5'd22: begin  START_ADDR<=25'd516544  ;AUDIO_SEC<=20'd73181  ;  end
		5'd23: begin  START_ADDR<=25'd921792  ;AUDIO_SEC<=20'd68690  ;  end
		
		5'd24: begin  START_ADDR<=25'd2149632 ;AUDIO_SEC<=20'd66534  ;  end
		5'd25: begin  START_ADDR<=25'd1870208 ;AUDIO_SEC<=20'd117358 ;  end
		5'd26: begin  START_ADDR<=25'd990528  ;AUDIO_SEC<=20'd89449  ;  end
		5'd27: begin  START_ADDR<=25'd2491776 ;AUDIO_SEC<=20'd65043  ;  end
		5'd28: begin  START_ADDR<=25'd2623808 ;AUDIO_SEC<=20'd123020 ;  end
		5'd29: begin  START_ADDR<=25'd589760  ;AUDIO_SEC<=20'd101109 ;  end
		5'd30: begin  START_ADDR<=25'd1779008 ;AUDIO_SEC<=20'd91140  ;  end
		5'd31: begin  START_ADDR<=25'd1182400 ;AUDIO_SEC<=20'd107569 ;  end
		
		default: begin  START_ADDR<=25'd10496 ;AUDIO_SEC<=20'd79076  ;  end
	endcase
end	
//音频处理
always @(posedge aud_bclk or negedge rst_n) begin
    if(!rst_n) begin
        dac_data <= 16'd0;
    end
    else if(tx_done)
        dac_data[15:0] <= {music_data[7:0],music_data[15:8]};
end

//打拍采上升沿
always @(posedge sd_clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_busy_d0 <= 1'b0;
        rd_busy_d1 <= 1'b0;
    end
    else begin
        rd_busy_d0 <= rd_busy;
        rd_busy_d1 <= rd_busy_d0;
    end
end

//SD扇区地址变更
always @(posedge sd_clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_sec_addr <= 32'd0;
    end
    else if(rd_sec_addr <= START_ADDR + AUDIO_SEC)
        rd_sec_addr <= rd_sec_cnt + START_ADDR;
end

//读取音频数据
always @(posedge sd_clk or negedge rst_n) begin
    if(!rst_n) begin
        flow_cnt   <=  2'b0;
        rd_sec_cnt <= 20'd0;
		playdone   <=     0;
    end
    else begin
        rd_start_en <= 1'b0;
        case(flow_cnt)
        2'd0: begin
            if(sd_init_done == 1'b1) begin
               flow_cnt     <= flow_cnt + 1'd1;
               rd_start_en  <= 1'b1;
            end
        end
        2'd1: begin
        //读忙信号下降沿说明单次读出结束,开始读取下一扇区地址数据
            if(rd_sec_cnt < AUDIO_SEC) begin
                if(neg_rd_busy) begin
                    rd_sec_cnt <= rd_sec_cnt + 20'd1;   
                    flow_cnt   <= flow_cnt + 1'd1;
				    playdone   <=     0;
                end
            end
            else begin
						rd_sec_cnt <= 20'd0;
						flow_cnt   <=  2'd0;
						playdone   <=     1;
					end
        end
        2'd2: begin
            if(wrusedw_cnt <= 10'd255) begin
                rd_start_en <= 1'b1;
                flow_cnt <= 2'd1;
            end
        end
        default: flow_cnt <= 2'd0;
        endcase
    end
end

endmodule

4.PLLモジュール

        図 2 は、フェーズ ロック ループ モジュールの RTL ゲート レベルの包括的なビューです. フェーズ ロック ループ モジュールは、WM8731 チップ (図の C0) を駆動するために必要な 18.432 MHz のメイン クロックを生成する役割を果たします。C2 は入力 50MHz クロック、C3 は入力 50MHz を反転した出力ですが、これは SD カードを SPI モードで動作させる場合に第 4 の動作モードを使用するためです。このモードでは、SD カードはクロックの立ち上がりエッジでデータを読み取るため、SD カードで読み取ったデータを安定させるには、クロックの立ち下がりエッジでデータを送信する必要があります。クロックの立ち上がりエッジは一般にプログラミングで使用されるため、対応するコードを書き込むときにクロックの立ち上がりエッジを使用しやすくするために、50MHz の反転クロックが生成されます。

図 2 フェーズロック ループ モジュールの RTL ゲートレベル合成図

 5.ボタンデバウンスモジュール

        ボタンのデバウンス 通常のボタンで使用されるスイッチは機械的な弾性スイッチです.機械的な接点が切断されて閉じられると、機械的な接点の弾性効果により、ボタンスイッチは閉じたときにすぐに安定して接続されません.切断すると接続されません。一気に切断されることはありません。そのため、鍵を閉める瞬間と外す瞬間に一連の振動が発生しますが、この現象を防ぐには、鍵の振動をなくすことです。鍵の振動対策では、鍵を閉める時と開ける時の両方の振動をなくすことを考える必要があります。したがって、キー デバウンスの処理については、最悪のケースを考慮する必要があります。メカニカルキーの揺れ時間、揺れ時間、揺れ波形は全てランダムです。ボタンの種類によって、最長の振動時間が異なります. 振動時間の長さは、ボタンの機械的特性に関連しています. ボタンによって出力される信号のジャンプ時間 (立ち上がりエッジと立ち下がりエッジ) は、最大で約 20ms です. ボタンが 1 回閉じられるまでの最短時間は約 120ms です。キー デバウンスの鍵は、安定したロー レベル (またはハイ レベル) 状態を抽出し、キーが安定する前後のジッター パルスをフィルター処理することです。ソフトウェア デバウンスの基本原理は、ボタンが押されたときに、ボタンが押されたとすぐに判断するのではなく、約 20 ミリ秒の遅延プログラムの後 (特定の時間は使用するボタンに応じて調整する必要があります)、その後、キーレベルがまだ閉じているかどうかを確認し、保持されている場合は、実際にキーが押されていることを確認します。

        主なデバウンス モジュール コードは次のとおりです。

module qudou(clk_50M,keyin,keyout,Rst_n);
input wire clk_50M,keyin,Rst_n;
output reg keyout;

reg [4:0] n;
parameter N=50_000;    //分频比
reg [20:0] divider_cnt;   //分频计数
reg clk_1K;

//	分频计数器计数模块
always@(posedge clk_50M or negedge Rst_n)
if(!Rst_n)
	divider_cnt <= 21'd0;
else if(divider_cnt == (N/2-1))
	divider_cnt <= 21'd0;
else
	divider_cnt <= divider_cnt + 1'b1;

//1K扫描时钟生成模块		
always@(posedge clk_50M or negedge Rst_n)
if(!Rst_n)
	clk_1K <= 1'b0;
else if(divider_cnt == (N/2-1))
	clk_1K <= ~clk_1K;
else
	clk_1K <= clk_1K;

always @(posedge clk_1K)
	begin
		if (keyin==1)    //按键未按下时
			begin
				n<=0;
				keyout<=1;
			end
		else
			begin         //按键按下时
				if (n<19)
					begin
						n<=n+1;
						keyout<=1;
					end
				else
					begin   //20ms消抖动
						n<=19;
						keyout<=0;
					end
			end
	end 
endmodule 

6. ボタン制御モジュール

        キー コントロール モジュールは、外部キーを介して音楽再生の効果を制御します。図 2.22 に機能のハードウェア記述を示します。

図 2.22 機能のハードウェア記述

        図 2.22 では、左から右への最初のデジタル チューブは、6 つのモードに対応する 0 ~ 5 の合計 6 つの数字を表示できます。数字 0 は音量調整モードに対応し、数字 1 は順次再生モードに対応します、番号 2 はランダム再生モードに対応し、番号 3 はシングル サイクル モードに対応し、番号 4 は倍速調整モードに対応し、番号 5 は一時停止モードに対応します。モードはボタン S2 で切り替えます。

        図 2.22 では、左から右へ 2 番目のデジタル チューブは、現在再生中の曲の音量である 1 ~ F、合計 15 の数字を表示できます。音量調整モードでは、音量はボタン S0 を押すことによって調整できます。ここで、S0 はボリュームアップ、S1 はボリュームダウンです。

        図 2.22 では、左から 3 番目と 4 番目のデジタル管の組み合わせで、現在再生中の曲の通し番号である 00 ~ 1F の合計 32 個の番号を表示できます。シーケンシャル プレイ、ランダム プレイ、およびシングル サイクル プレイ モードでは、ボタン S0 と S1 を押すことによって曲番号を調整できます。ここで、ボタン S0 は前の曲に対応し、ボタン S1 は次の曲に対応します。

        図 2.22 では、左から 5 番目のデジタルチューブに 1 と 2 を表示できます.ここで、1 は 1 倍速を表し、2 は 2 倍速を表します. 倍速調整モードでは、押すことで 1 倍速と 2 倍速を切り替えることができます.このモードでは、ボタン S1 を押しても何も起こりません。

        図 2.22 では、左から 6 番目、7 番目、8 番目のデジタル管を組み合わせて、現在のシリアル番号の曲の再生時間である 000-FFF を表示できます。

        他の 2 つのモジュール、デコード モジュールと 74HC595 駆動デジタル チューブ モジュールについては、ここでは説明しません。


要約する

        以上が私が共有したいFPGAベースのSDカード音楽プレーヤーの内容であり、完成後はただのMP3プレーヤーと言わざるを得ないが、ボードが少し大きく、選ぶのに不便であるそれ以外は、音楽を聴きながらのランニングにも使えそうな気がします。

おすすめ

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