FPGAベースのSDカード音楽プレーヤーの終焉
序文
WM8731 チップと SD カードの使用については以前に紹介しましたが、これら 2 つを使用して FPGA ベースの SD カード音楽プレーヤーを完成させ、ボタンを追加して音楽再生効果の制御を完了する方法を次に示します。
ヒント: 以下は、この記事の本文の内容です. 記事を書くのは簡単ではありません. 参考になれば幸いです.
1.WAV形式
2. FIFO モジュール
データを読み取るSDカードのクロック周波数は50MHzですが、WM8731のサンプリング周波数は48Kまたは96Kしかなく、2つの周波数が一致せず、読み取り速度が遅いため、FIFOモジュールはSDカードから読み取ったデータを一時的に保存する役割を果たします。 DA 変換速度より速いため、読み出したデータを一時的に格納するための FIFO モジュールが必要です。FIFO モジュールのサイズは 1024 16 ビットに設定されています.16 ビットに設定すると、読み取りデータを 2 バイトごとに 1 ワードにつなぎ合わせることができ、WM8731 の使用に便利です。
FIFO モジュールの構成を図 1 に示します。
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 の反転クロックが生成されます。
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 では、左から右への最初のデジタル チューブは、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プレーヤーと言わざるを得ないが、ボードが少し大きく、選ぶのに不便であるそれ以外は、音楽を聴きながらのランニングにも使えそうな気がします。