- Keiths' SDRのRx信号処理連鎖ブロック図
- NoiseBlankerオブジェクト(radioNoiseBlanker_F32クラス)
- RX_Hilbert_...オブジェクト(AudioFilterFIR_F32クラス)
- RX_Summerオブジェクト(AudioMixer4_F32クラス)
修正(2022/07/28)
信号処理連鎖を流れるデータの単位は128サンプルのデータブロックであることを認識し、修正しました。
修正(2022/07/23)
フィルタ特性をLPFとして見ていましたが、BPFとして見るのが適切でした。特にCW用の狭帯域フィルタはBPFとして見る必要があります。通過帯域幅以外のフィルタ設計諸元は不明ですが、周波数特性図に帯域中央周波数を推定して記入し、コメントも修正しました。
Keiths' SDRのRx信号処理連鎖ブロック図
Keiths' SDR(K7MDL局Mike OM版)のRx信号処理連鎖をコードから読み解いて抜き出したもの(α版)を下記に再録します。
今回は、朱色点線内★でマーキングした3つのオブジェクトを調べました。ノイズブランカ、ヒルベルトフィルタ、加算器です。
NoiseBlankerオブジェクト(radioNoiseBlanker_F32クラス)
役割
衝撃性パルス雑音を空白(blank)に置換して抑制します。
空白置換は急峻な信号変化となるため、周波数帯域が広がってしまいます。そのため、Noise Blankerは高域をカットするHilbert FIRフィルタの前に挿入されています。
信号処理連鎖の更新処理
update()関数では以下の信号処理を行っています。
- データブロックの128サンプルに以下の処理を逐次適用
・長さ256(5.3msec)のリング・バッファの1stデータに新規データの絶対値を積み、その総和を算出
・総和にしきい値(1.0E3f)を乗じた値よりデータの絶対値が大きければ、衝撃性ノイズと見做し、データ値をゼロに置換(blanked out) - 次の信号処理オブジェクトにデータを送信
- データブロックの解放
ディフォルトでは、平均信号強度の約4倍のパルス入力をノイズと見做す設定のようです。しきい値を変更する関数も準備されています。
RX_Hilbert_...オブジェクト(AudioFilterFIR_F32クラス)
役割
±45度のHilbert FIRフィルタのオブジェクトです。信号の低域通過によるエイリアス等の除去と、位相シフトによるイメージ除去の準備を行います。(後段の加算器Summerにてイメージ除去が行われます。)
PJRC社のオーディオ・ボードはアナログ・アンチエイリアス・フィルタを備えていません。RFフロントエンドにRS-HFIQ(HobbyPCB)を使用した場合は、RS-HFIQ側でQSDの出力をオペアンプのLPFに通してから出力しています。カットオフ周波数はfc=145kHz(C=220pF、R=4.99kΩ)です。販売元のHobbyPCBが推奨するUSBサウンドカード(StarTech - ICUSBAUDIO2D)は96KHz、24-bitの仕様です。これに対して、PJRC社のオーディオ・ボードは48KHz、16-bitの仕様です。サンプリング周波数fs=48kHz、ナイキスト周波数fn=24kHzに対してfc=145kHzのLPFは、有効なアンチエイリアス・フィルタにはならないようです。将来、エイリアス雑音が課題になる可能性があります。
Hilbert FIRフィルタの係数はオブジェクト宣言時ではなく、後にbegin()関数を呼んで設定しています。予め複数の遮断周波数(500Hz、700Hz、1.0kHz、1.8kHz、2.3kHz、2.8kHz、3.2kHz、4.0kHz)に対応したフィルタ係数が、ヘッダ・ファイルHilbert.hの中に定義されています。タップ数は151タップで共通です。GUI操作を通して適切なバンド幅のフィルタ係数に切り換えられるようになっているようです。SDRの利点として、好みのフィルタを追加することも容易と思います。
立ち上げ時には、最も帯域の広い4.0kHzのフィルタを設定しています。
RX_Hilbert_Plus_45.begin(Hilbert_Plus45_40K,151); // Left channel Rx
RX_Hilbert_Minus_45.begin(Hilbert_Minus45_40K,151); // Right channel Rx
信号処理連鎖の更新処理
update()関数では以下の信号処理を行っています。
- 128サンプルのデータブロックに以下の処理を一括適用
・入力データブロック(のポインタ)の取得
・FIRフィルタのタップ数に合わせてデータブロック長を調整
・arm_fir_f32()関数のコール - 次の信号処理オブジェクトへのデータ送信
- メモリ・ブロックの解放
単純な積和演算ですが、メモリ・ブロックの生成、解放に注意を払った実装がされています。余計なデータの複写を避けて、可能な限り演算時間を最小にする工夫と思われます。
CMSIS DSP Library: arm_fir_f32()
RX_Hilbert_...オブジェクトのupdate()関数では大量の積和演算が発生するため、CMSIS(Cortex Microcontroller Software Interface Standard)DSP Libraryのarm_fir_f32()関数をコールしています。このライブラリ関数はCortex-MプロセッサのDSP演算に最適化されてコンパイル済みです。
入力データブロック長128サンプルに対して、フィルタのタップ数は151で一致しません。不一致の場合は、内部のデータブロック長を調整するCMSIS DSP Libraryのarm_fir_init_f32()関数を呼んで再初期化をしています。フィルタを切り換えて再びタップ数が変更されたら、arm_fir_init_f32()関数を呼んで再々初期化を行います。このように、タップ数の異なるフィルタ切換を任意に適用することが保証されています。
CMSIS DSP Libraryのarm_fir_f32()関数は、フィルタのタップ数に合わせた内部データバッファの状態をarm_fir_instance_f32インスタンスとして、関数から戻った後も保持します。次回の信号処理連鎖が起動した時に、次回のデータブロックに含まれる128サンプルの頭からフィルタ計算を継続して行うために、現在の内部データバッファの状態を引き継ぐためです。
CMSIS DSP Libraryの関数はコンパイル済みのため、中身を調査することはできないと思っていましたが、githubにコードが公開されていました。
フィルタ特性
Hilbert.hのフィルタ係数をPython(Jupyter Notebook)にExportし、各フィルタのカットオフ周波数やスロープ特性等を確認するために可視化しました。上段に位相シフト+45度のフィルタの特性を、下段に位相シフト-45度のフィルタの特性を示します。左からインパルス応答、周波数特性(ゲイン)、周波数特性(位相)を示します。
カットオフ周波数狙い値4.0kHz
カットオフ周波数狙い値4.0kHzにおいて、-3dBの減衰が達成されているように見えます。-3dBの通過帯域幅は設計仕様通りの4.0kHzです。帯域中央周波数は2.0kHzより若干大きい値です。スロープは急峻です。
カットオフ周波数狙い値3.2kHz
カットオフ周波数狙い値3.2kHzにおいて、-10dB以上の減衰過達に見えます。-3dBの通過帯域幅は3.2kHzより狭いようです。
カットオフ周波数狙い値2.8kHz
カットオフ周波数狙い値2.8kHzにおいて、-10dB減衰が達成されているように見えます。-3dBの通過帯域幅は2.8kHzより狭いようです。
カットオフ周波数狙い値2.3kHz
カットオフ周波数狙い値2.3kHzにおいて、-10dB弱の減衰が達成されているように見えます。-3dBの通過帯域幅は2.3kHzより狭いようです。
カットオフ周波数狙い値1.8kHz
カットオフ周波数狙い値1.8kHzにおいて、-3dBの減衰が達成されているように見えます。-3dBの通過帯域幅は設計仕様通りの1.8kHz。帯域中央周波数は約800Hz。スロープは緩慢です。
カットオフ周波数狙い値1.0kHz
カットオフ周波数狙い値1kHzにおいて減衰はなく、通過域の中央のように見えます。-3dBの通過帯域幅は設計仕様通りの1kHz。帯域中央周波数も約1kHz。スロープは緩慢です。
カットオフ周波数狙い値700Hz
カットオフ周波数狙い値700Hzにおいて減衰はなく、通過域の中央のように見えます。-3dBの通過帯域幅は設計仕様通りの700Hz。帯域中央周波数も約700Hz。スロープは緩慢です。
カットオフ周波数狙い値500Hz
カットオフ周波数狙い値500Hzにおいて減衰はなく、通過域の中央のように見えます。-3dBの通過帯域幅は設計仕様通りの500Hz。帯域中央周波数は約700Hz。スロープは緩慢です。
フィルタ特性のまとめ
- +45度と-45度のフィルタのステップ応答は鏡像関係にある。
- +45度と-45度のフィルタの位相は90度シフトしている(明瞭ではない)。
- カットオフ周波数通過帯域幅狙い値4.0kHzのフィルタが最もスロープが急峻である。
- カットオフ周波数が低く通過帯域幅狙い値が狭くなるほどスロープが緩慢になり、設計が難しくなるように見える。
位相シフトのシミュレーション
+45度と-45度のフィルタの位相は90度シフトしているようですが、明瞭ではありません。そこで、シミュレーションにより確認しました。
2kHzのSin信号を生成し、+45度と-45度のFIRフィルタ(カットオフ周波数4.0kHz)に入力しました。計算にはscipy.signal.lfilterを用いました。S(青)は入力した2kHzのSin信号、S_Plus(橙)は+45度のFIRフィルタの出力、S_Minus(緑)は-45度のFIRフィルタの出力です。
S_Plus(橙)は125サンプル点目(2.6msec)までに入力信号Sに目視上重なります。カットオフ周波数4.0kHzのフィルタのインパルス応答は125サンプル点目までに目視上収束していることと呼応しています。+45度のFIRフィルタの位相シフトは0度でした。これに対して、S_Minus(緑)の-45度のFIRフィルタの位相シフトは-90度でした。±45度のFIRフィルタと言うより、0度と-90度のFIRフィルタと言う方が適切と思います。
RX_Summerオブジェクト(AudioMixer4_F32クラス)
役割
Audioミキサは信号加算器です。RFミキサの乗算器とは異なります。0度と-90度のFIRフィルタを通したIQ信号を加算し、イメージ信号を抑制することが役割です。
オブジェクトはゲインを持ち、ゲインを乗じてから加算します。2チャンネルのゲインは通常(1,1)であり、LSBモードの時に(1,-1)に切り換えます。-90度のFIRフィルタの出力に対して、180度の位相反転を行うことに相当します。通信モード毎のゲイン切り換えは、Mode.cppファイルに定義されています。
信号処理連鎖の更新処理
update()関数では以下の信号処理を行っています。
- 128サンプルのデータブロックに以下の処理を一括適用
・最初のチャネルの信号(I信号)データを取得し、共有出力変数に格納し、ゲインを乗算
・次のチャネルの信号(Q信号)データを取得し、ゲインを乗算し、一時的変数に格納
・共有出力変数と一時的変数を加算し、結果を共有出力変数に格納 - 次の信号処理オブジェクトへのデータ送信
- メモリ・ブロックの解放
単純な加算演算のため、RX_Summerオブジェクトのupdate()関数では大量の積和演算は発生しませんが、乗算はCMSIS DSP Libraryのarm_scale_f32()関数を、加算はarm_add_f32()関数をコールし、128サンプルのデータブロックに一括適用しています。
イメージ信号抑制のシミュレーション
イメージ信号の抑制を確認するシミュレーションのために、Sin/CosのIQ信号を生成し、それぞれ+45度と-45度(0度と-90度)のHilbert FIRフィルタ(カットオフ周波数4.0kHz)に入力し、その出力を加算しました。
チューニング信号
チューニング周波数のUSB側の対象信号と想定したIQ信号を生成しました。下記IQ試験信号は2kHzで半時計方向に回転します。
このIQ試験信号をHilbert FIRフィルタに入力し、出力を加算した結果を下記に示します。
2kHzのIQ試験信号はフィルタの通過帯域内にあるため、フィルタのタップ数に依存した過渡応答経過後は位相シフトのみが適用されます。位相シフトによってIQ信号の位相が揃った結果、定常状態の振幅は加算によって2倍になっています。フィルタのタップ数は151タップですが、目視上は125サンプル点目(2.6msec)で収束しています。先に見たように、カットオフ周波数4.0kHzのフィルタのインパルス応答は125サンプル点目までに目視上収束していることと呼応しています。
イメージ信号
チューニング周波数より高い周波数のLSB側のイメージ信号と想定したIQ信号を生成しました。下記IQ試験信号は時計方向に回転します。
このIQ試験信号をHilbert FIRフィルタに入力し、出力を加算した結果を下記に示します。
位相シフトによってIQ信号が逆位相の関係になった結果、定常状態の振幅は加算によってゼロに収束しています。フィルタのタップ数は151タップですが、目視上は125サンプル点目(2.6msec)までに収束しています。
Keiths' SDR(K7MDL局Mike OM版)に予め実装されたHilbert FIRフィルタによって、2.6msec程度の遅延を伴ってイメージの抑制が図られることがシミュレーションによって確認されました。