FPGA SDR(28)ステレオ復調
2019/03/06 追記:0Hz~19kHzと19kHz~38kHzの振幅を揃えました。
2018/07/25 追記:フィードバック量を調整して音が安定しました。題名を戻しました。
2018/07/25 追記:音が左右にフラフラしています。題名を「ステレオ復調(未完成)」に変えました。
2018/07/22 追記:MyAVERAGEを使うように変更しました。
雑誌の記事を何度も読み返してやっとステレオ復調ができました。NCOの位相とパイロット信号の位相の合わせ方が今一つ、二つ、三つほど理解できていませんが、聴いている分ではステレオ復調できているようです。
ディジタル・デザイン・テクノロジ NO.1 2009・SPRING「ディジタルFMステレオ・チューナの制作」 インターフェース 2015年7月号「オール・ソフトウェア無線」
まず19kHzと38kHzのSin信号を出力するMyNCO2X:FPGAラジオ(32)ステレオ復調用NCOを追加して、NCOの19kHz出力とFM復調出力との積をMyLOOPで1msごとの平均値にしてオシロで見てみます。位相が合っていないのでゼロに収束していません。青い横線のレベルがゼロです。
MyNCO2X #( .OUT_WIDTH(NCO19K_WIDTH) ) MyNCO2X_inst ( .clk (clk), .reset_n (RST_N), .clken (ifir_valid), .phi_inc_i 32'sd212448009, .fsin_o (sin19k), .fsin2x_o (sin38k), .out_valid () ); assign freqsin19k = $signed(freq[FIR_WIDTH-1:0]) * sin19k; assign freqsin38k = $signed(freq[FIR_WIDTH-1:0]) * sin38k; MyAverage #( .DATA_WIDTH(FIR_WIDTH+NCO19K_WIDTH), .AVERAGE_WIDTH(9), .AVERAGE(384), .MOVING_AVERAGE_WIDTH(2) ) MyAverage_LOOP_inst ( .clk (clk), .reset_n (RST_N), .in_data (freqsin19k), .in_valid (ifir_valid), .out_data (freqsin19k_ave), .out_valid (freqsin19k_ave_valid) ); assign freqsin19k_fb = freqsin19k_ave_valid ? freqsin19k_ave : 0;
MyAverageの中身です。
`timescale 1ns/1ns module MyAverage #( parameter DATA_WIDTH = 10, parameter AVERAGE_WIDTH = 9, parameter AVERAGE = 512, parameter MOVING_AVERAGE_WIDTH = 2 ) ( input wire clk, input wire reset_n, input wire signed [DATA_WIDTH-1:0] in_data, input wire in_valid, output wire signed [DATA_WIDTH-1:0] out_data, output wire out_valid ); localparam SUM_WIDTH = AVERAGE_WIDTH + DATA_WIDTH; reg [AVERAGE_WIDTH-1:0] cnt; reg signed [SUM_WIDTH-1:0] sum; reg signed [MOVING_AVERAGE_WIDTH+SUM_WIDTH-1:0] ave; always @(posedge clk) begin if (~reset_n) begin cnt <= 0; sum <= 0; ave <= 0; end else begin if (in_valid) begin if (cnt == AVERAGE-1) begin cnt <= 0; sum <= 0; ave <= ave - (ave >>> MOVING_AVERAGE_WIDTH) + (sum >>> AVERAGE_WIDTH); end else begin cnt <= cnt + 1; sum <= sum + in_data; end end end end assign out_data = ave >>> MOVING_AVERAGE_WIDTH; assign out_valid = (cnt == AVERAGE-1) ? 1'b1 : 1'b0; endmodule
MyAVERAGEの出力をNCOの入力にフィードバックして、MyAVERAGEの出力をゼロに収束させます。ここがまだ理解できていません。
MyNCO2X #( .OUT_WIDTH(NCO19K_WIDTH) ) MyNCO2X_inst ( .clk (clk), .reset_n (RST_N), .clken (ifir_valid), .phi_inc_i (32'sd212448009 - freqsin19k_fb), .fsin_o (sin19k), .fsin2x_o (sin38k), .out_valid () ); assign freqsin19k = $signed(freq[FIR_WIDTH-1:0]) * sin19k; assign freqsin38k = $signed(freq[FIR_WIDTH-1:0]) * sin38k; MyAverage #( .DATA_WIDTH(FIR_WIDTH+NCO19K_WIDTH), .AVERAGE_WIDTH(9), .AVERAGE(384), .MOVING_AVERAGE_WIDTH(2) ) MyAverage_LOOP_inst ( .clk (clk), .reset_n (RST_N), .in_data (freqsin19k), .in_valid (ifir_valid), .out_data (freqsin19k_ave), .out_valid (freqsin19k_ave_valid) ); assign freqsin19k_fb = freqsin19k_ave_valid ? freqsin19k_ave : 0;
NCOの38kHz出力を使って、FM復調出力内の38kHzで変調されたL-R成分を同期検波して取り出します。FM復調出力の0~15kHzに含まれるL+R成分と加算・減算することでL信号・R信号に戻せます。
MyNCO2X #( .OUT_WIDTH(NCO19K_WIDTH) ) MyNCO2X_inst ( .clk (clk), .reset_n (RST_N), .clken (ifir_valid), .phi_inc_i (32'sd212448009 - freqsin19k_fb), .fsin_o (sin19k), .fsin2x_o (sin38k), .out_valid () ); assign freqsin19k = $signed(freq[FIR_WIDTH-1:0]) * sin19k; assign freqsin38k = $signed(freq[FIR_WIDTH-1:0]) * sin38k; MyAverage #( .DATA_WIDTH(FIR_WIDTH+NCO19K_WIDTH), .AVERAGE_WIDTH(9), .AVERAGE(384), .MOVING_AVERAGE_WIDTH(2) ) MyAverage_LOOP_inst ( .clk (clk), .reset_n (RST_N), .in_data (freqsin19k), .in_valid (ifir_valid), .out_data (freqsin19k_ave), .out_valid (freqsin19k_ave_valid) ); assign freqsin19k_fb = freqsin19k_ave_valid ? freqsin19k_ave : 0; MyMEMLPF8 #( .DATA_WIDTH(FIR_WIDTH) ) MyLPF8_LPR_inst ( .clk (clk), .reset_n (RST_N), .ast_sink_data (fm ? freq[FIR_WIDTH-1:0] : ifir), .ast_sink_valid (ifir_valid), .ast_sink_error (2'b00), .ast_source_data (freq_LPR), .ast_source_valid (freq_LPR_valid), .ast_source_error () ); MyMEMLPF8 #( .DATA_WIDTH(FIR_WIDTH) ) MyLPF8_LMR_inst ( .clk (clk), .reset_n (RST_N), .ast_sink_data (fm ? freqsin38k[FIR_WIDTH+NCO19K_WIDTH-1-1 -: FIR_WIDTH] : qfir), .ast_sink_valid (ifir_valid), .ast_sink_error (2'b00), .ast_source_data (freq_LMR), .ast_source_valid (), .ast_source_error () ); assign freq_L = freq_LPR + freq_LMR; assign freq_R = freq_LPR - freq_LMR;
青:L-R、黄:L+R
青:R、黄:L