2012年7月15日日曜日

FPGA FMトランスミッタ: ステレオ変調器

ステレオ変調器の構成
ここでは、入力切替器とマトリクス回路と38kHzサブキャリアとの平衡変調器、19kHzステレオパイロット信号付加を行います。


マトリクス回路周りの信号処理の詳細を前回の記事から再掲します。



サブチャンネルの処理
サブチャンネルは、入力L/Rの差信号と38kHzサブキャリアの乗算で生成します。ここでサブキャリアの波形を考えます。サンプリング周波数152kHzのとき38kHzの正弦波は、わずか4点で表現することになります。下表を見るとサブキャリアがとる値としては0, 1, -1しかありませんから乗算といいながら、L-R信号をオンオフするか極性反転だけで済むことになります。



これを見て、スイッチング方式のステレオ変調そのものだなと感じました。


入力レベルと変調度の関係
アナログ入力端子があるので入力レベル -6dBFS で変調度100%を基本とすることにしました。

(1)ステレオモード
L/Rチャンネルが-6dBFSのとき、Lチャンネル45%・Rチャンネル45%・ステレオパイロット信号10%で合計100%変調。

(2)モノラルモード
Lチャンネルが-6dBFSのとき100%変調。



ステレオパイロット信号の発生
ステレオパイロット信号は、38kHzサブキャリアの半分の周波数で同位相です。振幅は、ステレオモードで入力信号0dBFSのときメインチャンネルの18分の1とします。ただし、処理の過程でシフト演算により振幅を1/16にすることを考慮して、下表では22ビットフルスケールの16/18としました。




特性実測値
まず、コーデックのアナログ出力でのコンポジット信号の波形です。Lチャンネルのみ1kHz 0dBFSでステレオパイロット信号は断にしています。
教科書によると、波形の交点が0基線(図では上にずらしています)と一致し一直線上にあるのでメインチャンネルとサブチャンネルで位相差がなく(基部にうねりがあるので)レベル差だけがある状態とのこと。


コンポジット信号波形(1kHz 0dBFS Lチャンネル)

2012.08.10追記 波形の うねり は、コーデックの周波数特性が原因でした。下図のとおり、コンポジット信号の最高周波数53kHz付近(0.35 fs)では、なんと-3dBも下がっています。これではコンポジット信号を扱うことができません。

CS4270の周波数特性


パイロット信号のレベルを確認するためスペクトルを確認しました。
(1)は、1kHz 0dBFS L/Rチャンネル同相です。メインチャンネル-12.53dB、パイロット-37.9dBで25.4dBのレベル差があります。メインチャンネル180%変調相当レベル、パイロット10%のレベル差は25.1dBなので概ね妥当です。L/Rチャンネル同相のためサブキャリアはありません。

(1)コンポジット信号(1kHz 0dBFS L/Rチャンネル)

(2)は、1kHz 0dBFS Lチャンネルのみです。メインチャンネル-18.55dB、パイロット -37.9dB、サブキャリアのうち37kHzは-24.51dB、39kHzは-25.4dBでした。サブキャリアの37/39kHzでレベル差0.9dBもあります。
(3)(4)は、周波数を1kHz/125Hzと切替え、さらにFFTのサンプル数を変えてみたところです。低域側/高域側のレベルは、(3)1kHz: -24.51/-25.4dBで+0.89dB、125Hz: -25.28dB/-24.56dBで-0.72dB。(4)1kHz: -25.93dB/-24.73dBで-1.2dB、125Hz: -24.7dB/-25.95dBで+1.25dB。
測定系の誤差もあるようですが、よくわからなくなってきました。普通に考えればアナログ周りの周波数特性差でしょうか。
(2)コンポジット信号(1kHz 0dBFS Lチャンネル)


(3)サブキャリア(赤1kHz/黒125Hzの比較 FFT:65536)

(4)サブキャリア(赤1kHz/黒125Hzの比較 FFT:8192)

(5)は、モノラルモード1kHz 0dBFSで200%変調相当レベルで-11.6dBでした。(1)のメインチャンネルの180%変調相当レベルより0.93dB高く、概ね設計通りであることが確認できました。

(5)モノラルモード(1kHz 0dBFS Lチャンネル)


mpx.v
module mpx(
  mpx_l0_i, mpx_r0_i, mpx_l1_i, mpx_r1_i, mpx_o,
  clk, reset, mpx_set, mpx_pilot_o
  );

  input [23:0] mpx_l0_i;
  input [23:0] mpx_r0_i;
  input [23:0] mpx_l1_i;
  input [23:0] mpx_r1_i;
  output [23:0] mpx_o;
  input clk;  // 64fs
  input reset;
  input [4:0] mpx_set;
  output [23:0] mpx_pilot_o;

  reg [8:0] state512;
  wire [5:0] state64;
  wire [1:0] sub38k_addr;
  wire [2:0] pilot19k_addr;
  wire [21:0] pilot19k;
  wire latch_out;
  wire INPUT_SEL;
  wire MPX_MODE;
  wire MAIN_CHANNEL_CONT;
  wire SUB_CHANNEL_CONT;
  wire PILOT_CONT;
  
  reg [23:0] input_reg_l;
  reg [23:0] input_reg_r;
  reg [24:0] main_channel;
  reg [24:0] sub_channel;
  reg [24:0] diff;
  reg [25:0] stereo_composite;
  reg [25:0] stereo_composite2;
  reg [23:0] mpx_o;
  reg [23:0] mpx_pilot_o;

  // State counter
  always @(posedge clk or posedge reset)
    begin
      if (reset)
        state512 <= 0;
      else if (state512 == 511)
        state512 <= 0;
      else
        state512 <= state512 + 1;
    end

  assign state64 = state512[5:0];
  assign sub38k_addr = state512[7:6];
  assign pilot19k_addr = state512[8:6];
  assign pilot19k = pilot19k_coe(pilot19k_addr);
  assign latch_out = (state64 == 9);
  assign INPUT_SEL = mpx_set[4];  // Selector 0:IN0, 1: IN1
  assign MPX_MODE = mpx_set[3];  // MPX mode  0: Stereo, 1: Mono
  assign MAIN_CHANNEL_CONT = mpx_set[2];  // Main Channel 0: On, 1: Off
  assign SUB_CHANNEL_CONT = mpx_set[1];  // Sub Channel 0: On, 1: Off
  assign PILOT_CONT = mpx_set[0];  // Stereo Pilot 0: On, 1: Off

  // Stereo MPX
  always @(posedge clk or posedge reset)
    begin
      if (reset)
        begin
          input_reg_l <= 0;
          input_reg_r <= 0;
          main_channel <= 0;
          sub_channel <= 0;
          diff <= 0;
          stereo_composite <= 0;
          stereo_composite2 <= 0;
          diff <= 0;
        end

      else
        begin
          case (state64)
            0:  begin 
                input_reg_l <= (INPUT_SEL)? mpx_l1_i : mpx_l0_i;
                input_reg_r <= (INPUT_SEL)? mpx_r1_i : mpx_r0_i;
              end

            1:  begin 
                // L + R
                main_channel <= (MAIN_CHANNEL_CONT) ? 0
                          : {input_reg_l[23], input_reg_l}
                            + {input_reg_r[23], input_reg_r};
                // L - R
                diff <= (SUB_CHANNEL_CONT) ? 0
                      : {input_reg_l[23], input_reg_l}
                      + ~{input_reg_r[23], input_reg_r} + 1;  
              end

            2:  begin 
                case (sub38k_addr)
                  1:  sub_channel <= diff;
                  3:  sub_channel <= ~diff + 1;
                  default:  sub_channel <= 0;
                endcase
              end

            3:  begin 
                // MAIN + SUB
                stereo_composite <= {main_channel[24], main_channel}
                              + {sub_channel[24], sub_channel};
              end

            4:  begin 
                // MAIN + SUB + PILOT
                if (~PILOT_CONT)
                  stereo_composite <= stereo_composite
                              + {{5{pilot19k[21]}}, pilot19k[21:1]};
              end

            5:  begin 
                // stereo_composite2 = stereo_composite * (115/128) -> (-0.93dB)
                stereo_composite2 <= {stereo_composite[25], stereo_composite[25:1]}
                              + {{2{stereo_composite[25]}}, stereo_composite[25:2]};
              end

            6:  begin 
                stereo_composite2 <= stereo_composite2
                              + {{3{stereo_composite[25]}}, stereo_composite[25:3]};
              end

            7:  begin 
                stereo_composite2 <= stereo_composite2
                              + {{6{stereo_composite[25]}}, stereo_composite[25:6]};
              end

            8:  begin 
                stereo_composite2 <= stereo_composite2
                              + {{7{stereo_composite[25]}}, stereo_composite[25:7]};
              end

            default:  begin
                stereo_composite2 <= 0;
              end

          endcase
        end
    end

  //  output data register
  always @(posedge clk or posedge reset)
    begin
      if (reset)
        begin
          mpx_o <= 0;
          mpx_pilot_o <= 0;
        end
      else if (latch_out)
        begin
          mpx_o <= (MPX_MODE) ? input_reg_l
                : stereo_composite2[25:1];
          mpx_pilot_o <= {pilot19k, 2'b0};
        end
    end

  // stereo pilot 19kHz coef
  function [21:0] pilot19k_coe;
    input [2:0] addr;

    case (addr)
      0: pilot19k_coe = 0;
      1: pilot19k_coe = 22'h141CFD;
      2: pilot19k_coe = 22'h1C71C6;
      3: pilot19k_coe = 22'h141CFD;
      4: pilot19k_coe = 0;
      5: pilot19k_coe = 22'h2BE303;
      6: pilot19k_coe = 22'h238E3A;
      7: pilot19k_coe = 22'h2BE303;
      default: pilot19k_coe = 0;
    endcase

  endfunction

endmodule

ステートカウンタ state64 に従って、処理を進めるように書いていますが、FPGAなのにこういうマイコン的な書き方でいいのか?と疑問を感じています。まあ動いているので結果オーライでしょうか。

0 件のコメント:

コメントを投稿