非職業的技師の覚え書き

JK1EJPの技術的検討事項を中心に記録を残します。

RS-HFIQ(6)QuiskのTuneとLOの同期制御

発振周波数と同調周波数の非同期問題

RFフロントエンドハードウェアであるRS-HFIQのSI5351A局部発振器(LO:Local Oscillator)の周波数は、Quiskの同調(Tune)周波数で制御するものと考えていました。しかし、QuiskのGUI操作時のTune周波数とLO周波数は同期していませんでした。

Quisk初期状態

具体的には、下記に示すように、Quisk立ち上げ時にはLO周波数とTune周波数は例えば初期設定値の7.021MHzで一致しています。Tune周波数はスペクトルグラフで赤線で示されています。LO周波数はLOノイズが発生している箇所で、ウォーターフォール上で明瞭に顕在化しています。

State at Quisk startup (LO and Tune frequencies match).

LO周波数変更

初期状態からLO周波数ステップアップボタン[・](右側)をクリックすることにより、LO周波数を1kHzステップアップして7.022MHzに設定した時の挙動を下記に示します。Tune周波数は7.021MHzから変化していません。

Quisk behavior when LO frequency is stepped up by 1 kHz (unchanged Tune frequency).

逆に、初期状態からLO周波数ステップアップボタン[・](左側)をクリックすることにより、LO周波数を1kHzステップダウンして7.020MHzに設定した時の挙動を下記に示します。Tune周波数はやはり7.021MHzから変化していません。

Quisk behavior when LO frequency is stepped down by 1 kHz (unchanged Tune frequency).

LOノイズがLSBフィルタの2.5kHz帯域に入ってきたため、ノイズが復調され再生音がPCから響きました。

Tune周波数変更

初期状態からTune周波数エントリ内のkHz桁の数字の上部をクリックすることにより、Tune周波数を1kHzアップして7.022MHzに設定した時の挙動を下記に示します。LO周波数は7.021MHzから変化していません。

Quisk behavior when Tune frequency is increased by 1 kHz (unchanged LO frequency).

LOノイズがLSBフィルタの2.5kHz帯域に入ってきたため、ノイズが復調され再生音がPCから響きました。
逆に、初期状態からTune周波数エントリ内のkHz桁の数字の下部をクリックすることにより、Tune周波数を1kHzダウンして7.020MHzに設定した時の挙動を下記に示します。LO周波数はやはり7.021MHzから変化していません。

Quisk behavior when Tune frequency is decreased by 1 kHz (unchanged LO frequency).

他のSDRソフトウェアのように、LOとTuneの同期設定や解除を行うオプションは見つかっていません。SDRの理論的建付けとしては両者が一致していなくても問題ありません。例えば、LO周波数固定のハードウェアに対してもTune周波数を可変できることはSDRの利点です。
しかし、LOノイズがTune周波数と関係のない位置に出現し、時には復調されてしまうことに違和感が生じました。そこで、GUIの操作に対して両者を一致させる改造を検討することにしました。

Quiskのドキュメント調査

Quisk GUIの周波数変更イベントの発生元は複数あります。これらに逐一改造を加えていては管理が大変になります。Quiskのドキュメントを精査したところ、N2ADR局 James C. Ahlstrom OMが重要なヒントを乗せてくれていました。

Quotes from Quisk detailed documentation (https://james.ahlstrom.name/quisk/docs.html#SDRIQ).

後半部分の意訳はこんな感じでしょうか。

ほとんどの場合、「GUIの周波数変更イベントの発生元」について気にする必要はありません。ユーザの操作にChangeFrequency関数で応答すれば良く、おそらくはハードウェアのVFO周波数を変更すればよいのです。必ずしも要求された変更を実際に行うことを遵守する必要はありません。ただ、必要に応じてハードウェアを調整し、あなたが望んで実際に設定した(Tune周波数、VFO周波数)の組み合わせを返して下さい。Quiskは、要求値の組み合わせを無視し、代わりにあなたが返した実際の設定値の組み合わせを使用します。

これをChangeFrequency関数の引数と返値に当てはめると、
 実際の設定値(Tune, LO)= ChangeFrequency(ユーザの要求値(Tune, LO))
となり、ユーザの要求値(要求Tune周波数、 要求LO周波数)に対して、Quiskに反映される実際の設定値(設定Tune周波数、設定LO周波数)をどう管理するかは、ハードウェアを制御するChangeFrequency関数の実装に依存することになります。

Quiskハードウェアファイルの改造

ハードウェハに依存するChangeFrequency関数は、Quiskハードウェアファイルの中のHardwareクラスのメンバ関数として定義されています。RS-HFIQをサポートするQuiskハードウェアファイルとしては、DL1KSV局Volker Schroer OMが公開してくれているhardware_usbserial.pyを利用しています。

改造前のChangeFrequency関数

hardware_usbserial.pyのHardwareクラスに継承定義されたChangeFrequency関数を下記に示します。

def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
    if self.vfo != vfo :
        if serialport.isOpen() :
            self.vfo =vfo 
            vfo_string = '*F' + str(self.vfo) + '\r'
            if DEBUG == 1:
                print("Tuning to: ", vfo_string)
            print("Tuning to: ", self.vfo)
            serialport.write(vfo_string.encode())

    return tune, self.vfo

LO周波数はQuiskプログラムの変数名ではvfoと定義されています。SDRの同調周波数(tune)とハードウェアの発振器周波数(vfo)の2つを引数に取っていますが、vfoのRS-HFIQへの設定のみを行い、tuneは引数をそのまま返値としていることが分かります。tuneの変更はRS-HFIQに対して何ら影響を与えていないことになります。また、vfoの変更はRS-HFIQの局部発振器(SI5351A)の周波数を変更しますが、tuneに影響を与えていないことが分かります。tuneとvfoは非同期とする実装になっています。

改造後のChangeFrequency関数

Quisk GUIでは、tune要求値が変更される場合と、vfo要求値が変更される場合の2つのケースがあります。そこで、tune要求値が変更された場合を検出するために、tuneの前回設定値を格納するクラス変数 self.tune をまず追加しました。

class Hardware(BaseHardware):
    def __init__(self, app, conf):
        ...
        self.tune = None    ## Added to synchronize vfo and tune. (2022/12/09)
        ...

つづいて、tuneとvfoを同期させるために、ChangeFrequency関数に以下の改造を施しました。

def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
    ## Added to synchronize vfo and tune. (2022/12/09)
    ##--------------------------------------------------------------------
    if self.tune != tune :
        if serialport.isOpen() :
            self.tune = tune
            vfo = tune
    ##--------------------------------------------------------------------
    if self.vfo != vfo :
        if serialport.isOpen() :
            self.vfo = vfo
            self.tune = vfo ## Added to synchronize vfo and tune.
            vfo_string = '*F' + str(self.vfo) + '\r'
            if DEBUG == 1:
                print("Tuning to: ", vfo_string)
            print("Tuning to: ", self.vfo)
            serialport.write(vfo_string.encode())

    return self.tune, self.vfo  ## modified to retune self.tune

tuneの要求値と前回設定値を比較して、tune要求値変更を検出します。変更を検出した場合は、tuneの前回設定値を今回の要求値に更新し、今回のvfo要求値も引数を無視してtune要求値に強制的に書き換えます。これで、tune要求値変更を優先したvfo同期制御ができます。
つづいて、vfo要求値変更を検出します。変更を検出した場合は、vfoの前回設定値を今回の要求値に更新し、tuneの前回設定値も今回のvfo要求値に設定します。これで、vfo要求値変更の場合のtune同期制御もできます。
以上により、tune要求値変更の場合も、vfo要求値変更の場合も、ハードウェアRS-HFIQのvfo周波数の変更が行われます。ChangeFrequency関数からは更新されたtune設定値とvfo設定値が返されます。Quiskはこれら返値を利用します。

Quiskハードウェアファイル改造後の動作検証

改造前と同じQuiskの初期状態から改造後の動作検証を行いました。すなわち、LO周波数とTune周波数の初期設定値は7.021MHzとしました。

LO周波数変更

初期状態からLO周波数ステップアップボタン[・](右側)をクリックすることにより、LO周波数を1kHzステップアップして7.022MHzに設定した時の挙動を下記に示します。Tune周波数もLO周波数に同期して7.022MHzに変化しています。

Quisk behavior when LO frequency is stepped up by 1 kHz (Synchronously changed Tune frequency).

逆に、初期状態からLO周波数ステップアップボタン[・](左側)をクリックすることにより、LO周波数を1kHzステップダウンして7.020MHzに設定した時の挙動を下記に示します。Tune周波数もLO周波数に同期して7.020MHzに変化しています。。

Quisk behavior when LO frequency is stepped down by 1 kHz (Synchronously changed Tune frequency).

何れの場合も、LOノイズがLSBフィルタの帯域内に侵入して復調されるようなことはありませんでした。

Tune周波数変更

初期状態からTune周波数エントリ内のkHz桁の数字の上部をクリックすることにより、Tune周波数を1kHzアップして7.022MHzに設定した時の挙動を下記に示します。LO周波数もTune周波数に同期して7.022MHzに変化しています。

Quisk behavior when Tune frequency is increased by 1 kHz (Synchronously changed LO frequency).

逆に、初期状態からTune周波数エントリ内のkHz桁の数字の下部をクリックすることにより、Tune周波数を1kHzダウンして7.020MHzに設定した時の挙動を下記に示します。LO周波数もTune周波数に同期して7.020MHzに変化しています。

Quisk behavior when Tune frequency is decreased by 1 kHz (Synchronously changed LO frequency).

何れの場合も、LOノイズがLSBフィルタの帯域内に侵入して復調されるようなことはありませんでした。

RS-HFIQ Built-In Test(BIT)信号の挙動

最後に、RS-HFIQ内臓テスト信号の挙動を確認しました。下記に結果を示します。上段は初期状態で、LO = Tune = 7.020MHz、BIT周波数を1kHzアップの7.021MHzに設定しています。下段はTune掃引後の状態で、Tune = 7.0187MHzに設定されています。

Behavior of the RS-HFIQ Built-In Test (BIT) signal for Tune/LO synchronization.

上段の初期状態では、BIT信号がLOで折り返して、1kHzダウンの鏡像位置7.019MHzにイメージが発生しています。下段のTune掃引後の状態では、Tuneに同期したLO(= 7.0187MHz)でBIT信号が折り返して、2.3kHzダウンの鏡像位置7.0164MHzにイメージが移動しています。Tune掃引(7.020 ⇒ 7.0187MHz)によってLOも同期して連続変化するため、イメージの再生音が1kHzから2.3kHzに連続変化することが確認できました。

気付きとして、ウォーターフォール上でBIT周波数にノイズが発生しています。Tune掃引(LO掃引)はPLLシンセサイザ(SI5351A)のCK0の周波数を変えているだけで、BIT信号が利用するCK2は変更していません。今のところ下記の理由が考えられます。

  • SI5351A周辺回路のパターン上でBIT信号にノイズが載る。
  • BIT信号をダイレクトコンバージョンするQSD回路の過渡応答でノイズが発生する。
  • 過渡応答信号に対するQuisk SDRの短時間FFT演算でノイズが発生する。

スイープ音に聴覚上の違和感はないため、3つ目のFFTノイズの可能性が高いと考えています。短時間FFTのバッファ長等は未調査です。

RS-HFIQ(5)QuiskへのRS-HFIQ内蔵テスト機能の実装

HobbyPCB社から提供される「RS-HFIQ Control Panel」を、Omni-Rigを使用しないQuiskと併用することが出来ないことが判明しました。そこで、「RS-HFIQ Control Panel」のBIT(Built-In Test)機能をQuiskに組み込むことにしました。

QuiskへのRS-HFIQ内蔵テスト機能の実装

RS-HFIQ内蔵テスト機能を追加したソフトウェアの構成

SDRソフトウェアは規模が大きく複雑ですが、Quiskのソースコードにはコメント記入が少なく、理解に時間を要しました。適所にコメントが記入されていたKeith's SDRと比較して、そのような感想を持ちました。

Quiskの制御系はPythonオブジェクト指向でコーディングされています。クラスの継承や相互参照が複雑になると、インスタンス化がどこで行われているかを追跡し難くなります。今回、RS-HFIQを表す「Hardware」クラスのインスタンス化がどこで行われているか分かりませんでした。それでも、テスト機能の実装はできました。

RS-HFIQ内蔵テスト機能を追加したQuiskソフトウェアの関連部分の構成を下記に示します。

Quisk-based RS-HFIQ built-in test function configuration.

Software configuration with additional RS-HFIQ built-in test function.

ファイルの構成と改造内容の骨子は下記の通りです。

  1. quisk.py ・・・QuiskのMain GUI
    RS-HFIQ内蔵テスト機能の制御パネルを開くボタンの追加。

  2. rshfiq_lib.py ・・・RS-HFIQ内蔵テスト機能の制御パネル
    HobbyPCB社「RS-HFIQ Control Panel」のBIT機能相当の新規開発。

  3. hardware_usbserial.py ・・・RS-HFIQとのシリアル制御通信
    BIT機能に係わる通信部分("BF"、"OB")の追加。

QuiskのGUI Toolkit

アプリのGUIを1から構築するには大変な労力を要するため、Toolkit(GUIを構成する部品widgetの集合)を利用するのが一般的です。Pythonでも、TkinterPyQtwxPython、・・・等の複数のToolkitを利用可能です。

この中、QuiskのGUIwxPythonを利用して構築されています。wxPythonの特徴はsizer(採寸機?)を活用して、ボタン等の部品widgetを柔軟に配置して行く作法を取る点かと思います。画面に部品を配置するためには、部品の寸法と配置座標を画面座標系で厳密に定義する必要がありました。wxPythonでは部品の相対寸法と行列位置を指定すれば、sizerが寸法と配置座標を計算して自動配置してくれます。他方、例えばTkinterではpack関数を用いて部品を積み上げたり並べることで部品配置を行います。

Quiskに機能を追加するためには、まずwxPythonを攻略する必要があります。Quisk GUIの元締めはwxPythonのアプリクラスを継承したAppです。Appがボタンクリック等のユーザイベントをポーリングして、イベントに対応するコールバック関数を実行します。

Quiskのボタンパネルは、左右の2列と上下の4行で構成されています。

The Quisk button panel consists of two rows on the left and right and four rows on the top and bottom.

継続して開発されてきた過程でコーディングスタイルが変遷してきた面もあるようですが、基本はAppクラスのMakeButtons関数の中で以下のスタイルでボタンを配置しているようです。

  1. ボタンパネルの左右2列の各行に対してボタンリストを初期化
  2. 実装機能のボタンwidgetを作成
  3. 作成したボタンwidgetをボタンリストに格納
  4. 上記2~3を繰り返してボタンリストを完成
  5. sizerを用いてボタンリストの各ボタンwidgetを反復配置

各ボタンwidgetのコールバック関数はAppクラスのメンバとして作成します。

RS-HFIQ内蔵テストパネル起動ボタンの実装

ボタンパネルの左列5行目にRS-HFIQ内蔵テストパネル起動ボタンを増設することにしました。上記手順の1から3のボタン作成のコードをMakeButtons関数の中に追加します。QuiskPushbuttonクラスはquisk_widgets.pyの中に定義されています。OnBtnRSHFIQがRS-HFIQ内蔵テストパネルを起動するコールバック関数です。

left_row4 = []
## A plain push button widget to open RS-HFIQ control panel.
b = QuiskPushbutton(frame, self.OnBtnRSHFIQ, text='RSHFIQ')
left_row4.append(b)

続いて、手順5のボタン配置のコードをMakeButtons関数の中に追加します。今回追加するボタンは「RSHFIQ」の1つだけですが、Quiskの作法に則り、反復配置でコーディングしています。これで、将来のボタン増設は容易になります。

row = 4
col = button_start_col
for b in left_row4:
    self.idName2Button[b.idName] = b
    ## Add() メソッドでは追加する widget の位置(行, 列)とサイズ(縦, 横)を指定
    botton_size_x = 1
    botton_size_y = 2
    gbs.Add(b, (row, col), (botton_size_x, botton_size_y), flag=flag)
    col += botton_size_y

最後に、ボタンクリックのコールバック関数OnBtnRSHFIQをAppクラスのメンバ関数として作成します。

def OnBtnRSHFIQ(self, event):
    print('OnBtnRSHFIQ() called back with event', event)
    rshfiq_lib.control_panel(Hardware)

RS-HFIQ内蔵テストパネルはGUIであり、rshfiq_lib.pyの中にcontrol_panelクラスとして新規実装しています(詳細後述)。ここでは、そのインスタンス化(実体化)を行うことで起動しています。コンストラクタの引数のHardwareインスタンスがRS-HFIQとのシリアルCAT通信機能をOmni-Rigに代ってQuiskでは担っており、RS-HFIQ内蔵テストパネルを実装する上で必須の引数になります。

Hardwareクラスは、ダウンロードしたhardware_usbserial.pyの中にQuiskのベースモデルクラスを継承する形で実装されています。元はVFO制御に係わるシリアル通信機能しか実装されていませんでしたが、今回改良してRS-HFIQ内蔵テストに係わるシリアル通信機能も追加実装しました(詳細後述)。

quisk.pyの先頭で新規開発のrshfiq_libを取り込んでおきます。インポート元のrshfiqはサブディレクトリ名です。

from rshfiq import rshfiq_lib  ## added for RS-HFIQ control pane

エラーが発生した際にlogファイルを自動表示する機能がQuiskには造り込まれています。親切な造りです。

A log file that is automatically displayed when an error occurs.

デバックが完了すれば、Quiskのボタンパネルの左下に「RSHFIQ」ボタンが表示されます。

RS-HFIQ built-in test panel activation button shown.

RS-HFIQ内蔵テスト制御パネルの実装

QuiskのGUIは前述の通りwxPythonをToolkitとして実装されていますが、RS-HFIQ内蔵テスト制御パネルはTKinterをToolkitとして実装しました。単なる慣れの問題で他意はありません。なお、オリジナルのHobbyPCB社提供の「RS-HFIQ Control Panel」はVisualBasicで実装されており、Windows専用になっています。

 RS-HFIQ built-in test control panel.

引数で受け取った前述のHardwareインスタンスのメソッド関数を用いて、GUIwidgetとRS-HFIQのシリアルCAT通信とを結びつけています。注意点はBIT周波数文字列のフォーマット変換でしょうか。人が直読し易いように3桁区切りのセミコロンを入れていますが、RS-HFIQは受け付けません。

約100行と短いため、付録にコードを掲載します。

RS-HFIQ内蔵テスト機能用シリアル通信の実装

ダウンロードしたhardware_usbserial.pyでサポートされていたRS-HFIQとのインターフェースコマンドは以下のVFO制御に係わるコマンドだけでした。

Interface commands for VFO control with RS-HFIQ originally supported in hardware_usbserial.py.

RS-HFIQ内蔵テストに係わる以下のインターフェースコマンドを追加実装しました。

Added interface commands for RS-HFIQ built-in testing.

迷ったのはPLLシンセサイザ(SI5351A)の出力電流レベルです。2 mA、4 mA、6 mA、8 mAから選択するのですが、RS-HFIQのInterface Commandsマニュアルには「Generally a 4 ma drive seems like it works OK.」と記載されており、通常は4 mAで良いとされているようです。駆動先回路の容量に依存していると思うのですが、RS-HFIQでもQSD回路(74AC74、2並列)を駆動するVFO用途では4 mAが設定されています。内臓テスト用途ではLNA前段のBPFに入力する受信信号レベルとするために小さい方が妥当と考え、最小の2 mAとしました。GUIで切り換えられるようにすると良かったかもしれません。

付録に追加したコードを掲載します。

Quisk上でのRS-HFIQ内蔵テストの試行

試行結果

実装したRS-HFIQ内蔵テスト機能を40mバンドで評価しました。

Evaluation results of the implemented RS-HFIQ built-in test function on the 40m band.

LOを7.020MHzとして、7.021MHzのテスト信号をUSB側に印加しました。LSB側に奇数次のイメージが発生しています。目視で、テスト信号の強度は-20dB、イメージの強度は-62dBとなり、イメージ抑圧性能は42dBcでした。この結果は、先のHDSDRの結果と誤差の範囲で一致します。

気付き

RS-HFIQ内蔵テストパネルは並列プロセスとして起動するのが理想的ですが、Omni-RigのようにHardwareインスタンスの共有を簡単に行う方法が分からなかったため、Quiskプロセス内の関数呼び出しで起動しています。プロセスの制御はRS-HFIQ内蔵テストパネルのGUIイベントループに移るため、IQサンプリングは停止すると予想していましたが、完全には停止しませんでした。GUIイベントループの隙間でサンプリングを実行できているのかもしれません。ただし、Quiskのスペクトル画面の更新周期は不安定になるため、BITテストを設定したらRS-HFIQ内蔵テストパネルを逐一Quitした方が良いようです。

今後は、Image抑圧性能の定量的計測、自動IQバランス調整に進むべきですが、さらなるQuiskコードへのダイブが必要になるため逡巡しています。

付録 コードの実装例

趣味の範疇の実装例であり、動作の保証はありません。

付録 RS-HFIQ内蔵テスト制御パネルの実装例

import tkinter as tk

class control_panel():
    ## Constructor
    def __init__(self, Hardware):
        self.Hardware = Hardware    ## USB serial to RS-HFIQ

        self.DEBUG = 0
        TEST = 0
        if TEST:
            self.set_BIT_to_LO_1kHz_up()
            self.set_BIT_on()
        
        self.open_panel()   ## GUI and event loop until closing

    #
    #   Methods to control the RS-HFIQ built-in test hardware.
    #
    def set_BIT_to_LO(self):
        """set BIT to LO freq"""
        _, vfo_string = self.Hardware.ReturnFrequency()
        frequency = int(vfo_string)
        if frequency > 1024000 and frequency < 55000000:
            if self.DEBUG:
                print('set_BIT_to_LO()>', vfo_string, '->', frequency)
            self.insert_entry(frequency)
        else:
            print("BIT frequency out of range.")
 
    def set_BIT_to_LO_1kHz_down(self):
        """set BIT to LO freq -1 kHz"""
        _, vfo_string = self.Hardware.ReturnFrequency()
        frequency = int(vfo_string) - 1000
        if frequency > 1024000 and frequency < 55000000:
            if self.DEBUG:
                print('set_BIT_to_LO_1kHz_down()>', vfo_string, ' ->', frequency)
            self.insert_entry(frequency)
        else:
            print("BIT frequency out of range.")

    def set_BIT_to_LO_1kHz_up(self):
        """set BIT to LO freq +1 kHz"""
        _, vfo_string = self.Hardware.ReturnFrequency()
        frequency = int(vfo_string) + 1000
        if frequency > 1024000 and frequency < 55000000:
            if self.DEBUG:
                print('set_BIT_to_LO_1kHz_up()>', vfo_string, ' ->', frequency)
            self.insert_entry(frequency)
        else:
            print("BIT frequency out of range.")

    def set_BIT_on(self):
        f_string = self.etyFrequency.get()
        frequency = int(f_string.replace(',',''))
        if frequency > 1024000 and frequency < 55000000:
            if self.DEBUG:
                print('set_BIT_to_LO_1kHz_up()>', f_string, ' ->', frequency)
            self.frequency = frequency
            self.Hardware.set_BIT_frequency(frequency)
            self.Hardware.set_BIT_on()
    
    def set_BIT_off(self):
        self.Hardware.set_BIT_off()

    #
    #   RS-HFIQ control panel 
    #       using tkinter: the standard Python interface to the Tcl/Tk GUI toolkit.
    #
    def open_panel(self):
        self.panel = tk.Tk()
        self.panel.title("RS-HFIQ control panel")

        frmTop = tk.Frame(self.panel, bd=1) ## [Top]=[Left][Center][Right]
        tk.Label(frmTop, text="BIT Oscillator Control").pack(side=tk.TOP, padx=1, pady=1, anchor=tk.NW)
        ## Buttons to set BIT frequency
        frmLeft = tk.Frame(frmTop, bd=4)
        tk.Button(frmLeft, text="Set BIT to LO Freq +1 kHz", command=self.set_BIT_to_LO_1kHz_up  ).pack(side=tk.TOP, padx=1, pady=1, fill=tk.BOTH)
        tk.Button(frmLeft, text="Set BIT to LO",             command=self.set_BIT_to_LO          ).pack(side=tk.TOP, padx=1, pady=1, fill=tk.BOTH)
        tk.Button(frmLeft, text="Set BIT to LO Freq -1 kHz", command=self.set_BIT_to_LO_1kHz_down).pack(side=tk.TOP, padx=1, pady=1, fill=tk.BOTH)
        frmLeft.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
        ## Entry of BIT frequency
        frmCenter = tk.Frame(frmTop, bd=4)
        tk.Label(frmCenter, text="BIT Frequency (Hz)").pack(side=tk.TOP, padx=1, pady=1, anchor=tk.NW)
        self.etyFrequency = tk.Entry(frmCenter, bg='yellow', font=("MSゴシック", "12", "bold"))
        self.etyFrequency.pack(side=tk.TOP, padx=1, pady=1)
        frmCenter.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
        ## Buttons to activate BIT
        frmRight = tk.Frame(frmTop, bd=4)
        tk.Button(frmRight, text="BIT ON",  command=self.set_BIT_on  ).pack(side=tk.TOP, padx=1, pady=1, expand=True, fill=tk.BOTH)
        tk.Button(frmRight, text="BIT OFF", command=self.set_BIT_off ).pack(side=tk.TOP, padx=1, pady=1, expand=True, fill=tk.BOTH)
        frmRight.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
        frmTop.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
        ## Button to quit
        frmBottom = tk.Frame(self.panel, bd=1)
        tk.Button(frmBottom, text="Quit", command=self.close_panel).pack(side=tk.TOP, padx=1, pady=1, expand=True, fill=tk.BOTH)
        frmBottom.pack(side=tk.BOTTOM, expand=True, fill=tk.BOTH)

        self.set_BIT_to_LO()    ## set LO as an initial value.
        self.panel.mainloop()   ## wait a button event.
    
    def insert_entry(self, frequency):
        f_string = format( frequency, '08,')
        if self.DEBUG:
            print('insert_entry()> f_string =', f_string)
        self.etyFrequency.delete(0,tk.END)
        self.etyFrequency.insert(0, f_string)
        
    def close_panel(self):
        self.panel.destroy()
## End of class control_panel():

付録 RS-HFIQ内蔵テストに係わるHardwareクラス追加部分の実装例

class Hardware(BaseHardware):
    ## (既存コード省略)
    #=========================================================================
    # [Additional supported interface commands]
    # '*Bf\r'   : Sets the Built-in test (BIT) Generator to frequency f Hz.  
    # '*OB1\r'  : Sets the output level, 1 (2 mA drive), for the BIT frequency
    # '*OB0\r'  : Sets the output level, 0 (off), for the BIT frequency
    #=========================================================================
    def set_BIT_frequency( self, frequency ):
        """set Built-in Test frequency to RS-HFIQ
        [arg]
            frequency : integer 8-digit target frequency
        """
        rtn_flag = 0
        if serialport.isOpen():
            f_string = format( frequency, '08')
            command = "*B" + f_string + '\r'
            print("Set BFO to: ", f_string)
            serialport.write(command.encode())
            rtn_flag = 1
        else:
            print("Failed to set BFO of RS-HFIQ as serialport not open.")
        return rtn_flag

    def set_BIT_on(self):
        """BIT ON"""
        rtn_flag = 0
        if serialport.isOpen():
            #self.set_BIT_freq( self.bfo )
            command = '*OB1\r'
            serialport.write(command.encode())
            time.sleep(.25)
            serialport.flush()
            rtn_flag = 1
        else:
            print("Failed to turn on the BIT of RS-HFIQ as serialport not open.")
        return rtn_flag

    def set_BIT_off(self):
        """BIT OFF"""
        rtn_flag = 0
        if serialport.isOpen():
            command = '*OB0\r'
            serialport.write(command.encode())
            time.sleep(.25)
            serialport.flush()
            rtn_flag = 1
        else:
            print("Failed to turn off the BIT of RS-HFIQ as serialport not open.")
        return rtn_flag
## End of class Hardware(BaseHardware):

RS-HFIQ(4)Quisk(Windows版)立ち上げ

Quiskとは?

RS-HFIQの試験用SDRバックエンドとして、HDSDRに代わるQuiskの立ち上げに着手しました。クローズドソースのHDSDRに対して、Quiskはオープンソースという違いがあり、学びの対象として勝っています。

Quiskは、N2ADR局 James C. Ahlstrom OMが開発を続けているSDRソフトウェアです。

上記ホームページの「Quiskとは?」から引用します。

Quisk はソフトウェア無線 (SDR) であり、受信機と送信機を制御するソフトウェアです。Quisk は「brisk」と韻を踏んでおり、発音しやすくするために QSK にいくつかの文字を加えたものです。QSK はフルブレークインの CW を意味する Q 符号であり、Quisk は低遅延 CW 運用のために設計されています。SSB や AM でも問題なく動作します。Quisk は Python と C で記述されており、すべてのソースが含まれているため、ご自身で変更することができます。Quisk 受信機は、サンプル データを読み取り、同調し、フィルタを適用し、復調し、オーディオをサウンド カードに送って、外部ヘッドフォンもしくはスピーカーに出力することができます。Quisk 送信機はマイク入力を受け取り、サウンドカードもしくはイーサネット経由で送信機に送ることができます。CW の場合、Quisk はオーディオをミュートし、サイド トーンに置き換えることができます。

...(以下、略)

当局の視点でのQuiskの利点を列挙します。

QuiskはRS-HFIQをサポートしていません。どうすれば良いかを上記ホームページから続けて引用します。

サポートされているハードウェアをお持ちの場合は、Quisk をすぐに使用できます。その他の受信ハードウェアをお持ちの場合は、ファイル quisk_hardware.py を変更して受信機を Quisk に接続する必要があります。たとえば、シリアル ポートで VFO 周波数を変更する場合、シリアル ポートに文字を送信するように quisk_hardware.py を変更する必要があります。ファイル quisk_hardware.py は、習得と使用が非常に簡単な言語である Python プログラミング言語で記述されています。私が保有するハードウェア以外にも使用できるように、Quisk を簡単に変更できるようにしました。

RS-HFIQのオーディオIQ信号はサウンドカード経由でQuiskに接続するため、VFO周波数を変更するシリアルCAT通信のコードをquisk_hardware.pyとして準備すれば良いことになります。幸いなことに、DL1KSV局Volker Schroer OMが既にRS-HFIQ用のquisk_hardware.py(hardware_usbserial.py)を作って公開してくれています。

Linux用のコードですが、USBポートの名前をWindows用に変更するだけでWindows上のQuiskでも動作しました(詳細後述)。Pythonなのでmake(コンパイル&リンク)の必要はありません。

Quisk(Windows版)のインストール

Python環境の準備とQuiskのインストール

最新バージョンの 64 ビット Python 3.9 をインストールする必要があるとのホームページの記述から、64 ビット Python 3.9の仮想環境を準備しました。後はインストール手順に従って、いくつかの Python モジュールを最新バージョンにアップグレードしてから、Quisk をpipでインストールすれば完了です。

HDSDRと比較すると、QuiskのGUIは操作ボタン類を下部にまとめた簡素な造りになっており、改造し易い雰囲気を纏っています。

Quisk's GUI has a simple structure with operation buttons grouped at the bottom.

RS-HFIQ用のhardware_usbserial.pyのインストール

DL1KSV局Volker Schroer OMの上記GitHubから、「Code 」→ 「Download ZIP」でダウンロードし、適当(適切)な場所に保存します。後でQuiskにその任意のパスを参照設定します。

オリジナルコードはLinux用です。

serialport.port = "/dev/ttyUSB0"   ## Linux

serialport.portの指定をWindows表現に変更します。

serialport.port = "COM6" ## Windows

なお、シリアルポート名の番号はWindowsの周辺機器接続状況に応じて変化します。ハードコーディングなのは不便ですが、Pythonなので変更は容易です。

QuiskへのRS-HFIQの登録設定

ここからはGUI上で設定を進めます。

新しいRadioとしてのRS-HFIQの登録

  1. 「Config」ボタンをクリックします。
  2. 「Radios」タグを選択します。
  3. 「RS-HFIQ」(任意)を新しいRadio名としてキー入力します。
  4. 「Add」ボタンをクリックします。
  5. Quiskスタート時のRadioとして「RS-HFIQ」をプルダウンから選択します。
  6. 「RS-HFIQ」タグが作られます。

Registration of RS-HFIQ as a new Radio in Quisk.

ハードウェアファイル(hardware_usbserial.py)のパス設定

  1. 「RS-HFIQ」タグを選択します。
  2. サブ画面の「Hardware」タグを選択します。
  3. 「Change」ボタンをクリックします
  4. 開いたファイルダイアログでhardware_usbserial.pyを選択しパスを設定します。

Path setting to inform Quisk of the location of the hardware file (hardware_usbserial.py).

サウンドドライバの設定

使用するPC周辺機器の構成によって変わります。下記は一例です。

  1. 「RS-HFIQ」タグのサブ画面の「Sound」タグを選択します。
  2. スピーカのドライバを選択します。
  3. スピーカのサンプリングレートを選択します。
  4. マイクのドライバを選択します。
    (HDSDRでは選択できなかったUSB PnP Audio Deviceを選択できました。)
  5. マイクのサンプリングレートを選択します。
  6. RS-HFIQのQSDからの受信IQ信号をPCに入力するサウンドカードのドライバを選択します。(PCから見るともう一つのマイク入力に見えます。)
  7. 受信IQ信号のサンプリングレートを選択します。
  8. RS-HFIQのQSEへの送信IQ信号をPCから出力するサウンドカードのドライバを選択します。(PCから見るともう一つのヘッドフォン出力に見えます。)
  9. 送信IQ信号のサンプリングレートを選択します。

Configuration of sound drivers to Quisk.

RS-HFIQと接続したQuiskの起動

ノイズレベルの確認

ダミーロードに接続したRS-HFIQのIQ信号をQuiskで復調しました。40mバンドで S3 -72.84 dB のノイズレベルでした。

サンプリングレート98kHzのサウンドカードを使用しているため、サンプリング定理からバンドスコープの帯域は48kHz(LO±24kHz)になります。HDSDRと同様に、バンドスコープの中央にキャリア漏れが見られました。その他に、両側の帯域外にアーティファクトと思しき角が立っていました。アンチエイリアスフィルタの性能不足による帯域外から帯域内への折り返し雑音の理屈は分かりますが、帯域外に角が立つ理屈は分かりませんでした。FFTの理屈からは、帯域内パターンが帯域外に繰り返すだけと思っていたのですが・・・。帯域外はクリップする等で表示を制限した方が分かり易いと思いました。

なお、Keith's SDRのサンプリングレートはこの半分の48kHzであり、バンドスコープの帯域も半分の24kHz(LO±12kHz)になります。

Noise level of Quisk connected to RS-HFIQ.

右端のズームスライダで周波数軸を拡大できます。拡大すると、キャリア漏れがツインピークになっていることが分かりました。これはHDSDRと同じ結果で矛盾はありません。RBW(Resolution Band Width)を可変できるHDSDRよりも、Quiskの表示周波数分解能は粗いようです。RBWを変更できるかどうかは不明です。この辺が低遅延のQuiskの軽快さを確保するためにバランスを取っている所かもしれません。

Zoom magnification of the frequency axis of the spectrum.

LSBのディフォルトフィルタは2.5kHzでした。「Rx Filter」ボタンをクリックすると、フィルタの形状を確認できます。

Frequency response of 2.5 kHz filter.

タップ数まで調べることが出来ていませんが、シャープな形状から判断すると、Keith's SDRのFilterConvオブジェクトのタップ数(513タップ)より多いように見えます。

送信仮試験

Quiskの「PTT」ボタンをクリックすると、RS-HFIQフロントパネルのTX LEDがオレンジ色に点灯しました。時間が経過してもCLIP LEDが赤色に点灯することはありませんでした。送信レベルが飽和するような動作はしていないことだけ現時点で確認できました。

気付き LO-Tuneの非同期

2つの「・」ボタンをクリックすると、LOが10kHzステップでUp/Downします。その時に、赤線のTuneマーカとその左のLSBフィルタ帯域幅を表すグレーハッチは移動しません。LO周波数の変更に対してTune周波数は非同期です。

周波数エントリ(図では 7 020 000 Hz)の数字の上側をクリックするとTune周波数がUpし、下側をクリックするとDownします。その時にLOは移動しません。Tune周波数の変更に対してLO周波数は非同期です。

サンプリング定理から48kHz(LO±24kHz)帯域の信号はQuiskに取り込んでいるため、その帯域内の任意の信号を切り取って再生できるというのはSDR受信機の動作としては正しいのかもしれません。しかし、SDRトランシーバの動作としては違和感があります。HDSDRでもLO周波数とTune周波数が上下2段に表示されていましたが、少なくともディフォルト状態で両者は同期していました。Quiskの同期ボタンや同期設定を探したのですが見つかっていません。LO-Tune同期機能が無ければ、改造課題の1つになります。

気付き RS-HFIQ組込みテスト機能の起動不可

Quiskの「Test 1」ボタンでAFトーン信号を発生できますが、Quiskの中で発生させ表示させているだけで、RS-HFIQのテストはできません。RS-HFIQ内蔵のテスト機能を使用してRFトーン信号を発生させるためには、HobbyPCB社提供の「RS-HFIQ Control Panel」を利用する必要があります。

しかし、Quiskが立ち上がっていると「RS-HFIQ Control Panel」は立ち上がりません。逆もまたしかりです。QuiskはOmni-Rigを利用せずにシリアル通信ポートを排他独占するため、当然の結果です。ここにきてOmni-Rigの便利さに気付いた次第です。「RS-HFIQ Control Panel」のテスト機能をQuiskに組み込むことが、もう一つの改造課題になりました。

付録 操作ボタンの調査

操作ボタンは最小限の大きさに留められ、見て機能が分かるラベルが必ずしも付いていません。そこで、ソースコードを検索して、ボタンからコールバックされるイベント関数を割り出しました。イベント関数名が機能が推測する一助になると思います。

スライダ

左にスピーカ音量調整のスライダを1つ配置しています。
右にサイドトーン、Ritスケール、グラフのY軸のスケール、グラフのY軸のゼロ点、グラフのX軸の拡大/縮小の5つのスライダを配置しています。

Table of Quisk slider labels and event functions to be called back.

ボタンパネルの左列1行目

Tune周波数、LO周波数の操作関係のボタンが配置されています。HDSDRのようにマウスホイールによる操作はサポートされていないようです。周波数entryで操作するTune周波数と、BandのUp/Downでステップ操作するLO周波数が同期していないのは上述の通りです。

Frequency adjustment widgets in the first row of the left column of the button panel.

ボタンパネルの右列1行目

右端に数値表示のSメータがあります。

Buttons and numerical S-meter in the first row of the right column of the button panel.

ボタンパネルの左列2行目

バンド切換のボタンが並んでいます。ソースコードが公開されているため、許可されている各バンドの上限及び下限に加えて、バンドプランも設定可能です。

Band switching button on the second row of the left column of the button panel.

ボタンパネルの右列2行目

モード切換ボタンが並んでいます。USBが無いようですが、バンド切換に連動してLSBと入れ代ります。

Mode switching button on the second row of the right column of the button panel.

ボタンパネルの左列3行目

受信に関係する機能のボタンが並んでいます。

Buttons for functions related to the receiver in the third row of the left column of the button panel.

ボタンパネルの右列3行目

フィルタ帯域の切換ボタンが並んでいます。モード切換に連動して、選択可能なフィルタが入れ代ります。

Buttons for switching the filter bandwidth in the third row of the right column of the button panel.

ボタンパネルの左列4行目

送信に関係する機能のボタンが並んでいます。

Buttons for functions related to the transmitter in the fourth row of the left column of the button panel.

ボタンパネルの右列4行目

ボタンパネル上部の画面を切り換えるボタンが並んでいます。

Button to switch the upper screen in the fourth row of the right column of the button panel.

RS-HFIQ(3)HDSDRのIQバランス調整

IQバランス調整系の構成

IQバランス調整系の構成を下図に示します。

Configuration of IQ balancing system.

RS-HFIQ内蔵テスト機能

RS-HFIQのPLLシンセサイザ(SI5351A)の3チャンネルの出力は、下記の用途に使用されています。

  1. CK0:LO(局発)
  2. CK1:ユーザ用外部供給(CW利用等)
  3. CK2:内臓テスト用RF_BIT(Built-In Test)信号

内臓テスト用CK2(RF_BIT)はBPFの入力に接続されています。SI5351Aの発振波形は矩形波ですが、選択されたバンドのBPFによって正弦波に近似されます。LNAを経由してQSDでRFサンプリングされ、Op-Ampのアンチエイリアスフィルタを経て、 IQ OUTアナログ信号としてRS-HFIQから出力されます。以後、サウンドカードによってAFサンプリングされ、PCのHDSDRソフトウェアによって信号処理されます。すなわち、RS-HFIQは任意の周波数で模擬受信信号を発生させる内臓テスト機能を備えていることになります。

QRP-LabsのQCXキットも、SDRではありませんが、PLLシンセサイザとQSDを備える点では構成が似ています。QCXキットは内臓調整機能が特徴の1つであり、IQバランス調整のために似たテスト構成を取っているのではないかと推測されます。QCXはプロセッサを備えたアナログトランシーバであるため、IQ信号の振幅と位相のバランス調整は後続の2段のOp-Ampのトリマによって行いました。一方、SDRのRFフロントエンドのRS-HFIQは調整トリマを備えていません。IQバランス調整はSDRソフトウェアで行う必要があります。

テスト機能制御ソフトウェア

RS-HFIQのテストに使用したSDRソフトウェアは、HobbyPCB社のサポートホームページで提供されているディフォルトのHDSDR(High Definition Software Defined Radio)v2.7です。Windows PC上のHDSDRは、Omni-Rigを通信ポートとしてRS-HFIQのArduino NanoとCAT通信を行い、バンド設定やチューニングを行います。

HobbyPCB社からは、HDSDRとは別に「RS-HFIQ Control Panel」という名称のソフトウェアが提供されています。「RS-HFIQ Control Panel」もOmni-Rigを介してRS-HFIQのArduino Nanoと通信し、HDSDRからはアクセスできない内臓テスト機能を制御します。

まず、「RS-HFIQ Control Panel」の「Set BIT to LO Freq」ボタンをクリックして、HDSDRによって設定されたLO周波数(下図7,020kHz)をRS-HFIQに問い合わせ、取得したLO周波数をBIT周波数として設定します。

Reading the current LO frequency of the RS-HFIQ to the BIT frequency of the "RS-HFIQ Control Panel" software.

ここで、HDSDRのLO周波数を動かすチューニング動作を1回は行わないと、「RS-HFIQ Control Panel」にLOの値を読み込めない不具合がありました。LOチューニングのイベントが必要なようです。チューニング動作を行わないと、CAT通信のLO問い合わせに返答する変数にLOの値が入らないイメージです。

次に、「Set BIT to LO Freq +1 kHz」ボタン、あるいは「Set BIT to LO Freq -1 kHz」ボタンをクリックして、LO ±1 kHz のBIT周波数を設定します。続いて「BIT ON」ボタンをクリックしてBIT信号をRS-HFIQの受信部に入力し、イメージを評価します。HDSDRにはIQバランス調整機能があるため、Options → Input Channel Calibration RX から下図の Channel Adjustment Panel (RX) を開き、IQ信号の振幅と位相差の微調整を行います。

Channel Adjustment Panel (RX) on the HDSDR.

IQバランス調整

IQバランス調整前

LO +1 kHz(7,021kHz)のBIT信号を入力し、LSBのイメージを評価しました。無調整の状態でイメージ抑圧は43dBでした。

LSB image evaluation result with LO +1 kHz BIT signal input.

LO -1 kHz(7,019kHz)のBIT信号を入力し、USBのイメージを評価しました。無調整の状態でイメージ抑圧は39dBでした。

USB image evaluation result with LO -1 kHz BIT signal input.

40mバンドに関しては、SSBよりFT8運用時のイメージノイズが4dBだけ大きいという結果になりました。

IQバランス調整後

USB とLSBのイメージ抑圧のバランスを取るのが難しく、USB側も43dBの抑圧に持って行くのが限界でした。

USB image evaluation result with LO -1 kHz BIT signal input after IQ balance adjustment.

USB とLSBの片側だけを優先するなら、さらにイメージ抑圧を測ることが可能との感触を得ました。その場合は、バンドおよびモード毎にIQバランス調整値を持つ必要があります。あるいは、バンドあるいはモード切換毎にIQバランス自動調整機能がバックグランドで働くことが理想的と思います。

 

IQバランス調整はRS-HFIQ側の機能ではサポートされていないため、SDRソフト側で対応する必要があります。Keith's SDRに関しては、フォーラムおよびソースコードを検索しましたが、「IQバランス」に関する話題やコードを見つけるには至っていません。既報の通り、コーディックの不具合に起因するTwin Peaks問題に対しては自動位相調整機能が実装されています。IQゲイン調整機能は見つかっていません。3dB程度のアンバランスは優先度の高い課題にはなっていないと思われます。

RS-HFIQ(2)ケース封入とテスト

「RS-HFIQ」スレッド

Keith's SDRのフロントエンドとして調達しておいたRS-HFIQ(HobbyPCB)の立ち上げに着手しました。RS-HFIQはPC(Windows PCやRaspberry Pi等)上で稼働するSDRソフトウェアのフロントエンドとしても使用できるため、「Teensy」スレッド(特定の話題の総体)とは別に「RS-HFIQ」スレッドを立てることにしました。下記既報をスレッドの(1)として本報告を(2)とします。

RS-HFIQのケース封入

RS-HFIQは部品実装済みの完成基板で供給されるため、専用ケースに封入するだけで済みます。

RF front-end RS-HFIQ enclosed in a case.

必要な作業は以下の通りです。

パネル化粧

ケース本体はアルミですが、前後のパネルはPCBで作られています。塗装されていないパネルのエッジ部を黒マジックで塗ります。エッジ部に加えて、穴の切断部にも塗ると見栄えが良いとのこと。

LED導光部品の光漏れ対策

4つのインジケータ(PWR、USB、TX、CLIP)のLEDはSMDタイプのため、PCB上方に向かって発光することになります。そこで、発光を90度曲げて正面パネルに導く透明プラスチック製の導光パイプをPCBに取り付けます。左右に光が漏れて隣の導光パイプに入らないように、導光パイプの左右側面を黒マジックで塗ります。

Light leakage prevention for LED light-guiding components.

電源スイッチの組み立て

背面パネルに取り付ける電源スイッチに2ピンソケットの付いたリード線をはんだ付けし、ソケットをPCB上の電源ジャンパに圧入します。ソケットのガイドをニッパで切り落としても、横向きのジャンパピンよりソケット穴の方が高いため、ジャンパピンを曲げながら圧入することになりました。

Assembling the power switch.

ケース封入

PCBをスライドレール付きのアルミケースに入れ、前後のPCBパネルをねじで締結して完成です。終段FETにはPCB上下にヒートシンクが付いており、ケースに熱を逃がす等の対策は特に取られていません。そのためPCBの封入は簡単ですが、以下の注意が必要でした。

アルミケースのねじ穴にめねじ加工はありませんでした。ねじ自身のねじ山でめねじ山を塑性成形させながらねじ込むセルフタップになっています。最初の塑性成形にトルクが要ることと切りくずが出ることから、PCBを入れる前に8か所のねじを仮締結してめねじを切っておいた方が良いようです。

導光パイプの左右位置はPCBに設けられた挿入穴で決まりますが、導光パイプ底部が着座するまでは圧入できない穴公差になっているようでした。導光パイプの上下傾斜は前パネルの穴に挿入して決めることになります。

Completed enclosure of the RS-HFIQ PCB into the case.

以上で完成です。上完成写真のパネル化粧に使用したマジックインクの箱はスケール比較用です。

ここにきて、USBコネクタがMini-Bであることに気付きました。家中を探し回ってなんとかType-A~Micro-Bケーブルを1本見つけました。周辺機器側のUSBコネクタはフォームファクタが小さい方が望ましいということで、4x1端子の平型Type-Aを2階建て2x2端子にしたType-Bを作って、さらにフォームファクタを小さくするためにMini-Bを作って、さらにさらに小さくするためにMicro-Bを作ったようです。電気特性は同じなのに形状の違う規格を3種類も作ったらユーザ視点ではケーブル貧乏になってしまい、もはや「Universal」ではないと思うのですが・・・。 

PC接続テスト

RS-HFIQを用いたSDR実現方法

RS-HFIQはダイレクトコンバージョン方式のSDRを構成するRFフロントエンドであり、RS-HFIQを用いたSDRの実現方法として以下の3案を考えています。

Configuration options for SDRs using RS-HFIQ as the RF front end.

(a)Stand-alone SDR

本命のKeth's SDRをバックエンドに使用します。Keth's SDRのTeensyをUSBホストにしてRS-HFIQを制御します。

公開されているArduino NanoのソースコードからRS-HFIQ側の通信プロトコルを調べる必要がありますが、Mike OMから既にKeth's SDRに実装済みとの連絡を頂いています。Keth's SDR(Mike OM版)をDebugディレクティブでコンパイルすると、Serial通信にRS-HFIQ制御Debug用のインタプリタが表示されることを確認済みです。

Keth's SDRとRS-HFIQの不具合を切り分けるために、Keth's SDRと接続する前にRS-HFIQの動作確認を下記(b)案で行いたいと考えています。

(b)SDR on PC

SoundカードでIQ信号をサンプリングして、Windows PC上のSDRソフトをバックエンドに使用します。

RS-HFIQがサポートしているSDRソフトはHDSDR一択になっています。HDSDRはWindows版限定、ソースコードが公開されていない等の制約があり、学びの対象としては良い選択とは言えません。

(c)SDR on Single Board Computer

SoundカードでIQ信号をサンプリングして、Linux Single Board Computer(Raspberry Pi)上のSDRソフトをバックエンドに使用します。

Windowsの他にLinuxに対応しているSDRソフトとして、Quiskが見つかりました。QuiskはPythonおよびCで開発されており、なんとPyPi repositoryからpipでインストールできるようです。RS-HFIQ用のhardware descriptionもGitHubに見つかりました。

なお、(b)案が実現すれば(c)案はいらないように見えますが、集合住宅環境からの要請があります。集合住宅で電波的環境が最も良い部屋は一般的にリビングになります。しかし、リビングで無線業務ができるのは家人が留守の時に限られます。そこで、家庭内リモート環境を安価に実現する(c)案が所望されます。

(b)HDSDR on Windows PCによるテスト

Windows PC上のHDSDRソフトウェアを用いたRS-HFIQテストシステムの構成を下記に示します。

RS-HFIQ test configuration with HDSDR on a Windows PC.

RS-HFIQのホームページで示されている標準構成?とほぼ同じです。Sound Cardには推奨されているStarTeck社のICUSBAUDIO2Dを使用しました。

USBマイクはジャンク箱の中の物(サンワサプライMM-MCUSB16)を臨時で接続しました。これがHDSDRに嫌われ、ダミーロードTXテストができませんでした。USB PnP Audio Deviceという名称のデバイスドライバーがWindowsによって自動割り付けされますが、HDSDR上でサウンドバイス選択をすると「ドライバーが有りません」旨のエラーメッセージが出てTXをイネーブルにできません。HDSDRおよびOmni-Rigのバージョンを最新にしても同じでした。

StarTeck社のMic入力を指定すればTXも動作しましたが、受信ノイズ信号の再帰循環送信系になり発散しそうです。実際、CLIPのLEDが赤々と点灯しました。なお、HDSDRの最新バージョンではUSBデバイスの循環指定をチェックしているらしく、TXをイネーブルにできないようになっています。

Why and when does the CLIP LED on the RS-HFIQ glow red?

CLIPのLEDが何を指すのかの説明は見つかりませんでした。Aruduino Nanoのスケッチを調べると、アナログ入力1の値を上限しきい値と比較して点灯させています。上限しきい値は周波数バンド毎に若干異なる値が設定されており、一見、周波数に対する系統性はないようです。回路図を追うと、アナログ入力1(AR_A1)は終段FETのドレイン電圧のAC波高値を蓄電して抵抗分圧した値を測定していることが分かりました。TXレベル過多を知らせる仕組みのようです。終段入力信号もBPFを通過させる回路仕様のため、周波数バンド毎に異なる上限しきい値はBPFのゲインの違いを反映しているのかもしれません。TXレベル検知はCLIPのLEDを点灯させる以外は何もしていません。CLIP有無の返信コマンドは備えているため、SDR側でCLIPをサポートする必要があるようですが、HDSDRはサポートしていないようです。

試行錯誤した結果、TXテストを諦め、RXテストの構成にしてHDSDRを稼働させた際のGUI画面を下記に示します。ANTにはダミロードを接続しているため、RX信号は内部あるいは外来のノイズと思われます。

HDSDR screen with RS-HFIQ receiving internal or external noise.

AF信号を拡大すると、50Hzと60Hzのベースノイズとその高調波が重畳していることが分かりました。RBWは0.1Hzまで下げられるため、精度高くノイズ周波数を同定可能です。

Enlarged AF sub-screen of HDSDR receiving internal or external noise from RS-HFIQ.

中央のキャリア周辺は、RS-HFIQ内部のPLLシンセサイザ周辺から発生するノイズと思われます。

50Hzノイズは、RS-HFIQに13.8Vを供給しているスイッチング電源、あるいはPC電源からの回り込みと思われます。13.8Vスイッチング電源をOFFにしても50Hzノイズは観測されたため、PC電源からの回り込みの可能性が濃厚です。Arduino NanoのUSBケーブルには2回巻きのコアが付いていますが、それぐらいでは役不足か、あるいはSound Cardの方から回り込んでいるのかもしれません。

60Hzノイズの出所は不明です。QTHは富士川静岡県)以東です。出力を60Hzに統一した関西系インバータがQTH周辺にあるのでしょうか。翌日のテストでは60Hzノイズは消えていました。

RS-HFIQのホームページをトレースすれば簡単にテストできると思ったのですが、やはりPCのサウンド周りで躓きました。日を改めてIQバランス等のテストを継続したいと思います。

Teensy(19)Keiths' SDRのEncoder

Encoderの種類と選定

Keiths' SDRでサポートされるEncoderの種類

自作やKitのTranscieverに使われるEncoderには、大別すると以下の種類があります。

  • (1)光学式 Encoder
  • (2)機械式 Encoder
  • (3)I2C Encoder

(1)光学式Encoderは、サーボモータ等の回転軸に直結して用いる回転センサを流用したものです。デテント(戻り止めのための回転位置保持抵抗)が無く、スムーズに回転します。シャフトも含めて大型であり、1回転当たりのパルス数が多いため、VFO(PLLシンセサイザ)の周波数チューニング用メインダイヤルに好まれて使われる例が多いようです。

(2)機械式Encoderは、小型かつ安価であり、QRP Transciever Kit の多くに採用されています。スラスト方向のプッシュスイッチが付いているものもあります。

(3)I2C Encoderは、(2)機械式Encoderの底部にI2C通信用のインターフェース基板を付けたものです。SDR Transcieverを複数のEncoderを持つ構成にしたとしても、Teensyとの接続が容易になります。また、インターフェース基板に搭載されたマイコンがカウンタ機能を持つため、Teensyの負荷が幾分かは軽減されます。90度位相のずれたA/Bパルスから回転方向を判別してカウントをUp/DownするところまでI2C Encoder側で実行します。

Keiths' SDRは上記の全てのタイプをサポートしますが、(1)も(2)も「Mechanical Encoder」に分類され、プッシュスイッチの有無で区別されます。先日組み立てたBackpackボードには、プッシュスイッチ無しの「Mechanical Encoder」用コネクタが一式、プッシュスイッチ有りの「Mechanical Encoder」用コネクタが二式、「I2C Encoder」用コネクタが一式それぞれ準備されています。

選定したI2C Encoder

K7MDL局Mike OMが採用した(3)I2C Encoderと同じものを選定しました。イタリアDUPPA社の「I2C Encoder V2」です。イタリアから発送と記されていましたが、実際はオランダから発送され、コロナ禍の影響か一時行方不明になりましたが、長旅の末に到着しました。蛇足ですが、DUPPA社のアイコンは日本のアニメキャラクタに類似しています。イタリアのスタートアップの若者は日本のアニメファンなのだろうか・・・?

「I2C Encoder V2」とはI2Cインターフェース基板のことを指し、他にEncoder本体とノブをアクセサリの中から選定して調達する必要があります。Mike OMと同じ構成を選定しました。EncoderはRGB LEDを内蔵した透明シャフトの「RGB Encoder」とし、ノブは専用の透明リングを備えたΦ22.2mmの「Aluminum black knob」を選定しました。選択可能なノブの中では最大径です。マイコン(PIC16F18345)のGPIOを使用してリングを調光し、HMI(Human Machine Interface)の一翼を担わせることができます。

DUPPA I2C Encoder.

Keiths' SDRのEncoder周辺ソフトウェアの調査

割込みとポーリング

EncoderのカウントUp/Downの取りこぼしを防ぐために、当初はパルス発生による割込みを利用していると思い込んでいました。VFO周波数がスムースに変化しないとストレスになるからです。

しかし、ソフトウェアを精査したところ、「I2C Encoder V2」のエッジマイコン(PIC16F18345)は割込みを利用してカウントしていると想定していますが、Teensyは割込みを受け付ける設定ではなく「I2C Encoder V2」からの割込み信号線のH/Lレベルをポーリングしていることが分かりました。信号処理連鎖をサンプリング周波数で正確に起動するDMA割込みが最優先となるため、複雑な多重割込みを避けているものと思われます。

I2C Encoderを使う限りは、エッジマイコンが専業でカウントするため、取りこぼしは発生しないと期待されます。シングルコアのTeensyに対してエッジマイコンがサポートしている格好です。

Block diagram of I2C encoder connection.

Encoder周辺ソフトウェア

Keiths' SDRのEncoderに係わるソフトウェアは以下の3階層から成ります。

  • (1)SDRアプリのメインプログラム{SDR_RA8875.ino}
  • (2)SDRアプリのI2C Encoderモジュール{SDR_I2C_Encoder.cpp}
  • (3)DUPPA提供I2C Encoderファームウェア{i2cEncoderLibV2.cpp}

Keiths' SDR software related to Encoder.

DUPPA社が提供する(3)I2C Encoderファームウェアは、「I2C Encoder V2」ボードを抽象化するi2cEncoderLibV2クラスを提供しています。(1)SDRアプリのメインプログラム{SDR_RA8875.ino}では、このi2cEncoderLibV2クラスのオブジェクトを標準で2つ宣言しています。マルチファンクション用の「MF_ENC」と、サブの「ENC2」です。I2C接続のためEncoderの増設対応は容易であり、最大で「ENC6」までI2Cアドレスが予約され、オブジェクト宣言がコメントアウトされています。

複数のEncoderの役割はUser_Settings構造体変数で初期定義されており、「MF_ENC」の回転に対しては"MFTUNE"(周波数チューニング)を、プッシュボタンに対しては"RATE_BTN"(周波数チューニングステップ切換)を割り付けています。また、「ENC2」の回転に対しては"RFGAIN_BTN"(RFゲイン調整)を、プッシュボタンに対しては"MODE_BTN"(モード切換)を割り付けています。

(1)メインプログラムのsetup()関数でI2C Encoderの初期設定を行い、(2)I2C Encoderモジュール{SDR_I2C_Encoder.cpp}の初期設定関数set_I2CEncoders()をコールしています。呼び出された初期設定関数の中で、EncoderイベントとSDRアプリ側のcallback関数を紐付けています。具体的には、onChange(カウント増減)イベントに対してはencoder_rotated()関数を、onButtonRelease(プッシュボタン押下)イベントに対してはencoder_click()関数をそれぞれ紐付けています。

(1)メインプログラムのloop()関数で逐次に(2)I2C EncoderモジュールのCheck_Encoders()関数をコールして、I2C Encoderの割込線のポーリングを行います。複数のEncoderが同じ割込線を共有しているため、割込イベントを検知すると(3)I2C EncoderファームウェアのupdateStatus()関数をコールしてEncoder個体とイベント内容を調べます。例えば、onChange(カウント増減)イベントであれば初期設定でcallback関数として割り付けたencoder_rotated()アプリケーション関数がファームウェアからコールバックされます。

I2C Encoderの接続

I2C Encoderの組立

RGBエンコーダとXHコネクタ二式を「I2C Encoder V2」基板にはんだ付けするだけで組立は完了です。5x4=20個の端子を圧着する配線材の製作の方が大変でした。

ノブはRGBエンコーダの平目ローレット軸に圧入するだけです。ラジアル方向全周に均一にLED光を配光する透明軸なので、ネジ止め式のノブは付けられない仕様です。RGBエンコーダには専用ノブが必要なため、DUPPA発注時に忘れないようにする必要があります。

I2C Encoders from Corso Sebastopoli, Torino, Italy, with assembly completed.

4方向のエッジのピン配置は点対称(180度回転対称)ではなく線対称(折り返し対称)です。これは対抗する上下(あるいは左右)のコネクタ同士が同じピン配置になるようにする措置です。XHコネクタをはんだ付けすると、シルク印刷の信号名が見えなくなるため、データシートで確認しながら配線を行う必要があります。見えている隣のエッジの信号名を頭の中で90度回転させてはいけません。「DuPPa.net」のコーナー印字が回転方向の目印です。

I2C address setting by jumper pins.

I2Cアドレスは7bitsのジャンパーピンをはんだでショートさせて設定します。ハードウェアの設定とソフトウェアの設定が整合していれば問題ないのですが、Mike OMの設定に倣いました。「MF_ENC」が0x61、「ENC2」が0x62です。

5-wire connection between the Teensy Backpack board and the I2C encoders.

Teensy Backpackボードのコネクタの信号配置は「I2C Encoder V2」のそれとは異なるため注意が必要です。Teensy Backpackボードのコネクタは「G-SCL-SDA-INT-5V」の配置です。他方、「I2C Encoder V2」のコネクタは「INT-SCL-SDA-5V-G」の配置です。線材を色分けした方が安全です。耐熱電子ワイヤAWG24(7色)から5色を用いました。

I2C Encoderのテスト

「I2C Encoder V2」側コネクタの電源電圧確認を行った後、「I2C Encoder V2」を接続してKeiths' SDRを起動しました。

Keith' SDR with I2C Encoders connected starts successfully.

Serialメッセージには、設定したアドレスのI2C Encoderが2式見つかった旨の報告がありました。

In the Serial message, the I2C scanner reports the discovery of two I2C Encoders.

問題なく2式のI2C Encoderが動作していることを確認しました。"MFTUNE"(周波数チューニング)の機能を割り付けた「MF_ENC」の回転に対してVFO周波数が連動して変化することが確認できました。"RATE_BTN"(周波数チューニングステップ切換)の機能を割り付けたプッシュボタンでステップ切換が動作しました。

また、"RFGAIN_BTN"(RFゲイン調整)の機能を割り付けた「ENC2」の回転に対してRFゲインの切換えがSメータに表示されることを確認しました。"MODE_BTN"(モード切換)の機能を割り付けたプッシュボタンでモードが切り変わることも確認できました。Encoderの回転デテント毎にLEDが緑色に発光して自然にフェードします。上下限値に到達すると赤色に発光しフェードします。

パルスの取りこぼしを評価するために、手首のスナップを効かせて一気に「MF_ENC」を回転させてみました。期待に反して、取りこぼしが発生することが分かりました。

「I2C Encoder V2」の特徴として以下の数値がDUPPAのホームページに掲載されています。

Maximum A/B signal frequency: 100Hz (Tested)

24 pulses for rotations

最大パルス周波数100Hz、1回転のパルス数24から、カウント可能な最大回転速度は250rpm(4.2r/s、0.24s/r)になります。いくらスナップを効かせても、これほど高速に回転できているとは思えません。

「I2C Encoder V2」のデータシートによれば、割込み発生後にエッジマイコン(PIC16F18345)の ESTATUS(Encoder Status?)レジスタをTeensyが読まないと、割込線のH/Lレベルがリセットされないとのこと。割込がリセットされないとエッジマイコンのカウントもストップすると考えれば、パルスの取りこぼし発生も妥当です。この仮説が正しければ、結局のところパルスの取りこぼしの有無はTeensyが実行するloop()関数の反復頻度に依存することになります。Encoder専用のエッジマイコンを配置しているのにもったいない気がします。

デテント付きのRGBエンコーダを高速に回転させることはないと思いますが、光学式エンコーダに代えた際にパルスの取りこぼしがどうなるか興味のあるところです。これは将来の課題です。

Teensy(18)Keiths' SDRのサンプリング周波数

GitHubからクローンしたKeiths' SDR(K7MDL版)のバージョン(特定のバージョン番号はありません)を、2022/04/09時点のクローンから2022/10/23時点のクローンに更新しました。

そして、実機を用いた実験によって、信号処理連鎖のサンプリング周波数をやっと確認できました。

Keiths' SDR(K7MDL版)の更新

設定ファイルの修正

コンパイルを行う前に、以下の設定ファイルを自環境に合わせて修正しました。

  1. SDR_RA8875/makefile:パスの修正。
  2. SDR_RA8875/.vscode/arduino.json:COM番号の修正。
  3. SDR_RA8875/.vscode/c_cpp_properties.json:パスの修正。
  4. SDR_RA8875/.vscode/settings.json:パスの修正。
  5. SDR_RA8875/.vscode/tasks.json:パスの修正.

なお、クローンした設定ファイルにおけるArduinoのバージョンは1.8.15で自環境と同じでしたが、Teensyduinoのバージョンは1.56で自環境の1.54より上がっていました(現時点で最新版は1.57)。取りあえず、Teensyduino 1.54のままでコンパイルを行いました。

make の結果

前のバージョンでは、「glcdfonts.cファイルが重複している」旨の原因不明の(Mike OMも原因不明としていた)エラーが出たため、glcdfonts.cファイルを移動して隠す必要がありました。今回の最新バージョンでは、逆に「glcdfonts.cファイルが無い」旨のエラーが出たため、移動していたファイルを元のフォルダに戻しました。根本原因が何であったのか、そしてその原因は取り除かれたのか・・・は不明ですが、新しいバージョンではglcdfonts.cファイルにまつわるエラーは無くなりました。

TyCommanderツールを用いてhexファイルをTeensyにアップロードすると、問題なく起動しました。

Keiths' SDR (K7MDL version) update.

前回は確認に至らなかったタッチボタンの動作も確認できました。タッチジェスチャを見ているため、緩慢にタッチするのでは反応せず、メリハリのあるタッチ&リリースが必要であることが分かりました。

Confirm normal operation of touch buttons (example of Band button).

Serial.print メッセージ

しかし、Serial.printメッセージが出力されません。ソースコードを精査すると、Serial.printメッセージは"DEBUG"ディレクティブによって抑制されていることが分かりました。

//#define DEBUG  //set to true for debug output, false for no debug output

"DEBUG"定義をアンコメントすると次のSerial.printメッセージが出力されました。

Serial.print message.

このバージョンの開発時点でMike OMはRFフロントエンドとしてRS-HFIQを使用しているため、RS-HFIQのコマンドインタープリタがアクティブになっています。

なお、I2S ScannerがタッチコントローラFT6206を見つけた旨の報告をしていますが、これはFT5206の間違いと思います。

I2C device found at address 0x38  (RA8875,FT6206)

PJRC社のライブラリからインポートしたと思しきソースコードに、デバイス名「FT6206」はハードコーディングされています。FT6206も実在するため、同じアドレス0x38を割り付けたディスプレイが実在するのかもしれません。

Serial.print メッセージの復活により、懸案だった信号処理連鎖のサンプリング周波数を確認するための実機を用いた実験の準備が整いました。

サンプリング周波数の確認

情報収集

keithsdr@groups.io フォーラムのスレッド「Re: Iowa Hills Hilbert Filter Designer」にKeith OMより次の投稿がありました。

The only difference is we clock at 48kHz ..

Keith OMはKeiths' SDRのHilbert Filterをサンプリング周波数48kHzで設計したことを示しています。

今回のKeiths' SDR(K7MDL版)更新バージョンでは、ソースコードのサンプリング周波数設定箇所は次のようになっていました。

//float sample_rate_Hz = 11000.0f;  //43Hz /bin  5K spectrum
//float sample_rate_Hz = 22000.0f;  //21Hz /bin 6K wide
//float sample_rate_Hz = 44100.0f;  //43Hz /bin  12.5K spectrum
float sample_rate_Hz = 48000.0f;  //46Hz /bin  24K spectrum for 1024.  
//float sample_rate_Hz = 96000.0f;    // <100Hz/bin at 1024FFT, 50Hz at 2048, 40Khz span at 800 pixels and 2048FFT
//float sample_rate_Hz = 192000.0f; // 190Hz/bin - does

前回は96KHzでしたが、今回は48KHzにダウングレードされています。スペクトルウオーターフォールの表示に必要なFFTとの絡みによる適正化ではないかと推定しています。これで、Keith OMによるHilbert Filter設計のサンプリング周波数48kHzとの齟齬は解消されています。

コーディックSGTL5000は96KHzが仕様上の上限ですが、オーバークロックの可能性が追求されたようです。そのため、192KHzまでサンプリング周波数を変更した形跡がソースコードに残っています。本来、サンプリング周波数を変更したらHilbert Filterを設計し直す必要がありますが、それが放置されていたようです。サンプリング周波数が96KHzに設定されていた前回、Hilbert Filterのシミュレーションで帯域が2倍に拡大してしまったのは、それが原因と推定しています。

実機による確認実験の準備

ソースコードに設定したコーディックのサンプリング周波数(sample_rate_Hz)が信号処理連鎖起動のサンプリング周波数と一致しているかどうかを確認するために、下記図の黄色ハッチのコードをDMA割込みのISR(Interrupt Service Routine)に追加しました。

Additional code to the DMA ISR (Interrupt Service Routine) to check the sampling frequency.

経過時間は、設定した定数の「サンプリング周波数」(48KHz)と「ブロックサイズ」(128ワード)、および逐次計数するサンプリング回数から計算します。経過時間2秒毎にSerial.print メッセージを出力します。

設定した「サンプリング周波数」とDMA割込みで起動する信号処理連鎖のサンプリング周波数(正確には、サンプリング周波数/ブロックサイズ)が一致すれば、Serial.print メッセージ出力のインターバルが設定の2秒と一致するはずです。

実機による確認実験の結果

Inputオブジェクトを宣言した時点で、コンストラクタによってコーディックは初期化されます。あとは何時にDMA割込みを許可するかに依存しますが、早いタイミングでサンプリングを開始するようです。エンコーダ確認中に最初の「2秒経過」メッセージが出力されています。

Output of 2-second elapsed message in Serial.print message.

コマンドラインインタープリタのコマンド待機状態になると、連続して「2秒経過」メッセージが出力されます。これを測定すると、正しく2秒間隔であることが確認されました。

すなわち、設定した「サンプリング周波数」によって、サンプリング周波数/ブロックサイズの周期で信号処理連鎖が起動されていることが確認されました。